blob: 85f9d1891a78b59524371331f999575ddcb16caf [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.
*/
module thrift.internal.codegen;
import std.algorithm : canFind;
import std.traits : InterfacesTuple, isSomeFunction, isSomeString;
import std.typetuple : staticIndexOf, staticMap, NoDuplicates, TypeTuple;
import thrift.codegen.base;
/**
* Removes all type qualifiers from T.
*
* In contrast to std.traits.Unqual, FullyUnqual also removes qualifiers from
* array elements (e.g. immutable(byte[]) -> byte[], not immutable(byte)[]),
* excluding strings (string isn't reduced to char[]).
*/
template FullyUnqual(T) {
static if (is(T _ == const(U), U)) {
alias FullyUnqual!U FullyUnqual;
} else static if (is(T _ == immutable(U), U)) {
alias FullyUnqual!U FullyUnqual;
} else static if (is(T _ == shared(U), U)) {
alias FullyUnqual!U FullyUnqual;
} else static if (is(T _ == U[], U) && !isSomeString!T) {
alias FullyUnqual!(U)[] FullyUnqual;
} else static if (is(T _ == V[K], K, V)) {
alias FullyUnqual!(V)[FullyUnqual!K] FullyUnqual;
} else {
alias T FullyUnqual;
}
}
/**
* true if null can be assigned to the passed type, false if not.
*/
template isNullable(T) {
enum isNullable = __traits(compiles, { T t = null; });
}
template isStruct(T) {
enum isStruct = is(T == struct);
}
template isException(T) {
enum isException = is(T : Exception);
}
template isEnum(T) {
enum isEnum = is(T == enum);
}
/**
* Aliases itself to T.name.
*/
template GetMember(T, string name) {
mixin("alias T." ~ name ~ " GetMember;");
}
/**
* Aliases itself to typeof(symbol).
*/
template TypeOf(alias symbol) {
alias typeof(symbol) TypeOf;
}
/**
* Aliases itself to the type of the T member called name.
*/
alias Compose!(TypeOf, GetMember) MemberType;
/**
* Returns the field metadata array for T if any, or an empty array otherwise.
*/
template getFieldMeta(T) if (isStruct!T || isException!T) {
static if (is(typeof(T.fieldMeta) == TFieldMeta[])) {
enum getFieldMeta = T.fieldMeta;
} else {
enum TFieldMeta[] getFieldMeta = [];
}
}
/**
* Merges the field metadata array for D with the passed array.
*/
template mergeFieldMeta(T, alias fieldMetaData = cast(TFieldMeta[])null) {
// Note: We don't use getFieldMeta here to avoid bug if it is instantiated
// from TIsSetFlags, see comment there.
static if (is(typeof(T.fieldMeta) == TFieldMeta[])) {
enum mergeFieldMeta = T.fieldMeta ~ fieldMetaData;
} else {
enum TFieldMeta[] mergeFieldMeta = fieldMetaData;
}
}
/**
* Returns the field requirement level for T.name.
*/
template memberReq(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) {
enum memberReq = memberReqImpl!(T, name, fieldMetaData).result;
}
private {
import std.algorithm : find;
// DMD @@BUG@@: Missing import leads to failing build without error
// message in unittest/debug/thrift/codegen/async_client.
import std.array : empty, front;
template memberReqImpl(T, string name, alias fieldMetaData) {
enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
static if (meta.empty || meta.front.req == TReq.AUTO) {
static if (isNullable!(MemberType!(T, name))) {
enum result = TReq.OPTIONAL;
} else {
enum result = TReq.REQUIRED;
}
} else {
enum result = meta.front.req;
}
}
}
template notIgnored(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) {
enum notIgnored = memberReq!(T, name, fieldMetaData) != TReq.IGNORE;
}
/**
* Returns the method metadata array for T if any, or an empty array otherwise.
*/
template getMethodMeta(T) if (isService!T) {
static if (is(typeof(T.methodMeta) == TMethodMeta[])) {
enum getMethodMeta = T.methodMeta;
} else {
enum TMethodMeta[] getMethodMeta = [];
}
}
/**
* true if T.name is a member variable. Exceptions include methods, static
* members, artifacts like package aliases, …
*/
template isValueMember(T, string name) {
static if (!is(MemberType!(T, name))) {
enum isValueMember = false;
} else static if (
is(MemberType!(T, name) == void) ||
isSomeFunction!(MemberType!(T, name)) ||
__traits(compiles, { return mixin("T." ~ name); }())
) {
enum isValueMember = false;
} else {
enum isValueMember = true;
}
}
/**
* Returns a tuple containing the names of the fields of T, not including
* inherited fields. If a member is marked as TReq.IGNORE, it is not included
* as well.
*/
template FieldNames(T, alias fieldMetaData = cast(TFieldMeta[])null) {
alias StaticFilter!(
All!(
doesNotReadMembers,
PApply!(isValueMember, T),
PApply!(notIgnored, T, PApplySkip, fieldMetaData)
),
__traits(derivedMembers, T)
) FieldNames;
}
/*
* true if the passed member name is not a method generated by the
* TStructHelpers template that in its implementations queries the struct
* members.
*
* Kludge used internally to break a cycle caused a DMD forward reference
* regression, see THRIFT-2130.
*/
enum doesNotReadMembers(string name) = !["opEquals", "thriftOpEqualsImpl",
"toString", "thriftToStringImpl"].canFind(name);
template derivedMembers(T) {
alias TypeTuple!(__traits(derivedMembers, T)) derivedMembers;
}
template AllMemberMethodNames(T) if (isService!T) {
alias NoDuplicates!(
FilterMethodNames!(
T,
staticMap!(
derivedMembers,
TypeTuple!(T, InterfacesTuple!T)
)
)
) AllMemberMethodNames;
}
template FilterMethodNames(T, MemberNames...) {
alias StaticFilter!(
CompilesAndTrue!(
Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T))
),
MemberNames
) FilterMethodNames;
}
/**
* Returns a type tuple containing only the elements of T for which the
* eponymous template predicate pred is true.
*
* Example:
* ---
* alias StaticFilter!(isIntegral, int, string, long, float[]) Filtered;
* static assert(is(Filtered == TypeTuple!(int, long)));
* ---
*/
template StaticFilter(alias pred, T...) {
static if (T.length == 0) {
alias TypeTuple!() StaticFilter;
} else static if (pred!(T[0])) {
alias TypeTuple!(T[0], StaticFilter!(pred, T[1 .. $])) StaticFilter;
} else {
alias StaticFilter!(pred, T[1 .. $]) StaticFilter;
}
}
/**
* Binds the first n arguments of a template to a particular value (where n is
* the number of arguments passed to PApply).
*
* The passed arguments are always applied starting from the left. However,
* the special PApplySkip marker template can be used to indicate that an
* argument should be skipped, so that e.g. the first and third argument
* to a template can be fixed, but the second and remaining arguments would
* still be left undefined.
*
* Skipping a number of parameters, but not providing enough arguments to
* assign all of them during instantiation of the resulting template is an
* error.
*
* Example:
* ---
* struct Foo(T, U, V) {}
* alias PApply!(Foo, int, long) PartialFoo;
* static assert(is(PartialFoo!float == Foo!(int, long, float)));
*
* alias PApply!(Test, int, PApplySkip, float) SkippedTest;
* static assert(is(SkippedTest!long == Test!(int, long, float)));
* ---
*/
template PApply(alias Target, T...) {
template PApply(U...) {
alias Target!(PApplyMergeArgs!(ConfinedTuple!T, U).Result) PApply;
}
}
/// Ditto.
template PApplySkip() {}
private template PApplyMergeArgs(alias Preset, Args...) {
static if (Preset.length == 0) {
alias Args Result;
} else {
enum nextSkip = staticIndexOf!(PApplySkip, Preset.Tuple);
static if (nextSkip == -1) {
alias TypeTuple!(Preset.Tuple, Args) Result;
} else static if (Args.length == 0) {
// Have to use a static if clause instead of putting the condition
// directly into the assert to avoid DMD trying to access Args[0]
// nevertheless below.
static assert(false,
"PArgsSkip encountered, but no argument left to bind.");
} else {
alias TypeTuple!(
Preset.Tuple[0 .. nextSkip],
Args[0],
PApplyMergeArgs!(
ConfinedTuple!(Preset.Tuple[nextSkip + 1 .. $]),
Args[1 .. $]
).Result
) Result;
}
}
}
unittest {
struct Test(T, U, V) {}
alias PApply!(Test, int, long) PartialTest;
static assert(is(PartialTest!float == Test!(int, long, float)));
alias PApply!(Test, int, PApplySkip, float) SkippedTest;
static assert(is(SkippedTest!long == Test!(int, long, float)));
alias PApply!(Test, int, PApplySkip, PApplySkip) TwoSkipped;
static assert(!__traits(compiles, TwoSkipped!long));
}
/**
* Composes a number of templates. The result is a template equivalent to
* all the passed templates evaluated from right to left, akin to the
* mathematical function composition notation: Instantiating Compose!(A, B, C)
* is the same as instantiating A!(B!(C!(…))).
*
* This is especially useful for creating a template to use with staticMap/
* StaticFilter, as demonstrated below.
*
* Example:
* ---
* template AllMethodNames(T) {
* alias StaticFilter!(
* CompilesAndTrue!(
* Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T))
* ),
* __traits(allMembers, T)
* ) AllMethodNames;
* }
*
* pragma(msg, AllMethodNames!Object);
* ---
*/
template Compose(T...) {
static if (T.length == 0) {
template Compose(U...) {
alias U Compose;
}
} else {
template Compose(U...) {
alias Instantiate!(T[0], Instantiate!(.Compose!(T[1 .. $]), U)) Compose;
}
}
}
/**
* Instantiates the given template with the given list of parameters.
*
* Used to work around syntactic limiations of D with regard to instantiating
* a template from a type tuple (e.g. T[0]!(...) is not valid) or a template
* returning another template (e.g. Foo!(Bar)!(Baz) is not allowed).
*/
template Instantiate(alias Template, Params...) {
alias Template!Params Instantiate;
}
/**
* Combines several template predicates using logical AND, i.e. instantiating
* All!(a, b, c) with parameters P for some templates a, b, c is equivalent to
* a!P && b!P && c!P.
*
* The templates are evaluated from left to right, aborting evaluation in a
* shurt-cut manner if a false result is encountered, in which case the latter
* instantiations do not need to compile.
*/
template All(T...) {
static if (T.length == 0) {
template All(U...) {
enum All = true;
}
} else {
template All(U...) {
static if (Instantiate!(T[0], U)) {
alias Instantiate!(.All!(T[1 .. $]), U) All;
} else {
enum All = false;
}
}
}
}
/**
* Combines several template predicates using logical OR, i.e. instantiating
* Any!(a, b, c) with parameters P for some templates a, b, c is equivalent to
* a!P || b!P || c!P.
*
* The templates are evaluated from left to right, aborting evaluation in a
* shurt-cut manner if a true result is encountered, in which case the latter
* instantiations do not need to compile.
*/
template Any(T...) {
static if (T.length == 0) {
template Any(U...) {
enum Any = false;
}
} else {
template Any(U...) {
static if (Instantiate!(T[0], U)) {
enum Any = true;
} else {
alias Instantiate!(.Any!(T[1 .. $]), U) Any;
}
}
}
}
template ConfinedTuple(T...) {
alias T Tuple;
enum length = T.length;
}
/*
* foreach (Item; Items) {
* List = Operator!(Item, List);
* }
* where Items is a ConfinedTuple and List is a type tuple.
*/
template ForAllWithList(alias Items, alias Operator, List...) if (
is(typeof(Items.length) : size_t)
){
static if (Items.length == 0) {
alias List ForAllWithList;
} else {
alias .ForAllWithList!(
ConfinedTuple!(Items.Tuple[1 .. $]),
Operator,
Operator!(Items.Tuple[0], List)
) ForAllWithList;
}
}
/**
* Wraps the passed template predicate so it returns true if it compiles and
* evaluates to true, false it it doesn't compile or evaluates to false.
*/
template CompilesAndTrue(alias T) {
template CompilesAndTrue(U...) {
static if (is(typeof(T!U) : bool)) {
enum bool CompilesAndTrue = T!U;
} else {
enum bool CompilesAndTrue = false;
}
}
}