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 /**
21  * Code generation metadata and templates used for implementing struct
22  * serialization.
23  *
24  * Many templates can be customized using field meta data, which is read from
25  * a manifest constant member of the given type called fieldMeta (if present),
26  * and is concatenated with the elements from the optional fieldMetaData
27  * template alias parameter.
28  *
29  * Some code generation templates take account of the optional TVerboseCodegen
30  * version declaration, which causes warning messages to be emitted if no
31  * metadata for a field/method has been found and the default behavior is
32  * used instead. If this version is not defined, the templates just silently
33  * behave like the Thrift compiler does in this situation, i.e. automatically
34  * assign negative ids (starting at -1) for fields and assume TReq.AUTO as
35  * requirement level.
36  */
37 // Implementation note: All the templates in here taking a field metadata
38 // parameter should ideally have a constraint that restricts the alias to
39 // TFieldMeta[]-typed values, but the is() expressions seems to always fail.
40 module thrift.codegen.base;
41 
42 import std.algorithm : find;
43 import std.array : empty, front;
44 import std.conv : to;
45 import std.exception : enforce;
46 import std.traits : BaseTypeTuple, isPointer, isSomeFunction, PointerTarget,
47   ReturnType;
48 import thrift.base;
49 import thrift.internal.codegen;
50 import thrift.protocol.base;
51 import thrift.util.hashset;
52 
53 /*
54  * Thrift struct/service meta data, which is used to store information from
55  * the interface definition files not representable in plain D, i.e. field
56  * requirement levels, Thrift field IDs, etc.
57  */
58 
59 /**
60  * Struct field requirement levels.
61  */
62 enum TReq {
63   /// Detect the requiredness from the field type: if it is nullable, treat
64   /// the field as optional, if it is non-nullable, treat the field as
65   /// required. This is the default used for handling structs not generated
66   /// from an IDL file, and never emitted by the Thrift compiler. TReq.AUTO
67   /// shouldn't be specified explicitly.
68   // Implementation note: thrift.codegen templates use
69   // thrift.internal.codegen.memberReq to resolve AUTO to REQUIRED/OPTIONAL
70   // instead of handling it directly.
71   AUTO,
72 
73   /// The field is treated as optional when deserializing/receiving the struct
74   /// and as required when serializing/sending. This is the Thrift default if
75   /// neither "required" nor "optional" are specified in the IDL file.
76   OPT_IN_REQ_OUT,
77 
78   /// The field is optional.
79   OPTIONAL,
80 
81   /// The field is required.
82   REQUIRED,
83 
84   /// Ignore the struct field when serializing/deserializing.
85   IGNORE
86 }
87 
88 /**
89  * The way how methods are called.
90  */
91 enum TMethodType {
92   /// Called in the normal two-way scheme consisting of a request and a
93   /// response.
94   REGULAR,
95 
96   /// A fire-and-forget one-way method, where no response is sent and the
97   /// client immediately returns.
98   ONEWAY
99 }
100 
101 /**
102  * Compile-time metadata for a struct field.
103  */
104 struct TFieldMeta {
105   /// The name of the field. Used for matching a TFieldMeta with the actual
106   /// D struct member during code generation.
107   string name;
108 
109   /// The (Thrift) id of the field.
110   short id;
111 
112   /// Whether the field is requried.
113   TReq req;
114 
115   /// A code string containing a D expression for the default value, if there
116   /// is one.
117   string defaultValue;
118 }
119 
120 /**
121  * Compile-time metadata for a service method.
122  */
123 struct TMethodMeta {
124   /// The name of the method. Used for matching a TMethodMeta with the actual
125   /// method during code generation.
126   string name;
127 
128   /// Meta information for the parameteres.
129   TParamMeta[] params;
130 
131   /// Specifies which exceptions can be thrown by the method. All other
132   /// exceptions are converted to a TApplicationException instead.
133   TExceptionMeta[] exceptions;
134 
135   /// The fundamental type of the method.
136   TMethodType type;
137 }
138 
139 /**
140  * Compile-time metadata for a service method parameter.
141  */
142 struct TParamMeta {
143   /// The name of the parameter. Contrary to TFieldMeta, it only serves
144   /// decorative purposes here.
145   string name;
146 
147   /// The Thrift id of the parameter in the param struct.
148   short id;
149 
150   /// A code string containing a D expression for the default value for the
151   /// parameter, if any.
152   string defaultValue;
153 }
154 
155 /**
156  * Compile-time metadata for a service method exception annotation.
157  */
158 struct TExceptionMeta {
159   /// The name of the exception »return value«. Contrary to TFieldMeta, it
160   /// only serves decorative purposes here, as it is only used in code not
161   /// visible to processor implementations/service clients.
162   string name;
163 
164   /// The Thrift id of the exception field in the return value struct.
165   short id;
166 
167   /// The name of the exception type.
168   string type;
169 }
170 
171 /**
172  * A pair of two TPorotocols. To be used in places where a list of protocols
173  * is expected, for specifying different protocols for input and output.
174  */
175 struct TProtocolPair(InputProtocol, OutputProtocol) if (
176   isTProtocol!InputProtocol && isTProtocol!OutputProtocol
177 ) {}
178 
179 /**
180  * true if T is a TProtocolPair.
181  */
182 template isTProtocolPair(T) {
183   static if (is(T _ == TProtocolPair!(I, O), I, O)) {
184     enum isTProtocolPair = true;
185   } else {
186     enum isTProtocolPair = false;
187   }
188 }
189 
190 unittest {
191   static assert(isTProtocolPair!(TProtocolPair!(TProtocol, TProtocol)));
192   static assert(!isTProtocolPair!TProtocol);
193 }
194 
195 /**
196  * true if T is a TProtocol or a TProtocolPair.
197  */
198 template isTProtocolOrPair(T) {
199   enum isTProtocolOrPair = isTProtocol!T || isTProtocolPair!T;
200 }
201 
202 unittest {
203   static assert(isTProtocolOrPair!TProtocol);
204   static assert(isTProtocolOrPair!(TProtocolPair!(TProtocol, TProtocol)));
205   static assert(!isTProtocolOrPair!void);
206 }
207 
208 /**
209  * true if T represents a Thrift service.
210  */
211 template isService(T) {
212   enum isService = isBaseService!T || isDerivedService!T;
213 }
214 
215 /**
216  * true if T represents a Thrift service not derived from another service.
217  */
218 template isBaseService(T) {
219   static if(is(T _ == interface) &&
220     (!is(T TBases == super) || TBases.length == 0)
221   ) {
222     enum isBaseService = true;
223   } else {
224     enum isBaseService = false;
225   }
226 }
227 
228 /**
229  * true if T represents a Thrift service derived from another service.
230  */
231 template isDerivedService(T) {
232   static if(is(T _ == interface) &&
233     is(T TBases == super) && TBases.length == 1
234   ) {
235     enum isDerivedService = isService!(TBases[0]);
236   } else {
237     enum isDerivedService = false;
238   }
239 }
240 
241 /**
242  * For derived services, gets the base service interface.
243  */
244 template BaseService(T) if (isDerivedService!T) {
245   alias BaseTypeTuple!T[0] BaseService;
246 }
247 
248 
249 /*
250  * Code generation templates.
251  */
252 
253 /**
254  * Mixin template defining additional helper methods for using a struct with
255  * Thrift, and a member called isSetFlags if the struct contains any fields
256  * for which an »is set« flag is needed.
257  *
258  * It can only be used inside structs or Exception classes.
259  *
260  * For example, consider the following struct definition:
261  * ---
262  * struct Foo {
263  *   string a;
264  *   int b;
265  *   int c;
266  *
267  *   mixin TStructHelpers!([
268  *     TFieldMeta("a", 1), // Implicitly optional (nullable).
269  *     TFieldMeta("b", 2), // Implicitly required (non-nullable).
270  *     TFieldMeta("c", 3, TReq.REQUIRED, "4")
271  *   ]);
272  * }
273  * ---
274  *
275  * TStructHelper adds the following methods to the struct:
276  * ---
277  * /++
278  *  + Sets member fieldName to the given value and marks it as set.
279  *  +
280  *  + Examples:
281  *  + ---
282  *  + auto f = Foo();
283  *  + f.set!"b"(12345);
284  *  + assert(f.isSet!"b");
285  *  + ---
286  *  +/
287  * void set(string fieldName)(MemberType!(This, fieldName) value);
288  *
289  * /++
290  *  + Resets member fieldName to the init property of its type and marks it as
291  *  + not set.
292  *  +
293  *  + Examples:
294  *  + ---
295  *  + // Set f.b to some value.
296  *  + auto f = Foo();
297  *  + f.set!"b"(12345);
298  *  +
299  *  + f.unset!b();
300  *  +
301  *  + // f.b is now unset again.
302  *  + assert(!f.isSet!"b");
303  *  + ---
304  *  +/
305  * void unset(string fieldName)();
306  *
307  * /++
308  *  + Returns whether member fieldName is set.
309  *  +
310  *  + Examples:
311  *  + ---
312  *  + auto f = Foo();
313  *  + assert(!f.isSet!"b");
314  *  + f.set!"b"(12345);
315  *  + assert(f.isSet!"b");
316  *  + ---
317  *  +/
318  * bool isSet(string fieldName)() const @property;
319  *
320  * /++
321  *  + Returns a string representation of the struct.
322  *  +
323  *  + Examples:
324  *  + ---
325  *  + auto f = Foo();
326  *  + f.a = "a string";
327  *  + assert(f.toString() == `Foo("a string", 0 (unset), 4)`);
328  *  + ---
329  *  +/
330  * string toString() const;
331  *
332  * /++
333  *  + Deserializes the struct, setting its members to the values read from the
334  *  + protocol. Forwards to readStruct(this, proto);
335  *  +/
336  * void read(Protocol)(Protocol proto) if (isTProtocol!Protocol);
337  *
338  * /++
339  *  + Serializes the struct to the target protocol. Forwards to
340  *  + writeStruct(this, proto);
341  *  +/
342  * void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol);
343  * ---
344  *
345  * Additionally, an opEquals() implementation is provided which simply
346  * compares all fields, but disregards the is set struct, if any (the exact
347  * signature obviously differs between structs and exception classes). The
348  * metadata is stored in a manifest constant called fieldMeta.
349  *
350  * Note: To set the default values for fields where one has been specified in
351  * the field metadata, a parameterless static opCall is generated, because D
352  * does not allow parameterless (default) constructors for structs. Thus, be
353  * always to use to initialize structs:
354  * ---
355  * Foo foo; // Wrong!
356  * auto foo = Foo(); // Correct.
357  * ---
358  */
359 mixin template TStructHelpers(alias fieldMetaData = cast(TFieldMeta[])null) if (
360   is(typeof(fieldMetaData) : TFieldMeta[])
361 ) {
362   import std.algorithm : any;
363   import thrift.codegen.base;
364   import thrift.internal.codegen : isNullable, MemberType, mergeFieldMeta,
365     FieldNames;
366   import thrift.protocol.base : TProtocol, isTProtocol;
367 
368   alias typeof(this) This;
369   static assert(is(This == struct) || is(This : Exception),
370     "TStructHelpers can only be used inside a struct or an Exception class.");
371 
372   static if (TIsSetFlags!(This, fieldMetaData).tupleof.length > 0) {
373     // If we need to keep isSet flags around, create an instance of the
374     // container struct.
375     TIsSetFlags!(This, fieldMetaData) isSetFlags;
376     enum fieldMeta = fieldMetaData ~ [TFieldMeta("isSetFlags", 0, TReq.IGNORE)];
377   } else {
378     enum fieldMeta = fieldMetaData;
379   }
380 
381   void set(string fieldName)(MemberType!(This, fieldName) value) if (
382     is(MemberType!(This, fieldName))
383   ) {
384     __traits(getMember, this, fieldName) = value;
385     static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) {
386       __traits(getMember, this.isSetFlags, fieldName) = true;
387     }
388   }
389 
390   void unset(string fieldName)() if (is(MemberType!(This, fieldName))) {
391     static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) {
392       __traits(getMember, this.isSetFlags, fieldName) = false;
393     }
394     __traits(getMember, this, fieldName) = MemberType!(This, fieldName).init;
395   }
396 
397   bool isSet(string fieldName)() const @property if (
398     is(MemberType!(This, fieldName))
399   ) {
400     static if (isNullable!(MemberType!(This, fieldName))) {
401       return __traits(getMember, this, fieldName) !is null;
402     } else static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) {
403       return __traits(getMember, this.isSetFlags, fieldName);
404     } else {
405       // This is a required field, which is always set.
406       return true;
407     }
408   }
409 
410   static if (is(This _ == class)) {
411     override string toString() const {
412       return thriftToStringImpl();
413     }
414 
415     override bool opEquals(Object other) const {
416       auto rhs = cast(This)other;
417       if (rhs) {
418         return thriftOpEqualsImpl(rhs);
419       }
420 
421       return (cast()super).opEquals(other);
422     }
423 
424     override size_t toHash() const {
425       return thriftToHashImpl();
426     }
427   } else {
428     string toString() const {
429       return thriftToStringImpl();
430     }
431 
432     bool opEquals(ref const This other) const {
433       return thriftOpEqualsImpl(other);
434     }
435 
436     size_t toHash() const @safe nothrow {
437       return thriftToHashImpl();
438     }
439   }
440 
441   private string thriftToStringImpl() const {
442     import std.conv : to;
443     string result = This.stringof ~ "(";
444     mixin({
445       string code = "";
446       bool first = true;
447       foreach (name; FieldNames!(This, fieldMeta)) {
448         if (first) {
449           first = false;
450         } else {
451           code ~= "result ~= `, `;\n";
452         }
453         code ~= "result ~= `" ~ name ~ ": ` ~ to!string(cast()this." ~ name ~ ");\n";
454         code ~= "if (!isSet!q{" ~ name ~ "}) {\n";
455         code ~= "result ~= ` (unset)`;\n";
456         code ~= "}\n";
457       }
458       return code;
459     }());
460     result ~= ")";
461     return result;
462   }
463 
464   private bool thriftOpEqualsImpl(const ref This rhs) const {
465     foreach (name; FieldNames!This) {
466       if (mixin("this." ~ name) != mixin("rhs." ~ name)) return false;
467     }
468     return true;
469   }
470 
471   private size_t thriftToHashImpl() const @trusted nothrow {
472     size_t hash = 0;
473     foreach (i, _; this.tupleof) {
474       auto val = this.tupleof[i];
475       hash += typeid(val).getHash(&val);
476     }
477     return hash;
478   }
479 
480   static if (any!`!a.defaultValue.empty`(mergeFieldMeta!(This, fieldMetaData))) {
481     static if (is(This _ == class)) {
482       this() {
483         mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("this"));
484       }
485     } else {
486       // DMD @@BUG@@: Have to use auto here to avoid »no size yet for forward
487       // reference« errors.
488       static auto opCall() {
489         auto result = This.init;
490         mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("result"));
491         return result;
492       }
493     }
494   }
495 
496   void read(Protocol)(Protocol proto) if (isTProtocol!Protocol) {
497     // Need to explicitly specify fieldMetaData here, since it isn't already
498     // picked up in some situations (e.g. the TArgs struct for methods with
499     // multiple parameters in async_test_servers) otherwise. Due to a DMD
500     // @@BUG@@, we need to explicitly specify the other template parameters
501     // as well.
502     readStruct!(This, Protocol, fieldMetaData, false)(this, proto);
503   }
504 
505   void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol) {
506     writeStruct!(This, Protocol, fieldMetaData, false)(this, proto);
507   }
508 }
509 
510 // DMD @@BUG@@: Having this inside TStructHelpers leads to weird lookup errors
511 // (e.g. for std.arry.empty).
512 string thriftFieldInitCode(alias fieldMeta)(string thisName) {
513   string code = "";
514   foreach (field; fieldMeta) {
515     if (field.defaultValue.empty) continue;
516     code ~= thisName ~ "." ~ field.name ~ " = " ~ field.defaultValue ~ ";\n";
517   }
518   return code;
519 }
520 
521 unittest {
522   // Cannot make this nested in the unittest block due to a »no size yet for
523   // forward reference« error.
524   static struct Foo {
525     string a;
526     int b;
527     int c;
528 
529     mixin TStructHelpers!([
530       TFieldMeta("a", 1),
531       TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT),
532       TFieldMeta("c", 3, TReq.REQUIRED, "4")
533     ]);
534   }
535 
536   auto f = Foo();
537 
538   f.set!"b"(12345);
539   assert(f.isSet!"b");
540   f.unset!"b"();
541   assert(!f.isSet!"b");
542   f.set!"b"(12345);
543   assert(f.isSet!"b");
544   f.unset!"b"();
545 
546   f.a = "a string";
547   assert(f.toString() == `Foo(a: a string, b: 0 (unset), c: 4)`);
548 }
549 
550 
551 /**
552  * Generates an eponymous struct with boolean flags for the non-required
553  * non-nullable fields of T.
554  *
555  * Nullable fields are just set to null to signal »not set«, so no flag is
556  * emitted for them, even if they are optional.
557  *
558  * In most cases, you do not want to use this directly, but via TStructHelpers
559  * instead.
560  */
561 template TIsSetFlags(T, alias fieldMetaData) {
562   mixin({
563     string code = "struct TIsSetFlags {\n";
564     foreach (meta; fieldMetaData) {
565       code ~= "static if (!is(MemberType!(T, `" ~ meta.name ~ "`))) {\n";
566       code ~= q{
567         static assert(false, "Field '" ~ meta.name ~
568           "' referenced in metadata not present in struct '" ~ T.stringof ~ "'.");
569       };
570       code ~= "}";
571       if (meta.req == TReq.OPTIONAL || meta.req == TReq.OPT_IN_REQ_OUT) {
572         code ~= "else static if (!isNullable!(MemberType!(T, `" ~ meta.name ~ "`))) {\n";
573         code ~= "  bool " ~ meta.name ~ ";\n";
574         code ~= "}\n";
575       }
576     }
577     code ~= "}";
578     return code;
579   }());
580 }
581 
582 /**
583  * Deserializes a Thrift struct from a protocol.
584  *
585  * Using the Protocol template parameter, the concrete TProtocol to use can be
586  * be specified. If the pointerStruct parameter is set to true, the struct
587  * fields are expected to be pointers to the actual data. This is used
588  * internally (combined with TPResultStruct) and usually should not be used in
589  * user code.
590  *
591  * This is a free function to make it possible to read exisiting structs from
592  * the wire without altering their definitions.
593  */
594 void readStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null,
595   bool pointerStruct = false)(auto ref T s, Protocol p) if (isTProtocol!Protocol)
596 {
597   mixin({
598     string code;
599 
600     // Check that all fields for which there is meta info are actually in the
601     // passed struct type.
602     foreach (field; mergeFieldMeta!(T, fieldMetaData)) {
603       code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
604     }
605 
606     // Returns the code string for reading a value of type F off the wire and
607     // assigning it to v. The level parameter is used to make sure that there
608     // are no conflicting variable names on recursive calls.
609     string readValueCode(ValueType)(string v, size_t level = 0) {
610       // Some non-ambigous names to use (shadowing is not allowed in D).
611       immutable i = "i" ~ to!string(level);
612       immutable elem = "elem" ~ to!string(level);
613       immutable key = "key" ~ to!string(level);
614       immutable list = "list" ~ to!string(level);
615       immutable map = "map" ~ to!string(level);
616       immutable set = "set" ~ to!string(level);
617       immutable value = "value" ~ to!string(level);
618 
619       alias FullyUnqual!ValueType F;
620 
621       static if (is(F == bool)) {
622         return v ~ " = p.readBool();";
623       } else static if (is(F == byte)) {
624         return v ~ " = p.readByte();";
625       } else static if (is(F == double)) {
626         return v ~ " = p.readDouble();";
627       } else static if (is(F == short)) {
628         return v ~ " = p.readI16();";
629       } else static if (is(F == int)) {
630         return v ~ " = p.readI32();";
631       } else static if (is(F == long)) {
632         return v ~ " = p.readI64();";
633       } else static if (is(F : string)) {
634         return v ~ " = p.readString();";
635       } else static if (is(F == enum)) {
636         return v ~ " = cast(typeof(" ~ v ~ "))p.readI32();";
637       } else static if (is(F _ : E[], E)) {
638         return "{\n" ~
639           "auto " ~ list ~ " = p.readListBegin();\n" ~
640           // TODO: Check element type here?
641           v ~ " = new typeof(" ~ v ~ "[0])[" ~ list ~ ".size];\n" ~
642           "foreach (" ~ i ~ "; 0 .. " ~ list ~ ".size) {\n" ~
643             readValueCode!E(v ~ "[" ~ i ~ "]", level + 1) ~ "\n" ~
644           "}\n" ~
645           "p.readListEnd();\n" ~
646         "}";
647       } else static if (is(F _ : V[K], K, V)) {
648         return "{\n" ~
649           "auto " ~ map ~ " = p.readMapBegin();" ~
650           v ~ " = null;\n" ~
651           // TODO: Check key/value types here?
652           "foreach (" ~ i ~ "; 0 .. " ~ map ~ ".size) {\n" ~
653             "FullyUnqual!(typeof(" ~ v ~ ".keys[0])) " ~ key ~ ";\n" ~
654             readValueCode!K(key, level + 1) ~ "\n" ~
655             "typeof(" ~ v ~ ".values[0]) " ~ value ~ ";\n" ~
656             readValueCode!V(value, level + 1) ~ "\n" ~
657             v ~ "[cast(typeof(" ~ v ~ ".keys[0]))" ~ key ~ "] = " ~ value ~ ";\n" ~
658           "}\n" ~
659           "p.readMapEnd();" ~
660         "}";
661       } else static if (is(F _ : HashSet!(E), E)) {
662         return "{\n" ~
663           "auto " ~ set ~ " = p.readSetBegin();" ~
664           // TODO: Check element type here?
665           v ~ " = new typeof(" ~ v ~ ")();\n" ~
666           "foreach (" ~ i ~ "; 0 .. " ~ set ~ ".size) {\n" ~
667             "typeof(" ~ v ~ "[][0]) " ~ elem ~ ";\n" ~
668             readValueCode!E(elem, level + 1) ~ "\n" ~
669             v ~ " ~= " ~ elem ~ ";\n" ~
670           "}\n" ~
671           "p.readSetEnd();" ~
672         "}";
673       } else static if (is(F == struct) || is(F : TException)) {
674         static if (is(F == struct)) {
675           auto result = v ~ " = typeof(" ~ v ~ ")();\n";
676         } else {
677           auto result = v ~ " = new typeof(" ~ v ~ ")();\n";
678         }
679 
680         static if (__traits(compiles, F.init.read(TProtocol.init))) {
681           result ~= v ~ ".read(p);";
682         } else {
683           result ~= "readStruct(" ~ v ~ ", p);";
684         }
685         return result;
686       } else {
687         static assert(false, "Cannot represent type in Thrift: " ~ F.stringof);
688       }
689     }
690 
691     string readFieldCode(FieldType)(string name, short id, TReq req) {
692       static if (pointerStruct && isPointer!FieldType) {
693         immutable v = "(*s." ~ name ~ ")";
694         alias PointerTarget!FieldType F;
695       } else {
696         immutable v = "s." ~ name;
697         alias FieldType F;
698       }
699 
700       string code = "case " ~ to!string(id) ~ ":\n";
701       code ~= "if (f.type == " ~ dToTTypeString!F ~ ") {\n";
702       code ~= readValueCode!F(v) ~ "\n";
703       if (req == TReq.REQUIRED) {
704         // For required fields, set the corresponding local isSet variable.
705         code ~= "isSet_" ~ name ~ " = true;\n";
706       } else if (!isNullable!F){
707         code ~= "s.isSetFlags." ~ name ~ " = true;\n";
708       }
709       code ~= "} else skip(p, f.type);\n";
710       code ~= "break;\n";
711       return code;
712     }
713 
714     // Code for the local boolean flags used to make sure required fields have
715     // been found.
716     string isSetFlagCode = "";
717 
718     // Code for checking whether the flags for the required fields are true.
719     string isSetCheckCode = "";
720 
721     /// Code for the case statements storing the fields to the result struct.
722     string readMembersCode = "";
723 
724     // The last automatically assigned id – fields with no meta information
725     // are assigned (in lexical order) descending negative ids, starting with
726     // -1, just like the Thrift compiler does.
727     short lastId;
728 
729     foreach (name; FieldNames!T) {
730       enum req = memberReq!(T, name, fieldMetaData);
731       if (req == TReq.REQUIRED) {
732         // For required fields, generate local bool flags to keep track
733         // whether the field has been encountered.
734         immutable n = "isSet_" ~ name;
735         isSetFlagCode ~= "bool " ~ n ~ ";\n";
736         isSetCheckCode ~= "enforce(" ~ n ~ ", new TProtocolException(" ~
737           "`Required field '" ~ name ~ "' not found in serialized data`, " ~
738           "TProtocolException.Type.INVALID_DATA));\n";
739       }
740 
741       enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
742       static if (meta.empty) {
743         --lastId;
744         version (TVerboseCodegen) {
745           code ~= "pragma(msg, `[thrift.codegen.base.readStruct] Warning: No " ~
746             "meta information for field '" ~ name ~ "' in struct '" ~
747             T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n";
748         }
749         readMembersCode ~= readFieldCode!(MemberType!(T, name))(
750           name, lastId, req);
751       } else static if (req != TReq.IGNORE) {
752         readMembersCode ~= readFieldCode!(MemberType!(T, name))(
753           name, meta.front.id, req);
754       }
755     }
756 
757     code ~= isSetFlagCode;
758     code ~= "p.readStructBegin();\n";
759     code ~= "while (true) {\n";
760     code ~= "auto f = p.readFieldBegin();\n";
761     code ~= "if (f.type == TType.STOP) break;\n";
762     code ~= "switch(f.id) {\n";
763     code ~= readMembersCode;
764     code ~= "default: skip(p, f.type);\n";
765     code ~= "}\n";
766     code ~= "p.readFieldEnd();\n";
767     code ~= "}\n";
768     code ~= "p.readStructEnd();\n";
769     code ~= isSetCheckCode;
770 
771     return code;
772   }());
773 }
774 
775 /**
776  * Serializes a struct to the target protocol.
777  *
778  * Using the Protocol template parameter, the concrete TProtocol to use can be
779  * be specified. If the pointerStruct parameter is set to true, the struct
780  * fields are expected to be pointers to the actual data. This is used
781  * internally (combined with TPargsStruct) and usually should not be used in
782  * user code.
783  *
784  * This is a free function to make it possible to read exisiting structs from
785  * the wire without altering their definitions.
786  */
787 void writeStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null,
788   bool pointerStruct = false) (const T s, Protocol p) if (isTProtocol!Protocol)
789 {
790   mixin({
791     // Check that all fields for which there is meta info are actually in the
792     // passed struct type.
793     string code = "";
794     foreach (field; mergeFieldMeta!(T, fieldMetaData)) {
795       code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
796     }
797 
798     // Check that required nullable members are non-null.
799     // WORKAROUND: To stop LDC from emitting the manifest constant »meta« below
800     // into the writeStruct function body this is inside the string mixin
801     // block – the code wouldn't depend on it (this is an LDC bug, and because
802     // of it a new array would be allocated on each method invocation at runtime).
803     foreach (name; StaticFilter!(
804       Compose!(isNullable, PApply!(MemberType, T)),
805       FieldNames!T
806     )) {
807        static if (memberReq!(T, name, fieldMetaData) == TReq.REQUIRED) {
808          code ~= "enforce(__traits(getMember, s, `" ~ name ~ "`) !is null,
809            new TException(`Required field '" ~ name ~ "' is null.`));\n";
810        }
811     }
812 
813     return code;
814   }());
815 
816   p.writeStructBegin(TStruct(T.stringof));
817   mixin({
818     string writeValueCode(ValueType)(string v, size_t level = 0) {
819       // Some non-ambigous names to use (shadowing is not allowed in D).
820       immutable elem = "elem" ~ to!string(level);
821       immutable key = "key" ~ to!string(level);
822       immutable value = "value" ~ to!string(level);
823 
824       alias FullyUnqual!ValueType F;
825       static if (is(F == bool)) {
826         return "p.writeBool(" ~ v ~ ");";
827       } else static if (is(F == byte)) {
828         return "p.writeByte(" ~ v ~ ");";
829       } else static if (is(F == double)) {
830         return "p.writeDouble(" ~ v ~ ");";
831       } else static if (is(F == short)) {
832         return "p.writeI16(" ~ v ~ ");";
833       } else static if (is(F == int)) {
834         return "p.writeI32(" ~ v ~ ");";
835       } else static if (is(F == long)) {
836         return "p.writeI64(" ~ v ~ ");";
837       } else static if (is(F : string)) {
838         return "p.writeString(" ~ v ~ ");";
839       } else static if (is(F == enum)) {
840         return "p.writeI32(cast(int)" ~ v ~ ");";
841       } else static if (is(F _ : E[], E)) {
842         return "p.writeListBegin(TList(" ~ dToTTypeString!E ~ ", " ~ v ~
843           ".length));\n" ~
844           "foreach (" ~ elem ~ "; " ~ v ~ ") {\n" ~
845             writeValueCode!E(elem, level + 1) ~ "\n" ~
846           "}\n" ~
847           "p.writeListEnd();";
848       } else static if (is(F _ : V[K], K, V)) {
849         return "p.writeMapBegin(TMap(" ~ dToTTypeString!K ~ ", " ~
850           dToTTypeString!V ~ ", " ~ v ~ ".length));\n" ~
851           "foreach (" ~ key ~ ", " ~ value ~ "; " ~ v ~ ") {\n" ~
852             writeValueCode!K(key, level + 1) ~ "\n" ~
853             writeValueCode!V(value, level + 1) ~ "\n" ~
854           "}\n" ~
855           "p.writeMapEnd();";
856       } else static if (is(F _ : HashSet!E, E)) {
857         return "p.writeSetBegin(TSet(" ~ dToTTypeString!E ~ ", " ~ v ~
858           ".length));\n" ~
859           "foreach (" ~ elem ~ "; " ~ v ~ "[]) {\n" ~
860             writeValueCode!E(elem, level + 1) ~ "\n" ~
861           "}\n" ~
862           "p.writeSetEnd();";
863       } else static if (is(F == struct) || is(F : TException)) {
864         static if (__traits(compiles, F.init.write(TProtocol.init))) {
865           return v ~ ".write(p);";
866         } else {
867           return "writeStruct(" ~ v ~ ", p);";
868         }
869       } else {
870         static assert(false, "Cannot represent type in Thrift: " ~ F.stringof);
871       }
872     }
873 
874     string writeFieldCode(FieldType)(string name, short id, TReq req) {
875       string code;
876       if (!pointerStruct && req == TReq.OPTIONAL) {
877         code ~= "if (s.isSet!`" ~ name ~ "`) {\n";
878       }
879 
880       static if (pointerStruct && isPointer!FieldType) {
881         immutable v = "(*s." ~ name ~ ")";
882         alias PointerTarget!FieldType F;
883       } else {
884         immutable v = "s." ~ name;
885         alias FieldType F;
886       }
887 
888       code ~= "p.writeFieldBegin(TField(`" ~ name ~ "`, " ~ dToTTypeString!F ~
889         ", " ~ to!string(id) ~ "));\n";
890       code ~= writeValueCode!F(v) ~ "\n";
891       code ~= "p.writeFieldEnd();\n";
892 
893       if (!pointerStruct && req == TReq.OPTIONAL) {
894         code ~= "}\n";
895       }
896       return code;
897     }
898 
899     // The last automatically assigned id – fields with no meta information
900     // are assigned (in lexical order) descending negative ids, starting with
901     // -1, just like the Thrift compiler does.
902     short lastId;
903 
904     string code = "";
905     foreach (name; FieldNames!T) {
906       alias MemberType!(T, name) F;
907       enum req = memberReq!(T, name, fieldMetaData);
908       enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
909       if (meta.empty) {
910         --lastId;
911         version (TVerboseCodegen) {
912           code ~= "pragma(msg, `[thrift.codegen.base.writeStruct] Warning: No " ~
913             "meta information for field '" ~ name ~ "' in struct '" ~
914             T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n";
915         }
916         code ~= writeFieldCode!F(name, lastId, req);
917       } else if (req != TReq.IGNORE) {
918         code ~= writeFieldCode!F(name, meta.front.id, req);
919       }
920     }
921 
922     return code;
923   }());
924   p.writeFieldStop();
925   p.writeStructEnd();
926 }
927 
928 unittest {
929   // Ensure that the generated code at least compiles for the basic field type
930   // combinations. Functionality checks are covered by the rest of the test
931   // suite.
932 
933   static struct Test {
934     // Non-nullable.
935     int a1;
936     int a2;
937     int a3;
938     int a4;
939 
940     // Nullable.
941     string b1;
942     string b2;
943     string b3;
944     string b4;
945 
946     mixin TStructHelpers!([
947       TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT),
948       TFieldMeta("a2", 2, TReq.OPTIONAL),
949       TFieldMeta("a3", 3, TReq.REQUIRED),
950       TFieldMeta("a4", 4, TReq.IGNORE),
951       TFieldMeta("b1", 5, TReq.OPT_IN_REQ_OUT),
952       TFieldMeta("b2", 6, TReq.OPTIONAL),
953       TFieldMeta("b3", 7, TReq.REQUIRED),
954       TFieldMeta("b4", 8, TReq.IGNORE),
955     ]);
956   }
957 
958   static assert(__traits(compiles, { Test t; t.read(cast(TProtocol)null); }));
959   static assert(__traits(compiles, { Test t; t.write(cast(TProtocol)null); }));
960 }
961 
962 // Ensure opEquals and toHash consistency.
963 unittest {
964   struct TestEquals {
965     int a1;
966 
967     mixin TStructHelpers!([
968       TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT),
969     ]);
970   }
971 
972   TestEquals a, b;
973   assert(a == b);
974   assert(a.toHash() == b.toHash());
975 
976   a.a1 = 42;
977   assert(a != b);
978   assert(a.toHash() != b.toHash());
979 
980   b.a1 = 42;
981   assert(a == b);
982   assert(a.toHash() == b.toHash());
983 }
984 
985 private {
986   /*
987    * Returns a D code string containing the matching TType value for a passed
988    * D type, e.g. dToTTypeString!byte == "TType.BYTE".
989    */
990   template dToTTypeString(T) {
991     static if (is(FullyUnqual!T == bool)) {
992       enum dToTTypeString = "TType.BOOL";
993     } else static if (is(FullyUnqual!T == byte)) {
994       enum dToTTypeString = "TType.BYTE";
995     } else static if (is(FullyUnqual!T == double)) {
996       enum dToTTypeString = "TType.DOUBLE";
997     } else static if (is(FullyUnqual!T == short)) {
998       enum dToTTypeString = "TType.I16";
999     } else static if (is(FullyUnqual!T == int)) {
1000       enum dToTTypeString = "TType.I32";
1001     } else static if (is(FullyUnqual!T == long)) {
1002       enum dToTTypeString = "TType.I64";
1003     } else static if (is(FullyUnqual!T : string)) {
1004       enum dToTTypeString = "TType.STRING";
1005     } else static if (is(FullyUnqual!T == enum)) {
1006       enum dToTTypeString = "TType.I32";
1007     } else static if (is(FullyUnqual!T _ : U[], U)) {
1008       enum dToTTypeString = "TType.LIST";
1009     } else static if (is(FullyUnqual!T _ : V[K], K, V)) {
1010       enum dToTTypeString = "TType.MAP";
1011     } else static if (is(FullyUnqual!T _ : HashSet!E, E)) {
1012       enum dToTTypeString = "TType.SET";
1013     } else static if (is(FullyUnqual!T == struct)) {
1014       enum dToTTypeString = "TType.STRUCT";
1015     } else static if (is(FullyUnqual!T : TException)) {
1016       enum dToTTypeString = "TType.STRUCT";
1017     } else {
1018       static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
1019     }
1020   }
1021 }