blob: db549928c251dad8b39eea015d049950f2921394 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Code generation metadata and templates used for implementing struct
* serialization.
*
* Many templates can be customized using field meta data, which is read from
* a manifest constant member of the given type called fieldMeta (if present),
* and is concatenated with the elements from the optional fieldMetaData
* template alias parameter.
*
* Some code generation templates take account of the optional TVerboseCodegen
* version declaration, which causes warning messages to be emitted if no
* metadata for a field/method has been found and the default behavior is
* used instead. If this version is not defined, the templates just silently
* behave like the Thrift compiler does in this situation, i.e. automatically
* assign negative ids (starting at -1) for fields and assume TReq.AUTO as
* requirement level.
*/
// Implementation note: All the templates in here taking a field metadata
// parameter should ideally have a constraint that restricts the alias to
// TFieldMeta[]-typed values, but the is() expressions seems to always fail.
module thrift.codegen.base;
import std.algorithm : find;
import std.array : empty, front;
import std.conv : to;
import std.exception : enforce;
import std.traits : BaseTypeTuple, isPointer, isSomeFunction, PointerTarget,
ReturnType;
import thrift.base;
import thrift.internal.codegen;
import thrift.protocol.base;
import thrift.util.hashset;
/*
* Thrift struct/service meta data, which is used to store information from
* the interface definition files not representable in plain D, i.e. field
* requirement levels, Thrift field IDs, etc.
*/
/**
* Struct field requirement levels.
*/
enum TReq {
/// Detect the requiredness from the field type: if it is nullable, treat
/// the field as optional, if it is non-nullable, treat the field as
/// required. This is the default used for handling structs not generated
/// from an IDL file, and never emitted by the Thrift compiler. TReq.AUTO
/// shouldn't be specified explicitly.
// Implementation note: thrift.codegen templates use
// thrift.internal.codegen.memberReq to resolve AUTO to REQUIRED/OPTIONAL
// instead of handling it directly.
AUTO,
/// The field is treated as optional when deserializing/receiving the struct
/// and as required when serializing/sending. This is the Thrift default if
/// neither "required" nor "optional" are specified in the IDL file.
OPT_IN_REQ_OUT,
/// The field is optional.
OPTIONAL,
/// The field is required.
REQUIRED,
/// Ignore the struct field when serializing/deserializing.
IGNORE
}
/**
* The way how methods are called.
*/
enum TMethodType {
/// Called in the normal two-way scheme consisting of a request and a
/// response.
REGULAR,
/// A fire-and-forget one-way method, where no response is sent and the
/// client immediately returns.
ONEWAY
}
/**
* Compile-time metadata for a struct field.
*/
struct TFieldMeta {
/// The name of the field. Used for matching a TFieldMeta with the actual
/// D struct member during code generation.
string name;
/// The (Thrift) id of the field.
short id;
/// Whether the field is requried.
TReq req;
/// A code string containing a D expression for the default value, if there
/// is one.
string defaultValue;
}
/**
* Compile-time metadata for a service method.
*/
struct TMethodMeta {
/// The name of the method. Used for matching a TMethodMeta with the actual
/// method during code generation.
string name;
/// Meta information for the parameteres.
TParamMeta[] params;
/// Specifies which exceptions can be thrown by the method. All other
/// exceptions are converted to a TApplicationException instead.
TExceptionMeta[] exceptions;
/// The fundamental type of the method.
TMethodType type;
}
/**
* Compile-time metadata for a service method parameter.
*/
struct TParamMeta {
/// The name of the parameter. Contrary to TFieldMeta, it only serves
/// decorative purposes here.
string name;
/// The Thrift id of the parameter in the param struct.
short id;
/// A code string containing a D expression for the default value for the
/// parameter, if any.
string defaultValue;
}
/**
* Compile-time metadata for a service method exception annotation.
*/
struct TExceptionMeta {
/// The name of the exception »return value«. Contrary to TFieldMeta, it
/// only serves decorative purposes here, as it is only used in code not
/// visible to processor implementations/service clients.
string name;
/// The Thrift id of the exception field in the return value struct.
short id;
/// The name of the exception type.
string type;
}
/**
* A pair of two TPorotocols. To be used in places where a list of protocols
* is expected, for specifying different protocols for input and output.
*/
struct TProtocolPair(InputProtocol, OutputProtocol) if (
isTProtocol!InputProtocol && isTProtocol!OutputProtocol
) {}
/**
* true if T is a TProtocolPair.
*/
template isTProtocolPair(T) {
static if (is(T _ == TProtocolPair!(I, O), I, O)) {
enum isTProtocolPair = true;
} else {
enum isTProtocolPair = false;
}
}
unittest {
static assert(isTProtocolPair!(TProtocolPair!(TProtocol, TProtocol)));
static assert(!isTProtocolPair!TProtocol);
}
/**
* true if T is a TProtocol or a TProtocolPair.
*/
template isTProtocolOrPair(T) {
enum isTProtocolOrPair = isTProtocol!T || isTProtocolPair!T;
}
unittest {
static assert(isTProtocolOrPair!TProtocol);
static assert(isTProtocolOrPair!(TProtocolPair!(TProtocol, TProtocol)));
static assert(!isTProtocolOrPair!void);
}
/**
* true if T represents a Thrift service.
*/
template isService(T) {
enum isService = isBaseService!T || isDerivedService!T;
}
/**
* true if T represents a Thrift service not derived from another service.
*/
template isBaseService(T) {
static if(is(T _ == interface) &&
(!is(T TBases == super) || TBases.length == 0)
) {
enum isBaseService = true;
} else {
enum isBaseService = false;
}
}
/**
* true if T represents a Thrift service derived from another service.
*/
template isDerivedService(T) {
static if(is(T _ == interface) &&
is(T TBases == super) && TBases.length == 1
) {
enum isDerivedService = isService!(TBases[0]);
} else {
enum isDerivedService = false;
}
}
/**
* For derived services, gets the base service interface.
*/
template BaseService(T) if (isDerivedService!T) {
alias BaseTypeTuple!T[0] BaseService;
}
/*
* Code generation templates.
*/
/**
* Mixin template defining additional helper methods for using a struct with
* Thrift, and a member called isSetFlags if the struct contains any fields
* for which an »is set« flag is needed.
*
* It can only be used inside structs or Exception classes.
*
* For example, consider the following struct definition:
* ---
* struct Foo {
* string a;
* int b;
* int c;
*
* mixin TStructHelpers!([
* TFieldMeta("a", 1), // Implicitly optional (nullable).
* TFieldMeta("b", 2), // Implicitly required (non-nullable).
* TFieldMeta("c", 3, TReq.REQUIRED, "4")
* ]);
* }
* ---
*
* TStructHelper adds the following methods to the struct:
* ---
* /++
* + Sets member fieldName to the given value and marks it as set.
* +
* + Examples:
* + ---
* + auto f = Foo();
* + f.set!"b"(12345);
* + assert(f.isSet!"b");
* + ---
* +/
* void set(string fieldName)(MemberType!(This, fieldName) value);
*
* /++
* + Resets member fieldName to the init property of its type and marks it as
* + not set.
* +
* + Examples:
* + ---
* + // Set f.b to some value.
* + auto f = Foo();
* + f.set!"b"(12345);
* +
* + f.unset!b();
* +
* + // f.b is now unset again.
* + assert(!f.isSet!"b");
* + ---
* +/
* void unset(string fieldName)();
*
* /++
* + Returns whether member fieldName is set.
* +
* + Examples:
* + ---
* + auto f = Foo();
* + assert(!f.isSet!"b");
* + f.set!"b"(12345);
* + assert(f.isSet!"b");
* + ---
* +/
* bool isSet(string fieldName)() const @property;
*
* /++
* + Returns a string representation of the struct.
* +
* + Examples:
* + ---
* + auto f = Foo();
* + f.a = "a string";
* + assert(f.toString() == `Foo("a string", 0 (unset), 4)`);
* + ---
* +/
* string toString() const;
*
* /++
* + Deserializes the struct, setting its members to the values read from the
* + protocol. Forwards to readStruct(this, proto);
* +/
* void read(Protocol)(Protocol proto) if (isTProtocol!Protocol);
*
* /++
* + Serializes the struct to the target protocol. Forwards to
* + writeStruct(this, proto);
* +/
* void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol);
* ---
*
* Additionally, an opEquals() implementation is provided which simply
* compares all fields, but disregards the is set struct, if any (the exact
* signature obviously differs between structs and exception classes). The
* metadata is stored in a manifest constant called fieldMeta.
*
* Note: To set the default values for fields where one has been specified in
* the field metadata, a parameterless static opCall is generated, because D
* does not allow parameterless (default) constructors for structs. Thus, be
* always to use to initialize structs:
* ---
* Foo foo; // Wrong!
* auto foo = Foo(); // Correct.
* ---
*/
mixin template TStructHelpers(alias fieldMetaData = cast(TFieldMeta[])null) if (
is(typeof(fieldMetaData) : TFieldMeta[])
) {
import std.algorithm : any;
import thrift.codegen.base;
import thrift.internal.codegen : isNullable, MemberType, mergeFieldMeta,
FieldNames;
import thrift.protocol.base : TProtocol, isTProtocol;
alias typeof(this) This;
static assert(is(This == struct) || is(This : Exception),
"TStructHelpers can only be used inside a struct or an Exception class.");
static if (TIsSetFlags!(This, fieldMetaData).tupleof.length > 0) {
// If we need to keep isSet flags around, create an instance of the
// container struct.
TIsSetFlags!(This, fieldMetaData) isSetFlags;
enum fieldMeta = fieldMetaData ~ [TFieldMeta("isSetFlags", 0, TReq.IGNORE)];
} else {
enum fieldMeta = fieldMetaData;
}
void set(string fieldName)(MemberType!(This, fieldName) value) if (
is(MemberType!(This, fieldName))
) {
__traits(getMember, this, fieldName) = value;
static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) {
__traits(getMember, this.isSetFlags, fieldName) = true;
}
}
void unset(string fieldName)() if (is(MemberType!(This, fieldName))) {
static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) {
__traits(getMember, this.isSetFlags, fieldName) = false;
}
__traits(getMember, this, fieldName) = MemberType!(This, fieldName).init;
}
bool isSet(string fieldName)() const @property if (
is(MemberType!(This, fieldName))
) {
static if (isNullable!(MemberType!(This, fieldName))) {
return __traits(getMember, this, fieldName) !is null;
} else static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) {
return __traits(getMember, this.isSetFlags, fieldName);
} else {
// This is a required field, which is always set.
return true;
}
}
static if (is(This _ == class)) {
override string toString() const {
return thriftToStringImpl();
}
override bool opEquals(Object other) const {
auto rhs = cast(This)other;
if (rhs) {
return thriftOpEqualsImpl(rhs);
}
return (cast()super).opEquals(other);
}
override size_t toHash() const {
return thriftToHashImpl();
}
} else {
string toString() const {
return thriftToStringImpl();
}
bool opEquals(ref const This other) const {
return thriftOpEqualsImpl(other);
}
size_t toHash() const @safe nothrow {
return thriftToHashImpl();
}
}
private string thriftToStringImpl() const {
import std.conv : to;
string result = This.stringof ~ "(";
mixin({
string code = "";
bool first = true;
foreach (name; FieldNames!(This, fieldMeta)) {
if (first) {
first = false;
} else {
code ~= "result ~= `, `;\n";
}
code ~= "result ~= `" ~ name ~ ": ` ~ to!string(cast()this." ~ name ~ ");\n";
code ~= "if (!isSet!q{" ~ name ~ "}) {\n";
code ~= "result ~= ` (unset)`;\n";
code ~= "}\n";
}
return code;
}());
result ~= ")";
return result;
}
private bool thriftOpEqualsImpl(const ref This rhs) const {
foreach (name; FieldNames!This) {
if (mixin("this." ~ name) != mixin("rhs." ~ name)) return false;
}
return true;
}
private size_t thriftToHashImpl() const @trusted nothrow {
size_t hash = 0;
foreach (i, _; this.tupleof) {
auto val = this.tupleof[i];
hash += typeid(val).getHash(&val);
}
return hash;
}
static if (any!`!a.defaultValue.empty`(mergeFieldMeta!(This, fieldMetaData))) {
static if (is(This _ == class)) {
this() {
mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("this"));
}
} else {
// DMD @@BUG@@: Have to use auto here to avoid »no size yet for forward
// reference« errors.
static auto opCall() {
auto result = This.init;
mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("result"));
return result;
}
}
}
void read(Protocol)(Protocol proto) if (isTProtocol!Protocol) {
// Need to explicitly specify fieldMetaData here, since it isn't already
// picked up in some situations (e.g. the TArgs struct for methods with
// multiple parameters in async_test_servers) otherwise. Due to a DMD
// @@BUG@@, we need to explicitly specify the other template parameters
// as well.
readStruct!(This, Protocol, fieldMetaData, false)(this, proto);
}
void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol) {
writeStruct!(This, Protocol, fieldMetaData, false)(this, proto);
}
}
// DMD @@BUG@@: Having this inside TStructHelpers leads to weird lookup errors
// (e.g. for std.arry.empty).
string thriftFieldInitCode(alias fieldMeta)(string thisName) {
string code = "";
foreach (field; fieldMeta) {
if (field.defaultValue.empty) continue;
code ~= thisName ~ "." ~ field.name ~ " = " ~ field.defaultValue ~ ";\n";
}
return code;
}
unittest {
// Cannot make this nested in the unittest block due to a »no size yet for
// forward reference« error.
static struct Foo {
string a;
int b;
int c;
mixin TStructHelpers!([
TFieldMeta("a", 1),
TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT),
TFieldMeta("c", 3, TReq.REQUIRED, "4")
]);
}
auto f = Foo();
f.set!"b"(12345);
assert(f.isSet!"b");
f.unset!"b"();
assert(!f.isSet!"b");
f.set!"b"(12345);
assert(f.isSet!"b");
f.unset!"b"();
f.a = "a string";
assert(f.toString() == `Foo(a: a string, b: 0 (unset), c: 4)`);
}
/**
* Generates an eponymous struct with boolean flags for the non-required
* non-nullable fields of T.
*
* Nullable fields are just set to null to signal »not set«, so no flag is
* emitted for them, even if they are optional.
*
* In most cases, you do not want to use this directly, but via TStructHelpers
* instead.
*/
template TIsSetFlags(T, alias fieldMetaData) {
mixin({
string code = "struct TIsSetFlags {\n";
foreach (meta; fieldMetaData) {
code ~= "static if (!is(MemberType!(T, `" ~ meta.name ~ "`))) {\n";
code ~= q{
static assert(false, "Field '" ~ meta.name ~
"' referenced in metadata not present in struct '" ~ T.stringof ~ "'.");
};
code ~= "}";
if (meta.req == TReq.OPTIONAL || meta.req == TReq.OPT_IN_REQ_OUT) {
code ~= "else static if (!isNullable!(MemberType!(T, `" ~ meta.name ~ "`))) {\n";
code ~= " bool " ~ meta.name ~ ";\n";
code ~= "}\n";
}
}
code ~= "}";
return code;
}());
}
/**
* Deserializes a Thrift struct from a protocol.
*
* Using the Protocol template parameter, the concrete TProtocol to use can be
* be specified. If the pointerStruct parameter is set to true, the struct
* fields are expected to be pointers to the actual data. This is used
* internally (combined with TPResultStruct) and usually should not be used in
* user code.
*
* This is a free function to make it possible to read exisiting structs from
* the wire without altering their definitions.
*/
void readStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null,
bool pointerStruct = false)(auto ref T s, Protocol p) if (isTProtocol!Protocol)
{
mixin({
string code;
// Check that all fields for which there is meta info are actually in the
// passed struct type.
foreach (field; mergeFieldMeta!(T, fieldMetaData)) {
code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
}
// Returns the code string for reading a value of type F off the wire and
// assigning it to v. The level parameter is used to make sure that there
// are no conflicting variable names on recursive calls.
string readValueCode(ValueType)(string v, size_t level = 0) {
// Some non-ambigous names to use (shadowing is not allowed in D).
immutable i = "i" ~ to!string(level);
immutable elem = "elem" ~ to!string(level);
immutable key = "key" ~ to!string(level);
immutable list = "list" ~ to!string(level);
immutable map = "map" ~ to!string(level);
immutable set = "set" ~ to!string(level);
immutable value = "value" ~ to!string(level);
alias FullyUnqual!ValueType F;
static if (is(F == bool)) {
return v ~ " = p.readBool();";
} else static if (is(F == byte)) {
return v ~ " = p.readByte();";
} else static if (is(F == double)) {
return v ~ " = p.readDouble();";
} else static if (is(F == short)) {
return v ~ " = p.readI16();";
} else static if (is(F == int)) {
return v ~ " = p.readI32();";
} else static if (is(F == long)) {
return v ~ " = p.readI64();";
} else static if (is(F : string)) {
return v ~ " = p.readString();";
} else static if (is(F == enum)) {
return v ~ " = cast(typeof(" ~ v ~ "))p.readI32();";
} else static if (is(F _ : E[], E)) {
return "{\n" ~
"auto " ~ list ~ " = p.readListBegin();\n" ~
// TODO: Check element type here?
v ~ " = new typeof(" ~ v ~ "[0])[" ~ list ~ ".size];\n" ~
"foreach (" ~ i ~ "; 0 .. " ~ list ~ ".size) {\n" ~
readValueCode!E(v ~ "[" ~ i ~ "]", level + 1) ~ "\n" ~
"}\n" ~
"p.readListEnd();\n" ~
"}";
} else static if (is(F _ : V[K], K, V)) {
return "{\n" ~
"auto " ~ map ~ " = p.readMapBegin();" ~
v ~ " = null;\n" ~
// TODO: Check key/value types here?
"foreach (" ~ i ~ "; 0 .. " ~ map ~ ".size) {\n" ~
"FullyUnqual!(typeof(" ~ v ~ ".keys[0])) " ~ key ~ ";\n" ~
readValueCode!K(key, level + 1) ~ "\n" ~
"typeof(" ~ v ~ ".values[0]) " ~ value ~ ";\n" ~
readValueCode!V(value, level + 1) ~ "\n" ~
v ~ "[cast(typeof(" ~ v ~ ".keys[0]))" ~ key ~ "] = " ~ value ~ ";\n" ~
"}\n" ~
"p.readMapEnd();" ~
"}";
} else static if (is(F _ : HashSet!(E), E)) {
return "{\n" ~
"auto " ~ set ~ " = p.readSetBegin();" ~
// TODO: Check element type here?
v ~ " = new typeof(" ~ v ~ ")();\n" ~
"foreach (" ~ i ~ "; 0 .. " ~ set ~ ".size) {\n" ~
"typeof(" ~ v ~ "[][0]) " ~ elem ~ ";\n" ~
readValueCode!E(elem, level + 1) ~ "\n" ~
v ~ " ~= " ~ elem ~ ";\n" ~
"}\n" ~
"p.readSetEnd();" ~
"}";
} else static if (is(F == struct) || is(F : TException)) {
static if (is(F == struct)) {
auto result = v ~ " = typeof(" ~ v ~ ")();\n";
} else {
auto result = v ~ " = new typeof(" ~ v ~ ")();\n";
}
static if (__traits(compiles, F.init.read(TProtocol.init))) {
result ~= v ~ ".read(p);";
} else {
result ~= "readStruct(" ~ v ~ ", p);";
}
return result;
} else {
static assert(false, "Cannot represent type in Thrift: " ~ F.stringof);
}
}
string readFieldCode(FieldType)(string name, short id, TReq req) {
static if (pointerStruct && isPointer!FieldType) {
immutable v = "(*s." ~ name ~ ")";
alias PointerTarget!FieldType F;
} else {
immutable v = "s." ~ name;
alias FieldType F;
}
string code = "case " ~ to!string(id) ~ ":\n";
code ~= "if (f.type == " ~ dToTTypeString!F ~ ") {\n";
code ~= readValueCode!F(v) ~ "\n";
if (req == TReq.REQUIRED) {
// For required fields, set the corresponding local isSet variable.
code ~= "isSet_" ~ name ~ " = true;\n";
} else if (!isNullable!F){
code ~= "s.isSetFlags." ~ name ~ " = true;\n";
}
code ~= "} else skip(p, f.type);\n";
code ~= "break;\n";
return code;
}
// Code for the local boolean flags used to make sure required fields have
// been found.
string isSetFlagCode = "";
// Code for checking whether the flags for the required fields are true.
string isSetCheckCode = "";
/// Code for the case statements storing the fields to the result struct.
string readMembersCode = "";
// The last automatically assigned id – fields with no meta information
// are assigned (in lexical order) descending negative ids, starting with
// -1, just like the Thrift compiler does.
short lastId;
foreach (name; FieldNames!T) {
enum req = memberReq!(T, name, fieldMetaData);
if (req == TReq.REQUIRED) {
// For required fields, generate local bool flags to keep track
// whether the field has been encountered.
immutable n = "isSet_" ~ name;
isSetFlagCode ~= "bool " ~ n ~ ";\n";
isSetCheckCode ~= "enforce(" ~ n ~ ", new TProtocolException(" ~
"`Required field '" ~ name ~ "' not found in serialized data`, " ~
"TProtocolException.Type.INVALID_DATA));\n";
}
enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
static if (meta.empty) {
--lastId;
version (TVerboseCodegen) {
code ~= "pragma(msg, `[thrift.codegen.base.readStruct] Warning: No " ~
"meta information for field '" ~ name ~ "' in struct '" ~
T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n";
}
readMembersCode ~= readFieldCode!(MemberType!(T, name))(
name, lastId, req);
} else static if (req != TReq.IGNORE) {
readMembersCode ~= readFieldCode!(MemberType!(T, name))(
name, meta.front.id, req);
}
}
code ~= isSetFlagCode;
code ~= "p.readStructBegin();\n";
code ~= "while (true) {\n";
code ~= "auto f = p.readFieldBegin();\n";
code ~= "if (f.type == TType.STOP) break;\n";
code ~= "switch(f.id) {\n";
code ~= readMembersCode;
code ~= "default: skip(p, f.type);\n";
code ~= "}\n";
code ~= "p.readFieldEnd();\n";
code ~= "}\n";
code ~= "p.readStructEnd();\n";
code ~= isSetCheckCode;
return code;
}());
}
/**
* Serializes a struct to the target protocol.
*
* Using the Protocol template parameter, the concrete TProtocol to use can be
* be specified. If the pointerStruct parameter is set to true, the struct
* fields are expected to be pointers to the actual data. This is used
* internally (combined with TPargsStruct) and usually should not be used in
* user code.
*
* This is a free function to make it possible to read exisiting structs from
* the wire without altering their definitions.
*/
void writeStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null,
bool pointerStruct = false) (const T s, Protocol p) if (isTProtocol!Protocol)
{
mixin({
// Check that all fields for which there is meta info are actually in the
// passed struct type.
string code = "";
foreach (field; mergeFieldMeta!(T, fieldMetaData)) {
code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
}
// Check that required nullable members are non-null.
// WORKAROUND: To stop LDC from emitting the manifest constant »meta« below
// into the writeStruct function body this is inside the string mixin
// block – the code wouldn't depend on it (this is an LDC bug, and because
// of it a new array would be allocated on each method invocation at runtime).
foreach (name; StaticFilter!(
Compose!(isNullable, PApply!(MemberType, T)),
FieldNames!T
)) {
static if (memberReq!(T, name, fieldMetaData) == TReq.REQUIRED) {
code ~= "enforce(__traits(getMember, s, `" ~ name ~ "`) !is null,
new TException(`Required field '" ~ name ~ "' is null.`));\n";
}
}
return code;
}());
p.writeStructBegin(TStruct(T.stringof));
mixin({
string writeValueCode(ValueType)(string v, size_t level = 0) {
// Some non-ambigous names to use (shadowing is not allowed in D).
immutable elem = "elem" ~ to!string(level);
immutable key = "key" ~ to!string(level);
immutable value = "value" ~ to!string(level);
alias FullyUnqual!ValueType F;
static if (is(F == bool)) {
return "p.writeBool(" ~ v ~ ");";
} else static if (is(F == byte)) {
return "p.writeByte(" ~ v ~ ");";
} else static if (is(F == double)) {
return "p.writeDouble(" ~ v ~ ");";
} else static if (is(F == short)) {
return "p.writeI16(" ~ v ~ ");";
} else static if (is(F == int)) {
return "p.writeI32(" ~ v ~ ");";
} else static if (is(F == long)) {
return "p.writeI64(" ~ v ~ ");";
} else static if (is(F : string)) {
return "p.writeString(" ~ v ~ ");";
} else static if (is(F == enum)) {
return "p.writeI32(cast(int)" ~ v ~ ");";
} else static if (is(F _ : E[], E)) {
return "p.writeListBegin(TList(" ~ dToTTypeString!E ~ ", " ~ v ~
".length));\n" ~
"foreach (" ~ elem ~ "; " ~ v ~ ") {\n" ~
writeValueCode!E(elem, level + 1) ~ "\n" ~
"}\n" ~
"p.writeListEnd();";
} else static if (is(F _ : V[K], K, V)) {
return "p.writeMapBegin(TMap(" ~ dToTTypeString!K ~ ", " ~
dToTTypeString!V ~ ", " ~ v ~ ".length));\n" ~
"foreach (" ~ key ~ ", " ~ value ~ "; " ~ v ~ ") {\n" ~
writeValueCode!K(key, level + 1) ~ "\n" ~
writeValueCode!V(value, level + 1) ~ "\n" ~
"}\n" ~
"p.writeMapEnd();";
} else static if (is(F _ : HashSet!E, E)) {
return "p.writeSetBegin(TSet(" ~ dToTTypeString!E ~ ", " ~ v ~
".length));\n" ~
"foreach (" ~ elem ~ "; " ~ v ~ "[]) {\n" ~
writeValueCode!E(elem, level + 1) ~ "\n" ~
"}\n" ~
"p.writeSetEnd();";
} else static if (is(F == struct) || is(F : TException)) {
static if (__traits(compiles, F.init.write(TProtocol.init))) {
return v ~ ".write(p);";
} else {
return "writeStruct(" ~ v ~ ", p);";
}
} else {
static assert(false, "Cannot represent type in Thrift: " ~ F.stringof);
}
}
string writeFieldCode(FieldType)(string name, short id, TReq req) {
string code;
if (!pointerStruct && req == TReq.OPTIONAL) {
code ~= "if (s.isSet!`" ~ name ~ "`) {\n";
}
static if (pointerStruct && isPointer!FieldType) {
immutable v = "(*s." ~ name ~ ")";
alias PointerTarget!FieldType F;
} else {
immutable v = "s." ~ name;
alias FieldType F;
}
code ~= "p.writeFieldBegin(TField(`" ~ name ~ "`, " ~ dToTTypeString!F ~
", " ~ to!string(id) ~ "));\n";
code ~= writeValueCode!F(v) ~ "\n";
code ~= "p.writeFieldEnd();\n";
if (!pointerStruct && req == TReq.OPTIONAL) {
code ~= "}\n";
}
return code;
}
// The last automatically assigned id – fields with no meta information
// are assigned (in lexical order) descending negative ids, starting with
// -1, just like the Thrift compiler does.
short lastId;
string code = "";
foreach (name; FieldNames!T) {
alias MemberType!(T, name) F;
enum req = memberReq!(T, name, fieldMetaData);
enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
if (meta.empty) {
--lastId;
version (TVerboseCodegen) {
code ~= "pragma(msg, `[thrift.codegen.base.writeStruct] Warning: No " ~
"meta information for field '" ~ name ~ "' in struct '" ~
T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n";
}
code ~= writeFieldCode!F(name, lastId, req);
} else if (req != TReq.IGNORE) {
code ~= writeFieldCode!F(name, meta.front.id, req);
}
}
return code;
}());
p.writeFieldStop();
p.writeStructEnd();
}
unittest {
// Ensure that the generated code at least compiles for the basic field type
// combinations. Functionality checks are covered by the rest of the test
// suite.
static struct Test {
// Non-nullable.
int a1;
int a2;
int a3;
int a4;
// Nullable.
string b1;
string b2;
string b3;
string b4;
mixin TStructHelpers!([
TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT),
TFieldMeta("a2", 2, TReq.OPTIONAL),
TFieldMeta("a3", 3, TReq.REQUIRED),
TFieldMeta("a4", 4, TReq.IGNORE),
TFieldMeta("b1", 5, TReq.OPT_IN_REQ_OUT),
TFieldMeta("b2", 6, TReq.OPTIONAL),
TFieldMeta("b3", 7, TReq.REQUIRED),
TFieldMeta("b4", 8, TReq.IGNORE),
]);
}
static assert(__traits(compiles, { Test t; t.read(cast(TProtocol)null); }));
static assert(__traits(compiles, { Test t; t.write(cast(TProtocol)null); }));
}
// Ensure opEquals and toHash consistency.
unittest {
struct TestEquals {
int a1;
mixin TStructHelpers!([
TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT),
]);
}
TestEquals a, b;
assert(a == b);
assert(a.toHash() == b.toHash());
a.a1 = 42;
assert(a != b);
assert(a.toHash() != b.toHash());
b.a1 = 42;
assert(a == b);
assert(a.toHash() == b.toHash());
}
private {
/*
* Returns a D code string containing the matching TType value for a passed
* D type, e.g. dToTTypeString!byte == "TType.BYTE".
*/
template dToTTypeString(T) {
static if (is(FullyUnqual!T == bool)) {
enum dToTTypeString = "TType.BOOL";
} else static if (is(FullyUnqual!T == byte)) {
enum dToTTypeString = "TType.BYTE";
} else static if (is(FullyUnqual!T == double)) {
enum dToTTypeString = "TType.DOUBLE";
} else static if (is(FullyUnqual!T == short)) {
enum dToTTypeString = "TType.I16";
} else static if (is(FullyUnqual!T == int)) {
enum dToTTypeString = "TType.I32";
} else static if (is(FullyUnqual!T == long)) {
enum dToTTypeString = "TType.I64";
} else static if (is(FullyUnqual!T : string)) {
enum dToTTypeString = "TType.STRING";
} else static if (is(FullyUnqual!T == enum)) {
enum dToTTypeString = "TType.I32";
} else static if (is(FullyUnqual!T _ : U[], U)) {
enum dToTTypeString = "TType.LIST";
} else static if (is(FullyUnqual!T _ : V[K], K, V)) {
enum dToTTypeString = "TType.MAP";
} else static if (is(FullyUnqual!T _ : HashSet!E, E)) {
enum dToTTypeString = "TType.SET";
} else static if (is(FullyUnqual!T == struct)) {
enum dToTTypeString = "TType.STRUCT";
} else static if (is(FullyUnqual!T : TException)) {
enum dToTTypeString = "TType.STRUCT";
} else {
static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
}
}
}