| // Written in the D programming language. | |
| /** | |
| This module implements a variety of type constructors, i.e., templates | |
| that allow construction of new, useful general-purpose types. | |
| $(SCRIPT inhibitQuickIndex = 1;) | |
| $(DIVC quickindex, | |
| $(BOOKTABLE, | |
| $(TR $(TH Category) $(TH Symbols)) | |
| $(TR $(TD Tuple) $(TD | |
| $(LREF isTuple) | |
| $(LREF Tuple) | |
| $(LREF tuple) | |
| $(LREF reverse) | |
| )) | |
| $(TR $(TD Flags) $(TD | |
| $(LREF BitFlags) | |
| $(LREF isBitFlagEnum) | |
| $(LREF Flag) | |
| $(LREF No) | |
| $(LREF Yes) | |
| )) | |
| $(TR $(TD Memory allocation) $(TD | |
| $(LREF SafeRefCounted) | |
| $(LREF safeRefCounted) | |
| $(LREF RefCountedAutoInitialize) | |
| $(LREF scoped) | |
| $(LREF Unique) | |
| )) | |
| $(TR $(TD Code generation) $(TD | |
| $(LREF AutoImplement) | |
| $(LREF BlackHole) | |
| $(LREF generateAssertTrap) | |
| $(LREF generateEmptyFunction) | |
| $(LREF WhiteHole) | |
| )) | |
| $(TR $(TD Nullable) $(TD | |
| $(LREF Nullable) | |
| $(LREF nullable) | |
| $(LREF NullableRef) | |
| $(LREF nullableRef) | |
| )) | |
| $(TR $(TD Proxies) $(TD | |
| $(LREF Proxy) | |
| $(LREF rebindable) | |
| $(LREF Rebindable) | |
| $(LREF ReplaceType) | |
| $(LREF unwrap) | |
| $(LREF wrap) | |
| )) | |
| $(TR $(TD Types) $(TD | |
| $(LREF alignForSize) | |
| $(LREF Ternary) | |
| $(LREF Typedef) | |
| $(LREF TypedefType) | |
| $(LREF UnqualRef) | |
| )) | |
| )) | |
| Copyright: Copyright the respective authors, 2008- | |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
| Source: $(PHOBOSSRC std/typecons.d) | |
| Authors: $(HTTP erdani.org, Andrei Alexandrescu), | |
| $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski), | |
| Don Clugston, | |
| Shin Fujishiro, | |
| Kenji Hara | |
| */ | |
| module std.typecons; | |
| import std.format.spec : singleSpec, FormatSpec; | |
| import std.format.write : formatValue; | |
| import std.meta : AliasSeq, allSatisfy; | |
| import std.range.primitives : isOutputRange; | |
| import std.traits; | |
| import std.internal.attributes : betterC; | |
| /// Value tuples | |
| @safe unittest | |
| { | |
| alias Coord = Tuple!(int, "x", int, "y", int, "z"); | |
| Coord c; | |
| c[1] = 1; // access by index | |
| c.z = 1; // access by given name | |
| assert(c == Coord(0, 1, 1)); | |
| // names can be omitted, types can be mixed | |
| alias DictEntry = Tuple!(string, int); | |
| auto dict = DictEntry("seven", 7); | |
| // element types can be inferred | |
| assert(tuple(2, 3, 4)[1] == 3); | |
| // type inference works with names too | |
| auto tup = tuple!("x", "y", "z")(2, 3, 4); | |
| assert(tup.y == 3); | |
| } | |
| /// Rebindable references to const and immutable objects | |
| @safe unittest | |
| { | |
| class Widget | |
| { | |
| void foo() const @safe {} | |
| } | |
| const w1 = new Widget, w2 = new Widget; | |
| w1.foo(); | |
| // w1 = w2 would not work; can't rebind const object | |
| auto r = Rebindable!(const Widget)(w1); | |
| // invoke method as if r were a Widget object | |
| r.foo(); | |
| // rebind r to refer to another object | |
| r = w2; | |
| } | |
| /** | |
| Encapsulates unique ownership of a resource. | |
| When a `Unique!T` goes out of scope it will call `destroy` | |
| on the resource `T` that it manages, unless it is transferred. | |
| One important consequence of `destroy` is that it will call the | |
| destructor of the resource `T`. GC-managed references are not | |
| guaranteed to be valid during a destructor call, but other members of | |
| `T`, such as file handles or pointers to `malloc` memory, will | |
| still be valid during the destructor call. This allows the resource | |
| `T` to deallocate or clean up any non-GC resources. | |
| If it is desirable to persist a `Unique!T` outside of its original | |
| scope, then it can be transferred. The transfer can be explicit, by | |
| calling `release`, or implicit, when returning Unique from a | |
| function. The resource `T` can be a polymorphic class object or | |
| instance of an interface, in which case Unique behaves polymorphically | |
| too. | |
| If `T` is a value type, then `Unique!T` will be implemented | |
| as a reference to a `T`. | |
| */ | |
| struct Unique(T) | |
| { | |
| /** Represents a reference to `T`. Resolves to `T*` if `T` is a value type. */ | |
| static if (is(T == class) || is(T == interface)) | |
| alias RefT = T; | |
| else | |
| alias RefT = T*; | |
| public: | |
| // Deferred in case we get some language support for checking uniqueness. | |
| version (None) | |
| /** | |
| Allows safe construction of `Unique`. It creates the resource and | |
| guarantees unique ownership of it (unless `T` publishes aliases of | |
| `this`). | |
| Note: Nested structs/classes cannot be created. | |
| Params: | |
| args = Arguments to pass to `T`'s constructor. | |
| --- | |
| static class C {} | |
| auto u = Unique!(C).create(); | |
| --- | |
| */ | |
| static Unique!T create(A...)(auto ref A args) | |
| if (__traits(compiles, new T(args))) | |
| { | |
| Unique!T u; | |
| u._p = new T(args); | |
| return u; | |
| } | |
| /** | |
| Constructor that takes an rvalue. | |
| It will ensure uniqueness, as long as the rvalue | |
| isn't just a view on an lvalue (e.g., a cast). | |
| Typical usage: | |
| ---- | |
| Unique!Foo f = new Foo; | |
| ---- | |
| */ | |
| this(RefT p) | |
| { | |
| _p = p; | |
| } | |
| /** | |
| Constructor that takes an lvalue. It nulls its source. | |
| The nulling will ensure uniqueness as long as there | |
| are no previous aliases to the source. | |
| */ | |
| this(ref RefT p) | |
| { | |
| _p = p; | |
| p = null; | |
| assert(p is null); | |
| } | |
| /** | |
| Constructor that takes a `Unique` of a type that is convertible to our type. | |
| Typically used to transfer a `Unique` rvalue of derived type to | |
| a `Unique` of base type. | |
| Example: | |
| --- | |
| class C : Object {} | |
| Unique!C uc = new C; | |
| Unique!Object uo = uc.release; | |
| --- | |
| */ | |
| this(U)(Unique!U u) | |
| if (is(u.RefT:RefT)) | |
| { | |
| _p = u._p; | |
| u._p = null; | |
| } | |
| /// Transfer ownership from a `Unique` of a type that is convertible to our type. | |
| void opAssign(U)(Unique!U u) | |
| if (is(u.RefT:RefT)) | |
| { | |
| // first delete any resource we own | |
| destroy(this); | |
| _p = u._p; | |
| u._p = null; | |
| } | |
| ~this() | |
| { | |
| if (_p !is null) | |
| { | |
| static if (is(T == class) || is(T == interface)) | |
| destroy(_p); | |
| else | |
| destroy(*_p); | |
| _p = null; | |
| } | |
| } | |
| /** Returns whether the resource exists. */ | |
| @property bool isEmpty() const | |
| { | |
| return _p is null; | |
| } | |
| /** Transfer ownership to a `Unique` rvalue. Nullifies the current contents. | |
| Same as calling std.algorithm.move on it. | |
| */ | |
| Unique release() | |
| { | |
| import std.algorithm.mutation : move; | |
| return this.move; | |
| } | |
| /** Forwards member access to contents. */ | |
| mixin Proxy!_p; | |
| /** | |
| Postblit operator is undefined to prevent the cloning of `Unique` objects. | |
| */ | |
| @disable this(this); | |
| private: | |
| RefT _p; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| struct S | |
| { | |
| int i; | |
| this(int i){this.i = i;} | |
| } | |
| Unique!S produce() | |
| { | |
| // Construct a unique instance of S on the heap | |
| Unique!S ut = new S(5); | |
| // Implicit transfer of ownership | |
| return ut; | |
| } | |
| // Borrow a unique resource by ref | |
| void increment(ref Unique!S ur) | |
| { | |
| ur.i++; | |
| } | |
| void consume(Unique!S u2) | |
| { | |
| assert(u2.i == 6); | |
| // Resource automatically deleted here | |
| } | |
| Unique!S u1; | |
| assert(u1.isEmpty); | |
| u1 = produce(); | |
| assert(u1.i == 5); | |
| increment(u1); | |
| assert(u1.i == 6); | |
| //consume(u1); // Error: u1 is not copyable | |
| // Transfer ownership of the resource | |
| consume(u1.release); | |
| assert(u1.isEmpty); | |
| } | |
| @safe unittest | |
| { | |
| int i; | |
| struct S | |
| { | |
| ~this() | |
| { | |
| // check context pointer still exists - dtor also called before GC frees struct | |
| if (this.tupleof[0]) | |
| i++; | |
| } | |
| } | |
| { | |
| Unique!S u = new S; | |
| } | |
| assert(i == 1); | |
| } | |
| @system unittest | |
| { | |
| // test conversion to base ref | |
| int deleted = 0; | |
| class C | |
| { | |
| ~this(){deleted++;} | |
| } | |
| // constructor conversion | |
| Unique!Object u = Unique!C(new C); | |
| static assert(!__traits(compiles, {u = new C;})); | |
| assert(!u.isEmpty); | |
| destroy(u); | |
| assert(deleted == 1); | |
| Unique!C uc = new C; | |
| static assert(!__traits(compiles, {Unique!Object uo = uc;})); | |
| Unique!Object uo = new C; | |
| // opAssign conversion, deleting uo resource first | |
| uo = uc.release; | |
| assert(uc.isEmpty); | |
| assert(!uo.isEmpty); | |
| assert(deleted == 2); | |
| } | |
| @system unittest | |
| { | |
| class Bar | |
| { | |
| ~this() { debug(Unique) writeln(" Bar destructor"); } | |
| int val() const { return 4; } | |
| } | |
| alias UBar = Unique!(Bar); | |
| UBar g(UBar u) | |
| { | |
| debug(Unique) writeln("inside g"); | |
| return u.release; | |
| } | |
| auto ub = UBar(new Bar); | |
| assert(!ub.isEmpty); | |
| assert(ub.val == 4); | |
| static assert(!__traits(compiles, {auto ub3 = g(ub);})); | |
| auto ub2 = g(ub.release); | |
| assert(ub.isEmpty); | |
| assert(!ub2.isEmpty); | |
| } | |
| @system unittest | |
| { | |
| interface Bar | |
| { | |
| int val() const; | |
| } | |
| class BarImpl : Bar | |
| { | |
| static int count; | |
| this() | |
| { | |
| count++; | |
| } | |
| ~this() | |
| { | |
| count--; | |
| } | |
| int val() const { return 4; } | |
| } | |
| alias UBar = Unique!Bar; | |
| UBar g(UBar u) | |
| { | |
| debug(Unique) writeln("inside g"); | |
| return u.release; | |
| } | |
| void consume(UBar u) | |
| { | |
| assert(u.val() == 4); | |
| // Resource automatically deleted here | |
| } | |
| auto ub = UBar(new BarImpl); | |
| assert(BarImpl.count == 1); | |
| assert(!ub.isEmpty); | |
| assert(ub.val == 4); | |
| static assert(!__traits(compiles, {auto ub3 = g(ub);})); | |
| auto ub2 = g(ub.release); | |
| assert(ub.isEmpty); | |
| assert(!ub2.isEmpty); | |
| consume(ub2.release); | |
| assert(BarImpl.count == 0); | |
| } | |
| @safe unittest | |
| { | |
| struct Foo | |
| { | |
| ~this() { } | |
| int val() const { return 3; } | |
| @disable this(this); | |
| } | |
| alias UFoo = Unique!(Foo); | |
| UFoo f(UFoo u) | |
| { | |
| return u.release; | |
| } | |
| auto uf = UFoo(new Foo); | |
| assert(!uf.isEmpty); | |
| assert(uf.val == 3); | |
| static assert(!__traits(compiles, {auto uf3 = f(uf);})); | |
| auto uf2 = f(uf.release); | |
| assert(uf.isEmpty); | |
| assert(!uf2.isEmpty); | |
| } | |
| // ensure Unique behaves correctly through const access paths | |
| @system unittest | |
| { | |
| struct Bar {int val;} | |
| struct Foo | |
| { | |
| Unique!Bar bar = new Bar; | |
| } | |
| Foo foo; | |
| foo.bar.val = 6; | |
| const Foo* ptr = &foo; | |
| static assert(is(typeof(ptr) == const(Foo*))); | |
| static assert(is(typeof(ptr.bar) == const(Unique!Bar))); | |
| static assert(is(typeof(ptr.bar.val) == const(int))); | |
| assert(ptr.bar.val == 6); | |
| foo.bar.val = 7; | |
| assert(ptr.bar.val == 7); | |
| } | |
| private enum bool distinctFieldNames(names...) = __traits(compiles, | |
| { | |
| static foreach (__name; names) | |
| static if (is(typeof(__name) : string)) | |
| mixin("enum int " ~ __name ~ " = 0;"); | |
| }); | |
| @safe unittest | |
| { | |
| static assert(!distinctFieldNames!(string, "abc", string, "abc")); | |
| static assert(distinctFieldNames!(string, "abc", int, "abd")); | |
| static assert(!distinctFieldNames!(int, "abc", string, "abd", int, "abc")); | |
| // https://issues.dlang.org/show_bug.cgi?id=19240 | |
| static assert(!distinctFieldNames!(int, "int")); | |
| } | |
| // Parse (type,name) pairs (FieldSpecs) out of the specified | |
| // arguments. Some fields would have name, others not. | |
| private template parseSpecs(Specs...) | |
| { | |
| static if (Specs.length == 0) | |
| { | |
| alias parseSpecs = AliasSeq!(); | |
| } | |
| else static if (is(Specs[0])) | |
| { | |
| static if (is(typeof(Specs[1]) : string)) | |
| { | |
| alias parseSpecs = | |
| AliasSeq!(FieldSpec!(Specs[0 .. 2]), | |
| parseSpecs!(Specs[2 .. $])); | |
| } | |
| else | |
| { | |
| alias parseSpecs = | |
| AliasSeq!(FieldSpec!(Specs[0]), | |
| parseSpecs!(Specs[1 .. $])); | |
| } | |
| } | |
| else | |
| { | |
| static assert(0, "Attempted to instantiate Tuple with an " | |
| ~"invalid argument: "~ Specs[0].stringof); | |
| } | |
| } | |
| private template FieldSpec(T, string s = "") | |
| { | |
| alias Type = T; | |
| alias name = s; | |
| } | |
| // Used with staticMap. | |
| private alias extractType(alias spec) = spec.Type; | |
| private alias extractName(alias spec) = spec.name; | |
| private template expandSpec(alias spec) | |
| { | |
| static if (spec.name.length == 0) | |
| alias expandSpec = AliasSeq!(spec.Type); | |
| else | |
| alias expandSpec = AliasSeq!(spec.Type, spec.name); | |
| } | |
| private enum areCompatibleTuples(Tup1, Tup2, string op) = | |
| isTuple!(OriginalType!Tup2) && Tup1.Types.length == Tup2.Types.length && is(typeof( | |
| (ref Tup1 tup1, ref Tup2 tup2) | |
| { | |
| static foreach (i; 0 .. Tup1.Types.length) | |
| {{ | |
| auto lhs = typeof(tup1.field[i]).init; | |
| auto rhs = typeof(tup2.field[i]).init; | |
| static if (op == "=") | |
| lhs = rhs; | |
| else | |
| auto result = mixin("lhs "~op~" rhs"); | |
| }} | |
| })); | |
| private enum areBuildCompatibleTuples(Tup1, Tup2) = | |
| isTuple!Tup2 && Tup1.Types.length == Tup2.Types.length && is(typeof( | |
| { | |
| static foreach (i; 0 .. Tup1.Types.length) | |
| static assert(isBuildable!(Tup1.Types[i], Tup2.Types[i])); | |
| })); | |
| // Returns `true` iff a `T` can be initialized from a `U`. | |
| private enum isBuildable(T, U) = is(typeof( | |
| { | |
| U u = U.init; | |
| T t = u; | |
| })); | |
| // Helper for partial instantiation | |
| private template isBuildableFrom(U) | |
| { | |
| enum isBuildableFrom(T) = isBuildable!(T, U); | |
| } | |
| private enum hasCopyCtor(T) = __traits(hasCopyConstructor, T); | |
| // T is expected to be an instantiation of Tuple. | |
| private template noMemberHasCopyCtor(T) | |
| { | |
| import std.meta : anySatisfy; | |
| enum noMemberHasCopyCtor = !anySatisfy!(hasCopyCtor, T.Types); | |
| } | |
| /** | |
| _Tuple of values, for example $(D Tuple!(int, string)) is a record that | |
| stores an `int` and a `string`. `Tuple` can be used to bundle | |
| values together, notably when returning multiple values from a | |
| function. If `obj` is a `Tuple`, the individual members are | |
| accessible with the syntax `obj[0]` for the first field, `obj[1]` | |
| for the second, and so on. | |
| See_Also: $(LREF tuple). | |
| Params: | |
| Specs = A list of types (and optionally, member names) that the `Tuple` contains. | |
| */ | |
| template Tuple(Specs...) | |
| if (distinctFieldNames!(Specs)) | |
| { | |
| import std.meta : staticMap; | |
| alias fieldSpecs = parseSpecs!Specs; | |
| // Generates named fields as follows: | |
| // alias name_0 = Identity!(field[0]); | |
| // alias name_1 = Identity!(field[1]); | |
| // : | |
| // NOTE: field[k] is an expression (which yields a symbol of a | |
| // variable) and can't be aliased directly. | |
| enum injectNamedFields = () | |
| { | |
| string decl = ""; | |
| static foreach (i, val; fieldSpecs) | |
| {{ | |
| immutable si = i.stringof; | |
| decl ~= "alias _" ~ si ~ " = Identity!(field[" ~ si ~ "]);"; | |
| if (val.name.length != 0) | |
| { | |
| decl ~= "alias " ~ val.name ~ " = _" ~ si ~ ";"; | |
| } | |
| }} | |
| return decl; | |
| }; | |
| // Returns Specs for a subtuple this[from .. to] preserving field | |
| // names if any. | |
| alias sliceSpecs(size_t from, size_t to) = | |
| staticMap!(expandSpec, fieldSpecs[from .. to]); | |
| struct Tuple | |
| { | |
| /** | |
| * The types of the `Tuple`'s components. | |
| */ | |
| alias Types = staticMap!(extractType, fieldSpecs); | |
| private alias _Fields = Specs; | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| import std.meta : AliasSeq; | |
| alias Fields = Tuple!(int, "id", string, float); | |
| static assert(is(Fields.Types == AliasSeq!(int, string, float))); | |
| } | |
| /** | |
| * The names of the `Tuple`'s components. Unnamed fields have empty names. | |
| */ | |
| alias fieldNames = staticMap!(extractName, fieldSpecs); | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| import std.meta : AliasSeq; | |
| alias Fields = Tuple!(int, "id", string, float); | |
| static assert(Fields.fieldNames == AliasSeq!("id", "", "")); | |
| } | |
| /** | |
| * Use `t.expand` for a `Tuple` `t` to expand it into its | |
| * components. The result of `expand` acts as if the `Tuple`'s components | |
| * were listed as a list of values. (Ordinarily, a `Tuple` acts as a | |
| * single value.) | |
| */ | |
| Types expand; | |
| mixin(injectNamedFields()); | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| auto t1 = tuple(1, " hello ", 'a'); | |
| assert(t1.toString() == `Tuple!(int, string, char)(1, " hello ", 'a')`); | |
| void takeSeveralTypes(int n, string s, bool b) | |
| { | |
| assert(n == 4 && s == "test" && b == false); | |
| } | |
| auto t2 = tuple(4, "test", false); | |
| //t.expand acting as a list of values | |
| takeSeveralTypes(t2.expand); | |
| } | |
| static if (is(Specs)) | |
| { | |
| // This is mostly to make t[n] work. | |
| alias expand this; | |
| } | |
| else | |
| { | |
| @property | |
| ref inout(Tuple!Types) _Tuple_super() inout @trusted | |
| { | |
| static foreach (i; 0 .. Types.length) // Rely on the field layout | |
| { | |
| static assert(typeof(return).init.tupleof[i].offsetof == | |
| expand[i].offsetof); | |
| } | |
| return *cast(typeof(return)*) &(field[0]); | |
| } | |
| // This is mostly to make t[n] work. | |
| alias _Tuple_super this; | |
| } | |
| // backwards compatibility | |
| alias field = expand; | |
| /** | |
| * Constructor taking one value for each field. | |
| * | |
| * Params: | |
| * values = A list of values that are either the same | |
| * types as those given by the `Types` field | |
| * of this `Tuple`, or can implicitly convert | |
| * to those types. They must be in the same | |
| * order as they appear in `Types`. | |
| */ | |
| static if (Types.length > 0) | |
| { | |
| this(Types values) | |
| { | |
| field[] = values[]; | |
| } | |
| } | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| alias ISD = Tuple!(int, string, double); | |
| auto tup = ISD(1, "test", 3.2); | |
| assert(tup.toString() == `Tuple!(int, string, double)(1, "test", 3.2)`); | |
| } | |
| /** | |
| * Constructor taking a compatible array. | |
| * | |
| * Params: | |
| * values = A compatible static array to build the `Tuple` from. | |
| * Array slices are not supported. | |
| */ | |
| this(U, size_t n)(U[n] values) | |
| if (n == Types.length && allSatisfy!(isBuildableFrom!U, Types)) | |
| { | |
| static foreach (i; 0 .. Types.length) | |
| { | |
| field[i] = values[i]; | |
| } | |
| } | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| int[2] ints; | |
| Tuple!(int, int) t = ints; | |
| } | |
| /** | |
| * Constructor taking a compatible `Tuple`. Two `Tuple`s are compatible | |
| * $(B iff) they are both of the same length, and, for each type `T` on the | |
| * left-hand side, the corresponding type `U` on the right-hand side can | |
| * implicitly convert to `T`. | |
| * | |
| * Params: | |
| * another = A compatible `Tuple` to build from. Its type must be | |
| * compatible with the target `Tuple`'s type. | |
| */ | |
| this(U)(U another) | |
| if (areBuildCompatibleTuples!(typeof(this), U) && | |
| (noMemberHasCopyCtor!(typeof(this)) || !is(Unqual!U == Unqual!(typeof(this))))) | |
| { | |
| field[] = another.field[]; | |
| } | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| alias IntVec = Tuple!(int, int, int); | |
| alias DubVec = Tuple!(double, double, double); | |
| IntVec iv = tuple(1, 1, 1); | |
| //Ok, int can implicitly convert to double | |
| DubVec dv = iv; | |
| //Error: double cannot implicitly convert to int | |
| //IntVec iv2 = dv; | |
| } | |
| /** | |
| * Comparison for equality. Two `Tuple`s are considered equal | |
| * $(B iff) they fulfill the following criteria: | |
| * | |
| * $(UL | |
| * $(LI Each `Tuple` is the same length.) | |
| * $(LI For each type `T` on the left-hand side and each type | |
| * `U` on the right-hand side, values of type `T` can be | |
| * compared with values of type `U`.) | |
| * $(LI For each value `v1` on the left-hand side and each value | |
| * `v2` on the right-hand side, the expression `v1 == v2` is | |
| * true.)) | |
| * | |
| * Params: | |
| * rhs = The `Tuple` to compare against. It must meeting the criteria | |
| * for comparison between `Tuple`s. | |
| * | |
| * Returns: | |
| * true if both `Tuple`s are equal, otherwise false. | |
| */ | |
| bool opEquals(R)(R rhs) | |
| if (areCompatibleTuples!(typeof(this), R, "==")) | |
| { | |
| return field[] == rhs.field[]; | |
| } | |
| /// ditto | |
| bool opEquals(R)(R rhs) const | |
| if (areCompatibleTuples!(typeof(this), R, "==")) | |
| { | |
| return field[] == rhs.field[]; | |
| } | |
| /// ditto | |
| bool opEquals(R...)(auto ref R rhs) | |
| if (R.length > 1 && areCompatibleTuples!(typeof(this), Tuple!R, "==")) | |
| { | |
| static foreach (i; 0 .. Types.length) | |
| if (field[i] != rhs[i]) | |
| return false; | |
| return true; | |
| } | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| Tuple!(int, string) t1 = tuple(1, "test"); | |
| Tuple!(double, string) t2 = tuple(1.0, "test"); | |
| //Ok, int can be compared with double and | |
| //both have a value of 1 | |
| assert(t1 == t2); | |
| } | |
| /** | |
| * Comparison for ordering. | |
| * | |
| * Params: | |
| * rhs = The `Tuple` to compare against. It must meet the criteria | |
| * for comparison between `Tuple`s. | |
| * | |
| * Returns: | |
| * For any values `v1` contained by the left-hand side tuple and any | |
| * values `v2` contained by the right-hand side: | |
| * | |
| * 0 if `v1 == v2` for all members or the following value for the | |
| * first position were the mentioned criteria is not satisfied: | |
| * | |
| * $(UL | |
| * $(LI NaN, in case one of the operands is a NaN.) | |
| * $(LI A negative number if the expression `v1 < v2` is true.) | |
| * $(LI A positive number if the expression `v1 > v2` is true.)) | |
| */ | |
| auto opCmp(R)(R rhs) | |
| if (areCompatibleTuples!(typeof(this), R, "<")) | |
| { | |
| static foreach (i; 0 .. Types.length) | |
| { | |
| if (field[i] != rhs.field[i]) | |
| { | |
| import std.math.traits : isNaN; | |
| static if (isFloatingPoint!(Types[i])) | |
| { | |
| if (isNaN(field[i])) | |
| return float.nan; | |
| } | |
| static if (isFloatingPoint!(typeof(rhs.field[i]))) | |
| { | |
| if (isNaN(rhs.field[i])) | |
| return float.nan; | |
| } | |
| static if (is(typeof(field[i].opCmp(rhs.field[i]))) && | |
| isFloatingPoint!(typeof(field[i].opCmp(rhs.field[i])))) | |
| { | |
| if (isNaN(field[i].opCmp(rhs.field[i]))) | |
| return float.nan; | |
| } | |
| return field[i] < rhs.field[i] ? -1 : 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| /// ditto | |
| auto opCmp(R)(R rhs) const | |
| if (areCompatibleTuples!(typeof(this), R, "<")) | |
| { | |
| static foreach (i; 0 .. Types.length) | |
| { | |
| if (field[i] != rhs.field[i]) | |
| { | |
| import std.math.traits : isNaN; | |
| static if (isFloatingPoint!(Types[i])) | |
| { | |
| if (isNaN(field[i])) | |
| return float.nan; | |
| } | |
| static if (isFloatingPoint!(typeof(rhs.field[i]))) | |
| { | |
| if (isNaN(rhs.field[i])) | |
| return float.nan; | |
| } | |
| static if (is(typeof(field[i].opCmp(rhs.field[i]))) && | |
| isFloatingPoint!(typeof(field[i].opCmp(rhs.field[i])))) | |
| { | |
| if (isNaN(field[i].opCmp(rhs.field[i]))) | |
| return float.nan; | |
| } | |
| return field[i] < rhs.field[i] ? -1 : 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| /** | |
| The first `v1` for which `v1 > v2` is true determines | |
| the result. This could lead to unexpected behaviour. | |
| */ | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| auto tup1 = tuple(1, 1, 1); | |
| auto tup2 = tuple(1, 100, 100); | |
| assert(tup1 < tup2); | |
| //Only the first result matters for comparison | |
| tup1[0] = 2; | |
| assert(tup1 > tup2); | |
| } | |
| /** | |
| Concatenate Tuples. | |
| Tuple concatenation is only allowed if all named fields are distinct (no named field of this tuple occurs in `t` | |
| and no named field of `t` occurs in this tuple). | |
| Params: | |
| t = The `Tuple` to concatenate with | |
| Returns: A concatenation of this tuple and `t` | |
| */ | |
| auto opBinary(string op, T)(auto ref T t) | |
| if (op == "~" && !(is(T : U[], U) && isTuple!U)) | |
| { | |
| static if (isTuple!T) | |
| { | |
| static assert(distinctFieldNames!(_Fields, T._Fields), | |
| "Cannot concatenate tuples with duplicate fields: " ~ fieldNames.stringof ~ | |
| " - " ~ T.fieldNames.stringof); | |
| return Tuple!(_Fields, T._Fields)(expand, t.expand); | |
| } | |
| else | |
| { | |
| return Tuple!(_Fields, T)(expand, t); | |
| } | |
| } | |
| /// ditto | |
| auto opBinaryRight(string op, T)(auto ref T t) | |
| if (op == "~" && !(is(T : U[], U) && isTuple!U)) | |
| { | |
| static if (isTuple!T) | |
| { | |
| static assert(distinctFieldNames!(_Fields, T._Fields), | |
| "Cannot concatenate tuples with duplicate fields: " ~ T.stringof ~ | |
| " - " ~ fieldNames.fieldNames.stringof); | |
| return Tuple!(T._Fields, _Fields)(t.expand, expand); | |
| } | |
| else | |
| { | |
| return Tuple!(T, _Fields)(t, expand); | |
| } | |
| } | |
| /** | |
| * Assignment from another `Tuple`. | |
| * | |
| * Params: | |
| * rhs = The source `Tuple` to assign from. Each element of the | |
| * source `Tuple` must be implicitly assignable to each | |
| * respective element of the target `Tuple`. | |
| */ | |
| ref Tuple opAssign(R)(auto ref R rhs) | |
| if (areCompatibleTuples!(typeof(this), R, "=")) | |
| { | |
| import std.algorithm.mutation : swap; | |
| /* | |
| This optimization caused compilation failures with no error message available: | |
| > Error: unknown, please file report on issues.dlang.org | |
| > std/sumtype.d(1262): Error: template instance `std.sumtype.SumType!(Flag, Tuple!(This*))` error instantiating | |
| */ | |
| version (none) | |
| { | |
| static if (is(R == Tuple!Types) && !__traits(isRef, rhs) && isTuple!R) | |
| { | |
| if (__ctfe) | |
| { | |
| // Cannot use swap at compile time | |
| field[] = rhs.field[]; | |
| } | |
| else | |
| { | |
| // Use swap-and-destroy to optimize rvalue assignment | |
| swap!(Tuple!Types)(this, rhs); | |
| } | |
| } | |
| else | |
| { | |
| // Do not swap; opAssign should be called on the fields. | |
| field[] = rhs.field[]; | |
| } | |
| } | |
| field[] = rhs.field[]; | |
| return this; | |
| } | |
| /** | |
| * Renames the elements of a $(LREF Tuple). | |
| * | |
| * `rename` uses the passed `names` and returns a new | |
| * $(LREF Tuple) using these names, with the content | |
| * unchanged. | |
| * If fewer names are passed than there are members | |
| * of the $(LREF Tuple) then those trailing members are unchanged. | |
| * An empty string will remove the name for that member. | |
| * It is an compile-time error to pass more names than | |
| * there are members of the $(LREF Tuple). | |
| */ | |
| ref rename(names...)() inout return | |
| if (names.length == 0 || allSatisfy!(isSomeString, typeof(names))) | |
| { | |
| import std.algorithm.comparison : equal; | |
| // to circumvent https://issues.dlang.org/show_bug.cgi?id=16418 | |
| static if (names.length == 0 || equal([names], [fieldNames])) | |
| return this; | |
| else | |
| { | |
| enum nT = Types.length; | |
| enum nN = names.length; | |
| static assert(nN <= nT, "Cannot have more names than tuple members"); | |
| alias allNames = AliasSeq!(names, fieldNames[nN .. $]); | |
| import std.meta : Alias, aliasSeqOf; | |
| template GetItem(size_t idx) | |
| { | |
| import std.array : empty; | |
| static if (idx < nT) | |
| alias GetItem = Alias!(Types[idx]); | |
| else static if (allNames[idx - nT].empty) | |
| alias GetItem = AliasSeq!(); | |
| else | |
| alias GetItem = Alias!(allNames[idx - nT]); | |
| } | |
| import std.range : roundRobin, iota; | |
| alias NewTupleT = Tuple!(staticMap!(GetItem, aliasSeqOf!( | |
| roundRobin(iota(nT), iota(nT, 2*nT))))); | |
| return *(() @trusted => cast(NewTupleT*)&this)(); | |
| } | |
| } | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| auto t0 = tuple(4, "hello"); | |
| auto t0Named = t0.rename!("val", "tag"); | |
| assert(t0Named.val == 4); | |
| assert(t0Named.tag == "hello"); | |
| Tuple!(float, "dat", size_t[2], "pos") t1; | |
| t1.pos = [2, 1]; | |
| auto t1Named = t1.rename!"height"; | |
| t1Named.height = 3.4f; | |
| assert(t1Named.height == 3.4f); | |
| assert(t1Named.pos == [2, 1]); | |
| t1Named.rename!"altitude".altitude = 5; | |
| assert(t1Named.height == 5); | |
| Tuple!(int, "a", int, int, "c") t2; | |
| t2 = tuple(3,4,5); | |
| auto t2Named = t2.rename!("", "b"); | |
| // "a" no longer has a name | |
| static assert(!__traits(hasMember, typeof(t2Named), "a")); | |
| assert(t2Named[0] == 3); | |
| assert(t2Named.b == 4); | |
| assert(t2Named.c == 5); | |
| // not allowed to specify more names than the tuple has members | |
| static assert(!__traits(compiles, t2.rename!("a","b","c","d"))); | |
| // use it in a range pipeline | |
| import std.range : iota, zip; | |
| import std.algorithm.iteration : map, sum; | |
| auto res = zip(iota(1, 4), iota(10, 13)) | |
| .map!(t => t.rename!("a", "b")) | |
| .map!(t => t.a * t.b) | |
| .sum; | |
| assert(res == 68); | |
| const tup = Tuple!(int, "a", int, "b")(2, 3); | |
| const renamed = tup.rename!("c", "d"); | |
| assert(renamed.c + renamed.d == 5); | |
| } | |
| /** | |
| * Overload of $(LREF _rename) that takes an associative array | |
| * `translate` as a template parameter, where the keys are | |
| * either the names or indices of the members to be changed | |
| * and the new names are the corresponding values. | |
| * Every key in `translate` must be the name of a member of the | |
| * $(LREF tuple). | |
| * The same rules for empty strings apply as for the variadic | |
| * template overload of $(LREF _rename). | |
| */ | |
| ref rename(alias translate)() inout | |
| if (is(typeof(translate) : V[K], V, K) && isSomeString!V && | |
| (isSomeString!K || is(K : size_t))) | |
| { | |
| import std.meta : aliasSeqOf; | |
| import std.range : ElementType; | |
| static if (isSomeString!(ElementType!(typeof(translate.keys)))) | |
| { | |
| { | |
| import std.conv : to; | |
| import std.algorithm.iteration : filter; | |
| import std.algorithm.searching : canFind; | |
| enum notFound = translate.keys | |
| .filter!(k => fieldNames.canFind(k) == -1); | |
| static assert(notFound.empty, "Cannot find members " | |
| ~ notFound.to!string ~ " in type " | |
| ~ typeof(this).stringof); | |
| } | |
| return this.rename!(aliasSeqOf!( | |
| { | |
| import std.array : empty; | |
| auto names = [fieldNames]; | |
| foreach (ref n; names) | |
| if (!n.empty) | |
| if (auto p = n in translate) | |
| n = *p; | |
| return names; | |
| }())); | |
| } | |
| else | |
| { | |
| { | |
| import std.algorithm.iteration : filter; | |
| import std.conv : to; | |
| enum invalid = translate.keys. | |
| filter!(k => k < 0 || k >= this.length); | |
| static assert(invalid.empty, "Indices " ~ invalid.to!string | |
| ~ " are out of bounds for tuple with length " | |
| ~ this.length.to!string); | |
| } | |
| return this.rename!(aliasSeqOf!( | |
| { | |
| auto names = [fieldNames]; | |
| foreach (k, v; translate) | |
| names[k] = v; | |
| return names; | |
| }())); | |
| } | |
| } | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| //replacing names by their current name | |
| Tuple!(float, "dat", size_t[2], "pos") t1; | |
| t1.pos = [2, 1]; | |
| auto t1Named = t1.rename!(["dat": "height"]); | |
| t1Named.height = 3.4; | |
| assert(t1Named.pos == [2, 1]); | |
| t1Named.rename!(["height": "altitude"]).altitude = 5; | |
| assert(t1Named.height == 5); | |
| Tuple!(int, "a", int, "b") t2; | |
| t2 = tuple(3, 4); | |
| auto t2Named = t2.rename!(["a": "b", "b": "c"]); | |
| assert(t2Named.b == 3); | |
| assert(t2Named.c == 4); | |
| const t3 = Tuple!(int, "a", int, "b")(3, 4); | |
| const t3Named = t3.rename!(["a": "b", "b": "c"]); | |
| assert(t3Named.b == 3); | |
| assert(t3Named.c == 4); | |
| } | |
| /// | |
| static if (Specs.length == 0) @system unittest | |
| { | |
| //replace names by their position | |
| Tuple!(float, "dat", size_t[2], "pos") t1; | |
| t1.pos = [2, 1]; | |
| auto t1Named = t1.rename!([0: "height"]); | |
| t1Named.height = 3.4; | |
| assert(t1Named.pos == [2, 1]); | |
| t1Named.rename!([0: "altitude"]).altitude = 5; | |
| assert(t1Named.height == 5); | |
| Tuple!(int, "a", int, "b", int, "c") t2; | |
| t2 = tuple(3, 4, 5); | |
| auto t2Named = t2.rename!([0: "c", 2: "a"]); | |
| assert(t2Named.a == 5); | |
| assert(t2Named.b == 4); | |
| assert(t2Named.c == 3); | |
| } | |
| static if (Specs.length == 0) @system unittest | |
| { | |
| //check that empty translations work fine | |
| enum string[string] a0 = null; | |
| enum string[int] a1 = null; | |
| Tuple!(float, "a", float, "b") t0; | |
| auto t1 = t0.rename!a0; | |
| t1.a = 3; | |
| t1.b = 4; | |
| auto t2 = t0.rename!a1; | |
| t2.a = 3; | |
| t2.b = 4; | |
| auto t3 = t0.rename; | |
| t3.a = 3; | |
| t3.b = 4; | |
| } | |
| /** | |
| * Takes a slice by-reference of this `Tuple`. | |
| * | |
| * Params: | |
| * from = A `size_t` designating the starting position of the slice. | |
| * to = A `size_t` designating the ending position (exclusive) of the slice. | |
| * | |
| * Returns: | |
| * A new `Tuple` that is a slice from `[from, to$(RPAREN)` of the original. | |
| * It has the same types and values as the range `[from, to$(RPAREN)` in | |
| * the original. | |
| */ | |
| @property | |
| ref inout(Tuple!(sliceSpecs!(from, to))) slice(size_t from, size_t to)() inout @trusted | |
| if (from <= to && to <= Types.length) | |
| { | |
| static assert( | |
| (typeof(this).alignof % typeof(return).alignof == 0) && | |
| (expand[from].offsetof % typeof(return).alignof == 0), | |
| "Slicing by reference is impossible because of an alignment mistmatch" ~ | |
| " (See https://issues.dlang.org/show_bug.cgi?id=15645)."); | |
| return *cast(typeof(return)*) &(field[from]); | |
| } | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| Tuple!(int, string, float, double) a; | |
| a[1] = "abc"; | |
| a[2] = 4.5; | |
| auto s = a.slice!(1, 3); | |
| static assert(is(typeof(s) == Tuple!(string, float))); | |
| assert(s[0] == "abc" && s[1] == 4.5); | |
| // https://issues.dlang.org/show_bug.cgi?id=15645 | |
| Tuple!(int, short, bool, double) b; | |
| static assert(!__traits(compiles, b.slice!(2, 4))); | |
| } | |
| /** | |
| Creates a hash of this `Tuple`. | |
| Returns: | |
| A `size_t` representing the hash of this `Tuple`. | |
| */ | |
| size_t toHash() const nothrow @safe | |
| { | |
| size_t h = 0; | |
| static foreach (i, T; Types) | |
| {{ | |
| static if (__traits(compiles, h = .hashOf(field[i]))) | |
| const k = .hashOf(field[i]); | |
| else | |
| { | |
| // Workaround for when .hashOf is not both @safe and nothrow. | |
| static if (is(T : shared U, U) && __traits(compiles, (U* a) nothrow @safe => .hashOf(*a)) | |
| && !__traits(hasMember, T, "toHash")) | |
| // BUG: Improperly casts away `shared`! | |
| const k = .hashOf(*(() @trusted => cast(U*) &field[i])()); | |
| else | |
| // BUG: Improperly casts away `shared`! | |
| const k = typeid(T).getHash((() @trusted => cast(const void*) &field[i])()); | |
| } | |
| static if (i == 0) | |
| h = k; | |
| else | |
| // As in boost::hash_combine | |
| // https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine | |
| h ^= k + 0x9e3779b9 + (h << 6) + (h >>> 2); | |
| }} | |
| return h; | |
| } | |
| /** | |
| * Converts to string. | |
| * | |
| * Returns: | |
| * The string representation of this `Tuple`. | |
| */ | |
| string toString()() | |
| { | |
| import std.array : appender; | |
| auto app = appender!string(); | |
| toString((const(char)[] chunk) => app ~= chunk); | |
| return app.data; | |
| } | |
| import std.format.spec : FormatSpec; | |
| /** | |
| * Formats `Tuple` with either `%s`, `%(inner%)` or `%(inner%|sep%)`. | |
| * | |
| * $(TABLE2 Formats supported by Tuple, | |
| * $(THEAD Format, Description) | |
| * $(TROW $(P `%s`), $(P Format like `Tuple!(types)(elements formatted with %s each)`.)) | |
| * $(TROW $(P `%(inner%)`), $(P The format `inner` is applied the expanded `Tuple`$(COMMA) so | |
| * it may contain as many formats as the `Tuple` has fields.)) | |
| * $(TROW $(P `%(inner%|sep%)`), $(P The format `inner` is one format$(COMMA) that is applied | |
| * on all fields of the `Tuple`. The inner format must be compatible to all | |
| * of them.))) | |
| * | |
| * Params: | |
| * sink = A `char` accepting delegate | |
| * fmt = A $(REF FormatSpec, std,format) | |
| */ | |
| void toString(DG)(scope DG sink) | |
| { | |
| auto f = FormatSpec!char(); | |
| toString(sink, f); | |
| } | |
| /// ditto | |
| void toString(DG, Char)(scope DG sink, scope const ref FormatSpec!Char fmt) | |
| { | |
| import std.format : format, FormatException; | |
| import std.format.write : formattedWrite; | |
| import std.range : only; | |
| if (fmt.nested) | |
| { | |
| if (fmt.sep) | |
| { | |
| foreach (i, Type; Types) | |
| { | |
| static if (i > 0) | |
| { | |
| sink(fmt.sep); | |
| } | |
| formattedWrite(sink, fmt.nested, this.field[i]); | |
| } | |
| } | |
| else | |
| { | |
| formattedWrite(sink, fmt.nested, this.expand); | |
| } | |
| } | |
| else if (fmt.spec == 's') | |
| { | |
| enum header = Unqual!(typeof(this)).stringof ~ "(", | |
| footer = ")", | |
| separator = ", "; | |
| sink(header); | |
| foreach (i, Type; Types) | |
| { | |
| static if (i > 0) | |
| { | |
| sink(separator); | |
| } | |
| // Among other things, using "only" causes string-fields to be inside quotes in the result | |
| sink.formattedWrite!("%(%s%)")(only(field[i])); | |
| } | |
| sink(footer); | |
| } | |
| else | |
| { | |
| const spec = fmt.spec; | |
| throw new FormatException( | |
| "Expected '%s' or '%(...%)' or '%(...%|...%)' format specifier for type '" ~ | |
| Unqual!(typeof(this)).stringof ~ "', not '%" ~ spec ~ "'."); | |
| } | |
| } | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| import std.format : format; | |
| Tuple!(int, double)[3] tupList = [ tuple(1, 1.0), tuple(2, 4.0), tuple(3, 9.0) ]; | |
| // Default format | |
| assert(format("%s", tuple("a", 1)) == `Tuple!(string, int)("a", 1)`); | |
| // One Format for each individual component | |
| assert(format("%(%#x v %.4f w %#x%)", tuple(1, 1.0, 10)) == `0x1 v 1.0000 w 0xa`); | |
| assert(format( "%#x v %.4f w %#x" , tuple(1, 1.0, 10).expand) == `0x1 v 1.0000 w 0xa`); | |
| // One Format for all components | |
| assert(format("%(>%s<%| & %)", tuple("abc", 1, 2.3, [4, 5])) == `>abc< & >1< & >2.3< & >[4, 5]<`); | |
| // Array of Tuples | |
| assert(format("%(%(f(%d) = %.1f%); %)", tupList) == `f(1) = 1.0; f(2) = 4.0; f(3) = 9.0`); | |
| } | |
| /// | |
| static if (Specs.length == 0) @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| import std.format : format, FormatException; | |
| // Error: %( %) missing. | |
| assertThrown!FormatException( | |
| format("%d, %f", tuple(1, 2.0)) == `1, 2.0` | |
| ); | |
| // Error: %( %| %) missing. | |
| assertThrown!FormatException( | |
| format("%d", tuple(1, 2)) == `1, 2` | |
| ); | |
| // Error: %d inadequate for double | |
| assertThrown!FormatException( | |
| format("%(%d%|, %)", tuple(1, 2.0)) == `1, 2.0` | |
| ); | |
| } | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| Tuple!(int, int) point; | |
| // assign coordinates | |
| point[0] = 5; | |
| point[1] = 6; | |
| // read coordinates | |
| auto x = point[0]; | |
| auto y = point[1]; | |
| } | |
| /** | |
| `Tuple` members can be named. It is legal to mix named and unnamed | |
| members. The method above is still applicable to all fields. | |
| */ | |
| @safe unittest | |
| { | |
| alias Entry = Tuple!(int, "index", string, "value"); | |
| Entry e; | |
| e.index = 4; | |
| e.value = "Hello"; | |
| assert(e[1] == "Hello"); | |
| assert(e[0] == 4); | |
| } | |
| /** | |
| A `Tuple` with named fields is a distinct type from a `Tuple` with unnamed | |
| fields, i.e. each naming imparts a separate type for the `Tuple`. Two | |
| `Tuple`s differing in naming only are still distinct, even though they | |
| might have the same structure. | |
| */ | |
| @safe unittest | |
| { | |
| Tuple!(int, "x", int, "y") point1; | |
| Tuple!(int, int) point2; | |
| assert(!is(typeof(point1) == typeof(point2))); | |
| } | |
| /// Use tuples as ranges | |
| @safe unittest | |
| { | |
| import std.algorithm.iteration : sum; | |
| import std.range : only; | |
| auto t = tuple(1, 2); | |
| assert(t.expand.only.sum == 3); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=4582 | |
| @safe unittest | |
| { | |
| static assert(!__traits(compiles, Tuple!(string, "id", int, "id"))); | |
| static assert(!__traits(compiles, Tuple!(string, "str", int, "i", string, "str", float))); | |
| } | |
| /// Concatenate tuples | |
| @safe unittest | |
| { | |
| import std.meta : AliasSeq; | |
| auto t = tuple(1, "2") ~ tuple(ushort(42), true); | |
| static assert(is(t.Types == AliasSeq!(int, string, ushort, bool))); | |
| assert(t[1] == "2"); | |
| assert(t[2] == 42); | |
| assert(t[3] == true); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=14637 | |
| // tuple concat | |
| @safe unittest | |
| { | |
| auto t = tuple!"foo"(1.0) ~ tuple!"bar"("3"); | |
| static assert(is(t.Types == AliasSeq!(double, string))); | |
| static assert(t.fieldNames == tuple("foo", "bar")); | |
| assert(t.foo == 1.0); | |
| assert(t.bar == "3"); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=18824 | |
| // tuple concat | |
| @safe unittest | |
| { | |
| alias Type = Tuple!(int, string); | |
| Type[] arr; | |
| auto t = tuple(2, "s"); | |
| // Test opBinaryRight | |
| arr = arr ~ t; | |
| // Test opBinary | |
| arr = t ~ arr; | |
| static assert(is(typeof(arr) == Type[])); | |
| immutable Type[] b; | |
| auto c = b ~ t; | |
| static assert(is(typeof(c) == immutable(Type)[])); | |
| } | |
| // tuple concat | |
| @safe unittest | |
| { | |
| auto t = tuple!"foo"(1.0) ~ "3"; | |
| static assert(is(t.Types == AliasSeq!(double, string))); | |
| assert(t.foo == 1.0); | |
| assert(t[1]== "3"); | |
| } | |
| // tuple concat | |
| @safe unittest | |
| { | |
| auto t = "2" ~ tuple!"foo"(1.0); | |
| static assert(is(t.Types == AliasSeq!(string, double))); | |
| assert(t.foo == 1.0); | |
| assert(t[0]== "2"); | |
| } | |
| // tuple concat | |
| @safe unittest | |
| { | |
| auto t = "2" ~ tuple!"foo"(1.0) ~ tuple(42, 3.0f) ~ real(1) ~ "a"; | |
| static assert(is(t.Types == AliasSeq!(string, double, int, float, real, string))); | |
| assert(t.foo == 1.0); | |
| assert(t[0] == "2"); | |
| assert(t[1] == 1.0); | |
| assert(t[2] == 42); | |
| assert(t[3] == 3.0f); | |
| assert(t[4] == 1.0); | |
| assert(t[5] == "a"); | |
| } | |
| // ensure that concatenation of tuples with non-distinct fields is forbidden | |
| @safe unittest | |
| { | |
| static assert(!__traits(compiles, | |
| tuple!("a")(0) ~ tuple!("a")("1"))); | |
| static assert(!__traits(compiles, | |
| tuple!("a", "b")(0, 1) ~ tuple!("b", "a")("3", 1))); | |
| static assert(!__traits(compiles, | |
| tuple!("a")(0) ~ tuple!("b", "a")("3", 1))); | |
| static assert(!__traits(compiles, | |
| tuple!("a1", "a")(1.0, 0) ~ tuple!("a2", "a")("3", 0))); | |
| } | |
| // Ensure that Tuple comparison with non-const opEquals works | |
| @safe unittest | |
| { | |
| static struct Bad | |
| { | |
| int a; | |
| bool opEquals(Bad b) | |
| { | |
| return a == b.a; | |
| } | |
| } | |
| auto t = Tuple!(int, Bad, string)(1, Bad(1), "asdf"); | |
| //Error: mutable method Bad.opEquals is not callable using a const object | |
| assert(t == AliasSeq!(1, Bad(1), "asdf")); | |
| } | |
| // Ensure Tuple.toHash works | |
| @safe unittest | |
| { | |
| Tuple!(int, int) point; | |
| assert(point.toHash == typeof(point).init.toHash); | |
| assert(tuple(1, 2) != point); | |
| assert(tuple(1, 2) == tuple(1, 2)); | |
| point[0] = 1; | |
| assert(tuple(1, 2) != point); | |
| point[1] = 2; | |
| assert(tuple(1, 2) == point); | |
| } | |
| @safe @betterC unittest | |
| { | |
| auto t = tuple(1, 2); | |
| assert(t == tuple(1, 2)); | |
| auto t3 = tuple(1, 'd'); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=20850 | |
| // Assignment to enum tuple | |
| @safe unittest | |
| { | |
| enum T : Tuple!(int*) { a = T(null) } | |
| T t; | |
| t = T.a; | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=13663 | |
| @safe unittest | |
| { | |
| auto t = tuple(real.nan); | |
| assert(!(t > t)); | |
| assert(!(t < t)); | |
| assert(!(t == t)); | |
| } | |
| @safe unittest | |
| { | |
| struct S | |
| { | |
| float opCmp(S s) { return float.nan; } | |
| bool opEquals(S s) { return false; } | |
| } | |
| auto t = tuple(S()); | |
| assert(!(t > t)); | |
| assert(!(t < t)); | |
| assert(!(t == t)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=8015 | |
| @safe unittest | |
| { | |
| struct MyStruct | |
| { | |
| string str; | |
| @property string toStr() | |
| { | |
| return str; | |
| } | |
| alias toStr this; | |
| } | |
| Tuple!(MyStruct) t; | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=24465 | |
| @safe unittest | |
| { | |
| { | |
| static struct S | |
| { | |
| this(ref return scope inout(S) rhs) scope @trusted inout pure nothrow {} | |
| } | |
| static void foo(Tuple!S) | |
| { | |
| } | |
| Tuple!S t; | |
| foo(t); | |
| auto t2 = Tuple!S(t); | |
| } | |
| { | |
| static struct S {} | |
| Tuple!S t; | |
| auto t2 = Tuple!S(t); | |
| // This can't be done if Tuple has a copy constructor, because it's not | |
| // allowed to have an rvalue constructor at that point, and the | |
| // compiler doesn't to something intelligent like transform it into a | |
| // move instead. However, it has been legal with Tuple for a while | |
| // (maybe even since it was first added) when the type doesn't have a | |
| // copy constructor, so this is testing to make sure that the fix to | |
| // make copy constructors work doesn't mess up the rvalue constructor | |
| // when none of the Tuple's members have copy constructors. | |
| auto t3 = Tuple!S(Tuple!S.init); | |
| } | |
| } | |
| /** | |
| Creates a copy of a $(LREF Tuple) with its fields in _reverse order. | |
| Params: | |
| t = The `Tuple` to copy. | |
| Returns: | |
| A new `Tuple`. | |
| */ | |
| auto reverse(T)(T t) | |
| if (isTuple!T) | |
| { | |
| import std.meta : Reverse; | |
| // @@@BUG@@@ Cannot be an internal function due to forward reference issues. | |
| // @@@BUG@@@ 9929 Need 'this' when calling template with expanded tuple | |
| // return tuple(Reverse!(t.expand)); | |
| ReverseTupleType!T result; | |
| auto tup = t.expand; | |
| result.expand = Reverse!tup; | |
| return result; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| auto tup = tuple(1, "2"); | |
| assert(tup.reverse == tuple("2", 1)); | |
| } | |
| /* Get a Tuple type with the reverse specification of Tuple T. */ | |
| private template ReverseTupleType(T) | |
| if (isTuple!T) | |
| { | |
| static if (is(T : Tuple!A, A...)) | |
| alias ReverseTupleType = Tuple!(ReverseTupleSpecs!A); | |
| } | |
| /* Reverse the Specs of a Tuple. */ | |
| private template ReverseTupleSpecs(T...) | |
| { | |
| static if (T.length > 1) | |
| { | |
| static if (is(typeof(T[$-1]) : string)) | |
| { | |
| alias ReverseTupleSpecs = AliasSeq!(T[$-2], T[$-1], ReverseTupleSpecs!(T[0 .. $-2])); | |
| } | |
| else | |
| { | |
| alias ReverseTupleSpecs = AliasSeq!(T[$-1], ReverseTupleSpecs!(T[0 .. $-1])); | |
| } | |
| } | |
| else | |
| { | |
| alias ReverseTupleSpecs = T; | |
| } | |
| } | |
| // ensure that internal Tuple unittests are compiled | |
| @safe unittest | |
| { | |
| Tuple!() t; | |
| } | |
| @safe unittest | |
| { | |
| import std.conv; | |
| { | |
| Tuple!(int, "a", int, "b") nosh; | |
| static assert(nosh.length == 2); | |
| nosh.a = 5; | |
| nosh.b = 6; | |
| assert(nosh.a == 5); | |
| assert(nosh.b == 6); | |
| } | |
| { | |
| Tuple!(short, double) b; | |
| static assert(b.length == 2); | |
| b[1] = 5; | |
| auto a = Tuple!(int, real)(b); | |
| assert(a[0] == 0 && a[1] == 5); | |
| a = Tuple!(int, real)(1, 2); | |
| assert(a[0] == 1 && a[1] == 2); | |
| auto c = Tuple!(int, "a", double, "b")(a); | |
| assert(c[0] == 1 && c[1] == 2); | |
| } | |
| { | |
| Tuple!(int, real) nosh; | |
| nosh[0] = 5; | |
| nosh[1] = 0; | |
| assert(nosh[0] == 5 && nosh[1] == 0); | |
| assert(nosh.to!string == "Tuple!(int, real)(5, 0)", nosh.to!string); | |
| Tuple!(int, int) yessh; | |
| nosh = yessh; | |
| } | |
| { | |
| class A {} | |
| Tuple!(int, shared A) nosh; | |
| nosh[0] = 5; | |
| assert(nosh[0] == 5 && nosh[1] is null); | |
| assert(nosh.to!string == "Tuple!(int, shared(A))(5, null)"); | |
| } | |
| { | |
| // Shared, without fmt.sep | |
| import std.format; | |
| import std.algorithm.searching; | |
| static class A {int i = 1;} | |
| Tuple!(int, shared A) nosh; | |
| nosh[0] = 5; | |
| assert(nosh[0] == 5 && nosh[1] is null); | |
| // needs trusted, because Object.toString() isn't @safe | |
| auto f = ()@trusted => format!("%(%s, %s%)")(nosh); | |
| assert(f() == "5, null"); | |
| nosh[1] = new shared A(); | |
| // Currently contains the mangled type name | |
| // 5, const(std.typecons.__unittest_L1750_C7.A) | |
| // This assert is not necessarily to prescribe this behaviour, only to signal if there is a breaking change. | |
| // See https://github.com/dlang/phobos/issues/9811 | |
| auto s = f(); | |
| assert(s.canFind("__unittest_L")); | |
| assert(s.endsWith(".A)")); | |
| } | |
| { | |
| static struct A {} | |
| Tuple!(int, shared A*) nosh; | |
| nosh[0] = 5; | |
| assert(nosh[0] == 5 && nosh[1] is null); | |
| assert(nosh.to!string == "Tuple!(int, shared(A*))(5, null)"); | |
| } | |
| { | |
| Tuple!(int, string) t; | |
| t[0] = 10; | |
| t[1] = "str"; | |
| assert(t[0] == 10 && t[1] == "str"); | |
| assert(t.to!string == `Tuple!(int, string)(10, "str")`, t.to!string); | |
| } | |
| /* https://github.com/dlang/phobos/issues/9811 | |
| * Note: This is just documenting current behaviour, dependent on `std.format` implementation | |
| * details. None of this is defined in a spec or should be regarded as rigid. | |
| */ | |
| { | |
| static struct X | |
| { | |
| /** Usually, toString() should be const where possible. | |
| * But as long as the tuple is also non-const, this will work | |
| */ | |
| string toString() | |
| { | |
| return "toString non-const"; | |
| } | |
| } | |
| assert(tuple(X()).to!string == "Tuple!(X)(toString non-const)"); | |
| const t = tuple(X()); | |
| // This is an implementation detail of `format` | |
| // if the tuple is const, than non-const toString will not be called | |
| assert(t.to!string == "const(Tuple!(X))(const(X)())"); | |
| static struct X2 | |
| { | |
| string toString() const /* const toString will work in more cases */ | |
| { | |
| return "toString const"; | |
| } | |
| } | |
| assert(tuple(X2()).to!string == "Tuple!(X2)(toString const)"); | |
| const t2 = tuple(X2()); | |
| // This is an implementation detail of `format` | |
| // if the tuple is const, than non-const toString will not be called | |
| assert(t2.to!string == "const(Tuple!(X2))(toString const)"); | |
| } | |
| { | |
| Tuple!(int, "a", double, "b") x; | |
| static assert(x.a.offsetof == x[0].offsetof); | |
| static assert(x.b.offsetof == x[1].offsetof); | |
| x.b = 4.5; | |
| x.a = 5; | |
| assert(x[0] == 5 && x[1] == 4.5); | |
| assert(x.a == 5 && x.b == 4.5); | |
| } | |
| // indexing | |
| { | |
| Tuple!(int, real) t; | |
| static assert(is(typeof(t[0]) == int)); | |
| static assert(is(typeof(t[1]) == real)); | |
| int* p0 = &t[0]; | |
| real* p1 = &t[1]; | |
| t[0] = 10; | |
| t[1] = -200.0L; | |
| assert(*p0 == t[0]); | |
| assert(*p1 == t[1]); | |
| } | |
| // slicing | |
| { | |
| Tuple!(int, "x", real, "y", double, "z", string) t; | |
| t[0] = 10; | |
| t[1] = 11; | |
| t[2] = 12; | |
| t[3] = "abc"; | |
| auto a = t.slice!(0, 3); | |
| assert(a.length == 3); | |
| assert(a.x == t.x); | |
| assert(a.y == t.y); | |
| assert(a.z == t.z); | |
| auto b = t.slice!(2, 4); | |
| assert(b.length == 2); | |
| assert(b.z == t.z); | |
| assert(b[1] == t[3]); | |
| } | |
| // nesting | |
| { | |
| Tuple!(Tuple!(int, real), Tuple!(string, "s")) t; | |
| static assert(is(typeof(t[0]) == Tuple!(int, real))); | |
| static assert(is(typeof(t[1]) == Tuple!(string, "s"))); | |
| static assert(is(typeof(t[0][0]) == int)); | |
| static assert(is(typeof(t[0][1]) == real)); | |
| static assert(is(typeof(t[1].s) == string)); | |
| t[0] = tuple(10, 20.0L); | |
| t[1].s = "abc"; | |
| assert(t[0][0] == 10); | |
| assert(t[0][1] == 20.0L); | |
| assert(t[1].s == "abc"); | |
| } | |
| // non-POD | |
| { | |
| static struct S | |
| { | |
| int count; | |
| this(this) { ++count; } | |
| ~this() { --count; } | |
| void opAssign(S rhs) { count = rhs.count; } | |
| } | |
| Tuple!(S, S) ss; | |
| Tuple!(S, S) ssCopy = ss; | |
| assert(ssCopy[0].count == 1); | |
| assert(ssCopy[1].count == 1); | |
| ssCopy[1] = ssCopy[0]; | |
| assert(ssCopy[1].count == 2); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=2800 | |
| { | |
| static struct R | |
| { | |
| Tuple!(int, int) _front; | |
| @property ref Tuple!(int, int) front() return { return _front; } | |
| @property bool empty() { return _front[0] >= 10; } | |
| void popFront() { ++_front[0]; } | |
| } | |
| foreach (a; R()) | |
| { | |
| static assert(is(typeof(a) == Tuple!(int, int))); | |
| assert(0 <= a[0] && a[0] < 10); | |
| assert(a[1] == 0); | |
| } | |
| } | |
| // Construction with compatible elements | |
| { | |
| auto t1 = Tuple!(int, double)(1, 1); | |
| // https://issues.dlang.org/show_bug.cgi?id=8702 | |
| auto t8702a = tuple(tuple(1)); | |
| auto t8702b = Tuple!(Tuple!(int))(Tuple!(int)(1)); | |
| } | |
| // Construction with compatible tuple | |
| { | |
| Tuple!(int, int) x; | |
| x[0] = 10; | |
| x[1] = 20; | |
| Tuple!(int, "a", double, "b") y = x; | |
| assert(y.a == 10); | |
| assert(y.b == 20); | |
| // incompatible | |
| static assert(!__traits(compiles, Tuple!(int, int)(y))); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=6275 | |
| { | |
| const int x = 1; | |
| auto t1 = tuple(x); | |
| alias T = Tuple!(const(int)); | |
| auto t2 = T(1); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=9431 | |
| { | |
| alias T = Tuple!(int[1][]); | |
| auto t = T([[10]]); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=7666 | |
| { | |
| auto tup = tuple(1, "2"); | |
| assert(tup.reverse == tuple("2", 1)); | |
| } | |
| { | |
| Tuple!(int, "x", string, "y") tup = tuple(1, "2"); | |
| auto rev = tup.reverse; | |
| assert(rev == tuple("2", 1)); | |
| assert(rev.x == 1 && rev.y == "2"); | |
| } | |
| { | |
| Tuple!(wchar, dchar, int, "x", string, "y", char, byte, float) tup; | |
| tup = tuple('a', 'b', 3, "4", 'c', cast(byte) 0x0D, 0.00); | |
| auto rev = tup.reverse; | |
| assert(rev == tuple(0.00, cast(byte) 0x0D, 'c', "4", 3, 'b', 'a')); | |
| assert(rev.x == 3 && rev.y == "4"); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| // opEquals | |
| { | |
| struct Equ1 { bool opEquals(Equ1) { return true; } } | |
| auto tm1 = tuple(Equ1.init); | |
| const tc1 = tuple(Equ1.init); | |
| static assert( is(typeof(tm1 == tm1))); | |
| static assert(!is(typeof(tm1 == tc1))); | |
| static assert(!is(typeof(tc1 == tm1))); | |
| static assert(!is(typeof(tc1 == tc1))); | |
| struct Equ2 { bool opEquals(const Equ2) const { return true; } } | |
| auto tm2 = tuple(Equ2.init); | |
| const tc2 = tuple(Equ2.init); | |
| static assert( is(typeof(tm2 == tm2))); | |
| static assert( is(typeof(tm2 == tc2))); | |
| static assert( is(typeof(tc2 == tm2))); | |
| static assert( is(typeof(tc2 == tc2))); | |
| // https://issues.dlang.org/show_bug.cgi?id=8686 | |
| struct Equ3 { bool opEquals(T)(T) { return true; } } | |
| auto tm3 = tuple(Equ3.init); | |
| const tc3 = tuple(Equ3.init); | |
| static assert( is(typeof(tm3 == tm3))); | |
| static assert( is(typeof(tm3 == tc3))); | |
| static assert(!is(typeof(tc3 == tm3))); | |
| static assert(!is(typeof(tc3 == tc3))); | |
| struct Equ4 { bool opEquals(T)(T) const { return true; } } | |
| auto tm4 = tuple(Equ4.init); | |
| const tc4 = tuple(Equ4.init); | |
| static assert( is(typeof(tm4 == tm4))); | |
| static assert( is(typeof(tm4 == tc4))); | |
| static assert( is(typeof(tc4 == tm4))); | |
| static assert( is(typeof(tc4 == tc4))); | |
| } | |
| // opCmp | |
| { | |
| struct Cmp1 { int opCmp(Cmp1) { return 0; } } | |
| auto tm1 = tuple(Cmp1.init); | |
| const tc1 = tuple(Cmp1.init); | |
| static assert( is(typeof(tm1 < tm1))); | |
| static assert(!is(typeof(tm1 < tc1))); | |
| static assert(!is(typeof(tc1 < tm1))); | |
| static assert(!is(typeof(tc1 < tc1))); | |
| struct Cmp2 { int opCmp(const Cmp2) const { return 0; } } | |
| auto tm2 = tuple(Cmp2.init); | |
| const tc2 = tuple(Cmp2.init); | |
| static assert( is(typeof(tm2 < tm2))); | |
| static assert( is(typeof(tm2 < tc2))); | |
| static assert( is(typeof(tc2 < tm2))); | |
| static assert( is(typeof(tc2 < tc2))); | |
| struct Cmp3 { int opCmp(T)(T) { return 0; } } | |
| auto tm3 = tuple(Cmp3.init); | |
| const tc3 = tuple(Cmp3.init); | |
| static assert( is(typeof(tm3 < tm3))); | |
| static assert( is(typeof(tm3 < tc3))); | |
| static assert(!is(typeof(tc3 < tm3))); | |
| static assert(!is(typeof(tc3 < tc3))); | |
| struct Cmp4 { int opCmp(T)(T) const { return 0; } } | |
| auto tm4 = tuple(Cmp4.init); | |
| const tc4 = tuple(Cmp4.init); | |
| static assert( is(typeof(tm4 < tm4))); | |
| static assert( is(typeof(tm4 < tc4))); | |
| static assert( is(typeof(tc4 < tm4))); | |
| static assert( is(typeof(tc4 < tc4))); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=14890 | |
| static void test14890(inout int[] dummy) | |
| { | |
| alias V = Tuple!(int, int); | |
| V mv; | |
| const V cv; | |
| immutable V iv; | |
| inout V wv; // OK <- NG | |
| inout const V wcv; // OK <- NG | |
| static foreach (v1; AliasSeq!(mv, cv, iv, wv, wcv)) | |
| static foreach (v2; AliasSeq!(mv, cv, iv, wv, wcv)) | |
| { | |
| assert(!(v1 < v2)); | |
| } | |
| } | |
| { | |
| int[2] ints = [ 1, 2 ]; | |
| Tuple!(int, int) t = ints; | |
| assert(t[0] == 1 && t[1] == 2); | |
| Tuple!(long, uint) t2 = ints; | |
| assert(t2[0] == 1 && t2[1] == 2); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| auto t1 = Tuple!(int, "x", string, "y")(1, "a"); | |
| assert(t1.x == 1); | |
| assert(t1.y == "a"); | |
| void foo(Tuple!(int, string) t2) {} | |
| foo(t1); | |
| Tuple!(int, int)[] arr; | |
| arr ~= tuple(10, 20); // OK | |
| arr ~= Tuple!(int, "x", int, "y")(10, 20); // NG -> OK | |
| static assert(is(typeof(Tuple!(int, "x", string, "y").tupleof) == | |
| typeof(Tuple!(int, string ).tupleof))); | |
| } | |
| @safe unittest | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=10686 | |
| immutable Tuple!(int) t1; | |
| auto r1 = t1[0]; // OK | |
| immutable Tuple!(int, "x") t2; | |
| auto r2 = t2[0]; // error | |
| } | |
| @safe unittest | |
| { | |
| import std.exception : assertCTFEable; | |
| // https://issues.dlang.org/show_bug.cgi?id=10218 | |
| assertCTFEable!( | |
| { | |
| auto t = tuple(1); | |
| t = tuple(2); // assignment | |
| }); | |
| } | |
| @safe unittest | |
| { | |
| class Foo{} | |
| Tuple!(immutable(Foo)[]) a; | |
| } | |
| @safe unittest | |
| { | |
| //Test non-assignable | |
| static struct S | |
| { | |
| int* p; | |
| } | |
| alias IS = immutable S; | |
| static assert(!isAssignable!IS); | |
| auto s = IS.init; | |
| alias TIS = Tuple!IS; | |
| TIS a = tuple(s); | |
| TIS b = a; | |
| alias TISIS = Tuple!(IS, IS); | |
| TISIS d = tuple(s, s); | |
| IS[2] ss; | |
| TISIS e = TISIS(ss); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=9819 | |
| @safe unittest | |
| { | |
| alias T = Tuple!(int, "x", double, "foo"); | |
| static assert(T.fieldNames[0] == "x"); | |
| static assert(T.fieldNames[1] == "foo"); | |
| alias Fields = Tuple!(int, "id", string, float); | |
| static assert(Fields.fieldNames == AliasSeq!("id", "", "")); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=13837 | |
| @safe unittest | |
| { | |
| // New behaviour, named arguments. | |
| static assert(is( | |
| typeof(tuple!("x")(1)) == Tuple!(int, "x"))); | |
| static assert(is( | |
| typeof(tuple!("x")(1.0)) == Tuple!(double, "x"))); | |
| static assert(is( | |
| typeof(tuple!("x")("foo")) == Tuple!(string, "x"))); | |
| static assert(is( | |
| typeof(tuple!("x", "y")(1, 2.0)) == Tuple!(int, "x", double, "y"))); | |
| auto a = tuple!("a", "b", "c")("1", 2, 3.0f); | |
| static assert(is(typeof(a.a) == string)); | |
| static assert(is(typeof(a.b) == int)); | |
| static assert(is(typeof(a.c) == float)); | |
| // Old behaviour, but with explicit type parameters. | |
| static assert(is( | |
| typeof(tuple!(int, double)(1, 2.0)) == Tuple!(int, double))); | |
| static assert(is( | |
| typeof(tuple!(const int)(1)) == Tuple!(const int))); | |
| static assert(is( | |
| typeof(tuple()) == Tuple!())); | |
| // Nonsensical behaviour | |
| static assert(!__traits(compiles, tuple!(1)(2))); | |
| static assert(!__traits(compiles, tuple!("x")(1, 2))); | |
| static assert(!__traits(compiles, tuple!("x", "y")(1))); | |
| static assert(!__traits(compiles, tuple!("x")())); | |
| static assert(!__traits(compiles, tuple!("x", int)(2))); | |
| } | |
| @safe unittest | |
| { | |
| class C { override size_t toHash() const nothrow @safe { return 0; } } | |
| Tuple!(Rebindable!(const C)) a; | |
| Tuple!(const C) b; | |
| a = b; | |
| } | |
| @nogc @safe unittest | |
| { | |
| alias T = Tuple!(string, "s"); | |
| T x; | |
| x = T.init; | |
| } | |
| @safe unittest | |
| { | |
| import std.format : format, FormatException; | |
| import std.exception : assertThrown; | |
| //enum tupStr = tuple(1, 1.0).toString; // toString is *impure*. | |
| //static assert(tupStr == `Tuple!(int, double)(1, 1)`); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=17803, parte uno | |
| @safe unittest | |
| { | |
| auto a = tuple(3, "foo"); | |
| assert(__traits(compiles, { a = (a = a); })); | |
| } | |
| // Ditto | |
| @safe unittest | |
| { | |
| Tuple!(int[]) a, b, c; | |
| a = tuple([0, 1, 2]); | |
| c = b = a; | |
| assert(a[0].length == b[0].length && b[0].length == c[0].length); | |
| assert(a[0].ptr == b[0].ptr && b[0].ptr == c[0].ptr); | |
| } | |
| /** | |
| Constructs a $(LREF Tuple) object instantiated and initialized according to | |
| the given arguments. | |
| Params: | |
| Names = An optional list of strings naming each successive field of the `Tuple` | |
| or a list of types that the elements are being casted to. | |
| For a list of names, | |
| each name matches up with the corresponding field given by `Args`. | |
| A name does not have to be provided for every field, but as | |
| the names must proceed in order, it is not possible to skip | |
| one field and name the next after it. | |
| For a list of types, | |
| there must be exactly as many types as parameters. | |
| */ | |
| template tuple(Names...) | |
| { | |
| /** | |
| Params: | |
| args = Values to initialize the `Tuple` with. The `Tuple`'s type will | |
| be inferred from the types of the values given. | |
| Returns: | |
| A new `Tuple` with its type inferred from the arguments given. | |
| */ | |
| auto tuple(Args...)(Args args) | |
| { | |
| static if (Names.length == 0) | |
| { | |
| // No specified names, just infer types from Args... | |
| return Tuple!Args(args); | |
| } | |
| else static if (!is(typeof(Names[0]) : string)) | |
| { | |
| // Names[0] isn't a string, must be explicit types. | |
| return Tuple!Names(args); | |
| } | |
| else | |
| { | |
| // Names[0] is a string, so must be specifying names. | |
| static assert(Names.length == Args.length, | |
| "Insufficient number of names given."); | |
| // Interleave(a, b).and(c, d) == (a, c, b, d) | |
| // This is to get the interleaving of types and names for Tuple | |
| // e.g. Tuple!(int, "x", string, "y") | |
| template Interleave(A...) | |
| { | |
| template and(B...) | |
| if (B.length == 1) | |
| { | |
| alias and = AliasSeq!(A[0], B[0]); | |
| } | |
| template and(B...) | |
| if (B.length != 1) | |
| { | |
| alias and = AliasSeq!(A[0], B[0], | |
| Interleave!(A[1..$]).and!(B[1..$])); | |
| } | |
| } | |
| return Tuple!(Interleave!(Args).and!(Names))(args); | |
| } | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| auto value = tuple(5, 6.7, "hello"); | |
| assert(value[0] == 5); | |
| assert(value[1] == 6.7); | |
| assert(value[2] == "hello"); | |
| // Field names can be provided. | |
| auto entry = tuple!("index", "value")(4, "Hello"); | |
| assert(entry.index == 4); | |
| assert(entry.value == "Hello"); | |
| } | |
| /** | |
| Returns `true` if and only if `T` is an instance of `std.typecons.Tuple`. | |
| Params: | |
| T = The type to check. | |
| Returns: | |
| true if `T` is a `Tuple` type, false otherwise. | |
| */ | |
| enum isTuple(T) = __traits(compiles, | |
| { | |
| void f(Specs...)(Tuple!Specs tup) {} | |
| f(T.init); | |
| } ); | |
| /// | |
| @safe unittest | |
| { | |
| static assert(isTuple!(Tuple!())); | |
| static assert(isTuple!(Tuple!(int))); | |
| static assert(isTuple!(Tuple!(int, real, string))); | |
| static assert(isTuple!(Tuple!(int, "x", real, "y"))); | |
| static assert(isTuple!(Tuple!(int, Tuple!(real), string))); | |
| } | |
| @safe unittest | |
| { | |
| static assert(isTuple!(const Tuple!(int))); | |
| static assert(isTuple!(immutable Tuple!(int))); | |
| static assert(!isTuple!(int)); | |
| static assert(!isTuple!(const int)); | |
| struct S {} | |
| static assert(!isTuple!(S)); | |
| } | |
| // used by both Rebindable and UnqualRef | |
| private mixin template RebindableCommon(T, U, alias This) | |
| if (is(T == class) || is(T == interface) || isAssociativeArray!T) | |
| { | |
| private union | |
| { | |
| T original; | |
| U stripped; | |
| } | |
| void opAssign(return scope T another) pure nothrow @nogc | |
| { | |
| // If `T` defines `opCast` we must infer the safety | |
| static if (hasMember!(T, "opCast")) | |
| { | |
| // This will allow the compiler to infer the safety of `T.opCast!U` | |
| // without generating any runtime cost | |
| if (false) { stripped = cast(U) another; } | |
| } | |
| () @trusted { stripped = cast(U) another; }(); | |
| } | |
| void opAssign(typeof(this) another) @trusted pure nothrow @nogc | |
| { | |
| stripped = another.stripped; | |
| } | |
| static if (is(T == const U) && is(T == const shared U)) | |
| { | |
| // safely assign immutable to const / const shared | |
| void opAssign(This!(immutable U) another) @trusted pure nothrow @nogc | |
| { | |
| stripped = another.stripped; | |
| } | |
| } | |
| this(T initializer) pure nothrow @nogc | |
| { | |
| // Infer safety from opAssign | |
| opAssign(initializer); | |
| } | |
| @property inout(T) get() @trusted pure nothrow @nogc return scope inout | |
| { | |
| return original; | |
| } | |
| bool opEquals()(auto ref const(typeof(this)) rhs) const | |
| { | |
| // Must forward explicitly because 'stripped' is part of a union. | |
| // The necessary 'toHash' is forwarded to the class via alias this. | |
| return stripped == rhs.stripped; | |
| } | |
| bool opEquals(const(U) rhs) const | |
| { | |
| return stripped == rhs; | |
| } | |
| alias get this; | |
| } | |
| /** | |
| `Rebindable!(T)` is a simple, efficient wrapper that behaves just | |
| like an object of type `T`, except that you can reassign it to | |
| refer to another object. For completeness, `Rebindable!(T)` aliases | |
| itself away to `T` if `T` is a non-const object type. | |
| You may want to use `Rebindable` when you want to have mutable | |
| storage referring to `const` objects, for example an array of | |
| references that must be sorted in place. `Rebindable` does not | |
| break the soundness of D's type system and does not incur any of the | |
| risks usually associated with `cast`. | |
| Params: | |
| T = Any type. | |
| */ | |
| template Rebindable(T) | |
| if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) | |
| { | |
| static if (is(T == const U, U) || is(T == immutable U, U)) | |
| { | |
| static if (isDynamicArray!T) | |
| { | |
| import std.range.primitives : ElementEncodingType; | |
| alias Rebindable = const(ElementEncodingType!T)[]; | |
| } | |
| else | |
| { | |
| struct Rebindable | |
| { | |
| mixin RebindableCommon!(T, U, Rebindable); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| alias Rebindable = T; | |
| } | |
| } | |
| ///Regular `const` object references cannot be reassigned. | |
| @safe unittest | |
| { | |
| class Widget { int x; int y() @safe const { return x; } } | |
| const a = new Widget; | |
| // Fine | |
| a.y(); | |
| // error! can't modify const a | |
| // a.x = 5; | |
| // error! can't modify const a | |
| // a = new Widget; | |
| } | |
| /** | |
| However, `Rebindable!(Widget)` does allow reassignment, | |
| while otherwise behaving exactly like a $(D const Widget). | |
| */ | |
| @safe unittest | |
| { | |
| class Widget { int x; int y() const @safe { return x; } } | |
| auto a = Rebindable!(const Widget)(new Widget); | |
| // Fine | |
| a.y(); | |
| // error! can't modify const a | |
| // a.x = 5; | |
| // Fine | |
| a = new Widget; | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=16054 | |
| @safe unittest | |
| { | |
| Rebindable!(immutable Object) r; | |
| static assert(__traits(compiles, r.get())); | |
| static assert(!__traits(compiles, &r.get())); | |
| } | |
| /// ditto | |
| struct Rebindable(T) | |
| if (!is(T == class) && !is(T == interface) && !isDynamicArray!T && !isAssociativeArray!T) | |
| { | |
| private: | |
| static if (isAssignable!(typeof(cast() T.init))) | |
| { | |
| enum useQualifierCast = true; | |
| typeof(cast() T.init) data; | |
| } | |
| else | |
| { | |
| enum useQualifierCast = false; | |
| align(T.alignof) | |
| static struct Payload | |
| { | |
| static if (hasIndirections!T) | |
| { | |
| void[T.sizeof] data; | |
| } | |
| else | |
| { | |
| ubyte[T.sizeof] data; | |
| } | |
| } | |
| Payload data; | |
| } | |
| public: | |
| static if (!__traits(compiles, { T value; })) | |
| { | |
| @disable this(); | |
| } | |
| /** | |
| * Constructs a `Rebindable` from a given value. | |
| */ | |
| this(T value) @trusted | |
| { | |
| static if (useQualifierCast) | |
| { | |
| this.data = cast() value; | |
| } | |
| else | |
| { | |
| set(value); | |
| } | |
| } | |
| /** | |
| * Overwrites the currently stored value with `value`. | |
| */ | |
| void opAssign(this This)(T value) @trusted | |
| { | |
| clear; | |
| set(value); | |
| } | |
| /** | |
| * Returns the value currently stored in the `Rebindable`. | |
| */ | |
| T get(this This)() @property @trusted | |
| { | |
| static if (useQualifierCast) | |
| { | |
| return cast(T) this.data; | |
| } | |
| else | |
| { | |
| return *cast(T*) &this.data; | |
| } | |
| } | |
| static if (!useQualifierCast) | |
| { | |
| ~this() @trusted | |
| { | |
| clear; | |
| } | |
| } | |
| /// | |
| alias get this; | |
| private: | |
| void set(this This)(T value) | |
| { | |
| static if (useQualifierCast) | |
| { | |
| this.data = cast() value; | |
| } | |
| else | |
| { | |
| // As we're escaping a copy of `value`, deliberately leak a copy: | |
| static union DontCallDestructor | |
| { | |
| T value; | |
| } | |
| DontCallDestructor copy = DontCallDestructor(value); | |
| this.data = *cast(Payload*) © | |
| } | |
| } | |
| void clear(this This)() | |
| { | |
| // work around reinterpreting cast being impossible in CTFE | |
| if (__ctfe) | |
| { | |
| return; | |
| } | |
| // call possible struct destructors | |
| .destroy!(No.initialize)(*cast(T*) &this.data); | |
| } | |
| } | |
| /// Using Rebindable in a generic algorithm: | |
| @safe unittest | |
| { | |
| import std.range.primitives : front, popFront; | |
| // simple version of std.algorithm.searching.maxElement | |
| typeof(R.init.front) maxElement(R)(R r) | |
| { | |
| auto max = rebindable(r.front); | |
| r.popFront; | |
| foreach (e; r) | |
| if (e > max) | |
| max = e; // Rebindable allows const-correct reassignment | |
| return max; | |
| } | |
| struct S | |
| { | |
| char[] arr; | |
| alias arr this; // for comparison | |
| } | |
| // can't convert to mutable | |
| const S cs; | |
| static assert(!__traits(compiles, { S s = cs; })); | |
| alias CS = const S; | |
| CS[] arr = [CS("harp"), CS("apple"), CS("pot")]; | |
| CS ms = maxElement(arr); | |
| assert(ms.arr == "pot"); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=18615 | |
| // Rebindable!A should use A.opEqualsa | |
| @system unittest | |
| { | |
| class CustomOpEq | |
| { | |
| int x; | |
| override bool opEquals(Object rhsObj) | |
| { | |
| if (auto rhs = cast(const(CustomOpEq)) rhsObj) | |
| return this.x == rhs.x; | |
| else | |
| return false; | |
| } | |
| } | |
| CustomOpEq a = new CustomOpEq(); | |
| CustomOpEq b = new CustomOpEq(); | |
| assert(a !is b); | |
| assert(a == b, "a.x == b.x should be true (0 == 0)."); | |
| Rebindable!(const(CustomOpEq)) ra = a; | |
| Rebindable!(const(CustomOpEq)) rb = b; | |
| assert(ra !is rb); | |
| assert(ra == rb, "Rebindable should use CustomOpEq's opEquals, not 'is'."); | |
| assert(ra == b, "Rebindable!(someQualifier(A)) should be comparable" | |
| ~ " against const(A) via A.opEquals."); | |
| assert(a == rb, "Rebindable!(someQualifier(A)) should be comparable" | |
| ~ " against const(A) via A.opEquals."); | |
| b.x = 1; | |
| assert(a != b); | |
| assert(ra != b, "Rebindable!(someQualifier(A)) should be comparable" | |
| ~ " against const(A) via A.opEquals."); | |
| assert(a != rb, "Rebindable!(someQualifier(A)) should be comparable" | |
| ~ " against const(A) via A.opEquals."); | |
| Rebindable!(const(Object)) o1 = new Object(); | |
| Rebindable!(const(Object)) o2 = new Object(); | |
| assert(o1 !is o2); | |
| assert(o1 == o1, "When the class doesn't provide its own opEquals," | |
| ~ " Rebindable treats 'a == b' as 'a is b' like Object.opEquals."); | |
| assert(o1 != o2, "When the class doesn't provide its own opEquals," | |
| ~ " Rebindable treats 'a == b' as 'a is b' like Object.opEquals."); | |
| assert(o1 != new Object(), "Rebindable!(const(Object)) should be" | |
| ~ " comparable against Object itself and use Object.opEquals."); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| int* ptr; | |
| } | |
| S s = S(new int); | |
| const cs = s; | |
| // Can't assign s.ptr to cs.ptr | |
| static assert(!__traits(compiles, {s = cs;})); | |
| Rebindable!(const S) rs = s; | |
| assert(rs.ptr is s.ptr); | |
| // rs.ptr is const | |
| static assert(!__traits(compiles, {rs.ptr = null;})); | |
| // Can't assign s.ptr to rs.ptr | |
| static assert(!__traits(compiles, {s = rs;})); | |
| const S cs2 = rs; | |
| // Rebind rs | |
| rs = cs2; | |
| rs = S(); | |
| assert(rs.ptr is null); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=18755 | |
| @safe unittest | |
| { | |
| static class Foo | |
| { | |
| auto opCast(T)() @system immutable pure nothrow | |
| { | |
| *(cast(uint*) 0xdeadbeef) = 0xcafebabe; | |
| return T.init; | |
| } | |
| } | |
| static assert(!__traits(compiles, () @safe { | |
| auto r = Rebindable!(immutable Foo)(new Foo); | |
| })); | |
| static assert(__traits(compiles, () @system { | |
| auto r = Rebindable!(immutable Foo)(new Foo); | |
| })); | |
| } | |
| @safe unittest | |
| { | |
| class CustomToHash | |
| { | |
| override size_t toHash() const nothrow @trusted { return 42; } | |
| } | |
| Rebindable!(immutable(CustomToHash)) a = new immutable CustomToHash(); | |
| assert(a.toHash() == 42, "Rebindable!A should offer toHash()" | |
| ~ " by forwarding to A.toHash()."); | |
| } | |
| // Test Rebindable!immutable | |
| @safe unittest | |
| { | |
| static struct S | |
| { | |
| int* ptr; | |
| } | |
| S s = S(new int); | |
| Rebindable!(immutable S) ri = S(new int); | |
| assert(ri.ptr !is null); | |
| static assert(!__traits(compiles, {ri.ptr = null;})); | |
| // ri is not compatible with mutable S | |
| static assert(!__traits(compiles, {s = ri;})); | |
| static assert(!__traits(compiles, {ri = s;})); | |
| auto ri2 = ri; | |
| assert(ri2.ptr == ri.ptr); | |
| const S cs3 = ri; | |
| static assert(!__traits(compiles, {ri = cs3;})); | |
| immutable S si = ri; | |
| // Rebind ri | |
| ri = si; | |
| ri = S(); | |
| assert(ri.ptr is null); | |
| // Test RB!immutable -> RB!const | |
| Rebindable!(const S) rc = ri; | |
| assert(rc.ptr is null); | |
| ri = S(new int); | |
| rc = ri; | |
| assert(rc.ptr !is null); | |
| // test rebindable, opAssign | |
| rc.destroy; | |
| assert(rc.ptr is null); | |
| rc = rebindable(cs3); | |
| rc = rebindable(si); | |
| assert(rc.ptr !is null); | |
| ri.destroy; | |
| assert(ri.ptr is null); | |
| ri = rebindable(si); | |
| assert(ri.ptr !is null); | |
| } | |
| // Test disabled default ctor | |
| @safe unittest | |
| { | |
| static struct ND | |
| { | |
| int i; | |
| @disable this(); | |
| this(int i) inout {this.i = i;} | |
| } | |
| static assert(!__traits(compiles, Rebindable!ND())); | |
| Rebindable!(const ND) rb = const ND(1); | |
| assert(rb.i == 1); | |
| rb = immutable ND(2); | |
| assert(rb.i == 2); | |
| rb = rebindable(const ND(3)); | |
| assert(rb.i == 3); | |
| static assert(!__traits(compiles, rb.i++)); | |
| } | |
| // Test copying | |
| @safe unittest | |
| { | |
| int del; | |
| int post; | |
| struct S | |
| { | |
| int* ptr; | |
| int level; | |
| this(this) | |
| { | |
| post++; | |
| level++; | |
| } | |
| ~this() | |
| { | |
| del++; | |
| } | |
| } | |
| // test ref count | |
| { | |
| Rebindable!S rc = S(new int); | |
| } | |
| assert(post == del - 1); | |
| } | |
| @safe unittest | |
| { | |
| int del; | |
| int post; | |
| struct S | |
| { | |
| immutable int x; | |
| int level; | |
| this(this) | |
| { | |
| post++; | |
| level++; | |
| } | |
| ~this() | |
| { | |
| del++; | |
| } | |
| } | |
| // test ref count | |
| { | |
| Rebindable!S rc = S(0); | |
| } | |
| assert(post == del - 1); | |
| } | |
| /** | |
| Convenience function for creating a `Rebindable` using automatic type | |
| inference. | |
| Params: | |
| obj = A reference to a value to initialize the `Rebindable` with. | |
| Returns: | |
| A newly constructed `Rebindable` initialized with the given reference. | |
| */ | |
| Rebindable!T rebindable(T)(T obj) | |
| if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) | |
| { | |
| typeof(return) ret; | |
| ret = obj; | |
| return ret; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| class C | |
| { | |
| int payload; | |
| this(int p) { payload = p; } | |
| } | |
| const c = new C(1); | |
| auto c2 = c.rebindable; | |
| assert(c2.payload == 1); | |
| // passing Rebindable to rebindable | |
| c2 = c2.rebindable; | |
| c2 = new C(2); | |
| assert(c2.payload == 2); | |
| const c3 = c2.get; | |
| assert(c3.payload == 2); | |
| } | |
| /// ditto | |
| Rebindable!T rebindable(T)(T value) | |
| if (!is(T == class) && !is(T == interface) && !isDynamicArray!T && !isAssociativeArray!T | |
| && !is(T : Rebindable!U, U)) | |
| { | |
| return Rebindable!T(value); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| immutable struct S | |
| { | |
| int[] array; | |
| } | |
| auto s1 = [3].idup.rebindable; | |
| s1 = [4].idup.rebindable; | |
| assert(s1 == [4]); | |
| } | |
| /** | |
| This function simply returns the `Rebindable` object passed in. It's useful | |
| in generic programming cases when a given object may be either a regular | |
| `class` or a `Rebindable`. | |
| Params: | |
| obj = An instance of Rebindable!T. | |
| Returns: | |
| `obj` without any modification. | |
| */ | |
| Rebindable!T rebindable(T)(Rebindable!T obj) | |
| { | |
| return obj; | |
| } | |
| // TODO: remove me once the rebindable overloads have been joined | |
| /// | |
| @system unittest | |
| { | |
| class C | |
| { | |
| int payload; | |
| this(int p) { payload = p; } | |
| } | |
| const c = new C(1); | |
| auto c2 = c.rebindable; | |
| assert(c2.payload == 1); | |
| // passing Rebindable to rebindable | |
| c2 = c2.rebindable; | |
| assert(c2.payload == 1); | |
| } | |
| @system unittest | |
| { | |
| interface CI { int foo() const; } | |
| class C : CI { | |
| int foo() const { return 42; } | |
| @property int bar() const { return 23; } | |
| } | |
| Rebindable!(C) obj0; | |
| static assert(is(typeof(obj0) == C)); | |
| Rebindable!(const(C)) obj1; | |
| static assert(is(typeof(obj1.get) == const(C)), typeof(obj1.get).stringof); | |
| static assert(is(typeof(obj1.stripped) == C)); | |
| obj1 = new C; | |
| assert(obj1.get !is null); | |
| obj1 = new const(C); | |
| assert(obj1.get !is null); | |
| Rebindable!(immutable(C)) obj2; | |
| static assert(is(typeof(obj2.get) == immutable(C))); | |
| static assert(is(typeof(obj2.stripped) == C)); | |
| obj2 = new immutable(C); | |
| assert(obj1.get !is null); | |
| // test opDot | |
| assert(obj2.foo() == 42); | |
| assert(obj2.bar == 23); | |
| interface I { final int foo() const { return 42; } } | |
| Rebindable!(I) obj3; | |
| static assert(is(typeof(obj3) == I)); | |
| Rebindable!(const I) obj4; | |
| static assert(is(typeof(obj4.get) == const I)); | |
| static assert(is(typeof(obj4.stripped) == I)); | |
| static assert(is(typeof(obj4.foo()) == int)); | |
| obj4 = new class I {}; | |
| Rebindable!(immutable C) obj5i; | |
| Rebindable!(const C) obj5c; | |
| obj5c = obj5c; | |
| obj5c = obj5i; | |
| obj5i = obj5i; | |
| static assert(!__traits(compiles, obj5i = obj5c)); | |
| // Test the convenience functions. | |
| auto obj5convenience = rebindable(obj5i); | |
| assert(obj5convenience is obj5i); | |
| auto obj6 = rebindable(new immutable(C)); | |
| static assert(is(typeof(obj6) == Rebindable!(immutable C))); | |
| assert(obj6.foo() == 42); | |
| auto obj7 = rebindable(new C); | |
| CI interface1 = obj7; | |
| auto interfaceRebind1 = rebindable(interface1); | |
| assert(interfaceRebind1.foo() == 42); | |
| const interface2 = interface1; | |
| auto interfaceRebind2 = rebindable(interface2); | |
| assert(interfaceRebind2.foo() == 42); | |
| auto arr = [1,2,3,4,5]; | |
| const arrConst = arr; | |
| assert(rebindable(arr) == arr); | |
| assert(rebindable(arrConst) == arr); | |
| // https://issues.dlang.org/show_bug.cgi?id=7654 | |
| immutable(char[]) s7654; | |
| Rebindable!(typeof(s7654)) r7654 = s7654; | |
| static foreach (T; AliasSeq!(char, wchar, char, int)) | |
| { | |
| static assert(is(Rebindable!(immutable(T[])) == immutable(T)[])); | |
| static assert(is(Rebindable!(const(T[])) == const(T)[])); | |
| static assert(is(Rebindable!(T[]) == T[])); | |
| } | |
| // Pull request 3341 | |
| Rebindable!(immutable int[int]) pr3341 = [123:345]; | |
| assert(pr3341[123] == 345); | |
| immutable int[int] pr3341_aa = [321:543]; | |
| pr3341 = pr3341_aa; | |
| assert(pr3341[321] == 543); | |
| assert(rebindable(pr3341_aa)[321] == 543); | |
| } | |
| package(std) struct Rebindable2(T) | |
| { | |
| private: | |
| static if (isAssignable!(typeof(cast() T.init))) | |
| { | |
| enum useQualifierCast = true; | |
| typeof(cast() T.init) data; | |
| } | |
| else | |
| { | |
| enum useQualifierCast = false; | |
| align(T.alignof) | |
| static struct Payload | |
| { | |
| static if (hasIndirections!T) | |
| { | |
| void[T.sizeof] data; | |
| } | |
| else | |
| { | |
| ubyte[T.sizeof] data; | |
| } | |
| } | |
| Payload data; | |
| } | |
| public: | |
| static if (!__traits(compiles, { T value; })) | |
| { | |
| @disable this(); | |
| } | |
| /** | |
| * Constructs a `Rebindable2` from a given value. | |
| */ | |
| this(T value) @trusted | |
| { | |
| static if (useQualifierCast) | |
| { | |
| this.data = cast() value; | |
| } | |
| else | |
| { | |
| set(value); | |
| } | |
| } | |
| /** | |
| * Overwrites the currently stored value with `value`. | |
| */ | |
| void opAssign(this This)(T value) @trusted | |
| { | |
| clear; | |
| set(value); | |
| } | |
| /** | |
| * Returns the value currently stored in the `Rebindable2`. | |
| */ | |
| T get(this This)() @property @trusted | |
| { | |
| static if (useQualifierCast) | |
| { | |
| return cast(T) this.data; | |
| } | |
| else | |
| { | |
| return *cast(T*) &this.data; | |
| } | |
| } | |
| /// Ditto | |
| inout(T) get() inout @property @trusted | |
| { | |
| static if (useQualifierCast) | |
| { | |
| return cast(inout(T)) this.data; | |
| } | |
| else | |
| { | |
| return *cast(inout(T)*) &this.data; | |
| } | |
| } | |
| static if (!useQualifierCast) | |
| { | |
| ~this() @trusted | |
| { | |
| clear; | |
| } | |
| } | |
| private: | |
| void set(this This)(T value) | |
| { | |
| static if (useQualifierCast) | |
| { | |
| static if (hasElaborateAssign!T) | |
| { | |
| import core.lifetime : copyEmplace; | |
| copyEmplace(cast() value, this.data); | |
| } | |
| else | |
| this.data = cast() value; | |
| } | |
| else | |
| { | |
| import core.lifetime : copyEmplace; | |
| copyEmplace(cast() value, cast() *cast(T*) &this.data); | |
| } | |
| } | |
| void clear(this This)() | |
| { | |
| // work around reinterpreting cast being impossible in CTFE | |
| if (__ctfe) | |
| { | |
| return; | |
| } | |
| // call possible struct destructors | |
| static if (is(T == struct)) | |
| { | |
| .destroy!(No.initialize)(*cast(T*) &this.data); | |
| } | |
| } | |
| } | |
| package(std) Rebindable2!T rebindable2(T)(T value) | |
| { | |
| return Rebindable2!T(value); | |
| } | |
| // Verify that the destructor is called properly if there is one. | |
| @system unittest | |
| { | |
| { | |
| bool destroyed; | |
| struct S | |
| { | |
| int i; | |
| this(int i) @safe | |
| { | |
| this.i = i; | |
| } | |
| ~this() @safe | |
| { | |
| destroyed = true; | |
| } | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| // Whether destruction has occurred here depends on whether the | |
| // temporary gets moved or not, so we won't assume that it has or | |
| // hasn't happened. What we care about here is that foo gets destroyed | |
| // properly when it leaves the scope. | |
| destroyed = false; | |
| } | |
| assert(destroyed); | |
| { | |
| auto foo = rebindable2(const S(42)); | |
| destroyed = false; | |
| } | |
| assert(destroyed); | |
| } | |
| // Test for double destruction with qualifer cast being used | |
| { | |
| static struct S | |
| { | |
| int i; | |
| bool destroyed; | |
| this(int i) @safe | |
| { | |
| this.i = i; | |
| } | |
| ~this() @safe | |
| { | |
| destroyed = true; | |
| } | |
| @safe invariant | |
| { | |
| assert(!destroyed); | |
| } | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| assert(typeof(foo).useQualifierCast); | |
| assert(foo.data.i == 42); | |
| assert(!foo.data.destroyed); | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| destroy(foo); | |
| } | |
| { | |
| auto foo = rebindable2(const S(42)); | |
| assert(typeof(foo).useQualifierCast); | |
| assert(foo.data.i == 42); | |
| assert(!foo.data.destroyed); | |
| } | |
| { | |
| auto foo = rebindable2(const S(42)); | |
| destroy(foo); | |
| } | |
| } | |
| // Test for double destruction without qualifer cast being used | |
| { | |
| static struct S | |
| { | |
| int i; | |
| bool destroyed; | |
| this(int i) @safe | |
| { | |
| this.i = i; | |
| } | |
| ~this() @safe | |
| { | |
| destroyed = true; | |
| } | |
| @disable ref S opAssign()(auto ref S rhs); | |
| @safe invariant | |
| { | |
| assert(!destroyed); | |
| } | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| assert(!typeof(foo).useQualifierCast); | |
| assert((cast(S*)&(foo.data)).i == 42); | |
| assert(!(cast(S*)&(foo.data)).destroyed); | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| destroy(foo); | |
| } | |
| } | |
| } | |
| // Verify that if there is an overloaded assignment operator, it's not assigned | |
| // to garbage. | |
| @safe unittest | |
| { | |
| static struct S | |
| { | |
| int i; | |
| bool destroyed; | |
| this(int i) @safe | |
| { | |
| this.i = i; | |
| } | |
| ~this() @safe | |
| { | |
| destroyed = true; | |
| } | |
| ref opAssign()(auto ref S rhs) | |
| { | |
| assert(!this.destroyed); | |
| this.i = rhs.i; | |
| return this; | |
| } | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| foo = S(99); | |
| assert(foo.data.i == 99); | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| foo = const S(99); | |
| assert(foo.data.i == 99); | |
| } | |
| } | |
| // Verify that postblit or copy constructor is called properly if there is one. | |
| @system unittest | |
| { | |
| // postblit with type qualifier cast | |
| { | |
| static struct S | |
| { | |
| int i; | |
| static bool copied; | |
| this(this) @safe | |
| { | |
| copied = true; | |
| } | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| // Whether a copy has occurred here depends on whether the | |
| // temporary gets moved or not, so we won't assume that it has or | |
| // hasn't happened. What we care about here is that foo gets copied | |
| // properly when we copy it below. | |
| S.copied = false; | |
| auto bar = foo; | |
| assert(S.copied); | |
| } | |
| { | |
| auto foo = rebindable2(const S(42)); | |
| assert(typeof(foo).useQualifierCast); | |
| S.copied = false; | |
| auto bar = foo; | |
| assert(S.copied); | |
| } | |
| } | |
| // copy constructor with type qualifier cast | |
| { | |
| static struct S | |
| { | |
| int i; | |
| static bool copied; | |
| this(ref inout S rhs) @safe inout | |
| { | |
| this.i = rhs.i; | |
| copied = true; | |
| } | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| assert(typeof(foo).useQualifierCast); | |
| S.copied = false; | |
| auto bar = foo; | |
| assert(S.copied); | |
| } | |
| { | |
| auto foo = rebindable2(const S(42)); | |
| S.copied = false; | |
| auto bar = foo; | |
| assert(S.copied); | |
| } | |
| } | |
| // FIXME https://issues.dlang.org/show_bug.cgi?id=24829 | |
| // Making this work requires either reworking how the !useQualiferCast | |
| // version works so that the compiler can correctly generate postblit | |
| // constructors and copy constructors as appropriate, or an explicit | |
| // postblit or copy constructor needs to be added for such cases, which | |
| // gets pretty complicated if we want to correctly add the same attributes | |
| // that T's postblit or copy constructor has. | |
| /+ | |
| // postblit without type qualifier cast | |
| { | |
| static struct S | |
| { | |
| int* ptr; | |
| static bool copied; | |
| this(int i) | |
| { | |
| ptr = new int(i); | |
| } | |
| this(this) @safe | |
| { | |
| if (ptr !is null) | |
| ptr = new int(*ptr); | |
| copied = true; | |
| } | |
| @disable ref S opAssign()(auto ref S rhs); | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| assert(!typeof(foo).useQualifierCast); | |
| S.copied = false; | |
| auto bar = foo; | |
| assert(S.copied); | |
| assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr); | |
| assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr); | |
| } | |
| { | |
| auto foo = rebindable2(const S(42)); | |
| S.copied = false; | |
| auto bar = foo; | |
| assert(S.copied); | |
| assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr); | |
| assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr); | |
| } | |
| } | |
| // copy constructor without type qualifier cast | |
| { | |
| static struct S | |
| { | |
| int* ptr; | |
| static bool copied; | |
| this(int i) | |
| { | |
| ptr = new int(i); | |
| } | |
| this(ref inout S rhs) @safe inout | |
| { | |
| if (rhs.ptr !is null) | |
| ptr = new inout int(*rhs.ptr); | |
| copied = true; | |
| } | |
| @disable ref S opAssign()(auto ref S rhs); | |
| } | |
| { | |
| auto foo = rebindable2(S(42)); | |
| assert(!typeof(foo).useQualifierCast); | |
| S.copied = false; | |
| auto bar = foo; | |
| assert(S.copied); | |
| assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr); | |
| assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr); | |
| } | |
| { | |
| auto foo = rebindable2(const S(42)); | |
| S.copied = false; | |
| auto bar = foo; | |
| assert(S.copied); | |
| assert(*(cast(S*)&(foo.data)).ptr == *(cast(S*)&(bar.data)).ptr); | |
| assert((cast(S*)&(foo.data)).ptr !is (cast(S*)&(bar.data)).ptr); | |
| } | |
| } | |
| +/ | |
| } | |
| /** | |
| Similar to `Rebindable!(T)` but strips all qualifiers from the reference as | |
| opposed to just constness / immutability. Primary intended use case is with | |
| shared (having thread-local reference to shared class data) | |
| Params: | |
| T = A class or interface type. | |
| */ | |
| template UnqualRef(T) | |
| if (is(T == class) || is(T == interface)) | |
| { | |
| static if (is(T == immutable U, U) | |
| || is(T == const shared U, U) | |
| || is(T == const U, U) | |
| || is(T == shared U, U)) | |
| { | |
| struct UnqualRef | |
| { | |
| mixin RebindableCommon!(T, U, UnqualRef); | |
| } | |
| } | |
| else | |
| { | |
| alias UnqualRef = T; | |
| } | |
| } | |
| /// | |
| @system unittest | |
| { | |
| class Data {} | |
| static shared(Data) a; | |
| static UnqualRef!(shared Data) b; | |
| import core.thread; | |
| auto thread = new core.thread.Thread({ | |
| a = new shared Data(); | |
| b = new shared Data(); | |
| }); | |
| thread.start(); | |
| thread.join(); | |
| assert(a !is null); | |
| assert(b is null); | |
| } | |
| @safe unittest | |
| { | |
| class C { } | |
| alias T = UnqualRef!(const shared C); | |
| static assert(is(typeof(T.stripped) == C)); | |
| } | |
| /** | |
| Order the provided members to minimize size while preserving alignment. | |
| Alignment is not always optimal for 80-bit reals, nor for structs declared | |
| as align(1). | |
| Params: | |
| E = A list of the types to be aligned, representing fields | |
| of an aggregate such as a `struct` or `class`. | |
| names = The names of the fields that are to be aligned. | |
| Returns: | |
| A string to be mixed in to an aggregate, such as a `struct` or `class`. | |
| */ | |
| string alignForSize(E...)(const char[][] names...) | |
| { | |
| // Sort all of the members by .alignof. | |
| // BUG: Alignment is not always optimal for align(1) structs | |
| // or 80-bit reals or 64-bit primitives on x86. | |
| // TRICK: Use the fact that .alignof is always a power of 2, | |
| // and maximum 16 on extant systems. Thus, we can perform | |
| // a very limited radix sort. | |
| // Contains the members with .alignof = 64,32,16,8,4,2,1 | |
| assert(E.length == names.length, | |
| "alignForSize: There should be as many member names as the types"); | |
| string[7] declaration = ["", "", "", "", "", "", ""]; | |
| foreach (i, T; E) | |
| { | |
| auto a = T.alignof; | |
| auto k = a >= 64? 0 : a >= 32? 1 : a >= 16? 2 : a >= 8? 3 : a >= 4? 4 : a >= 2? 5 : 6; | |
| declaration[k] ~= T.stringof ~ " " ~ names[i] ~ ";\n"; | |
| } | |
| auto s = ""; | |
| foreach (decl; declaration) | |
| s ~= decl; | |
| return s; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| struct Banner { | |
| mixin(alignForSize!(byte[6], double)(["name", "height"])); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| enum x = alignForSize!(int[], char[3], short, double[5])("x", "y","z", "w"); | |
| struct Foo { int x; } | |
| enum y = alignForSize!(ubyte, Foo, double)("x", "y", "z"); | |
| enum passNormalX = x == "double[5] w;\nint[] x;\nshort z;\nchar[3] y;\n"; | |
| enum passNormalY = y == "double z;\nFoo y;\nubyte x;\n"; | |
| enum passAbnormalX = x == "int[] x;\ndouble[5] w;\nshort z;\nchar[3] y;\n"; | |
| enum passAbnormalY = y == "Foo y;\ndouble z;\nubyte x;\n"; | |
| // ^ blame https://issues.dlang.org/show_bug.cgi?id=231 | |
| static assert(passNormalX || passAbnormalX && double.alignof <= (int[]).alignof); | |
| static assert(passNormalY || passAbnormalY && double.alignof <= int.alignof); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=12914 | |
| @safe unittest | |
| { | |
| immutable string[] fieldNames = ["x", "y"]; | |
| struct S | |
| { | |
| mixin(alignForSize!(byte, int)(fieldNames)); | |
| } | |
| } | |
| /** | |
| Defines a value paired with a distinctive "null" state that denotes | |
| the absence of a value. If default constructed, a $(D | |
| Nullable!T) object starts in the null state. Assigning it renders it | |
| non-null. Calling `nullify` can nullify it again. | |
| Practically `Nullable!T` stores a `T` and a `bool`. | |
| See also: | |
| $(LREF apply), an alternative way to use the payload. | |
| */ | |
| struct Nullable(T) | |
| { | |
| private union DontCallDestructorT | |
| { | |
| import std.traits : hasIndirections; | |
| static if (hasIndirections!T) | |
| T payload; | |
| else | |
| T payload = void; | |
| } | |
| private DontCallDestructorT _value = DontCallDestructorT.init; | |
| private bool _isNull = true; | |
| /** | |
| * Constructor initializing `this` with `value`. | |
| * | |
| * Params: | |
| * value = The value to initialize this `Nullable` with. | |
| */ | |
| static if (isCopyable!T) | |
| this(inout T value) inout | |
| { | |
| _value.payload = value; | |
| _isNull = false; | |
| } | |
| else | |
| this(T value) inout | |
| { | |
| import std.algorithm.mutation : move; | |
| _value.payload = move(value); | |
| _isNull = false; | |
| } | |
| static if (hasElaborateDestructor!T) | |
| { | |
| ~this() | |
| { | |
| if (!_isNull) | |
| { | |
| import std.traits : Unqual; | |
| auto ptr = () @trusted { return cast(Unqual!T*) &_value.payload; }(); | |
| destroy!false(*ptr); | |
| } | |
| } | |
| } | |
| static if (!isCopyable!T) | |
| @disable this(this); | |
| else | |
| static if (__traits(hasPostblit, T)) | |
| { | |
| this(this) | |
| { | |
| if (!_isNull) | |
| _value.payload.__xpostblit(); | |
| } | |
| } | |
| else static if (__traits(hasCopyConstructor, T)) | |
| { | |
| this(ref return scope inout Nullable!T rhs) inout | |
| { | |
| _isNull = rhs._isNull; | |
| if (!_isNull) | |
| _value.payload = rhs._value.payload; | |
| else | |
| _value = DontCallDestructorT.init; | |
| } | |
| } | |
| /** | |
| * If they are both null, then they are equal. If one is null and the other | |
| * is not, then they are not equal. If they are both non-null, then they are | |
| * equal if their values are equal. | |
| */ | |
| bool opEquals(this This, Rhs)(auto ref Rhs rhs) | |
| if (!is(CommonType!(This, Rhs) == void)) | |
| { | |
| static if (is(This == Rhs)) | |
| { | |
| if (_isNull) | |
| return rhs._isNull; | |
| if (rhs._isNull) | |
| return false; | |
| return _value.payload == rhs._value.payload; | |
| } | |
| else | |
| { | |
| alias Common = CommonType!(This, Rhs); | |
| return cast(Common) this == cast(Common) rhs; | |
| } | |
| } | |
| /// Ditto | |
| bool opEquals(this This, Rhs)(auto ref Rhs rhs) | |
| if (is(CommonType!(This, Rhs) == void) && is(typeof(this.get == rhs))) | |
| { | |
| return _isNull ? false : rhs == _value.payload; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| Nullable!int empty; | |
| Nullable!int a = 42; | |
| Nullable!int b = 42; | |
| Nullable!int c = 27; | |
| assert(empty == empty); | |
| assert(empty == Nullable!int.init); | |
| assert(empty != a); | |
| assert(empty != b); | |
| assert(empty != c); | |
| assert(a == b); | |
| assert(a != c); | |
| assert(empty != 42); | |
| assert(a == 42); | |
| assert(c != 42); | |
| } | |
| @safe unittest | |
| { | |
| // Test constness | |
| immutable Nullable!int a = 42; | |
| Nullable!int b = 42; | |
| immutable Nullable!int c = 29; | |
| Nullable!int d = 29; | |
| immutable e = 42; | |
| int f = 29; | |
| assert(a == a); | |
| assert(a == b); | |
| assert(a != c); | |
| assert(a != d); | |
| assert(a == e); | |
| assert(a != f); | |
| // Test rvalue | |
| assert(a == const Nullable!int(42)); | |
| assert(a != Nullable!int(29)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=17482 | |
| @system unittest | |
| { | |
| import std.variant : Variant; | |
| Nullable!Variant a = Variant(12); | |
| assert(a == 12); | |
| Nullable!Variant e; | |
| assert(e != 12); | |
| } | |
| size_t toHash() const @safe nothrow | |
| { | |
| static if (__traits(compiles, .hashOf(_value.payload))) | |
| return _isNull ? 0 : .hashOf(_value.payload); | |
| else | |
| // Workaround for when .hashOf is not both @safe and nothrow. | |
| return _isNull ? 0 : typeid(T).getHash(&_value.payload); | |
| } | |
| /** | |
| * Gives the string `"Nullable.null"` if `isNull` is `true`. Otherwise, the | |
| * result is equivalent to calling $(REF formattedWrite, std,format) on the | |
| * underlying value. | |
| * | |
| * Params: | |
| * writer = A `char` accepting | |
| * $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) | |
| * fmt = A $(REF FormatSpec, std,format) which is used to represent | |
| * the value if this Nullable is not null | |
| * Returns: | |
| * A `string` if `writer` and `fmt` are not set; `void` otherwise. | |
| */ | |
| string toString() | |
| { | |
| import std.array : appender; | |
| auto app = appender!string(); | |
| auto spec = singleSpec("%s"); | |
| toString(app, spec); | |
| return app.data; | |
| } | |
| /// ditto | |
| string toString() const | |
| { | |
| import std.array : appender; | |
| auto app = appender!string(); | |
| auto spec = singleSpec("%s"); | |
| toString(app, spec); | |
| return app.data; | |
| } | |
| /// ditto | |
| void toString(W)(ref W writer, scope const ref FormatSpec!char fmt) | |
| if (isOutputRange!(W, char)) | |
| { | |
| import std.range.primitives : put; | |
| if (isNull) | |
| put(writer, "Nullable.null"); | |
| else | |
| formatValue(writer, _value.payload, fmt); | |
| } | |
| /// ditto | |
| void toString(W)(ref W writer, scope const ref FormatSpec!char fmt) const | |
| if (isOutputRange!(W, char)) | |
| { | |
| import std.range.primitives : put; | |
| if (isNull) | |
| put(writer, "Nullable.null"); | |
| else | |
| formatValue(writer, _value.payload, fmt); | |
| } | |
| /** | |
| * Check if `this` is in the null state. | |
| * | |
| * Returns: | |
| * true $(B iff) `this` is in the null state, otherwise false. | |
| */ | |
| @property bool isNull() const @safe pure nothrow | |
| { | |
| return _isNull; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| Nullable!int ni; | |
| assert(ni.isNull); | |
| ni = 0; | |
| assert(!ni.isNull); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=14940 | |
| @safe unittest | |
| { | |
| import std.array : appender; | |
| import std.format.write : formattedWrite; | |
| auto app = appender!string(); | |
| Nullable!int a = 1; | |
| formattedWrite(app, "%s", a); | |
| assert(app.data == "1"); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=19799 | |
| @safe unittest | |
| { | |
| import std.format : format; | |
| const Nullable!string a = const(Nullable!string)(); | |
| format!"%s"(a); | |
| } | |
| /** | |
| * Returns true if `this` has a value, otherwise false. | |
| * | |
| * Allows a `Nullable` to be used as the condition in an `if` statement: | |
| * | |
| * --- | |
| * if (auto result = functionReturningNullable()) | |
| * { | |
| * doSomethingWith(result.get); | |
| * } | |
| * --- | |
| */ | |
| bool opCast(T : bool)() const | |
| { | |
| return !isNull; | |
| } | |
| /// Prevents `opCast` from disabling built-in conversions. | |
| auto ref T opCast(T, this This)() | |
| if (is(This : T) || This.sizeof == T.sizeof) | |
| { | |
| static if (is(This : T)) | |
| // Convert implicitly | |
| return this; | |
| else | |
| // Reinterpret | |
| return *cast(T*) &this; | |
| } | |
| /** | |
| * Forces `this` to the null state. | |
| */ | |
| void nullify()() | |
| { | |
| static if (is(T == class) || is(T == interface)) | |
| _value.payload = null; | |
| else | |
| .destroy(_value.payload); | |
| _isNull = true; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| Nullable!int ni = 0; | |
| assert(!ni.isNull); | |
| ni.nullify(); | |
| assert(ni.isNull); | |
| } | |
| /** | |
| * Assigns `value` to the internally-held state. If the assignment | |
| * succeeds, `this` becomes non-null. | |
| * | |
| * Params: | |
| * value = A value of type `T` to assign to this `Nullable`. | |
| */ | |
| ref Nullable opAssign()(T value) return | |
| { | |
| import std.algorithm.mutation : moveEmplace, move; | |
| if (_isNull) | |
| { | |
| // trusted since payload is known to be uninitialized. | |
| () @trusted { moveEmplace(value, _value.payload); }(); | |
| } | |
| else | |
| { | |
| move(value, _value.payload); | |
| } | |
| _isNull = false; | |
| return this; | |
| } | |
| /** | |
| * If this `Nullable` wraps a type that already has a null value | |
| * (such as a pointer), then assigning the null value to this | |
| * `Nullable` is no different than assigning any other value of | |
| * type `T`, and the resulting code will look very strange. It | |
| * is strongly recommended that this be avoided by instead using | |
| * the version of `Nullable` that takes an additional `nullValue` | |
| * template argument. | |
| */ | |
| @safe unittest | |
| { | |
| //Passes | |
| Nullable!(int*) npi; | |
| assert(npi.isNull); | |
| //Passes?! | |
| npi = null; | |
| assert(!npi.isNull); | |
| } | |
| /** | |
| * Gets the value if not null. If `this` is in the null state, and the optional | |
| * parameter `fallback` was provided, it will be returned. Without `fallback`, | |
| * calling `get` with a null state is invalid. | |
| * | |
| * When the fallback type is different from the Nullable type, `get(T)` returns | |
| * the common type. | |
| * | |
| * Params: | |
| * fallback = the value to return in case the `Nullable` is null. | |
| * | |
| * Returns: | |
| * The value held internally by this `Nullable`. | |
| */ | |
| @property ref inout(T) get() inout @safe pure nothrow | |
| { | |
| enum message = "Called `get' on null Nullable!" ~ T.stringof ~ "."; | |
| assert(!isNull, message); | |
| return _value.payload; | |
| } | |
| /// ditto | |
| @property inout(T) get()(inout(T) fallback) inout | |
| { | |
| return isNull ? fallback : _value.payload; | |
| } | |
| /// ditto | |
| @property auto get(U)(inout(U) fallback) inout | |
| { | |
| return isNull ? fallback : _value.payload; | |
| } | |
| /// $(MREF_ALTTEXT Range interface, std, range, primitives) functions. | |
| alias empty = isNull; | |
| /// ditto | |
| alias popFront = nullify; | |
| /// ditto | |
| alias popBack = nullify; | |
| /// ditto | |
| @property ref inout(T) front() inout @safe pure nothrow | |
| { | |
| return get(); | |
| } | |
| /// ditto | |
| alias back = front; | |
| /// ditto | |
| static if (isCopyable!T) | |
| @property inout(typeof(this)) save() inout | |
| { | |
| return this; | |
| } | |
| /// ditto | |
| static if (isCopyable!T) | |
| inout(typeof(this)) opIndex(size_t[2] dim) inout | |
| in (dim[0] <= length && dim[1] <= length && dim[1] >= dim[0]) | |
| { | |
| return (dim[0] == 0 && dim[1] == 1) ? this : this.init; | |
| } | |
| /// ditto | |
| size_t[2] opSlice(size_t dim : 0)(size_t from, size_t to) const | |
| { | |
| return [from, to]; | |
| } | |
| /// ditto | |
| @property size_t length() const @safe pure nothrow | |
| { | |
| return !empty; | |
| } | |
| /// ditto | |
| alias opDollar(size_t dim : 0) = length; | |
| /// ditto | |
| ref inout(T) opIndex(size_t index) inout @safe pure nothrow | |
| in (index < length) | |
| { | |
| return get(); | |
| } | |
| /** | |
| * Converts `Nullable` to a range. Works even when the contained type is `immutable`. | |
| */ | |
| auto opSlice(this This)() | |
| { | |
| static struct NullableRange | |
| { | |
| private This value; | |
| // starts out true if value is null | |
| private bool empty_; | |
| @property bool empty() const @safe pure nothrow | |
| { | |
| return empty_; | |
| } | |
| void popFront() @safe pure nothrow | |
| { | |
| empty_ = true; | |
| } | |
| alias popBack = popFront; | |
| @property ref inout(typeof(value.get())) front() inout @safe pure nothrow | |
| { | |
| return value.get(); | |
| } | |
| alias back = front; | |
| @property inout(typeof(this)) save() inout | |
| { | |
| return this; | |
| } | |
| size_t[2] opSlice(size_t dim : 0)(size_t from, size_t to) const | |
| { | |
| return [from, to]; | |
| } | |
| @property size_t length() const @safe pure nothrow | |
| { | |
| return !empty; | |
| } | |
| alias opDollar(size_t dim : 0) = length; | |
| ref inout(typeof(value.get())) opIndex(size_t index) inout @safe pure nothrow | |
| in (index < length) | |
| { | |
| return value.get(); | |
| } | |
| inout(typeof(this)) opIndex(size_t[2] dim) inout | |
| in (dim[0] <= length && dim[1] <= length && dim[1] >= dim[0]) | |
| { | |
| return (dim[0] == 0 && dim[1] == 1) ? this : this.init; | |
| } | |
| auto opIndex() inout | |
| { | |
| return this; | |
| } | |
| } | |
| return NullableRange(this, isNull); | |
| } | |
| } | |
| /// ditto | |
| auto nullable(T)(T t) | |
| { | |
| return Nullable!T(t); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| struct CustomerRecord | |
| { | |
| string name; | |
| string address; | |
| int customerNum; | |
| } | |
| Nullable!CustomerRecord getByName(string name) | |
| { | |
| //A bunch of hairy stuff | |
| return Nullable!CustomerRecord.init; | |
| } | |
| auto queryResult = getByName("Doe, John"); | |
| if (!queryResult.isNull) | |
| { | |
| //Process Mr. Doe's customer record | |
| auto address = queryResult.get.address; | |
| auto customerNum = queryResult.get.customerNum; | |
| //Do some things with this customer's info | |
| } | |
| else | |
| { | |
| //Add the customer to the database | |
| } | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto a = 42.nullable; | |
| assert(!a.isNull); | |
| assert(a.get == 42); | |
| a.nullify(); | |
| assert(a.isNull); | |
| assertThrown!Throwable(a.get); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.algorithm.iteration : each, joiner; | |
| Nullable!int a = 42; | |
| Nullable!int b; | |
| // Add each value to an array | |
| int[] arr; | |
| a.each!((n) => arr ~= n); | |
| assert(arr == [42]); | |
| b.each!((n) => arr ~= n); | |
| assert(arr == [42]); | |
| // Take first value from an array of Nullables | |
| Nullable!int[] c = new Nullable!int[](10); | |
| c[7] = Nullable!int(42); | |
| assert(c.joiner.front == 42); | |
| } | |
| @safe unittest | |
| { | |
| auto k = Nullable!int(74); | |
| assert(k == 74); | |
| k.nullify(); | |
| assert(k.isNull); | |
| } | |
| @safe unittest | |
| { | |
| static int f(scope const Nullable!int x) { | |
| return x.isNull ? 42 : x.get; | |
| } | |
| Nullable!int a; | |
| assert(f(a) == 42); | |
| a = 8; | |
| assert(f(a) == 8); | |
| a.nullify(); | |
| assert(f(a) == 42); | |
| } | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| static struct S { int x; } | |
| Nullable!S s; | |
| assert(s.isNull); | |
| s = S(6); | |
| assert(s == S(6)); | |
| assert(s != S(0)); | |
| assert(s.get != S(0)); | |
| s.get.x = 9190; | |
| assert(s.get.x == 9190); | |
| s.nullify(); | |
| assertThrown!Throwable(s.get.x = 9441); | |
| } | |
| @safe unittest | |
| { | |
| // Ensure Nullable can be used in pure/nothrow/@safe environment. | |
| function() @safe pure nothrow | |
| { | |
| Nullable!int n; | |
| assert(n.isNull); | |
| n = 4; | |
| assert(!n.isNull); | |
| assert(n == 4); | |
| n.nullify(); | |
| assert(n.isNull); | |
| }(); | |
| } | |
| @system unittest | |
| { | |
| // Ensure Nullable can be used when the value is not pure/nothrow/@safe | |
| static struct S | |
| { | |
| int x; | |
| this(this) @system {} | |
| } | |
| Nullable!S s; | |
| assert(s.isNull); | |
| s = S(5); | |
| assert(!s.isNull); | |
| assert(s.get.x == 5); | |
| s.nullify(); | |
| assert(s.isNull); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=9404 | |
| @safe unittest | |
| { | |
| alias N = Nullable!int; | |
| void foo(N a) | |
| { | |
| N b; | |
| b = a; // `N b = a;` works fine | |
| } | |
| N n; | |
| foo(n); | |
| } | |
| @safe unittest | |
| { | |
| //Check nullable immutable is constructable | |
| { | |
| auto a1 = Nullable!(immutable int)(); | |
| auto a2 = Nullable!(immutable int)(1); | |
| auto i = a2.get; | |
| } | |
| //Check immutable nullable is constructable | |
| { | |
| auto a1 = immutable (Nullable!int)(); | |
| auto a2 = immutable (Nullable!int)(1); | |
| auto i = a2.get; | |
| } | |
| } | |
| @safe unittest | |
| { | |
| alias NInt = Nullable!int; | |
| //Construct tests | |
| { | |
| //from other Nullable null | |
| NInt a1; | |
| NInt b1 = a1; | |
| assert(b1.isNull); | |
| //from other Nullable non-null | |
| NInt a2 = NInt(1); | |
| NInt b2 = a2; | |
| assert(b2 == 1); | |
| //Construct from similar nullable | |
| auto a3 = immutable(NInt)(); | |
| NInt b3 = a3; | |
| assert(b3.isNull); | |
| } | |
| //Assign tests | |
| { | |
| //from other Nullable null | |
| NInt a1; | |
| NInt b1; | |
| b1 = a1; | |
| assert(b1.isNull); | |
| //from other Nullable non-null | |
| NInt a2 = NInt(1); | |
| NInt b2; | |
| b2 = a2; | |
| assert(b2 == 1); | |
| //Construct from similar nullable | |
| auto a3 = immutable(NInt)(); | |
| NInt b3 = a3; | |
| b3 = a3; | |
| assert(b3.isNull); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| //Check nullable is nicelly embedable in a struct | |
| static struct S1 | |
| { | |
| Nullable!int ni; | |
| } | |
| static struct S2 //inspired from 9404 | |
| { | |
| Nullable!int ni; | |
| this(ref S2 other) | |
| { | |
| ni = other.ni; | |
| } | |
| void opAssign(ref S2 other) | |
| { | |
| ni = other.ni; | |
| } | |
| } | |
| static foreach (S; AliasSeq!(S1, S2)) | |
| {{ | |
| S a; | |
| S b = a; | |
| S c; | |
| c = a; | |
| }} | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=10268 | |
| @system unittest | |
| { | |
| import std.json; | |
| JSONValue value = null; | |
| auto na = Nullable!JSONValue(value); | |
| struct S1 { int val; } | |
| struct S2 { int* val; } | |
| struct S3 { immutable int* val; } | |
| { | |
| auto sm = S1(1); | |
| immutable si = immutable S1(1); | |
| auto x1 = Nullable!S1(sm); | |
| auto x2 = immutable Nullable!S1(sm); | |
| auto x3 = Nullable!S1(si); | |
| auto x4 = immutable Nullable!S1(si); | |
| assert(x1.get.val == 1); | |
| assert(x2.get.val == 1); | |
| assert(x3.get.val == 1); | |
| assert(x4.get.val == 1); | |
| } | |
| auto nm = 10; | |
| immutable ni = 10; | |
| { | |
| auto sm = S2(&nm); | |
| immutable si = immutable S2(&ni); | |
| auto x1 = Nullable!S2(sm); | |
| static assert(!__traits(compiles, { auto x2 = immutable Nullable!S2(sm); })); | |
| static assert(!__traits(compiles, { auto x3 = Nullable!S2(si); })); | |
| auto x4 = immutable Nullable!S2(si); | |
| assert(*x1.get.val == 10); | |
| assert(*x4.get.val == 10); | |
| } | |
| { | |
| auto sm = S3(&ni); | |
| immutable si = immutable S3(&ni); | |
| auto x1 = Nullable!S3(sm); | |
| auto x2 = immutable Nullable!S3(sm); | |
| auto x3 = Nullable!S3(si); | |
| auto x4 = immutable Nullable!S3(si); | |
| assert(*x1.get.val == 10); | |
| assert(*x2.get.val == 10); | |
| assert(*x3.get.val == 10); | |
| assert(*x4.get.val == 10); | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=10357 | |
| @safe unittest | |
| { | |
| import std.datetime; | |
| Nullable!SysTime time = SysTime(0); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=10915 | |
| @system unittest | |
| { | |
| import std.conv : to; | |
| import std.array; | |
| Appender!string buffer; | |
| Nullable!int ni; | |
| assert(ni.to!string() == "Nullable.null"); | |
| assert((cast(const) ni).to!string() == "Nullable.null"); | |
| struct Test { string s; } | |
| alias NullableTest = Nullable!Test; | |
| NullableTest nt = Test("test"); | |
| // test output range version | |
| assert(nt.to!string() == `Test("test")`); | |
| // test appender version | |
| assert(nt.toString() == `Test("test")`); | |
| // test const version | |
| assert((cast(const) nt).toString() == `const(Test)("test")`); | |
| NullableTest ntn = Test("null"); | |
| assert(ntn.to!string() == `Test("null")`); | |
| class TestToString | |
| { | |
| double d; | |
| this (double d) | |
| { | |
| this.d = d; | |
| } | |
| override string toString() | |
| { | |
| return d.to!string(); | |
| } | |
| } | |
| Nullable!TestToString ntts = new TestToString(2.5); | |
| assert(ntts.to!string() == "2.5"); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=14477 | |
| @safe unittest | |
| { | |
| static struct DisabledDefaultConstructor | |
| { | |
| @disable this(); | |
| this(int i) { } | |
| } | |
| Nullable!DisabledDefaultConstructor var; | |
| var = DisabledDefaultConstructor(5); | |
| var.nullify; | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=17440 | |
| @system unittest | |
| { | |
| static interface I { } | |
| static class C : I | |
| { | |
| int canary; | |
| ~this() | |
| { | |
| canary = 0x5050DEAD; | |
| } | |
| } | |
| auto c = new C; | |
| c.canary = 0xA71FE; | |
| auto nc = nullable(c); | |
| nc.nullify; | |
| assert(c.canary == 0xA71FE); | |
| I i = c; | |
| auto ni = nullable(i); | |
| ni.nullify; | |
| assert(c.canary == 0xA71FE); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=19037 | |
| @safe unittest | |
| { | |
| import std.datetime : SysTime; | |
| struct Test | |
| { | |
| SysTime _st; | |
| static bool destroyed; | |
| @disable this(); | |
| this(int _dummy) {} | |
| ~this() @safe { destroyed = true; } | |
| // mustn't call opAssign on Test.init in Nullable!Test, because the invariant | |
| // will be called before opAssign on the Test.init that is in Nullable | |
| // and Test.init violates its invariant. | |
| void opAssign(Test rhs) @safe { assert(false); } | |
| } | |
| { | |
| Nullable!Test nt; | |
| nt = Test(1); | |
| // destroy value | |
| Test.destroyed = false; | |
| nt.nullify; | |
| assert(Test.destroyed); | |
| Test.destroyed = false; | |
| } | |
| // don't run destructor on T.init in Nullable on scope exit! | |
| assert(!Test.destroyed); | |
| } | |
| // check that the contained type's destructor is called on assignment | |
| @system unittest | |
| { | |
| struct S | |
| { | |
| // can't be static, since we need a specific value's pointer | |
| bool* destroyedRef; | |
| ~this() | |
| { | |
| if (this.destroyedRef) | |
| { | |
| *this.destroyedRef = true; | |
| } | |
| } | |
| } | |
| Nullable!S ns; | |
| bool destroyed; | |
| ns = S(&destroyed); | |
| // reset from rvalue destruction in Nullable's opAssign | |
| destroyed = false; | |
| // overwrite Nullable | |
| ns = S(null); | |
| // the original S should be destroyed. | |
| assert(destroyed == true); | |
| } | |
| // check that the contained type's destructor is still called when required | |
| @system unittest | |
| { | |
| bool destructorCalled = false; | |
| struct S | |
| { | |
| bool* destroyed; | |
| ~this() { *this.destroyed = true; } | |
| } | |
| { | |
| Nullable!S ns; | |
| } | |
| assert(!destructorCalled); | |
| { | |
| Nullable!S ns = Nullable!S(S(&destructorCalled)); | |
| destructorCalled = false; // reset after S was destroyed in the NS constructor | |
| } | |
| assert(destructorCalled); | |
| } | |
| // check that toHash on Nullable is forwarded to the contained type | |
| @system unittest | |
| { | |
| struct S | |
| { | |
| size_t toHash() const @safe pure nothrow { return 5; } | |
| } | |
| Nullable!S s1 = S(); | |
| Nullable!S s2 = Nullable!S(); | |
| assert(typeid(Nullable!S).getHash(&s1) == 5); | |
| assert(typeid(Nullable!S).getHash(&s2) == 0); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=21704 | |
| @safe unittest | |
| { | |
| import std.array : staticArray; | |
| bool destroyed; | |
| struct Probe | |
| { | |
| ~this() { destroyed = true; } | |
| } | |
| { | |
| Nullable!(Probe[1]) test = [Probe()].staticArray; | |
| destroyed = false; | |
| } | |
| assert(destroyed); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=21705 | |
| @safe unittest | |
| { | |
| static struct S | |
| { | |
| int n; | |
| bool opEquals(S rhs) { return n == rhs.n; } | |
| } | |
| Nullable!S test1 = S(1), test2 = S(1); | |
| S s = S(1); | |
| assert(test1 == s); | |
| assert(test1 == test2); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=22101 | |
| @safe unittest | |
| { | |
| static int impure; | |
| struct S | |
| { | |
| ~this() { impure++; } | |
| } | |
| Nullable!S s; | |
| s.get(S()); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=22100 | |
| @safe unittest | |
| { | |
| Nullable!int a, b, c; | |
| a = b = c = 5; | |
| a = b = c = nullable(5); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=18374 | |
| @safe pure nothrow unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.range : only, takeNone; | |
| import std.range.primitives : hasAssignableElements, hasLength, | |
| hasLvalueElements, hasSlicing, hasSwappableElements, | |
| isRandomAccessRange; | |
| Nullable!int a = 42; | |
| assert(!a.empty); | |
| assert(a.front == 42); | |
| assert(a.back == 42); | |
| assert(a[0] == 42); | |
| assert(a.equal(only(42))); | |
| assert(a[0 .. $].equal(only(42))); | |
| a[0] = 43; | |
| assert(a.equal(only(43))); | |
| --a[0]; | |
| assert(a.equal(only(42))); | |
| Nullable!int b; | |
| assert(b.empty); | |
| assert(b.equal(takeNone(b))); | |
| Nullable!int c = a.save(); | |
| assert(!c.empty); | |
| c.popFront(); | |
| assert(!a.empty); | |
| assert(c.empty); | |
| assert(isRandomAccessRange!(Nullable!int)); | |
| assert(hasLength!(Nullable!int)); | |
| assert(hasSlicing!(Nullable!int)); | |
| assert(hasAssignableElements!(Nullable!int)); | |
| assert(hasSwappableElements!(Nullable!int)); | |
| assert(hasLvalueElements!(Nullable!int)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=23640 | |
| @safe pure nothrow unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.range : only; | |
| import std.range.primitives : hasLength, hasSlicing, | |
| isRandomAccessRange; | |
| static immutable struct S { int[] array; } | |
| auto value = S([42]); | |
| alias ImmutableNullable = immutable Nullable!S; | |
| auto a = ImmutableNullable(value)[]; | |
| alias Range = typeof(a); | |
| assert(isRandomAccessRange!Range); | |
| assert(hasLength!Range); | |
| assert(hasSlicing!Range); | |
| assert(!a.empty); | |
| assert(a.front == value); | |
| assert(a.back == value); | |
| assert(a[0] == value); | |
| assert(a.equal(only(value))); | |
| assert(a[0 .. $].equal(only(value))); | |
| Range b = a.save(); | |
| assert(!b.empty); | |
| b.popFront(); | |
| assert(!a.empty); | |
| assert(b.empty); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=24403 | |
| @safe unittest | |
| { | |
| static bool destroyed; | |
| static struct S { ~this() { destroyed = true; } } | |
| { | |
| Nullable!S s = S.init; | |
| destroyed = false; | |
| } | |
| assert(destroyed); | |
| { | |
| Nullable!(const S) s = S.init; | |
| destroyed = false; | |
| } | |
| assert(destroyed); | |
| { | |
| Nullable!(immutable S) s = S.init; | |
| destroyed = false; | |
| } | |
| assert(destroyed); | |
| { | |
| Nullable!(shared S) s = S.init; | |
| destroyed = false; | |
| } | |
| assert(destroyed); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=22293 | |
| @safe unittest | |
| { | |
| Nullable!int empty; | |
| Nullable!int full = 123; | |
| assert(cast(bool) empty == false); | |
| assert(cast(bool) full == true); | |
| if (empty) assert(0); | |
| if (!full) assert(0); | |
| } | |
| // check that opCast doesn't break unsafe casts | |
| @system unittest | |
| { | |
| Nullable!(const(int*)) a; | |
| auto result = cast(immutable(Nullable!(int*))) a; | |
| } | |
| /** | |
| Just like `Nullable!T`, except that the null state is defined as a | |
| particular value. For example, $(D Nullable!(uint, uint.max)) is an | |
| `uint` that sets aside the value `uint.max` to denote a null | |
| state. $(D Nullable!(T, nullValue)) is more storage-efficient than $(D | |
| Nullable!T) because it does not need to store an extra `bool`. | |
| Params: | |
| T = The wrapped type for which Nullable provides a null value. | |
| nullValue = The null value which denotes the null state of this | |
| `Nullable`. Must be of type `T`. | |
| */ | |
| struct Nullable(T, T nullValue) | |
| { | |
| private T _value = nullValue; | |
| /** | |
| Constructor initializing `this` with `value`. | |
| Params: | |
| value = The value to initialize this `Nullable` with. | |
| */ | |
| this(T value) | |
| { | |
| _value = value; | |
| } | |
| template toString() | |
| { | |
| import std.format.spec : FormatSpec; | |
| import std.format.write : formatValue; | |
| // Needs to be a template because of https://issues.dlang.org/show_bug.cgi?id=13737. | |
| void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) | |
| { | |
| if (isNull) | |
| { | |
| sink.formatValue("Nullable.null", fmt); | |
| } | |
| else | |
| { | |
| sink.formatValue(_value, fmt); | |
| } | |
| } | |
| void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) const | |
| { | |
| if (isNull) | |
| { | |
| sink.formatValue("Nullable.null", fmt); | |
| } | |
| else | |
| { | |
| sink.formatValue(_value, fmt); | |
| } | |
| } | |
| } | |
| @system unittest | |
| { | |
| import std.conv : to; | |
| const Nullable!(ulong, 0) x = 1; | |
| assert(x.to!string == "1"); | |
| } | |
| /** | |
| Check if `this` is in the null state. | |
| Returns: | |
| true $(B iff) `this` is in the null state, otherwise false. | |
| */ | |
| @property bool isNull() const | |
| { | |
| //Need to use 'is' if T is a nullable type and | |
| //nullValue is null, or it's a compiler error | |
| static if (is(CommonType!(T, typeof(null)) == T) && nullValue is null) | |
| { | |
| return _value is nullValue; | |
| } | |
| //Need to use 'is' if T is a float type | |
| //because NaN != NaN | |
| else static if (__traits(isFloating, T) || __traits(compiles, { static assert(!(nullValue == nullValue)); })) | |
| { | |
| return _value is nullValue; | |
| } | |
| else | |
| { | |
| return _value == nullValue; | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| Nullable!(int, -1) ni; | |
| //Initialized to "null" state | |
| assert(ni.isNull); | |
| ni = 0; | |
| assert(!ni.isNull); | |
| } | |
| @system unittest | |
| { | |
| assert(typeof(this).init.isNull, typeof(this).stringof ~ | |
| ".isNull does not work correctly because " ~ T.stringof ~ | |
| " has an == operator that is non-reflexive and could not be" ~ | |
| " determined before runtime to be non-reflexive!"); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=11135 | |
| // disable test until https://issues.dlang.org/show_bug.cgi?id=15316 gets fixed | |
| version (none) @system unittest | |
| { | |
| static foreach (T; AliasSeq!(float, double, real)) | |
| {{ | |
| Nullable!(T, T.init) nf; | |
| //Initialized to "null" state | |
| assert(nf.isNull); | |
| assert(nf is typeof(nf).init); | |
| nf = 0; | |
| assert(!nf.isNull); | |
| nf.nullify(); | |
| assert(nf.isNull); | |
| }} | |
| } | |
| /** | |
| Forces `this` to the null state. | |
| */ | |
| void nullify()() | |
| { | |
| _value = nullValue; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| Nullable!(int, -1) ni = 0; | |
| assert(!ni.isNull); | |
| ni = -1; | |
| assert(ni.isNull); | |
| } | |
| /** | |
| Assigns `value` to the internally-held state. If the assignment | |
| succeeds, `this` becomes non-null. No null checks are made. Note | |
| that the assignment may leave `this` in the null state. | |
| Params: | |
| value = A value of type `T` to assign to this `Nullable`. | |
| If it is `nullvalue`, then the internal state of | |
| this `Nullable` will be set to null. | |
| */ | |
| void opAssign()(T value) | |
| { | |
| import std.algorithm.mutation : swap; | |
| swap(value, _value); | |
| } | |
| /** | |
| If this `Nullable` wraps a type that already has a null value | |
| (such as a pointer), and that null value is not given for | |
| `nullValue`, then assigning the null value to this `Nullable` | |
| is no different than assigning any other value of type `T`, | |
| and the resulting code will look very strange. It is strongly | |
| recommended that this be avoided by using `T`'s "built in" | |
| null value for `nullValue`. | |
| */ | |
| @system unittest | |
| { | |
| //Passes | |
| enum nullVal = cast(int*) 0xCAFEBABE; | |
| Nullable!(int*, nullVal) npi; | |
| assert(npi.isNull); | |
| //Passes?! | |
| npi = null; | |
| assert(!npi.isNull); | |
| } | |
| /** | |
| Gets the value. `this` must not be in the null state. | |
| This function is also called for the implicit conversion to `T`. | |
| Preconditions: `isNull` must be `false`. | |
| Returns: | |
| The value held internally by this `Nullable`. | |
| */ | |
| @property ref inout(T) get() inout | |
| { | |
| //@@@6169@@@: We avoid any call that might evaluate nullValue's %s, | |
| //Because it might messup get's purity and safety inference. | |
| enum message = "Called `get' on null Nullable!(" ~ T.stringof ~ ",nullValue)."; | |
| assert(!isNull, message); | |
| return _value; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.exception : assertThrown, assertNotThrown; | |
| Nullable!(int, -1) ni; | |
| //`get` is implicitly called. Will throw | |
| //an error in non-release mode | |
| assertThrown!Throwable(ni == 0); | |
| ni = 0; | |
| assertNotThrown!Throwable(ni == 0); | |
| } | |
| /** | |
| Implicitly converts to `T`. | |
| `this` must not be in the null state. | |
| */ | |
| alias get this; | |
| } | |
| /// ditto | |
| auto nullable(alias nullValue, T)(T t) | |
| if (is (typeof(nullValue) == T)) | |
| { | |
| return Nullable!(T, nullValue)(t); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| Nullable!(size_t, size_t.max) indexOf(string[] haystack, string needle) | |
| { | |
| //Find the needle, returning -1 if not found | |
| return Nullable!(size_t, size_t.max).init; | |
| } | |
| void sendLunchInvite(string name) | |
| { | |
| } | |
| //It's safer than C... | |
| auto coworkers = ["Jane", "Jim", "Marry", "Fred"]; | |
| auto pos = indexOf(coworkers, "Bob"); | |
| if (!pos.isNull) | |
| { | |
| //Send Bob an invitation to lunch | |
| sendLunchInvite(coworkers[pos]); | |
| } | |
| else | |
| { | |
| //Bob not found; report the error | |
| } | |
| //And there's no overhead | |
| static assert(Nullable!(size_t, size_t.max).sizeof == size_t.sizeof); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| Nullable!(int, int.min) a; | |
| assert(a.isNull); | |
| assertThrown!Throwable(a.get); | |
| a = 5; | |
| assert(!a.isNull); | |
| assert(a == 5); | |
| static assert(a.sizeof == int.sizeof); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| auto a = nullable!(int.min)(8); | |
| assert(a == 8); | |
| a.nullify(); | |
| assert(a.isNull); | |
| } | |
| @nogc nothrow pure @safe unittest | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=19226 | |
| // fully handle non-self-equal nullValue | |
| static struct Fraction | |
| { | |
| int denominator; | |
| bool isNaN() const | |
| { | |
| return denominator == 0; | |
| } | |
| bool opEquals(const Fraction rhs) const | |
| { | |
| return !isNaN && denominator == rhs.denominator; | |
| } | |
| } | |
| alias N = Nullable!(Fraction, Fraction.init); | |
| assert(N.init.isNull); | |
| } | |
| @safe unittest | |
| { | |
| static int f(scope const Nullable!(int, int.min) x) { | |
| return x.isNull ? 42 : x.get; | |
| } | |
| Nullable!(int, int.min) a; | |
| assert(f(a) == 42); | |
| a = 8; | |
| assert(f(a) == 8); | |
| a.nullify(); | |
| assert(f(a) == 42); | |
| } | |
| @safe unittest | |
| { | |
| // Ensure Nullable can be used in pure/nothrow/@safe environment. | |
| function() @safe pure nothrow | |
| { | |
| Nullable!(int, int.min) n; | |
| assert(n.isNull); | |
| n = 4; | |
| assert(!n.isNull); | |
| assert(n == 4); | |
| n.nullify(); | |
| assert(n.isNull); | |
| }(); | |
| } | |
| @system unittest | |
| { | |
| // Ensure Nullable can be used when the value is not pure/nothrow/@system | |
| static struct S | |
| { | |
| int x; | |
| bool opEquals(const S s) const @system { return s.x == x; } | |
| } | |
| Nullable!(S, S(711)) s; | |
| assert(s.isNull); | |
| s = S(5); | |
| assert(!s.isNull); | |
| assert(s.x == 5); | |
| s.nullify(); | |
| assert(s.isNull); | |
| } | |
| @safe unittest | |
| { | |
| //Check nullable is nicelly embedable in a struct | |
| static struct S1 | |
| { | |
| Nullable!(int, 0) ni; | |
| } | |
| static struct S2 //inspired from 9404 | |
| { | |
| Nullable!(int, 0) ni; | |
| this(S2 other) | |
| { | |
| ni = other.ni; | |
| } | |
| void opAssign(S2 other) | |
| { | |
| ni = other.ni; | |
| } | |
| } | |
| static foreach (S; AliasSeq!(S1, S2)) | |
| {{ | |
| S a; | |
| S b = a; | |
| S c; | |
| c = a; | |
| }} | |
| } | |
| @system unittest | |
| { | |
| import std.conv : to; | |
| // https://issues.dlang.org/show_bug.cgi?id=10915 | |
| Nullable!(int, 1) ni = 1; | |
| assert(ni.to!string() == "Nullable.null"); | |
| struct Test { string s; } | |
| alias NullableTest = Nullable!(Test, Test("null")); | |
| NullableTest nt = Test("test"); | |
| assert(nt.to!string() == `Test("test")`); | |
| NullableTest ntn = Test("null"); | |
| assert(ntn.to!string() == "Nullable.null"); | |
| class TestToString | |
| { | |
| double d; | |
| this(double d) | |
| { | |
| this.d = d; | |
| } | |
| override string toString() | |
| { | |
| return d.to!string(); | |
| } | |
| } | |
| alias NullableTestToString = Nullable!(TestToString, null); | |
| NullableTestToString ntts = new TestToString(2.5); | |
| assert(ntts.to!string() == "2.5"); | |
| } | |
| // apply | |
| /** | |
| Unpacks the content of a `Nullable`, performs an operation and packs it again. Does nothing if isNull. | |
| When called on a `Nullable`, `apply` will unpack the value contained in the `Nullable`, | |
| pass it to the function you provide and wrap the result in another `Nullable` (if necessary). | |
| If the `Nullable` is null, `apply` will return null itself. | |
| Params: | |
| t = a `Nullable` | |
| fun = a function operating on the content of the nullable | |
| Returns: | |
| `fun(t.get).nullable` if `!t.isNull`, else `Nullable.init`. | |
| See also: | |
| $(HTTPS en.wikipedia.org/wiki/Monad_(functional_programming)#The_Maybe_monad, The `Maybe` monad) | |
| */ | |
| template apply(alias fun) | |
| { | |
| import std.functional : unaryFun; | |
| auto apply(T)(auto ref T t) | |
| if (isInstanceOf!(Nullable, T)) | |
| { | |
| alias FunType = typeof(unaryFun!fun(T.init.get)); | |
| enum MustWrapReturn = !isInstanceOf!(Nullable, FunType); | |
| static if (MustWrapReturn) | |
| { | |
| alias ReturnType = Nullable!FunType; | |
| } | |
| else | |
| { | |
| alias ReturnType = FunType; | |
| } | |
| if (!t.isNull) | |
| { | |
| static if (MustWrapReturn) | |
| { | |
| return unaryFun!fun(t.get).nullable; | |
| } | |
| else | |
| { | |
| return unaryFun!fun(t.get); | |
| } | |
| } | |
| else | |
| { | |
| return ReturnType.init; | |
| } | |
| } | |
| } | |
| /// | |
| nothrow pure @nogc @safe unittest | |
| { | |
| alias toFloat = i => cast(float) i; | |
| Nullable!int sample; | |
| // apply(null) results in a null `Nullable` of the function's return type. | |
| Nullable!float f = sample.apply!toFloat; | |
| assert(sample.isNull && f.isNull); | |
| sample = 3; | |
| // apply(non-null) calls the function and wraps the result in a `Nullable`. | |
| f = sample.apply!toFloat; | |
| assert(!sample.isNull && !f.isNull); | |
| assert(f.get == 3.0f); | |
| } | |
| /// | |
| nothrow pure @nogc @safe unittest | |
| { | |
| alias greaterThree = i => (i > 3) ? i.nullable : Nullable!(typeof(i)).init; | |
| Nullable!int sample; | |
| // when the function already returns a `Nullable`, that `Nullable` is not wrapped. | |
| auto result = sample.apply!greaterThree; | |
| assert(sample.isNull && result.isNull); | |
| // The function may decide to return a null `Nullable`. | |
| sample = 3; | |
| result = sample.apply!greaterThree; | |
| assert(!sample.isNull && result.isNull); | |
| // Or it may return a value already wrapped in a `Nullable`. | |
| sample = 4; | |
| result = sample.apply!greaterThree; | |
| assert(!sample.isNull && !result.isNull); | |
| assert(result.get == 4); | |
| } | |
| // test that Nullable.get(default) can merge types | |
| @safe @nogc nothrow pure | |
| unittest | |
| { | |
| Nullable!ubyte sample = Nullable!ubyte(); | |
| // Test that get(U) returns the common type of the Nullable type and the parameter type. | |
| assert(sample.get(1000) == 1000); | |
| } | |
| // Workaround for https://issues.dlang.org/show_bug.cgi?id=20670 | |
| @safe @nogc nothrow pure | |
| unittest | |
| { | |
| immutable struct S { } | |
| S[] array = Nullable!(S[])().get(S[].init); | |
| } | |
| // regression test for https://issues.dlang.org/show_bug.cgi?id=21199 | |
| @safe @nogc nothrow pure | |
| unittest | |
| { | |
| struct S { int i; } | |
| assert(S(5).nullable.apply!"a.i" == 5); | |
| } | |
| // regression test for https://issues.dlang.org/show_bug.cgi?id=22176 | |
| @safe @nogc nothrow pure | |
| unittest | |
| { | |
| struct S | |
| { | |
| int i; | |
| invariant(i != 0); | |
| // Nullable shouldn't cause S to generate an | |
| // opAssign that would check the invariant. | |
| Nullable!int j; | |
| } | |
| S s; | |
| s = S(5); | |
| } | |
| /** | |
| Just like `Nullable!T`, except that the object refers to a value | |
| sitting elsewhere in memory. This makes assignments overwrite the | |
| initially assigned value. Internally `NullableRef!T` only stores a | |
| pointer to `T` (i.e., $(D Nullable!T.sizeof == (T*).sizeof)). | |
| */ | |
| struct NullableRef(T) | |
| { | |
| private T* _value; | |
| /** | |
| Constructor binding `this` to `value`. | |
| Params: | |
| value = The value to bind to. | |
| */ | |
| this(T* value) @safe pure nothrow | |
| { | |
| _value = value; | |
| } | |
| template toString() | |
| { | |
| import std.format.spec : FormatSpec; | |
| import std.format.write : formatValue; | |
| // Needs to be a template because of https://issues.dlang.org/show_bug.cgi?id=13737. | |
| void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) | |
| { | |
| if (isNull) | |
| { | |
| sink.formatValue("Nullable.null", fmt); | |
| } | |
| else | |
| { | |
| sink.formatValue(*_value, fmt); | |
| } | |
| } | |
| void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) const | |
| { | |
| if (isNull) | |
| { | |
| sink.formatValue("Nullable.null", fmt); | |
| } | |
| else | |
| { | |
| sink.formatValue(*_value, fmt); | |
| } | |
| } | |
| } | |
| @system unittest | |
| { | |
| import std.conv : to; | |
| const NullableRef!(ulong) x = new ulong(1); | |
| assert(x.to!string == "1"); | |
| } | |
| /** | |
| Binds the internal state to `value`. | |
| Params: | |
| value = A pointer to a value of type `T` to bind this `NullableRef` to. | |
| */ | |
| void bind(T* value) @safe pure nothrow | |
| { | |
| _value = value; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| NullableRef!int nr = new int(42); | |
| assert(nr == 42); | |
| int* n = new int(1); | |
| nr.bind(n); | |
| assert(nr == 1); | |
| } | |
| /** | |
| Returns `true` if and only if `this` is in the null state. | |
| Returns: | |
| true if `this` is in the null state, otherwise false. | |
| */ | |
| @property bool isNull() const @safe pure nothrow | |
| { | |
| return _value is null; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| NullableRef!int nr; | |
| assert(nr.isNull); | |
| int* n = new int(42); | |
| nr.bind(n); | |
| assert(!nr.isNull && nr == 42); | |
| } | |
| /** | |
| Forces `this` to the null state. | |
| */ | |
| void nullify() @safe pure nothrow | |
| { | |
| _value = null; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| NullableRef!int nr = new int(42); | |
| assert(!nr.isNull); | |
| nr.nullify(); | |
| assert(nr.isNull); | |
| } | |
| /** | |
| Assigns `value` to the internally-held state. | |
| Params: | |
| value = A value of type `T` to assign to this `NullableRef`. | |
| If the internal state of this `NullableRef` has not | |
| been initialized, an error will be thrown in | |
| non-release mode. | |
| */ | |
| void opAssign()(T value) | |
| if (isAssignable!T) //@@@9416@@@ | |
| { | |
| enum message = "Called `opAssign' on null NullableRef!" ~ T.stringof ~ "."; | |
| assert(!isNull, message); | |
| *_value = value; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.exception : assertThrown, assertNotThrown; | |
| NullableRef!int nr; | |
| assert(nr.isNull); | |
| assertThrown!Throwable(nr = 42); | |
| nr.bind(new int(0)); | |
| assert(!nr.isNull); | |
| assertNotThrown!Throwable(nr = 42); | |
| assert(nr == 42); | |
| } | |
| /** | |
| Gets the value. `this` must not be in the null state. | |
| This function is also called for the implicit conversion to `T`. | |
| */ | |
| @property ref inout(T) get() inout @safe pure nothrow | |
| { | |
| enum message = "Called `get' on null NullableRef!" ~ T.stringof ~ "."; | |
| assert(!isNull, message); | |
| return *_value; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.exception : assertThrown, assertNotThrown; | |
| NullableRef!int nr; | |
| //`get` is implicitly called. Will throw | |
| //an error in non-release mode | |
| assertThrown!Throwable(nr == 0); | |
| nr.bind(new int(0)); | |
| assertNotThrown!Throwable(nr == 0); | |
| } | |
| /** | |
| Implicitly converts to `T`. | |
| `this` must not be in the null state. | |
| */ | |
| alias get this; | |
| } | |
| /// ditto | |
| auto nullableRef(T)(T* t) | |
| { | |
| return NullableRef!T(t); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| int x = 5, y = 7; | |
| auto a = nullableRef(&x); | |
| assert(!a.isNull); | |
| assert(a == 5); | |
| assert(x == 5); | |
| a = 42; | |
| assert(x == 42); | |
| assert(!a.isNull); | |
| assert(a == 42); | |
| a.nullify(); | |
| assert(x == 42); | |
| assert(a.isNull); | |
| assertThrown!Throwable(a.get); | |
| assertThrown!Throwable(a = 71); | |
| a.bind(&y); | |
| assert(a == 7); | |
| y = 135; | |
| assert(a == 135); | |
| } | |
| @system unittest | |
| { | |
| static int f(scope const NullableRef!int x) { | |
| return x.isNull ? 42 : x.get; | |
| } | |
| int x = 5; | |
| auto a = nullableRef(&x); | |
| assert(f(a) == 5); | |
| a.nullify(); | |
| assert(f(a) == 42); | |
| } | |
| @safe unittest | |
| { | |
| // Ensure NullableRef can be used in pure/nothrow/@safe environment. | |
| function() @safe pure nothrow | |
| { | |
| auto storage = new int; | |
| *storage = 19902; | |
| NullableRef!int n; | |
| assert(n.isNull); | |
| n.bind(storage); | |
| assert(!n.isNull); | |
| assert(n == 19902); | |
| n = 2294; | |
| assert(n == 2294); | |
| assert(*storage == 2294); | |
| n.nullify(); | |
| assert(n.isNull); | |
| }(); | |
| } | |
| @system unittest | |
| { | |
| // Ensure NullableRef can be used when the value is not pure/nothrow/@safe | |
| static struct S | |
| { | |
| int x; | |
| this(this) @system {} | |
| bool opEquals(const S s) const @system { return s.x == x; } | |
| } | |
| auto storage = S(5); | |
| NullableRef!S s; | |
| assert(s.isNull); | |
| s.bind(&storage); | |
| assert(!s.isNull); | |
| assert(s.x == 5); | |
| s.nullify(); | |
| assert(s.isNull); | |
| } | |
| @safe unittest | |
| { | |
| //Check nullable is nicelly embedable in a struct | |
| static struct S1 | |
| { | |
| NullableRef!int ni; | |
| } | |
| static struct S2 //inspired from 9404 | |
| { | |
| NullableRef!int ni; | |
| this(S2 other) | |
| { | |
| ni = other.ni; | |
| } | |
| void opAssign(S2 other) | |
| { | |
| ni = other.ni; | |
| } | |
| } | |
| static foreach (S; AliasSeq!(S1, S2)) | |
| {{ | |
| S a; | |
| S b = a; | |
| S c; | |
| c = a; | |
| }} | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=10915 | |
| @system unittest | |
| { | |
| import std.conv : to; | |
| NullableRef!int nri; | |
| assert(nri.to!string() == "Nullable.null"); | |
| struct Test | |
| { | |
| string s; | |
| } | |
| NullableRef!Test nt = new Test("test"); | |
| assert(nt.to!string() == `Test("test")`); | |
| class TestToString | |
| { | |
| double d; | |
| this(double d) | |
| { | |
| this.d = d; | |
| } | |
| override string toString() | |
| { | |
| return d.to!string(); | |
| } | |
| } | |
| TestToString tts = new TestToString(2.5); | |
| NullableRef!TestToString ntts = &tts; | |
| assert(ntts.to!string() == "2.5"); | |
| } | |
| /** | |
| `BlackHole!Base` is a subclass of `Base` which automatically implements | |
| all abstract member functions in `Base` as do-nothing functions. Each | |
| auto-implemented function just returns the default value of the return type | |
| without doing anything. | |
| The name came from | |
| $(HTTP search.cpan.org/~sburke/Class-_BlackHole-0.04/lib/Class/_BlackHole.pm, Class::_BlackHole) | |
| Perl module by Sean M. Burke. | |
| Params: | |
| Base = A non-final class for `BlackHole` to inherit from. | |
| See_Also: | |
| $(LREF AutoImplement), $(LREF generateEmptyFunction) | |
| */ | |
| alias BlackHole(Base) = AutoImplement!(Base, generateEmptyFunction, isAbstractFunction); | |
| /// | |
| @system unittest | |
| { | |
| import std.math.traits : isNaN; | |
| static abstract class C | |
| { | |
| int m_value; | |
| this(int v) { m_value = v; } | |
| int value() @property { return m_value; } | |
| abstract real realValue() @property; | |
| abstract void doSomething(); | |
| } | |
| auto c = new BlackHole!C(42); | |
| assert(c.value == 42); | |
| // Returns real.init which is NaN | |
| assert(c.realValue.isNaN); | |
| // Abstract functions are implemented as do-nothing | |
| c.doSomething(); | |
| } | |
| @system unittest | |
| { | |
| import std.math.traits : isNaN; | |
| // return default | |
| { | |
| interface I_1 { real test(); } | |
| auto o = new BlackHole!I_1; | |
| assert(o.test().isNaN()); // NaN | |
| } | |
| // doc example | |
| { | |
| static class C | |
| { | |
| int m_value; | |
| this(int v) { m_value = v; } | |
| int value() @property { return m_value; } | |
| abstract real realValue() @property; | |
| abstract void doSomething(); | |
| } | |
| auto c = new BlackHole!C(42); | |
| assert(c.value == 42); | |
| assert(c.realValue.isNaN); // NaN | |
| c.doSomething(); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=12058 | |
| interface Foo | |
| { | |
| inout(Object) foo() inout; | |
| } | |
| BlackHole!Foo o; | |
| } | |
| nothrow pure @nogc @safe unittest | |
| { | |
| static interface I | |
| { | |
| I foo() nothrow pure @nogc @safe return scope; | |
| } | |
| scope cb = new BlackHole!I(); | |
| cb.foo(); | |
| } | |
| /** | |
| `WhiteHole!Base` is a subclass of `Base` which automatically implements | |
| all abstract member functions as functions that always fail. These functions | |
| simply throw an `Error` and never return. `Whitehole` is useful for | |
| trapping the use of class member functions that haven't been implemented. | |
| The name came from | |
| $(HTTP search.cpan.org/~mschwern/Class-_WhiteHole-0.04/lib/Class/_WhiteHole.pm, Class::_WhiteHole) | |
| Perl module by Michael G Schwern. | |
| Params: | |
| Base = A non-final class for `WhiteHole` to inherit from. | |
| See_Also: | |
| $(LREF AutoImplement), $(LREF generateAssertTrap) | |
| */ | |
| alias WhiteHole(Base) = AutoImplement!(Base, generateAssertTrap, isAbstractFunction); | |
| /// | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| static class C | |
| { | |
| abstract void notYetImplemented(); | |
| } | |
| auto c = new WhiteHole!C; | |
| assertThrown!NotImplementedError(c.notYetImplemented()); // throws an Error | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=20232 | |
| nothrow pure @safe unittest | |
| { | |
| static interface I | |
| { | |
| I foo() nothrow pure @safe return scope; | |
| } | |
| if (0) // Just checking attribute interference | |
| { | |
| scope cw = new WhiteHole!I(); | |
| cw.foo(); | |
| } | |
| } | |
| /// ditto | |
| class NotImplementedError : Error | |
| { | |
| /// | |
| this(string method) nothrow pure @safe | |
| { | |
| super(method ~ " is not implemented"); | |
| } | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| // nothrow | |
| { | |
| interface I_1 | |
| { | |
| void foo(); | |
| void bar() nothrow; | |
| } | |
| auto o = new WhiteHole!I_1; | |
| assertThrown!NotImplementedError(o.foo()); | |
| assertThrown!NotImplementedError(o.bar()); | |
| } | |
| // doc example | |
| { | |
| static class C | |
| { | |
| abstract void notYetImplemented(); | |
| } | |
| auto c = new WhiteHole!C; | |
| try | |
| { | |
| c.notYetImplemented(); | |
| assert(0); | |
| } | |
| catch (Error e) {} | |
| } | |
| } | |
| /** | |
| `AutoImplement` automatically implements (by default) all abstract member | |
| functions in the class or interface `Base` in specified way. | |
| The second version of `AutoImplement` automatically implements | |
| `Interface`, while deriving from `BaseClass`. | |
| Params: | |
| how = template which specifies _how functions will be implemented/overridden. | |
| Two arguments are passed to `how`: the type `Base` and an alias | |
| to an implemented function. Then `how` must return an implemented | |
| function body as a string. | |
| The generated function body can use these keywords: | |
| $(UL | |
| $(LI `a0`, `a1`, …: arguments passed to the function;) | |
| $(LI `args`: a tuple of the arguments;) | |
| $(LI `self`: an alias to the function itself;) | |
| $(LI `parent`: an alias to the overridden function (if any).) | |
| ) | |
| You may want to use templated property functions (instead of Implicit | |
| Template Properties) to generate complex functions: | |
| -------------------- | |
| // Prints log messages for each call to overridden functions. | |
| string generateLogger(C, alias fun)() @property | |
| { | |
| import std.traits; | |
| enum qname = C.stringof ~ "." ~ __traits(identifier, fun); | |
| string stmt; | |
| stmt ~= q{ struct Importer { import std.stdio; } }; | |
| stmt ~= `Importer.writeln("Log: ` ~ qname ~ `(", args, ")");`; | |
| static if (!__traits(isAbstractFunction, fun)) | |
| { | |
| static if (is(ReturnType!fun == void)) | |
| stmt ~= q{ parent(args); }; | |
| else | |
| stmt ~= q{ | |
| auto r = parent(args); | |
| Importer.writeln("--> ", r); | |
| return r; | |
| }; | |
| } | |
| return stmt; | |
| } | |
| -------------------- | |
| what = template which determines _what functions should be | |
| implemented/overridden. | |
| An argument is passed to `what`: an alias to a non-final member | |
| function in `Base`. Then `what` must return a boolean value. | |
| Return `true` to indicate that the passed function should be | |
| implemented/overridden. | |
| -------------------- | |
| // Sees if fun returns something. | |
| enum bool hasValue(alias fun) = !is(ReturnType!(fun) == void); | |
| -------------------- | |
| Note: | |
| Generated code is inserted in the scope of `std.typecons` module. Thus, | |
| any useful functions outside `std.typecons` cannot be used in the generated | |
| code. To workaround this problem, you may `import` necessary things in a | |
| local struct, as done in the `generateLogger()` template in the above | |
| example. | |
| BUGS: | |
| $(UL | |
| $(LI Variadic arguments to constructors are not forwarded to super.) | |
| $(LI Deep interface inheritance causes compile error with messages like | |
| "Error: function std.typecons._AutoImplement!(Foo)._AutoImplement.bar | |
| does not override any function". [$(BUGZILLA 2525)] ) | |
| $(LI The `parent` keyword is actually a delegate to the super class' | |
| corresponding member function. [$(BUGZILLA 2540)] ) | |
| $(LI Using alias template parameter in `how` and/or `what` may cause | |
| strange compile error. Use template tuple parameter instead to workaround | |
| this problem. [$(BUGZILLA 4217)] ) | |
| ) | |
| */ | |
| class AutoImplement(Base, alias how, alias what = isAbstractFunction) : Base | |
| if (!is(how == class)) | |
| { | |
| private alias autoImplement_helper_ = | |
| AutoImplement_Helper!("autoImplement_helper_", "Base", Base, typeof(this), how, what); | |
| mixin(autoImplement_helper_.code); | |
| } | |
| /// ditto | |
| class AutoImplement( | |
| Interface, BaseClass, alias how, | |
| alias what = isAbstractFunction) : BaseClass, Interface | |
| if (is(Interface == interface) && is(BaseClass == class)) | |
| { | |
| private alias autoImplement_helper_ = AutoImplement_Helper!( | |
| "autoImplement_helper_", "Interface", Interface, typeof(this), how, what); | |
| mixin(autoImplement_helper_.code); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| interface PackageSupplier | |
| { | |
| int foo(); | |
| int bar(); | |
| } | |
| static abstract class AbstractFallbackPackageSupplier : PackageSupplier | |
| { | |
| protected PackageSupplier default_, fallback; | |
| this(PackageSupplier default_, PackageSupplier fallback) | |
| { | |
| this.default_ = default_; | |
| this.fallback = fallback; | |
| } | |
| abstract int foo(); | |
| abstract int bar(); | |
| } | |
| template fallback(T, alias func) | |
| { | |
| import std.format : format; | |
| // for all implemented methods: | |
| // - try default first | |
| // - only on a failure run & return fallback | |
| enum fallback = q{ | |
| try | |
| { | |
| return default_.%1$s(args); | |
| } | |
| catch (Exception) | |
| { | |
| return fallback.%1$s(args); | |
| } | |
| }.format(__traits(identifier, func)); | |
| } | |
| // combines two classes and use the second one as fallback | |
| alias FallbackPackageSupplier = AutoImplement!(AbstractFallbackPackageSupplier, fallback); | |
| class FailingPackageSupplier : PackageSupplier | |
| { | |
| int foo(){ throw new Exception("failure"); } | |
| int bar(){ return 2;} | |
| } | |
| class BackupPackageSupplier : PackageSupplier | |
| { | |
| int foo(){ return -1; } | |
| int bar(){ return -1;} | |
| } | |
| auto registry = new FallbackPackageSupplier(new FailingPackageSupplier(), new BackupPackageSupplier()); | |
| assert(registry.foo() == -1); | |
| assert(registry.bar() == 2); | |
| } | |
| /* | |
| * Code-generating stuffs are encupsulated in this helper template so that | |
| * namespace pollution, which can cause name confliction with Base's public | |
| * members, should be minimized. | |
| */ | |
| private template AutoImplement_Helper(string myName, string baseName, | |
| Base, Self, alias generateMethodBody, alias cherrypickMethod) | |
| { | |
| private static: | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| // Internal stuffs | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| // Returns function overload sets in the class C, filtered with pred. | |
| template enumerateOverloads(C, alias pred) | |
| { | |
| template Impl(names...) | |
| { | |
| import std.meta : Filter; | |
| static if (names.length > 0) | |
| { | |
| alias methods = Filter!(pred, MemberFunctionsTuple!(C, names[0])); | |
| alias next = Impl!(names[1 .. $]); | |
| static if (methods.length > 0) | |
| alias Impl = AliasSeq!(OverloadSet!(names[0], methods), next); | |
| else | |
| alias Impl = next; | |
| } | |
| else | |
| alias Impl = AliasSeq!(); | |
| } | |
| alias enumerateOverloads = Impl!(__traits(allMembers, C)); | |
| } | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| // Target functions | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| // Add a non-final check to the cherrypickMethod. | |
| enum bool canonicalPicker(fun.../+[https://issues.dlang.org/show_bug.cgi?id=4217]+/) = | |
| !__traits(isFinalFunction, fun[0]) && cherrypickMethod!(fun); | |
| /* | |
| * A tuple of overload sets, each item of which consists of functions to be | |
| * implemented by the generated code. | |
| */ | |
| alias targetOverloadSets = enumerateOverloads!(Base, canonicalPicker); | |
| /* | |
| * Super class of this AutoImplement instance | |
| */ | |
| alias Super = BaseTypeTuple!(Self)[0]; | |
| static assert(is(Super == class)); | |
| static assert(is(Base == interface) || is(Super == Base)); | |
| /* | |
| * A tuple of the super class' constructors. Used for forwarding | |
| * constructor calls. | |
| */ | |
| static if (__traits(hasMember, Super, "__ctor")) | |
| alias ctorOverloadSet = OverloadSet!("__ctor", __traits(getOverloads, Super, "__ctor")); | |
| else | |
| alias ctorOverloadSet = OverloadSet!("__ctor"); // empty | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| // Type information | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| /* | |
| * The generated code will be mixed into AutoImplement, which will be | |
| * instantiated in this module's scope. Thus, any user-defined types are | |
| * out of scope and cannot be used directly (i.e. by their names). | |
| * | |
| * We will use FuncInfo instances for accessing return types and parameter | |
| * types of the implemented functions. The instances will be populated to | |
| * the AutoImplement's scope in a certain way; see the populate() below. | |
| */ | |
| // Returns the preferred identifier for the FuncInfo instance for the i-th | |
| // overloaded function with the name. | |
| template INTERNAL_FUNCINFO_ID(string name, size_t i) | |
| { | |
| import std.format : format; | |
| enum string INTERNAL_FUNCINFO_ID = format("F_%s_%s", name, i); | |
| } | |
| /* | |
| * Insert FuncInfo instances about all the target functions here. This | |
| * enables the generated code to access type information via, for example, | |
| * "autoImplement_helper_.F_foo_1". | |
| */ | |
| template populate(overloads...) | |
| { | |
| static if (overloads.length > 0) | |
| { | |
| mixin populate!(overloads[0].name, overloads[0].contents); | |
| mixin populate!(overloads[1 .. $]); | |
| } | |
| } | |
| template populate(string name, methods...) | |
| { | |
| static if (methods.length > 0) | |
| { | |
| mixin populate!(name, methods[0 .. $ - 1]); | |
| // | |
| alias target = methods[$ - 1]; | |
| enum ith = methods.length - 1; | |
| mixin("alias " ~ INTERNAL_FUNCINFO_ID!(name, ith) ~ " = FuncInfo!target;"); | |
| } | |
| } | |
| public mixin populate!(targetOverloadSets); | |
| public mixin populate!( ctorOverloadSet ); | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| // Code-generating policies | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| /* Common policy configurations for generating constructors and methods. */ | |
| template CommonGeneratingPolicy() | |
| { | |
| // base class identifier which generated code should use | |
| enum string BASE_CLASS_ID = baseName; | |
| // FuncInfo instance identifier which generated code should use | |
| template FUNCINFO_ID(string name, size_t i) | |
| { | |
| enum string FUNCINFO_ID = | |
| myName ~ "." ~ INTERNAL_FUNCINFO_ID!(name, i); | |
| } | |
| } | |
| /* Policy configurations for generating constructors. */ | |
| template ConstructorGeneratingPolicy() | |
| { | |
| mixin CommonGeneratingPolicy; | |
| /* Generates constructor body. Just forward to the base class' one. */ | |
| string generateFunctionBody(ctor.../+[https://issues.dlang.org/show_bug.cgi?id=4217]+/)() @property | |
| { | |
| enum varstyle = variadicFunctionStyle!(typeof(&ctor[0])); | |
| static if (varstyle & (Variadic.c | Variadic.d)) | |
| { | |
| // the argptr-forwarding problem | |
| //pragma(msg, "Warning: AutoImplement!(", Base, ") ", | |
| // "ignored variadic arguments to the constructor ", | |
| // FunctionTypeOf!(typeof(&ctor[0])) ); | |
| } | |
| return "super(args);"; | |
| } | |
| } | |
| /* Policy configurations for genearting target methods. */ | |
| template MethodGeneratingPolicy() | |
| { | |
| mixin CommonGeneratingPolicy; | |
| /* Geneartes method body. */ | |
| string generateFunctionBody(func.../+[https://issues.dlang.org/show_bug.cgi?id=4217]+/)() @property | |
| { | |
| return generateMethodBody!(Base, func); // given | |
| } | |
| } | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| // Generated code | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| alias ConstructorGenerator = MemberFunctionGenerator!(ConstructorGeneratingPolicy!()); | |
| alias MethodGenerator = MemberFunctionGenerator!(MethodGeneratingPolicy!()); | |
| public enum string code = | |
| ConstructorGenerator.generateCode!( ctorOverloadSet ) ~ "\n" ~ | |
| MethodGenerator.generateCode!(targetOverloadSets); | |
| debug (SHOW_GENERATED_CODE) | |
| { | |
| pragma(msg, "-------------------- < ", Base, " >"); | |
| pragma(msg, code); | |
| pragma(msg, "--------------------"); | |
| } | |
| } | |
| //debug = SHOW_GENERATED_CODE; | |
| @system unittest | |
| { | |
| import core.vararg; | |
| // no function to implement | |
| { | |
| interface I_1 {} | |
| auto o = new BlackHole!I_1; | |
| } | |
| // parameters | |
| { | |
| interface I_3 { void test(int, in int, out int, ref int, lazy int); } | |
| auto o = new BlackHole!I_3; | |
| } | |
| // use of user-defined type | |
| { | |
| struct S {} | |
| interface I_4 { S test(); } | |
| auto o = new BlackHole!I_4; | |
| } | |
| // overloads | |
| { | |
| interface I_5 | |
| { | |
| void test(string); | |
| real test(real); | |
| int test(); | |
| } | |
| auto o = new BlackHole!I_5; | |
| } | |
| // constructor forwarding | |
| { | |
| static class C_6 | |
| { | |
| this(int n) { assert(n == 42); } | |
| this(string s) { assert(s == "Deeee"); } | |
| this(...) {} | |
| } | |
| auto o1 = new BlackHole!C_6(42); | |
| auto o2 = new BlackHole!C_6("Deeee"); | |
| auto o3 = new BlackHole!C_6(1, 2, 3, 4); | |
| } | |
| // attributes | |
| { | |
| interface I_7 | |
| { | |
| ref int test_ref(); | |
| int test_pure() pure; | |
| int test_nothrow() nothrow; | |
| int test_property() @property; | |
| int test_safe() @safe; | |
| int test_trusted() @trusted; | |
| int test_system() @system; | |
| int test_pure_nothrow() pure nothrow; | |
| } | |
| auto o = new BlackHole!I_7; | |
| } | |
| // storage classes | |
| { | |
| interface I_8 | |
| { | |
| void test_const() const; | |
| void test_immutable() immutable; | |
| void test_shared() shared; | |
| void test_shared_const() shared const; | |
| } | |
| auto o = new BlackHole!I_8; | |
| } | |
| // use baseclass | |
| { | |
| static class C_9 | |
| { | |
| private string foo_; | |
| this(string s) { | |
| foo_ = s; | |
| } | |
| protected string boilerplate() @property | |
| { | |
| return "Boilerplate stuff."; | |
| } | |
| public string foo() @property | |
| { | |
| return foo_; | |
| } | |
| } | |
| interface I_10 | |
| { | |
| string testMethod(size_t); | |
| } | |
| static string generateTestMethod(C, alias fun)() @property | |
| { | |
| return "return this.boilerplate[0 .. a0];"; | |
| } | |
| auto o = new AutoImplement!(I_10, C_9, generateTestMethod)("Testing"); | |
| assert(o.testMethod(11) == "Boilerplate"); | |
| assert(o.foo == "Testing"); | |
| } | |
| /+ // deep inheritance | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=2525 | |
| // https://issues.dlang.org/show_bug.cgi?id=3525 | |
| // NOTE: [r494] func.c(504-571) FuncDeclaration::semantic() | |
| interface I { void foo(); } | |
| interface J : I {} | |
| interface K : J {} | |
| static abstract class C_9 : K {} | |
| auto o = new BlackHole!C_9; | |
| }+/ | |
| // test `parent` alias | |
| { | |
| interface I_11 | |
| { | |
| void simple(int) @safe; | |
| int anotherSimple(string); | |
| int overloaded(int); | |
| /+ XXX [BUG 19715] | |
| void overloaded(string) @safe; | |
| +/ | |
| } | |
| static class C_11 | |
| { | |
| import std.traits : Parameters, ReturnType; | |
| import std.meta : Alias; | |
| protected ReturnType!fn _impl(alias fn)(Parameters!fn) | |
| if (is(Alias!(__traits(parent, fn)) == interface)) | |
| { | |
| static if (!is(typeof(return) == void)) | |
| return typeof(return).init; | |
| } | |
| } | |
| template tpl(I, alias fn) | |
| if (is(I == interface) && __traits(isSame, __traits(parent, fn), I)) | |
| { | |
| enum string tpl = q{ | |
| enum bool haveReturn = !is(typeof(return) == void); | |
| static if (is(typeof(return) == void)) | |
| _impl!parent(args); | |
| else | |
| return _impl!parent(args); | |
| }; | |
| } | |
| auto o = new AutoImplement!(I_11, C_11, tpl); | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=17177 | |
| // AutoImplement fails on function overload sets with | |
| // "cannot infer type from overloaded function symbol" | |
| @system unittest | |
| { | |
| static class Issue17177 | |
| { | |
| private string n_; | |
| public { | |
| Issue17177 overloaded(string n) | |
| { | |
| this.n_ = n; | |
| return this; | |
| } | |
| string overloaded() | |
| { | |
| return this.n_; | |
| } | |
| } | |
| } | |
| static string how(C, alias fun)() | |
| { | |
| static if (!is(ReturnType!fun == void)) | |
| { | |
| return q{ | |
| return parent(args); | |
| }; | |
| } | |
| else | |
| { | |
| return q{ | |
| parent(args); | |
| }; | |
| } | |
| } | |
| import std.meta : templateNot; | |
| alias Implementation = AutoImplement!(Issue17177, how, templateNot!isFinalFunction); | |
| } | |
| version (StdUnittest) | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=10647 | |
| // Add prefix "issue10647_" as a workaround for | |
| // https://issues.dlang.org/show_bug.cgi?id=1238 | |
| private string issue10647_generateDoNothing(C, alias fun)() @property | |
| { | |
| string stmt; | |
| static if (is(ReturnType!fun == void)) | |
| stmt ~= ""; | |
| else | |
| { | |
| string returnType = ReturnType!fun.stringof; | |
| stmt ~= "return "~returnType~".init;"; | |
| } | |
| return stmt; | |
| } | |
| private template issue10647_isAlwaysTrue(alias fun) | |
| { | |
| enum issue10647_isAlwaysTrue = true; | |
| } | |
| // Do nothing template | |
| private template issue10647_DoNothing(Base) | |
| { | |
| alias issue10647_DoNothing = AutoImplement!(Base, issue10647_generateDoNothing, issue10647_isAlwaysTrue); | |
| } | |
| // A class to be overridden | |
| private class issue10647_Foo{ | |
| void bar(int a) { } | |
| } | |
| } | |
| @system unittest | |
| { | |
| auto foo = new issue10647_DoNothing!issue10647_Foo(); | |
| foo.bar(13); | |
| } | |
| /* | |
| Used by MemberFunctionGenerator. | |
| */ | |
| package template OverloadSet(string nam, T...) | |
| { | |
| enum string name = nam; | |
| alias contents = T; | |
| } | |
| /* | |
| Used by MemberFunctionGenerator. | |
| */ | |
| package template FuncInfo(alias func) | |
| if (is(typeof(&func))) | |
| { | |
| alias RT = ReturnType!(typeof(&func)); | |
| alias PT = Parameters!(typeof(&func)); | |
| } | |
| package template FuncInfo(Func) | |
| { | |
| alias RT = ReturnType!Func; | |
| alias PT = Parameters!Func; | |
| } | |
| /* | |
| General-purpose member function generator. | |
| -------------------- | |
| template GeneratingPolicy() | |
| { | |
| // [optional] the name of the class where functions are derived | |
| enum string BASE_CLASS_ID; | |
| // [optional] define this if you have only function types | |
| enum bool WITHOUT_SYMBOL; | |
| // [optional] Returns preferred identifier for i-th parameter. | |
| template PARAMETER_VARIABLE_ID(size_t i); | |
| // Returns the identifier of the FuncInfo instance for the i-th overload | |
| // of the specified name. The identifier must be accessible in the scope | |
| // where generated code is mixed. | |
| template FUNCINFO_ID(string name, size_t i); | |
| // Returns implemented function body as a string. When WITHOUT_SYMBOL is | |
| // defined, the latter is used. | |
| template generateFunctionBody(alias func); | |
| template generateFunctionBody(string name, FuncType); | |
| } | |
| -------------------- | |
| */ | |
| package template MemberFunctionGenerator(alias Policy) | |
| { | |
| private static: | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| // Internal stuffs | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| import std.format; | |
| alias format = std.format.format; | |
| enum CONSTRUCTOR_NAME = "__ctor"; | |
| // true if functions are derived from a base class | |
| enum WITH_BASE_CLASS = __traits(hasMember, Policy, "BASE_CLASS_ID"); | |
| // true if functions are specified as types, not symbols | |
| enum WITHOUT_SYMBOL = __traits(hasMember, Policy, "WITHOUT_SYMBOL"); | |
| // preferred identifier for i-th parameter variable | |
| static if (__traits(hasMember, Policy, "PARAMETER_VARIABLE_ID")) | |
| { | |
| alias PARAMETER_VARIABLE_ID = Policy.PARAMETER_VARIABLE_ID; | |
| } | |
| else | |
| { | |
| enum string PARAMETER_VARIABLE_ID(size_t i) = format("a%s", i); | |
| // default: a0, a1, ... | |
| } | |
| // Returns a tuple consisting of 0,1,2,...,n-1. For static foreach. | |
| template CountUp(size_t n) | |
| { | |
| static if (n > 0) | |
| alias CountUp = AliasSeq!(CountUp!(n - 1), n - 1); | |
| else | |
| alias CountUp = AliasSeq!(); | |
| } | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| // Code generator | |
| //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// | |
| /* | |
| * Runs through all the target overload sets and generates D code which | |
| * implements all the functions in the overload sets. | |
| */ | |
| public string generateCode(overloads...)() @property | |
| { | |
| string code = ""; | |
| // run through all the overload sets | |
| foreach (i_; CountUp!(0 + overloads.length)) // workaround | |
| { | |
| enum i = 0 + i_; // workaround | |
| alias oset = overloads[i]; | |
| code ~= generateCodeForOverloadSet!(oset); | |
| static if (WITH_BASE_CLASS && oset.name != CONSTRUCTOR_NAME) | |
| { | |
| // The generated function declarations may hide existing ones | |
| // in the base class (cf. HiddenFuncError), so we put an alias | |
| // declaration here to reveal possible hidden functions. | |
| code ~= format("alias %s = %s.%s;\n", | |
| oset.name, | |
| // super: https://issues.dlang.org/show_bug.cgi?id=2540 | |
| Policy.BASE_CLASS_ID, | |
| oset.name); | |
| } | |
| } | |
| return code; | |
| } | |
| // handle each overload set | |
| string generateCodeForOverloadSet(alias oset)() @property | |
| { | |
| string code = ""; | |
| foreach (i_; CountUp!(0 + oset.contents.length)) // workaround | |
| { | |
| enum i = 0 + i_; // workaround | |
| code ~= generateFunction!( | |
| Policy.FUNCINFO_ID!(oset.name, i), oset.name, | |
| oset.contents[i]) ~ "\n"; | |
| } | |
| return code; | |
| } | |
| /* | |
| * Returns D code which implements the function func. This function | |
| * actually generates only the declarator part; the function body part is | |
| * generated by the functionGenerator() policy. | |
| */ | |
| public string generateFunction( | |
| string myFuncInfo, string name, func... )() @property | |
| { | |
| import std.format : format; | |
| enum isCtor = (name == CONSTRUCTOR_NAME); | |
| string code; // the result | |
| auto paramsRes = generateParameters!(myFuncInfo, func)(); | |
| code ~= paramsRes.imports; | |
| /*** Function Declarator ***/ | |
| { | |
| alias Func = FunctionTypeOf!(func); | |
| alias FA = FunctionAttribute; | |
| enum atts = functionAttributes!(func); | |
| enum realName = isCtor ? "this" : name; | |
| // FIXME?? Make it so that these aren't CTFE funcs any more, since | |
| // Format is deprecated, and format works at compile time? | |
| /* Made them CTFE funcs just for the sake of Format!(...) */ | |
| // return type with optional "ref" | |
| static string make_returnType() | |
| { | |
| string rtype = ""; | |
| if (!isCtor) | |
| { | |
| if (atts & FA.ref_) rtype ~= "ref "; | |
| rtype ~= myFuncInfo ~ ".RT"; | |
| } | |
| return rtype; | |
| } | |
| enum returnType = make_returnType(); | |
| // function attributes attached after declaration | |
| static string make_postAtts() | |
| { | |
| string poatts = ""; | |
| if (atts & FA.pure_ ) poatts ~= " pure"; | |
| if (atts & FA.nothrow_) poatts ~= " nothrow"; | |
| if (atts & FA.property) poatts ~= " @property"; | |
| if (atts & FA.safe ) poatts ~= " @safe"; | |
| if (atts & FA.trusted ) poatts ~= " @trusted"; | |
| if (atts & FA.scope_ ) poatts ~= " scope"; | |
| if (atts & FA.return_ ) poatts ~= " return"; | |
| return poatts; | |
| } | |
| enum postAtts = make_postAtts(); | |
| // function storage class | |
| static string make_storageClass() | |
| { | |
| string postc = ""; | |
| if (is(Func == shared)) postc ~= " shared"; | |
| if (is(Func == const)) postc ~= " const"; | |
| if (is(Func == inout)) postc ~= " inout"; | |
| if (is(Func == immutable)) postc ~= " immutable"; | |
| return postc; | |
| } | |
| enum storageClass = make_storageClass(); | |
| // | |
| if (__traits(isVirtualMethod, func)) | |
| code ~= "override "; | |
| code ~= format("extern(%s) %s %s(%s) %s %s\n", | |
| functionLinkage!(func), | |
| returnType, | |
| realName, | |
| paramsRes.params, | |
| postAtts, storageClass ); | |
| } | |
| /*** Function Body ***/ | |
| code ~= "{\n"; | |
| { | |
| enum nparams = Parameters!(func).length; | |
| /* Declare keywords: args, self and parent. */ | |
| string preamble; | |
| preamble ~= "alias args = AliasSeq!(" ~ enumerateParameters!(nparams) ~ ");\n"; | |
| if (!isCtor) | |
| { | |
| preamble ~= "alias self = " ~ name ~ ";\n"; | |
| static if (WITH_BASE_CLASS) | |
| preamble ~= `alias parent = __traits(getMember, ` ~ Policy.BASE_CLASS_ID ~ `, "` ~ name ~ `");`; | |
| } | |
| // Function body | |
| static if (WITHOUT_SYMBOL) | |
| enum fbody = Policy.generateFunctionBody!(name, func); | |
| else | |
| enum fbody = Policy.generateFunctionBody!(func); | |
| code ~= preamble; | |
| code ~= fbody; | |
| } | |
| code ~= "}"; | |
| return code; | |
| } | |
| /* | |
| * Returns D code which declares function parameters, | |
| * and optionally any imports (e.g. core.vararg) | |
| * "ref int a0, real a1, ..." | |
| */ | |
| static struct GenParams { string imports, params; } | |
| GenParams generateParameters(string myFuncInfo, func...)() | |
| { | |
| alias STC = ParameterStorageClass; | |
| alias stcs = ParameterStorageClassTuple!(func); | |
| enum nparams = stcs.length; | |
| string imports = ""; // any imports required | |
| string params = ""; // parameters | |
| foreach (i, stc; stcs) | |
| { | |
| if (i > 0) params ~= ", "; | |
| // Parameter storage classes. | |
| if (stc & STC.scope_) params ~= "scope "; | |
| if (stc & STC.in_) params ~= "in "; | |
| if (stc & STC.out_ ) params ~= "out "; | |
| if (stc & STC.ref_ ) params ~= "ref "; | |
| if (stc & STC.lazy_ ) params ~= "lazy "; | |
| // Take parameter type from the FuncInfo. | |
| params ~= format("%s.PT[%s]", myFuncInfo, i); | |
| // Declare a parameter variable. | |
| params ~= " " ~ PARAMETER_VARIABLE_ID!(i); | |
| } | |
| // Add some ellipsis part if needed. | |
| auto style = variadicFunctionStyle!(func); | |
| final switch (style) | |
| { | |
| case Variadic.no: | |
| break; | |
| case Variadic.c, Variadic.d: | |
| imports ~= "import core.vararg;\n"; | |
| // (...) or (a, b, ...) | |
| params ~= (nparams == 0) ? "..." : ", ..."; | |
| break; | |
| case Variadic.typesafe: | |
| params ~= " ..."; | |
| break; | |
| } | |
| return typeof(return)(imports, params); | |
| } | |
| // Returns D code which enumerates n parameter variables using comma as the | |
| // separator. "a0, a1, a2, a3" | |
| string enumerateParameters(size_t n)() @property | |
| { | |
| string params = ""; | |
| foreach (i_; CountUp!(n)) | |
| { | |
| enum i = 0 + i_; // workaround | |
| if (i > 0) params ~= ", "; | |
| params ~= PARAMETER_VARIABLE_ID!(i); | |
| } | |
| return params; | |
| } | |
| } | |
| /** | |
| Predefined how-policies for `AutoImplement`. These templates are also used by | |
| `BlackHole` and `WhiteHole`, respectively. | |
| */ | |
| template generateEmptyFunction(C, func.../+[https://issues.dlang.org/show_bug.cgi?id=4217]+/) | |
| { | |
| static if (is(ReturnType!(func) == void)) | |
| enum string generateEmptyFunction = q{ | |
| }; | |
| else static if (functionAttributes!(func) & FunctionAttribute.ref_) | |
| enum string generateEmptyFunction = q{ | |
| static typeof(return) dummy; | |
| return dummy; | |
| }; | |
| else | |
| enum string generateEmptyFunction = q{ | |
| return typeof(return).init; | |
| }; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| alias BlackHole(Base) = AutoImplement!(Base, generateEmptyFunction); | |
| interface I | |
| { | |
| int foo(); | |
| string bar(); | |
| } | |
| auto i = new BlackHole!I(); | |
| // generateEmptyFunction returns the default value of the return type without doing anything | |
| assert(i.foo == 0); | |
| assert(i.bar is null); | |
| } | |
| /// ditto | |
| template generateAssertTrap(C, func...) | |
| { | |
| enum string generateAssertTrap = | |
| `throw new NotImplementedError("` ~ C.stringof ~ "." | |
| ~ __traits(identifier, func) ~ `");`; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| alias WhiteHole(Base) = AutoImplement!(Base, generateAssertTrap); | |
| interface I | |
| { | |
| int foo(); | |
| string bar(); | |
| } | |
| auto i = new WhiteHole!I(); | |
| // generateAssertTrap throws an exception for every unimplemented function of the interface | |
| assertThrown!NotImplementedError(i.foo); | |
| assertThrown!NotImplementedError(i.bar); | |
| } | |
| private | |
| { | |
| pragma(mangle, "_d_toObject") | |
| extern(C) pure nothrow Object typecons_d_toObject(void* p); | |
| } | |
| /* | |
| * Avoids opCast operator overloading. | |
| */ | |
| private template dynamicCast(T) | |
| if (is(T == class) || is(T == interface)) | |
| { | |
| @trusted | |
| T dynamicCast(S)(inout S source) | |
| if (is(S == class) || is(S == interface)) | |
| { | |
| static if (is(Unqual!S : Unqual!T)) | |
| { | |
| import std.traits : QualifierOf; | |
| alias Qual = QualifierOf!S; // SharedOf or MutableOf | |
| alias TmpT = Qual!(Unqual!T); | |
| inout(TmpT) tmp = source; // bypass opCast by implicit conversion | |
| return *cast(T*)(&tmp); // + variable pointer cast + dereference | |
| } | |
| else | |
| { | |
| return cast(T) typecons_d_toObject(*cast(void**)(&source)); | |
| } | |
| } | |
| } | |
| @system unittest | |
| { | |
| class C { @disable void opCast(T)(); } | |
| auto c = new C; | |
| static assert(!__traits(compiles, cast(Object) c)); | |
| auto o = dynamicCast!Object(c); | |
| assert(c is o); | |
| interface I { @disable void opCast(T)(); Object instance(); } | |
| interface J { @disable void opCast(T)(); Object instance(); } | |
| class D : I, J { Object instance() { return this; } } | |
| I i = new D(); | |
| static assert(!__traits(compiles, cast(J) i)); | |
| J j = dynamicCast!J(i); | |
| assert(i.instance() is j.instance()); | |
| } | |
| /** | |
| Supports structural based typesafe conversion. | |
| If `Source` has structural conformance with the `interface` `Targets`, | |
| wrap creates an internal wrapper class which inherits `Targets` and | |
| wraps the `src` object, then returns it. | |
| `unwrap` can be used to extract objects which have been wrapped by `wrap`. | |
| */ | |
| template wrap(Targets...) | |
| if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) | |
| { | |
| import std.meta : staticMap; | |
| // strict upcast | |
| auto wrap(Source)(inout Source src) @trusted pure nothrow | |
| if (Targets.length == 1 && is(Source : Targets[0])) | |
| { | |
| alias T = Select!(is(Source == shared), shared Targets[0], Targets[0]); | |
| return dynamicCast!(inout T)(src); | |
| } | |
| // structural upcast | |
| template wrap(Source) | |
| if (!allSatisfy!(Bind!(isImplicitlyConvertible, Source), Targets)) | |
| { | |
| auto wrap(inout Source src) | |
| { | |
| static assert(hasRequireMethods!(), | |
| "Source "~Source.stringof~ | |
| " does not have structural conformance to "~ | |
| Targets.stringof); | |
| alias T = Select!(is(Source == shared), shared Impl, Impl); | |
| return new inout T(src); | |
| } | |
| template FuncInfo(string s, F) | |
| { | |
| enum name = s; | |
| alias type = F; | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=12064: Remove NVI members | |
| template OnlyVirtual(members...) | |
| { | |
| enum notFinal(alias T) = !__traits(isFinalFunction, T); | |
| import std.meta : Filter; | |
| alias OnlyVirtual = Filter!(notFinal, members); | |
| } | |
| // Concat all Targets function members into one tuple | |
| template Concat(size_t i = 0) | |
| { | |
| static if (i >= Targets.length) | |
| alias Concat = AliasSeq!(); | |
| else | |
| { | |
| alias Concat = AliasSeq!(OnlyVirtual!(GetOverloadedMethods!(Targets[i]), Concat!(i + 1))); | |
| } | |
| } | |
| // Remove duplicated functions based on the identifier name and function type covariance | |
| template Uniq(members...) | |
| { | |
| static if (members.length == 0) | |
| alias Uniq = AliasSeq!(); | |
| else | |
| { | |
| alias func = members[0]; | |
| enum name = __traits(identifier, func); | |
| alias type = FunctionTypeOf!func; | |
| template check(size_t i, mem...) | |
| { | |
| static if (i >= mem.length) | |
| enum ptrdiff_t check = -1; | |
| else | |
| { | |
| enum ptrdiff_t check = | |
| __traits(identifier, func) == __traits(identifier, mem[i]) && | |
| !is(DerivedFunctionType!(type, FunctionTypeOf!(mem[i])) == void) | |
| ? i : check!(i + 1, mem); | |
| } | |
| } | |
| enum ptrdiff_t x = 1 + check!(0, members[1 .. $]); | |
| static if (x >= 1) | |
| { | |
| alias typex = DerivedFunctionType!(type, FunctionTypeOf!(members[x])); | |
| alias remain = Uniq!(members[1 .. x], members[x + 1 .. $]); | |
| static if (remain.length >= 1 && remain[0].name == name && | |
| !is(DerivedFunctionType!(typex, remain[0].type) == void)) | |
| { | |
| alias F = DerivedFunctionType!(typex, remain[0].type); | |
| alias Uniq = AliasSeq!(FuncInfo!(name, F), remain[1 .. $]); | |
| } | |
| else | |
| alias Uniq = AliasSeq!(FuncInfo!(name, typex), remain); | |
| } | |
| else | |
| { | |
| alias Uniq = AliasSeq!(FuncInfo!(name, type), Uniq!(members[1 .. $])); | |
| } | |
| } | |
| } | |
| alias TargetMembers = Uniq!(Concat!()); // list of FuncInfo | |
| alias SourceMembers = GetOverloadedMethods!Source; // list of function symbols | |
| // Check whether all of SourceMembers satisfy covariance target in TargetMembers | |
| template hasRequireMethods(size_t i = 0) | |
| { | |
| static if (i >= TargetMembers.length) | |
| enum hasRequireMethods = true; | |
| else | |
| { | |
| enum hasRequireMethods = | |
| findCovariantFunction!(TargetMembers[i], Source, SourceMembers) != -1 && | |
| hasRequireMethods!(i + 1); | |
| } | |
| } | |
| // Internal wrapper class | |
| final class Impl : Structural, Targets | |
| { | |
| private: | |
| Source _wrap_source; | |
| this( inout Source s) inout @safe pure nothrow { _wrap_source = s; } | |
| this(shared inout Source s) shared inout @safe pure nothrow { _wrap_source = s; } | |
| // BUG: making private should work with NVI. | |
| protected final inout(Object) _wrap_getSource() inout @trusted | |
| { | |
| return dynamicCast!(inout Object)(_wrap_source); | |
| } | |
| import std.conv : to; | |
| import core.lifetime : forward; | |
| template generateFun(size_t i) | |
| { | |
| enum name = TargetMembers[i].name; | |
| enum fa = functionAttributes!(TargetMembers[i].type); | |
| static @property stc() | |
| { | |
| string r; | |
| if (fa & FunctionAttribute.property) r ~= "@property "; | |
| if (fa & FunctionAttribute.ref_) r ~= "ref "; | |
| if (fa & FunctionAttribute.pure_) r ~= "pure "; | |
| if (fa & FunctionAttribute.nothrow_) r ~= "nothrow "; | |
| if (fa & FunctionAttribute.trusted) r ~= "@trusted "; | |
| if (fa & FunctionAttribute.safe) r ~= "@safe "; | |
| return r; | |
| } | |
| static @property mod() | |
| { | |
| alias type = AliasSeq!(TargetMembers[i].type)[0]; | |
| string r; | |
| static if (is(type == immutable)) r ~= " immutable"; | |
| else | |
| { | |
| static if (is(type == shared)) r ~= " shared"; | |
| static if (is(type == const)) r ~= " const"; | |
| else static if (is(type == inout)) r ~= " inout"; | |
| //else --> mutable | |
| } | |
| return r; | |
| } | |
| enum n = to!string(i); | |
| static if (fa & FunctionAttribute.property) | |
| { | |
| static if (Parameters!(TargetMembers[i].type).length == 0) | |
| enum fbody = "_wrap_source."~name; | |
| else | |
| enum fbody = "_wrap_source."~name~" = forward!args"; | |
| } | |
| else | |
| { | |
| enum fbody = "_wrap_source."~name~"(forward!args)"; | |
| } | |
| enum generateFun = | |
| "override "~stc~"ReturnType!(TargetMembers["~n~"].type) " | |
| ~ name~"(Parameters!(TargetMembers["~n~"].type) args) "~mod~ | |
| "{ return "~fbody~"; }"; | |
| } | |
| public: | |
| static foreach (i; 0 .. TargetMembers.length) | |
| mixin(generateFun!i); | |
| } | |
| } | |
| } | |
| /// ditto | |
| template wrap(Targets...) | |
| if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets)) | |
| { | |
| import std.meta : staticMap; | |
| alias wrap = .wrap!(staticMap!(Unqual, Targets)); | |
| } | |
| /// ditto | |
| template unwrap(Target) | |
| if (isMutable!Target) | |
| { | |
| // strict downcast | |
| auto unwrap(Source)(inout Source src) @trusted pure nothrow | |
| if (is(Target : Source)) | |
| { | |
| alias T = Select!(is(Source == shared), shared Target, Target); | |
| return dynamicCast!(inout T)(src); | |
| } | |
| // structural downcast | |
| auto unwrap(Source)(inout Source src) @trusted pure nothrow | |
| if (!is(Target : Source)) | |
| { | |
| alias T = Select!(is(Source == shared), shared Target, Target); | |
| Object o = dynamicCast!(Object)(src); // remove qualifier | |
| do | |
| { | |
| if (auto a = dynamicCast!(Structural)(o)) | |
| { | |
| if (auto d = dynamicCast!(inout T)(o = a._wrap_getSource())) | |
| return d; | |
| } | |
| else if (auto d = dynamicCast!(inout T)(o)) | |
| return d; | |
| else | |
| break; | |
| } while (o); | |
| return null; | |
| } | |
| } | |
| /// ditto | |
| template unwrap(Target) | |
| if (!isMutable!Target) | |
| { | |
| alias unwrap = .unwrap!(Unqual!Target); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| interface Quack | |
| { | |
| int quack(); | |
| @property int height(); | |
| } | |
| interface Flyer | |
| { | |
| @property int height(); | |
| } | |
| class Duck : Quack | |
| { | |
| int quack() { return 1; } | |
| @property int height() { return 10; } | |
| } | |
| class Human | |
| { | |
| int quack() { return 2; } | |
| @property int height() { return 20; } | |
| } | |
| Duck d1 = new Duck(); | |
| Human h1 = new Human(); | |
| interface Refleshable | |
| { | |
| int reflesh(); | |
| } | |
| // does not have structural conformance | |
| static assert(!__traits(compiles, d1.wrap!Refleshable)); | |
| static assert(!__traits(compiles, h1.wrap!Refleshable)); | |
| // strict upcast | |
| Quack qd = d1.wrap!Quack; | |
| assert(qd is d1); | |
| assert(qd.quack() == 1); // calls Duck.quack | |
| // strict downcast | |
| Duck d2 = qd.unwrap!Duck; | |
| assert(d2 is d1); | |
| // structural upcast | |
| Quack qh = h1.wrap!Quack; | |
| assert(qh.quack() == 2); // calls Human.quack | |
| // structural downcast | |
| Human h2 = qh.unwrap!Human; | |
| assert(h2 is h1); | |
| // structural upcast (two steps) | |
| Quack qx = h1.wrap!Quack; // Human -> Quack | |
| Flyer fx = qx.wrap!Flyer; // Quack -> Flyer | |
| assert(fx.height == 20); // calls Human.height | |
| // structural downcast (two steps) | |
| Quack qy = fx.unwrap!Quack; // Flyer -> Quack | |
| Human hy = qy.unwrap!Human; // Quack -> Human | |
| assert(hy is h1); | |
| // structural downcast (one step) | |
| Human hz = fx.unwrap!Human; // Flyer -> Human | |
| assert(hz is h1); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.traits : FunctionAttribute, functionAttributes; | |
| interface A { int run(); } | |
| interface B { int stop(); @property int status(); } | |
| class X | |
| { | |
| int run() { return 1; } | |
| int stop() { return 2; } | |
| @property int status() { return 3; } | |
| } | |
| auto x = new X(); | |
| auto ab = x.wrap!(A, B); | |
| A a = ab; | |
| B b = ab; | |
| assert(a.run() == 1); | |
| assert(b.stop() == 2); | |
| assert(b.status == 3); | |
| static assert(functionAttributes!(typeof(ab).status) & FunctionAttribute.property); | |
| } | |
| // Internal class to support dynamic cross-casting | |
| private interface Structural | |
| { | |
| inout(Object) _wrap_getSource() inout @safe pure nothrow; | |
| } | |
| @system unittest | |
| { | |
| class A | |
| { | |
| int draw() { return 1; } | |
| int draw(int v) { return v; } | |
| int draw() const { return 2; } | |
| int draw() shared { return 3; } | |
| int draw() shared const { return 4; } | |
| int draw() immutable { return 5; } | |
| } | |
| interface Drawable | |
| { | |
| int draw(); | |
| int draw() const; | |
| int draw() shared; | |
| int draw() shared const; | |
| int draw() immutable; | |
| } | |
| interface Drawable2 | |
| { | |
| int draw(int v); | |
| } | |
| auto ma = new A(); | |
| auto sa = new shared A(); | |
| auto ia = new immutable A(); | |
| { | |
| Drawable md = ma.wrap!Drawable; | |
| const Drawable cd = ma.wrap!Drawable; | |
| shared Drawable sd = sa.wrap!Drawable; | |
| shared const Drawable scd = sa.wrap!Drawable; | |
| immutable Drawable id = ia.wrap!Drawable; | |
| assert( md.draw() == 1); | |
| assert( cd.draw() == 2); | |
| assert( sd.draw() == 3); | |
| assert(scd.draw() == 4); | |
| assert( id.draw() == 5); | |
| } | |
| { | |
| Drawable2 d = ma.wrap!Drawable2; | |
| static assert(!__traits(compiles, d.draw())); | |
| assert(d.draw(10) == 10); | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=10377 | |
| @system unittest | |
| { | |
| import std.range, std.algorithm; | |
| interface MyInputRange(T) | |
| { | |
| @property T front(); | |
| void popFront(); | |
| @property bool empty(); | |
| } | |
| //auto o = iota(0,10,1).inputRangeObject(); | |
| //pragma(msg, __traits(allMembers, typeof(o))); | |
| auto r = iota(0,10,1).inputRangeObject().wrap!(MyInputRange!int)(); | |
| assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=10536 | |
| @system unittest | |
| { | |
| interface Interface | |
| { | |
| int foo(); | |
| } | |
| class Pluggable | |
| { | |
| int foo() { return 1; } | |
| @disable void opCast(T, this X)(); // ! | |
| } | |
| Interface i = new Pluggable().wrap!Interface; | |
| assert(i.foo() == 1); | |
| } | |
| @system unittest | |
| { | |
| // Enhancement 10538 | |
| interface Interface | |
| { | |
| int foo(); | |
| int bar(int); | |
| } | |
| class Pluggable | |
| { | |
| int opDispatch(string name, A...)(A args) { return 100; } | |
| } | |
| Interface i = wrap!Interface(new Pluggable()); | |
| assert(i.foo() == 100); | |
| assert(i.bar(10) == 100); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=12064 | |
| @system unittest | |
| { | |
| interface I | |
| { | |
| int foo(); | |
| final int nvi1(){return foo();} | |
| } | |
| interface J | |
| { | |
| int bar(); | |
| final int nvi2(){return bar();} | |
| } | |
| class Baz | |
| { | |
| int foo() { return 42;} | |
| int bar() { return 12064;} | |
| } | |
| auto baz = new Baz(); | |
| auto foobar = baz.wrap!(I, J)(); | |
| assert(foobar.nvi1 == 42); | |
| assert(foobar.nvi2 == 12064); | |
| } | |
| // Make a tuple of non-static function symbols | |
| package template GetOverloadedMethods(T) | |
| { | |
| import std.meta : Filter; | |
| alias allMembers = __traits(allMembers, T); | |
| template follows(size_t i = 0) | |
| { | |
| static if (i >= allMembers.length) | |
| { | |
| alias follows = AliasSeq!(); | |
| } | |
| else static if (!__traits(compiles, mixin("T."~allMembers[i]))) | |
| { | |
| alias follows = follows!(i + 1); | |
| } | |
| else | |
| { | |
| enum name = allMembers[i]; | |
| template isMethod(alias f) | |
| { | |
| static if (is(typeof(&f) F == F*) && is(F == function)) | |
| enum isMethod = !__traits(isStaticFunction, f); | |
| else | |
| enum isMethod = false; | |
| } | |
| alias follows = AliasSeq!( | |
| Filter!(isMethod, __traits(getOverloads, T, name)), | |
| follows!(i + 1)); | |
| } | |
| } | |
| alias GetOverloadedMethods = follows!(); | |
| } | |
| // find a function from Fs that has same identifier and covariant type with f | |
| private template findCovariantFunction(alias finfo, Source, Fs...) | |
| { | |
| template check(size_t i = 0) | |
| { | |
| static if (i >= Fs.length) | |
| enum ptrdiff_t check = -1; | |
| else | |
| { | |
| enum ptrdiff_t check = | |
| (finfo.name == __traits(identifier, Fs[i])) && | |
| isCovariantWith!(FunctionTypeOf!(Fs[i]), finfo.type) | |
| ? i : check!(i + 1); | |
| } | |
| } | |
| enum x = check!(); | |
| static if (x == -1 && is(typeof(Source.opDispatch))) | |
| { | |
| alias Params = Parameters!(finfo.type); | |
| enum ptrdiff_t findCovariantFunction = | |
| is(typeof(( Source).init.opDispatch!(finfo.name)(Params.init))) || | |
| is(typeof(( const Source).init.opDispatch!(finfo.name)(Params.init))) || | |
| is(typeof(( immutable Source).init.opDispatch!(finfo.name)(Params.init))) || | |
| is(typeof(( shared Source).init.opDispatch!(finfo.name)(Params.init))) || | |
| is(typeof((shared const Source).init.opDispatch!(finfo.name)(Params.init))) | |
| ? ptrdiff_t.max : -1; | |
| } | |
| else | |
| enum ptrdiff_t findCovariantFunction = x; | |
| } | |
| private enum TypeModifier | |
| { | |
| mutable = 0, // type is mutable | |
| const_ = 1, // type is const | |
| immutable_ = 2, // type is immutable | |
| shared_ = 4, // type is shared | |
| inout_ = 8, // type is wild | |
| } | |
| private template TypeMod(T) | |
| { | |
| static if (is(T == immutable)) | |
| { | |
| enum mod1 = TypeModifier.immutable_; | |
| enum mod2 = 0; | |
| } | |
| else | |
| { | |
| enum mod1 = is(T == shared) ? TypeModifier.shared_ : 0; | |
| static if (is(T == const)) | |
| enum mod2 = TypeModifier.const_; | |
| else static if (is(T == inout)) | |
| enum mod2 = TypeModifier.inout_; | |
| else | |
| enum mod2 = TypeModifier.mutable; | |
| } | |
| enum TypeMod = cast(TypeModifier)(mod1 | mod2); | |
| } | |
| @system unittest | |
| { | |
| template UnittestFuncInfo(alias f) | |
| { | |
| enum name = __traits(identifier, f); | |
| alias type = FunctionTypeOf!f; | |
| } | |
| class A | |
| { | |
| int draw() { return 1; } | |
| @property int value() { return 2; } | |
| final int run() { return 3; } | |
| } | |
| alias methods = GetOverloadedMethods!A; | |
| alias int F1(); | |
| alias @property int F2(); | |
| alias string F3(); | |
| alias nothrow @trusted uint F4(); | |
| alias int F5(Object); | |
| alias bool F6(Object); | |
| static assert(methods.length == 3 + 4); | |
| static assert(__traits(identifier, methods[0]) == "draw" && is(typeof(&methods[0]) == F1*)); | |
| static assert(__traits(identifier, methods[1]) == "value" && is(typeof(&methods[1]) == F2*)); | |
| static assert(__traits(identifier, methods[2]) == "run" && is(typeof(&methods[2]) == F1*)); | |
| int draw(); | |
| @property int value(); | |
| void opEquals(); | |
| int nomatch(); | |
| static assert(findCovariantFunction!(UnittestFuncInfo!draw, A, methods) == 0); | |
| static assert(findCovariantFunction!(UnittestFuncInfo!value, A, methods) == 1); | |
| static assert(findCovariantFunction!(UnittestFuncInfo!opEquals, A, methods) == -1); | |
| static assert(findCovariantFunction!(UnittestFuncInfo!nomatch, A, methods) == -1); | |
| // considering opDispatch | |
| class B | |
| { | |
| void opDispatch(string name, A...)(A) {} | |
| } | |
| alias methodsB = GetOverloadedMethods!B; | |
| static assert(findCovariantFunction!(UnittestFuncInfo!draw, B, methodsB) == ptrdiff_t.max); | |
| static assert(findCovariantFunction!(UnittestFuncInfo!value, B, methodsB) == ptrdiff_t.max); | |
| static assert(findCovariantFunction!(UnittestFuncInfo!opEquals, B, methodsB) == ptrdiff_t.max); | |
| static assert(findCovariantFunction!(UnittestFuncInfo!nomatch, B, methodsB) == ptrdiff_t.max); | |
| } | |
| package template DerivedFunctionType(T...) | |
| { | |
| static if (!T.length) | |
| { | |
| alias DerivedFunctionType = void; | |
| } | |
| else static if (T.length == 1) | |
| { | |
| static if (is(T[0] == function)) | |
| { | |
| alias DerivedFunctionType = T[0]; | |
| } | |
| else | |
| { | |
| alias DerivedFunctionType = void; | |
| } | |
| } | |
| else static if (is(T[0] P0 == function) && is(T[1] P1 == function)) | |
| { | |
| alias FA = FunctionAttribute; | |
| alias F0 = T[0], R0 = ReturnType!F0, PSTC0 = ParameterStorageClassTuple!F0; | |
| alias F1 = T[1], R1 = ReturnType!F1, PSTC1 = ParameterStorageClassTuple!F1; | |
| enum FA0 = functionAttributes!F0; | |
| enum FA1 = functionAttributes!F1; | |
| template CheckParams(size_t i = 0) | |
| { | |
| static if (i >= P0.length) | |
| enum CheckParams = true; | |
| else | |
| { | |
| enum CheckParams = (is(P0[i] == P1[i]) && PSTC0[i] == PSTC1[i]) && | |
| CheckParams!(i + 1); | |
| } | |
| } | |
| static if (R0.sizeof == R1.sizeof && !is(CommonType!(R0, R1) == void) && | |
| P0.length == P1.length && CheckParams!() && TypeMod!F0 == TypeMod!F1 && | |
| variadicFunctionStyle!F0 == variadicFunctionStyle!F1 && | |
| functionLinkage!F0 == functionLinkage!F1 && | |
| ((FA0 ^ FA1) & (FA.ref_ | FA.property)) == 0) | |
| { | |
| alias R = Select!(is(R0 : R1), R0, R1); | |
| alias FX = FunctionTypeOf!(R function(P0)); | |
| // @system is default | |
| alias FY = SetFunctionAttributes!(FX, functionLinkage!F0, (FA0 | FA1) & ~FA.system); | |
| alias DerivedFunctionType = DerivedFunctionType!(FY, T[2 .. $]); | |
| } | |
| else | |
| alias DerivedFunctionType = void; | |
| } | |
| else | |
| alias DerivedFunctionType = void; | |
| } | |
| @safe unittest | |
| { | |
| // attribute covariance | |
| alias int F1(); | |
| static assert(is(DerivedFunctionType!(F1, F1) == F1)); | |
| alias int F2() pure nothrow; | |
| static assert(is(DerivedFunctionType!(F1, F2) == F2)); | |
| alias int F3() @safe; | |
| alias int F23() @safe pure nothrow; | |
| static assert(is(DerivedFunctionType!(F2, F3) == F23)); | |
| // return type covariance | |
| alias long F4(); | |
| static assert(is(DerivedFunctionType!(F1, F4) == void)); | |
| class C {} | |
| class D : C {} | |
| alias C F5(); | |
| alias D F6(); | |
| static assert(is(DerivedFunctionType!(F5, F6) == F6)); | |
| alias typeof(null) F7(); | |
| alias int[] F8(); | |
| alias int* F9(); | |
| static assert(is(DerivedFunctionType!(F5, F7) == F7)); | |
| static assert(is(DerivedFunctionType!(F7, F8) == void)); | |
| static assert(is(DerivedFunctionType!(F7, F9) == F7)); | |
| // variadic type equality | |
| alias int F10(int); | |
| alias int F11(int...); | |
| alias int F12(int, ...); | |
| static assert(is(DerivedFunctionType!(F10, F11) == void)); | |
| static assert(is(DerivedFunctionType!(F10, F12) == void)); | |
| static assert(is(DerivedFunctionType!(F11, F12) == void)); | |
| // linkage equality | |
| alias extern(C) int F13(int); | |
| alias extern(D) int F14(int); | |
| alias extern(Windows) int F15(int); | |
| static assert(is(DerivedFunctionType!(F13, F14) == void)); | |
| static assert(is(DerivedFunctionType!(F13, F15) == void)); | |
| static assert(is(DerivedFunctionType!(F14, F15) == void)); | |
| // ref & @property equality | |
| alias int F16(int); | |
| alias ref int F17(int); | |
| alias @property int F18(int); | |
| static assert(is(DerivedFunctionType!(F16, F17) == void)); | |
| static assert(is(DerivedFunctionType!(F16, F18) == void)); | |
| static assert(is(DerivedFunctionType!(F17, F18) == void)); | |
| } | |
| package template Bind(alias Template, args1...) | |
| { | |
| alias Bind(args2...) = Template!(args1, args2); | |
| } | |
| /** | |
| Options regarding auto-initialization of a `SafeRefCounted` object (see | |
| the definition of `SafeRefCounted` below). | |
| */ | |
| enum RefCountedAutoInitialize | |
| { | |
| /// Do not auto-initialize the object | |
| no, | |
| /// Auto-initialize the object | |
| yes, | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import core.exception : AssertError; | |
| import std.exception : assertThrown; | |
| struct Foo | |
| { | |
| int a = 42; | |
| } | |
| SafeRefCounted!(Foo, RefCountedAutoInitialize.yes) rcAuto; | |
| SafeRefCounted!(Foo, RefCountedAutoInitialize.no) rcNoAuto; | |
| assert(rcAuto.refCountedPayload.a == 42); | |
| assertThrown!AssertError(rcNoAuto.refCountedPayload); | |
| rcNoAuto.refCountedStore.ensureInitialized; | |
| assert(rcNoAuto.refCountedPayload.a == 42); | |
| } | |
| // Same the above but for old RefCounted and not documented | |
| @system unittest | |
| { | |
| import core.exception : AssertError; | |
| import std.exception : assertThrown; | |
| struct Foo | |
| { | |
| int a = 42; | |
| } | |
| RefCounted!(Foo, RefCountedAutoInitialize.yes) rcAuto; | |
| RefCounted!(Foo, RefCountedAutoInitialize.no) rcNoAuto; | |
| assert(rcAuto.refCountedPayload.a == 42); | |
| assertThrown!AssertError(rcNoAuto.refCountedPayload); | |
| rcNoAuto.refCountedStore.ensureInitialized; | |
| assert(rcNoAuto.refCountedPayload.a == 42); | |
| } | |
| /** | |
| Defines a reference-counted object containing a `T` value as | |
| payload. | |
| An instance of `SafeRefCounted` is a reference to a structure, | |
| which is referred to as the $(I store), or $(I storage implementation | |
| struct) in this documentation. The store contains a reference count | |
| and the `T` payload. `SafeRefCounted` uses `malloc` to allocate | |
| the store. As instances of `SafeRefCounted` are copied or go out of | |
| scope, they will automatically increment or decrement the reference | |
| count. When the reference count goes down to zero, `SafeRefCounted` | |
| will call `destroy` against the payload and call `free` to | |
| deallocate the store. If the `T` payload contains any references | |
| to GC-allocated memory, then `SafeRefCounted` will add it to the GC memory | |
| that is scanned for pointers, and remove it from GC scanning before | |
| `free` is called on the store. | |
| One important consequence of `destroy` is that it will call the | |
| destructor of the `T` payload. GC-managed references are not | |
| guaranteed to be valid during a destructor call, but other members of | |
| `T`, such as file handles or pointers to `malloc` memory, will | |
| still be valid during the destructor call. This allows the `T` to | |
| deallocate or clean up any non-GC resources immediately after the | |
| reference count has reached zero. | |
| Without -preview=dip1000, `SafeRefCounted` is unsafe and should be | |
| used with care. No references to the payload should be escaped outside | |
| the `SafeRefCounted` object. | |
| With -preview=dip1000, `SafeRefCounted` is safe if it's payload is accessed only | |
| with the $(LREF borrow) function. Scope semantics can also prevent accidental | |
| escaping of `refCountedPayload`, but it's still up to the user to not destroy | |
| the last counted reference while the payload is in use. Due to that, | |
| `refCountedPayload` remains accessible only in `@system` code. | |
| The `autoInit` option makes the object ensure the store is | |
| automatically initialized. Leaving $(D autoInit == | |
| RefCountedAutoInitialize.yes) (the default option) is convenient but | |
| has the cost of a test whenever the payload is accessed. If $(D | |
| autoInit == RefCountedAutoInitialize.no), user code must call either | |
| `refCountedStore.isInitialized` or `refCountedStore.ensureInitialized` | |
| before attempting to access the payload. Not doing so results in null | |
| pointer dereference. | |
| If `T.this()` is annotated with `@disable` then `autoInit` must be | |
| `RefCountedAutoInitialize.no` in order to compile. | |
| See_Also: | |
| $(LREF RefCounted) | |
| */ | |
| struct SafeRefCounted(T, RefCountedAutoInitialize autoInit = | |
| RefCountedAutoInitialize.yes) | |
| if (!is(T == class) && !(is(T == interface))) | |
| { | |
| version (D_BetterC) | |
| { | |
| private enum enableGCScan = false; | |
| } | |
| else | |
| { | |
| private enum enableGCScan = hasIndirections!T; | |
| } | |
| extern(C) private pure nothrow @nogc static | |
| { | |
| pragma(mangle, "free") void pureFree( void *ptr ); | |
| static if (enableGCScan) | |
| import core.memory : GC; | |
| } | |
| pragma(inline, true) private void checkInit()() | |
| if (autoInit == RefCountedAutoInitialize.yes) | |
| { | |
| _refCounted.ensureInitialized(); | |
| } | |
| pragma(inline, true) private void checkInit()() inout | |
| if (autoInit == RefCountedAutoInitialize.no) | |
| { | |
| assert(_refCounted.isInitialized, | |
| "Attempted to use an uninitialized payload."); | |
| } | |
| /// `SafeRefCounted` storage implementation. | |
| struct RefCountedStore | |
| { | |
| private struct Impl | |
| { | |
| T _payload; | |
| size_t _count; | |
| } | |
| private Impl* _store; | |
| private void initialize(A...)(auto ref A args) | |
| { | |
| import core.lifetime : emplace, forward; | |
| allocateStore(); | |
| version (D_Exceptions) scope(failure) () @trusted { deallocateStore(); }(); | |
| emplace(&_store._payload, forward!args); | |
| _store._count = 1; | |
| } | |
| private void move(ref T source) nothrow pure | |
| { | |
| import std.algorithm.mutation : moveEmplace; | |
| allocateStore(); | |
| () @trusted { moveEmplace(source, _store._payload); }(); | |
| _store._count = 1; | |
| } | |
| // 'nothrow': can only generate an Error | |
| private void allocateStore() nothrow pure | |
| { | |
| static if (enableGCScan) | |
| { | |
| import std.internal.memory : enforceCalloc; | |
| auto ptr = enforceCalloc(1, Impl.sizeof); | |
| _store = () @trusted { return cast(Impl*) ptr; }(); | |
| () @trusted { GC.addRange(&_store._payload, T.sizeof); }(); | |
| } | |
| else | |
| { | |
| import std.internal.memory : enforceMalloc; | |
| auto ptr = enforceMalloc(Impl.sizeof); | |
| _store = () @trusted { return cast(Impl*) ptr; }(); | |
| } | |
| } | |
| private void deallocateStore() nothrow pure | |
| { | |
| static if (enableGCScan) | |
| { | |
| GC.removeRange(&this._store._payload); | |
| } | |
| pureFree(_store); | |
| _store = null; | |
| } | |
| /** | |
| Returns `true` if and only if the underlying store has been | |
| allocated and initialized. | |
| */ | |
| @property nothrow @safe pure @nogc | |
| bool isInitialized() const | |
| { | |
| return _store !is null; | |
| } | |
| /** | |
| Returns underlying reference count if it is allocated and initialized | |
| (a positive integer), and `0` otherwise. | |
| */ | |
| @property nothrow @safe pure @nogc | |
| size_t refCount() const | |
| { | |
| return isInitialized ? _store._count : 0; | |
| } | |
| /** | |
| Makes sure the payload was properly initialized. Such a | |
| call is typically inserted before using the payload. | |
| This function is unavailable if `T.this()` is annotated with | |
| `@disable`. | |
| */ | |
| @safe pure nothrow | |
| void ensureInitialized()() | |
| { | |
| // By checking for `@disable this()` and failing early we can | |
| // produce a clearer error message. | |
| static assert(__traits(compiles, { static T t; }), | |
| "Cannot automatically initialize `" ~ fullyQualifiedName!T ~ | |
| "` because `" ~ fullyQualifiedName!T ~ | |
| ".this()` is annotated with `@disable`."); | |
| if (!isInitialized) initialize(); | |
| } | |
| } | |
| RefCountedStore _refCounted; | |
| /// Returns storage implementation struct. | |
| @property nothrow @safe | |
| ref inout(RefCountedStore) refCountedStore() inout | |
| { | |
| return _refCounted; | |
| } | |
| /** | |
| Constructor that initializes the payload. | |
| Postcondition: `refCountedStore.isInitialized` | |
| */ | |
| this(A...)(auto ref A args) | |
| if (A.length > 0) | |
| out | |
| { | |
| assert(refCountedStore.isInitialized); | |
| } | |
| do | |
| { | |
| import core.lifetime : forward; | |
| _refCounted.initialize(forward!args); | |
| } | |
| /// Ditto | |
| this(return scope T val) | |
| { | |
| _refCounted.move(val); | |
| } | |
| /** | |
| Constructor that tracks the reference count appropriately. If $(D | |
| !refCountedStore.isInitialized), does nothing. | |
| */ | |
| this(this) @safe pure nothrow @nogc | |
| { | |
| if (!_refCounted.isInitialized) return; | |
| ++_refCounted._store._count; | |
| } | |
| /** | |
| Destructor that tracks the reference count appropriately. If $(D | |
| !refCountedStore.isInitialized), does nothing. When the reference count goes | |
| down to zero, calls `destroy` agaist the payload and calls `free` | |
| to deallocate the corresponding resource. | |
| */ | |
| ~this() | |
| { | |
| import std.traits : dip1000Enabled; | |
| // This prevents the same reference from decrementing the count twice. | |
| scope(exit) _refCounted = _refCounted.init; | |
| if (!_refCounted.isInitialized) return; | |
| assert(_refCounted._store._count > 0); | |
| if (--_refCounted._store._count) return; | |
| // Done, destroy and deallocate | |
| .destroy(_refCounted._store._payload); | |
| static if (dip1000Enabled) | |
| { | |
| () @trusted { _refCounted.deallocateStore(); }(); | |
| } | |
| else _refCounted.deallocateStore(); | |
| } | |
| /** | |
| Assignment operators. | |
| Note: You may not assign a new payload to an uninitialized SafeRefCounted, if | |
| auto initialization is off. Assigning another counted reference is still okay. | |
| */ | |
| void opAssign(typeof(this) rhs) | |
| { | |
| import std.algorithm.mutation : swap; | |
| swap(_refCounted._store, rhs._refCounted._store); | |
| } | |
| /// Ditto | |
| void opAssign(T rhs) | |
| { | |
| import std.algorithm.mutation : move; | |
| checkInit(); | |
| move(rhs, _refCounted._store._payload); | |
| } | |
| //version to have a single properly ddoc'ed function (w/ correct sig) | |
| version (StdDdoc) | |
| { | |
| /** | |
| Returns a reference to the payload. If (autoInit == | |
| RefCountedAutoInitialize.yes), calls $(D | |
| refCountedStore.ensureInitialized). Otherwise, just issues $(D | |
| assert(refCountedStore.isInitialized)). Used with $(D alias | |
| refCountedPayload this;), so callers can just use the `SafeRefCounted` | |
| object as a `T`. | |
| $(BLUE The first overload exists only if $(D autoInit == RefCountedAutoInitialize.yes).) | |
| So if $(D autoInit == RefCountedAutoInitialize.no) | |
| or called for a constant or immutable object, then | |
| `refCountedPayload` will also be qualified as nothrow | |
| (but will still assert if not initialized). | |
| */ | |
| @property @system | |
| ref T refCountedPayload() return; | |
| /// ditto | |
| @property nothrow @system pure @nogc | |
| ref inout(T) refCountedPayload() inout return; | |
| } | |
| else | |
| { | |
| static if (autoInit == RefCountedAutoInitialize.yes) | |
| { | |
| //Can't use inout here because of potential mutation | |
| @property @system | |
| ref T refCountedPayload() return | |
| { | |
| checkInit(); | |
| return _refCounted._store._payload; | |
| } | |
| } | |
| else | |
| { | |
| @property nothrow @system pure @nogc | |
| ref inout(T) refCountedPayload() inout return | |
| { | |
| checkInit(); | |
| return _refCounted._store._payload; | |
| } | |
| } | |
| } | |
| /** | |
| Returns a reference to the payload. If (autoInit == | |
| RefCountedAutoInitialize.yes), calls $(D | |
| refCountedStore.ensureInitialized). Otherwise, just issues $(D | |
| assert(refCountedStore.isInitialized)). | |
| */ | |
| alias refCountedPayload this; | |
| static if (is(T == struct) && !is(typeof((ref T t) => t.toString()))) | |
| { | |
| string toString(this This)() | |
| { | |
| import std.conv : to; | |
| static if (autoInit) | |
| return to!string(refCountedPayload); | |
| else | |
| { | |
| if (!_refCounted.isInitialized) | |
| return This.stringof ~ "(RefCountedStore(null))"; | |
| else | |
| return to!string(_refCounted._store._payload); | |
| } | |
| } | |
| } | |
| } | |
| /// | |
| @betterC pure @system nothrow @nogc unittest | |
| { | |
| // A pair of an `int` and a `size_t` - the latter being the | |
| // reference count - will be dynamically allocated | |
| auto rc1 = SafeRefCounted!int(5); | |
| assert(rc1 == 5); | |
| // No more allocation, add just one extra reference count | |
| auto rc2 = rc1; | |
| // Reference semantics | |
| rc2 = 42; | |
| assert(rc1 == 42); | |
| // the pair will be freed when rc1 and rc2 go out of scope | |
| } | |
| // This test can't be betterC because the test extractor won't see the private | |
| // `initialize` method accessed here | |
| pure @safe nothrow @nogc unittest | |
| { | |
| auto rc1 = SafeRefCounted!(int, RefCountedAutoInitialize.no)(5); | |
| rc1._refCounted.initialize(); | |
| } | |
| pure @system unittest | |
| { | |
| foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted)) | |
| { | |
| MyRefCounted!int* p; | |
| auto rc1 = MyRefCounted!int(5); | |
| p = &rc1; | |
| assert(rc1 == 5); | |
| assert(rc1._refCounted._store._count == 1); | |
| { | |
| auto rc2 = rc1; | |
| assert(rc1._refCounted._store._count == 2); | |
| // Reference semantics | |
| rc2 = 42; | |
| assert(rc1 == 42); | |
| rc2 = rc2; | |
| assert(rc2._refCounted._store._count == 2); | |
| rc1 = rc2; | |
| assert(rc1._refCounted._store._count == 2); | |
| } | |
| // Artificially end scope of rc1 by calling ~this() explicitly | |
| rc1.__xdtor(); | |
| assert(p._refCounted._store == null); | |
| // [Safe]RefCounted as a member | |
| struct A | |
| { | |
| MyRefCounted!int x; | |
| this(int y) | |
| { | |
| x._refCounted.initialize(y); | |
| } | |
| A copy() | |
| { | |
| auto another = this; | |
| return another; | |
| } | |
| } | |
| auto a = A(4); | |
| auto b = a.copy(); | |
| assert(a.x._refCounted._store._count == 2, | |
| "https://issues.dlang.org/show_bug.cgi?id=4356 still unfixed"); | |
| } | |
| } | |
| @betterC pure @safe nothrow @nogc unittest | |
| { | |
| import std.algorithm.mutation : swap; | |
| SafeRefCounted!int p1, p2; | |
| swap(p1, p2); | |
| } | |
| // Same as above but for old RefCounted and not @safe | |
| @betterC pure @system nothrow @nogc unittest | |
| { | |
| import std.algorithm.mutation : swap; | |
| RefCounted!int p1, p2; | |
| swap(p1, p2); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=6606 | |
| @betterC @safe pure nothrow @nogc unittest | |
| { | |
| union U { | |
| size_t i; | |
| void* p; | |
| } | |
| struct S { | |
| U u; | |
| } | |
| alias SRC = SafeRefCounted!S; | |
| } | |
| // Same as above but for old RefCounted and not @safe | |
| @betterC @system pure nothrow @nogc unittest | |
| { | |
| union U { | |
| size_t i; | |
| void* p; | |
| } | |
| struct S { | |
| U u; | |
| } | |
| alias SRC = RefCounted!S; | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=6436 | |
| @betterC @system pure unittest | |
| { | |
| import std.meta : AliasSeq; | |
| struct S | |
| { | |
| this(int rval) { assert(rval == 1); } | |
| this(ref int lval) { assert(lval == 3); ++lval; } | |
| } | |
| foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted)) | |
| { | |
| auto s1 = MyRefCounted!S(1); | |
| int lval = 3; | |
| auto s2 = MyRefCounted!S(lval); | |
| assert(lval == 4); | |
| } | |
| } | |
| // gc_addRange coverage | |
| @betterC @safe pure unittest | |
| { | |
| struct S { int* p; } | |
| auto s = SafeRefCounted!S(null); | |
| } | |
| // Same as above but for old RefCounted and not @safe | |
| @betterC @system pure unittest | |
| { | |
| struct S { int* p; } | |
| auto s = RefCounted!S(null); | |
| } | |
| @betterC @system pure nothrow @nogc unittest | |
| { | |
| import std.meta : AliasSeq; | |
| foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted)) | |
| { | |
| MyRefCounted!int a; | |
| a = 5; //This should not assert | |
| assert(a == 5); | |
| MyRefCounted!int b; | |
| b = a; //This should not assert either | |
| assert(b == 5); | |
| MyRefCounted!(int*) c; | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=21638 | |
| @betterC @system pure nothrow @nogc unittest | |
| { | |
| import std.meta : AliasSeq; | |
| static struct NoDefaultCtor | |
| { | |
| @disable this(); | |
| this(int x) @nogc nothrow pure { this.x = x; } | |
| int x; | |
| } | |
| foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted)) | |
| { | |
| auto rc = MyRefCounted!(NoDefaultCtor, RefCountedAutoInitialize.no)(5); | |
| assert(rc.x == 5); | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=20502 | |
| @system unittest | |
| { | |
| alias Types = AliasSeq!(SafeRefCounted, RefCounted); | |
| alias funcs = AliasSeq!(safeRefCounted, refCounted); | |
| static foreach (aliasI; 0 .. 2) | |
| {{ | |
| alias MyRefCounted = Types[aliasI]; | |
| alias myRefCounted = funcs[aliasI]; | |
| import std.conv : to; | |
| // Check that string conversion is transparent for refcounted | |
| // structs that do not have either toString or alias this. | |
| static struct A { Object a; } | |
| auto a = A(new Object()); | |
| auto r = myRefCounted(a); | |
| assert(to!string(r) == to!string(a)); | |
| assert(to!string(cast(const) r) == to!string(cast(const) a)); | |
| // Check that string conversion is still transparent for refcounted | |
| // structs that have alias this. | |
| static struct B { int b; alias b this; } | |
| static struct C { B b; alias b this; } | |
| assert(to!string(myRefCounted(C(B(123)))) == to!string(C(B(123)))); | |
| // https://issues.dlang.org/show_bug.cgi?id=22093 | |
| // Check that uninitialized refcounted structs that previously could be | |
| // converted to strings still can be. | |
| alias R = typeof(r); | |
| R r2; | |
| cast(void) (((const ref R a) => to!string(a))(r2)); | |
| cast(void) to!string(MyRefCounted!(A, RefCountedAutoInitialize.no).init); | |
| }} | |
| } | |
| // We tried to make `refCountedPayload` `@safe` in | |
| // https://github.com/dlang/phobos/pull/8368 . It proved impossible, but it may | |
| // be possible in the future. This test checks for false `@safe` issues we | |
| // encountered. | |
| @safe unittest | |
| { | |
| struct Container | |
| { | |
| int[] data; | |
| } | |
| int[] getArr1 (scope Container local) | |
| { | |
| // allowed because the argument is inferred as return scope. | |
| return local.data; | |
| } | |
| int[] getArr2 (scope Container local) | |
| { | |
| SafeRefCounted!Container rc = local; | |
| // Escapes a reference to expired reference counted struct | |
| // don't do this! | |
| return rc.refCountedPayload().data; | |
| } | |
| int destroyFirstAndUseLater() | |
| { | |
| auto rc = SafeRefCounted!int(123); | |
| int* ptr = &rc.refCountedPayload(); | |
| destroy(rc); | |
| return *ptr; | |
| } | |
| // This is here mainly to test that safety gets inferred correctly for the | |
| // next tests | |
| static assert(isSafe!getArr1); | |
| // https://github.com/dlang/phobos/pull/8101#issuecomment-843017282 | |
| // This got apparently fixed automatically by compiler updates. | |
| static assert(!isSafe!getArr2); | |
| // As of writing, this is the issue that is still preventing payload access | |
| // from being `@safe` | |
| static assert(!isSafe!destroyFirstAndUseLater); | |
| } | |
| /** | |
| Borrows the payload of $(LREF SafeRefCounted) for use in `fun`. Inferred as `@safe` | |
| if `fun` is `@safe` and does not escape a reference to the payload. | |
| The reference count will be incremented for the duration of the operation, | |
| so destroying the last reference will not leave dangling references in | |
| `fun`. | |
| Params: | |
| fun = A callable accepting the payload either by value or by reference. | |
| refCount = The counted reference to the payload. | |
| Returns: | |
| The return value of `fun`, if any. `ref` in the return value will be | |
| forwarded. | |
| Issues: | |
| For yet unknown reason, code that uses this function with UFCS syntax | |
| will not be inferred as `@safe`. It will still compile if the code is | |
| explicitly marked `@safe` and nothing in `fun` prevents that. | |
| */ | |
| template borrow(alias fun) | |
| { | |
| import std.functional : unaryFun; | |
| auto ref borrow(RC)(RC refCount) | |
| if | |
| ( | |
| isInstanceOf!(SafeRefCounted, RC) | |
| && is(typeof(unaryFun!fun(refCount.refCountedPayload))) | |
| ) | |
| { | |
| refCount.checkInit(); | |
| // If `fun` escapes a reference to the payload, it will be inferred | |
| // as unsafe due to the scope storage class here. | |
| scope plPtr = &refCount._refCounted._store._payload; | |
| return unaryFun!fun(*plPtr); | |
| // We destroy our copy of the reference here, automatically destroying | |
| // the payload if `fun` destroyed the last reference outside. | |
| } | |
| } | |
| /// This example can be marked `@safe` with `-preview=dip1000`. | |
| @safe pure nothrow unittest | |
| { | |
| auto rcInt = safeRefCounted(5); | |
| assert(rcInt.borrow!(theInt => theInt) == 5); | |
| auto sameInt = rcInt; | |
| assert(sameInt.borrow!"a" == 5); | |
| // using `ref` in the function | |
| auto arr = [0, 1, 2, 3, 4, 5, 6]; | |
| sameInt.borrow!(ref (x) => arr[x]) = 10; | |
| assert(arr == [0, 1, 2, 3, 4, 10, 6]); | |
| // modifying the payload via an alias | |
| sameInt.borrow!"a*=2"; | |
| assert(rcInt.borrow!"a" == 10); | |
| } | |
| // Some memory safety penetration testing. | |
| @system unittest | |
| { | |
| int* globalPtr; | |
| int torpedoesFired = 0; | |
| struct Destroyer { ~this() @safe { torpedoesFired++; } } | |
| alias RcInt = typeof(safeRefCounted(0)); | |
| auto standardUsage(RcInt arg) | |
| { | |
| return borrow!((ref x) => x)(arg); | |
| } | |
| ref harmlessRefReturn(RcInt arg) | |
| { | |
| return borrow!(ref (ref x) => *globalPtr = x)(arg); | |
| } | |
| ref problematicRefReturn(RcInt arg) | |
| { | |
| return borrow!(ref (ref x) => x)(arg); | |
| } | |
| auto sideChannelEscape(RcInt arg) | |
| { | |
| return borrow!((ref x) | |
| { | |
| globalPtr = &x; | |
| return x; | |
| })(arg); | |
| } | |
| auto destroyDuringApply() | |
| { | |
| auto rc = safeRefCounted(Destroyer()); | |
| return borrow!((ref x) | |
| { | |
| // Destroys the last reference to the payload, decrementing it's | |
| // reference count. | |
| rc.__dtor(); | |
| // Destroy again! rc should be set to it's init value so that this | |
| // won't decrement the reference count of the original payload. | |
| rc.__dtor(); | |
| // The internal reference count increment done for duration of | |
| // `apply` should make sure that the payload destructor is not yet | |
| // run, and this value thus not incremented. | |
| return torpedoesFired; | |
| })(rc); | |
| } | |
| // First, let's verify the dangerous functions really do what they are | |
| // supposed to do. | |
| auto testRc = safeRefCounted(42); | |
| assert(sideChannelEscape(testRc) == 42); | |
| assert(&problematicRefReturn(testRc) == globalPtr); | |
| // Now, are the @safe attributes inferred correctly? | |
| assert(isSafe!standardUsage); | |
| assert(isSafe!harmlessRefReturn); | |
| assert(!isSafe!problematicRefReturn); | |
| assert(!isSafe!sideChannelEscape); | |
| assert(isSafe!destroyDuringApply); | |
| // Finally, we test protection against early destruction during `apply`. | |
| auto torpedoesUpToReturn = destroyDuringApply(); | |
| assert(torpedoesFired == torpedoesUpToReturn + 1); | |
| } | |
| /** | |
| * Initializes a `SafeRefCounted` with `val`. The template parameter | |
| * `T` of `SafeRefCounted` is inferred from `val`. | |
| * This function can be used to move non-copyable values to the heap. | |
| * It also disables the `autoInit` option of `SafeRefCounted`. | |
| * | |
| * Params: | |
| * val = The value to be reference counted | |
| * Returns: | |
| * An initialized `SafeRefCounted` containing `val`. | |
| * See_Also: | |
| * $(LREF refCounted) | |
| * $(HTTP en.cppreference.com/w/cpp/memory/shared_ptr/make_shared, C++'s make_shared) | |
| */ | |
| SafeRefCounted!(T, RefCountedAutoInitialize.no) safeRefCounted(T)(T val) | |
| { | |
| typeof(return) res; | |
| res._refCounted.move(val); | |
| return res; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| static struct File | |
| { | |
| static size_t nDestroyed; | |
| string name; | |
| @disable this(this); // not copyable | |
| ~this() { name = null; ++nDestroyed; } | |
| } | |
| auto file = File("name"); | |
| assert(file.name == "name"); | |
| // file cannot be copied and has unique ownership | |
| static assert(!__traits(compiles, {auto file2 = file;})); | |
| assert(File.nDestroyed == 0); | |
| // make the file ref counted to share ownership | |
| // Note: | |
| // We write a compound statement (brace-delimited scope) in which all `SafeRefCounted!File` handles are created and deleted. | |
| // This allows us to see (after the scope) what happens after all handles have been destroyed. | |
| { | |
| // We move the content of `file` to a separate (and heap-allocated) `File` object, | |
| // managed-and-accessed via one-or-multiple (initially: one) `SafeRefCounted!File` objects ("handles"). | |
| // This "moving": | |
| // (1) invokes `file`'s destructor (=> `File.nDestroyed` is incremented from 0 to 1 and `file.name` becomes `null`); | |
| // (2) overwrites `file` with `File.init` (=> `file.name` becomes `null`). | |
| // It appears that writing `name = null;` in the destructor is redundant, | |
| // but please note that (2) is only performed if `File` defines a destructor (or post-blit operator), | |
| // and in the absence of the `nDestroyed` instrumentation there would have been no reason to define a destructor. | |
| import std.algorithm.mutation : move; | |
| auto rcFile = safeRefCounted(move(file)); | |
| assert(rcFile.name == "name"); | |
| assert(File.nDestroyed == 1); | |
| assert(file.name == null); | |
| // We create another `SafeRefCounted!File` handle to the same separate `File` object. | |
| // While any of the handles is still alive, the `File` object is kept alive (=> `File.nDestroyed` is not modified). | |
| auto rcFile2 = rcFile; | |
| assert(rcFile.refCountedStore.refCount == 2); | |
| assert(File.nDestroyed == 1); | |
| } | |
| // The separate `File` object is deleted when the last `SafeRefCounted!File` handle is destroyed | |
| // (i.e. at the closing brace of the compound statement above, which destroys both handles: `rcFile` and `rcFile2`) | |
| // (=> `File.nDestroyed` is incremented again, from 1 to 2): | |
| assert(File.nDestroyed == 2); | |
| } | |
| /** | |
| Creates a proxy for the value `a` that will forward all operations | |
| while disabling implicit conversions. The aliased item `a` must be | |
| an $(B lvalue). This is useful for creating a new type from the | |
| "base" type (though this is $(B not) a subtype-supertype | |
| relationship; the new type is not related to the old type in any way, | |
| by design). | |
| The new type supports all operations that the underlying type does, | |
| including all operators such as `+`, `--`, `<`, `[]`, etc. | |
| Params: | |
| a = The value to act as a proxy for all operations. It must | |
| be an lvalue. | |
| */ | |
| mixin template Proxy(alias a) | |
| { | |
| private alias ValueType = typeof({ return a; }()); | |
| /* Determine if 'T.a' can referenced via a const(T). | |
| * Use T* as the parameter because 'scope' inference needs a fully | |
| * analyzed T, which doesn't work when accessibleFrom() is used in a | |
| * 'static if' in the definition of Proxy or T. | |
| */ | |
| private enum bool accessibleFrom(T) = | |
| is(typeof((T* self){ cast(void) mixin("(*self)."~__traits(identifier, a)); })); | |
| static if (is(typeof(this) == class)) | |
| { | |
| override bool opEquals(Object o) | |
| { | |
| if (auto b = cast(typeof(this))o) | |
| { | |
| return a == mixin("b."~__traits(identifier, a)); | |
| } | |
| return false; | |
| } | |
| bool opEquals(T)(T b) | |
| if (is(ValueType : T) || is(typeof(a.opEquals(b))) || is(typeof(b.opEquals(a)))) | |
| { | |
| static if (is(typeof(a.opEquals(b)))) | |
| return a.opEquals(b); | |
| else static if (is(typeof(b.opEquals(a)))) | |
| return b.opEquals(a); | |
| else | |
| return a == b; | |
| } | |
| override int opCmp(Object o) | |
| { | |
| if (auto b = cast(typeof(this))o) | |
| { | |
| return a < mixin("b."~__traits(identifier, a)) ? -1 | |
| : a > mixin("b."~__traits(identifier, a)) ? +1 : 0; | |
| } | |
| static if (is(ValueType == class)) | |
| return a.opCmp(o); | |
| else | |
| throw new Exception("Attempt to compare a "~typeid(this).toString~" and a "~typeid(o).toString); | |
| } | |
| int opCmp(T)(auto ref const T b) | |
| if (is(ValueType : T) || is(typeof(a.opCmp(b))) || is(typeof(b.opCmp(a)))) | |
| { | |
| static if (is(typeof(a.opCmp(b)))) | |
| return a.opCmp(b); | |
| else static if (is(typeof(b.opCmp(a)))) | |
| return -b.opCmp(a); | |
| else | |
| return a < b ? -1 : a > b ? +1 : 0; | |
| } | |
| static if (accessibleFrom!(const typeof(this))) | |
| { | |
| override size_t toHash() const nothrow @safe | |
| { | |
| static if (__traits(compiles, .hashOf(a))) | |
| return .hashOf(a); | |
| else | |
| // Workaround for when .hashOf is not both @safe and nothrow. | |
| { | |
| static if (is(typeof(&a) == ValueType*)) | |
| alias v = a; | |
| else | |
| auto v = a; // if a is (property) function | |
| // BUG: Improperly casts away `shared`! | |
| return typeid(ValueType).getHash((() @trusted => cast(const void*) &v)()); | |
| } | |
| } | |
| } | |
| } | |
| else | |
| { | |
| auto ref opEquals(this X, B)(auto ref B b) | |
| { | |
| static if (is(immutable B == immutable typeof(this))) | |
| { | |
| return a == mixin("b."~__traits(identifier, a)); | |
| } | |
| else | |
| return a == b; | |
| } | |
| auto ref opCmp(this X, B)(auto ref B b) | |
| { | |
| static if (is(typeof(a.opCmp(b)))) | |
| return a.opCmp(b); | |
| else static if (is(typeof(b.opCmp(a)))) | |
| return -b.opCmp(a); | |
| else static if (isFloatingPoint!ValueType || isFloatingPoint!B) | |
| return a < b ? -1 : a > b ? +1 : a == b ? 0 : float.nan; | |
| else | |
| return a < b ? -1 : (a > b); | |
| } | |
| static if (accessibleFrom!(const typeof(this))) | |
| { | |
| size_t toHash() const nothrow @safe | |
| { | |
| static if (__traits(compiles, .hashOf(a))) | |
| return .hashOf(a); | |
| else | |
| // Workaround for when .hashOf is not both @safe and nothrow. | |
| { | |
| static if (is(typeof(&a) == ValueType*)) | |
| alias v = a; | |
| else | |
| auto v = a; // if a is (property) function | |
| // BUG: Improperly casts away `shared`! | |
| return typeid(ValueType).getHash((() @trusted => cast(const void*) &v)()); | |
| } | |
| } | |
| } | |
| } | |
| auto ref opCall(this X, Args...)(auto ref Args args) { return a(args); } | |
| auto ref opCast(T, this X)() { return cast(T) a; } | |
| auto ref opIndex(this X, D...)(auto ref D i) { return a[i]; } | |
| auto ref opSlice(this X )() { return a[]; } | |
| auto ref opSlice(this X, B, E)(auto ref B b, auto ref E e) { return a[b .. e]; } | |
| auto ref opUnary (string op, this X )() { return mixin(op~"a"); } | |
| auto ref opIndexUnary(string op, this X, D...)(auto ref D i) { return mixin(op~"a[i]"); } | |
| auto ref opSliceUnary(string op, this X )() { return mixin(op~"a[]"); } | |
| auto ref opSliceUnary(string op, this X, B, E)(auto ref B b, auto ref E e) { return mixin(op~"a[b .. e]"); } | |
| auto ref opBinary(string op, this X, B)(auto ref B b) | |
| if (op == "in" && is(typeof(a in b)) || op != "in") | |
| { | |
| return mixin("a "~op~" b"); | |
| } | |
| auto ref opBinaryRight(string op, this X, B)(auto ref B b) { return mixin("b "~op~" a"); } | |
| static if (!is(typeof(this) == class)) | |
| { | |
| import std.traits; | |
| static if (isAssignable!ValueType) | |
| { | |
| auto ref opAssign(this X)(auto ref typeof(this) v) | |
| { | |
| a = mixin("v."~__traits(identifier, a)); | |
| return this; | |
| } | |
| } | |
| else | |
| { | |
| @disable void opAssign(this X)(auto ref typeof(this) v); | |
| } | |
| } | |
| auto ref opAssign (this X, V )(auto ref V v) | |
| if (!is(V == typeof(this))) { return a = v; } | |
| auto ref opIndexAssign(this X, V, D...)(auto ref V v, auto ref D i) { return a[i] = v; } | |
| auto ref opSliceAssign(this X, V )(auto ref V v) { return a[] = v; } | |
| auto ref opSliceAssign(this X, V, B, E)(auto ref V v, auto ref B b, auto ref E e) { return a[b .. e] = v; } | |
| auto ref opOpAssign (string op, this X, V )(auto ref V v) | |
| { | |
| return mixin("a = a "~op~" v"); | |
| } | |
| auto ref opIndexOpAssign(string op, this X, V, D...)(auto ref V v, auto ref D i) | |
| { | |
| return mixin("a[i] " ~op~"= v"); | |
| } | |
| auto ref opSliceOpAssign(string op, this X, V )(auto ref V v) | |
| { | |
| return mixin("a[] " ~op~"= v"); | |
| } | |
| auto ref opSliceOpAssign(string op, this X, V, B, E)(auto ref V v, auto ref B b, auto ref E e) | |
| { | |
| return mixin("a[b .. e] "~op~"= v"); | |
| } | |
| template opDispatch(string name) | |
| { | |
| static if (is(typeof(__traits(getMember, a, name)) == function)) | |
| { | |
| // non template function | |
| auto ref opDispatch(this X, Args...)(auto ref Args args) { return mixin("a."~name~"(args)"); } | |
| } | |
| else static if (is(typeof({ enum x = mixin("a."~name); }))) | |
| { | |
| // built-in type field, manifest constant, and static non-mutable field | |
| enum opDispatch = mixin("a."~name); | |
| } | |
| else static if (__traits(isTemplate, mixin("a."~name))) | |
| { | |
| // member template | |
| template opDispatch(T...) | |
| { | |
| enum targs = T.length ? "!T" : ""; | |
| auto ref opDispatch(this X, Args...)(auto ref Args args){ return mixin("a."~name~targs~"(args)"); } | |
| } | |
| } | |
| else | |
| { | |
| // field or property function | |
| @property auto ref opDispatch(this X)() { return mixin("a."~name); } | |
| @property auto ref opDispatch(this X, V)(auto ref V v) { return mixin("a."~name~" = v"); } | |
| } | |
| } | |
| import std.traits : isArray; | |
| static if (isArray!ValueType) | |
| { | |
| auto opDollar() const { return a.length; } | |
| } | |
| else static if (is(typeof(a.opDollar!0))) | |
| { | |
| auto ref opDollar(size_t pos)() { return a.opDollar!pos(); } | |
| } | |
| else static if (is(typeof(a.opDollar) == function)) | |
| { | |
| auto ref opDollar() { return a.opDollar(); } | |
| } | |
| else static if (is(typeof(a.opDollar))) | |
| { | |
| alias opDollar = a.opDollar; | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| struct MyInt | |
| { | |
| private int value; | |
| mixin Proxy!value; | |
| this(int n){ value = n; } | |
| } | |
| MyInt n = 10; | |
| // Enable operations that original type has. | |
| ++n; | |
| assert(n == 11); | |
| assert(n * 2 == 22); | |
| void func(int n) { } | |
| // Disable implicit conversions to original type. | |
| //int x = n; | |
| //func(n); | |
| } | |
| ///The proxied value must be an $(B lvalue). | |
| @safe unittest | |
| { | |
| struct NewIntType | |
| { | |
| //Won't work; the literal '1' | |
| //is an rvalue, not an lvalue | |
| //mixin Proxy!1; | |
| //Okay, n is an lvalue | |
| int n; | |
| mixin Proxy!n; | |
| this(int n) { this.n = n; } | |
| } | |
| NewIntType nit = 0; | |
| nit++; | |
| assert(nit == 1); | |
| struct NewObjectType | |
| { | |
| Object obj; | |
| //Ok, obj is an lvalue | |
| mixin Proxy!obj; | |
| this (Object o) { obj = o; } | |
| } | |
| NewObjectType not = new Object(); | |
| assert(__traits(compiles, not.toHash())); | |
| } | |
| /** | |
| There is one exception to the fact that the new type is not related to the | |
| old type. $(DDSUBLINK spec/function,pseudo-member, Pseudo-member) | |
| functions are usable with the new type; they will be forwarded on to the | |
| proxied value. | |
| */ | |
| @safe unittest | |
| { | |
| import std.math.traits : isInfinity; | |
| float f = 1.0; | |
| assert(!f.isInfinity); | |
| struct NewFloat | |
| { | |
| float _; | |
| mixin Proxy!_; | |
| this(float f) { _ = f; } | |
| } | |
| NewFloat nf = 1.0f; | |
| assert(!nf.isInfinity); | |
| } | |
| @safe unittest | |
| { | |
| static struct MyInt | |
| { | |
| private int value; | |
| mixin Proxy!value; | |
| this(int n) inout { value = n; } | |
| enum str = "str"; | |
| static immutable arr = [1,2,3]; | |
| } | |
| static foreach (T; AliasSeq!(MyInt, const MyInt, immutable MyInt)) | |
| {{ | |
| T m = 10; | |
| static assert(!__traits(compiles, { int x = m; })); | |
| static assert(!__traits(compiles, { void func(int n){} func(m); })); | |
| assert(m == 10); | |
| assert(m != 20); | |
| assert(m < 20); | |
| assert(+m == 10); | |
| assert(-m == -10); | |
| assert(cast(double) m == 10.0); | |
| assert(m + 10 == 20); | |
| assert(m - 5 == 5); | |
| assert(m * 20 == 200); | |
| assert(m / 2 == 5); | |
| assert(10 + m == 20); | |
| assert(15 - m == 5); | |
| assert(20 * m == 200); | |
| assert(50 / m == 5); | |
| static if (is(T == MyInt)) // mutable | |
| { | |
| assert(++m == 11); | |
| assert(m++ == 11); assert(m == 12); | |
| assert(--m == 11); | |
| assert(m-- == 11); assert(m == 10); | |
| m = m; | |
| m = 20; assert(m == 20); | |
| } | |
| static assert(T.max == int.max); | |
| static assert(T.min == int.min); | |
| static assert(T.init == int.init); | |
| static assert(T.str == "str"); | |
| static assert(T.arr == [1,2,3]); | |
| }} | |
| } | |
| @system unittest | |
| { | |
| static struct MyArray | |
| { | |
| private int[] value; | |
| mixin Proxy!value; | |
| this(int[] arr) { value = arr; } | |
| this(immutable int[] arr) immutable { value = arr; } | |
| } | |
| static foreach (T; AliasSeq!(MyArray, const MyArray, immutable MyArray)) | |
| {{ | |
| static if (is(T == immutable) && !is(typeof({ T a = [1,2,3,4]; }))) | |
| T a = [1,2,3,4].idup; // workaround until qualified ctor is properly supported | |
| else | |
| T a = [1,2,3,4]; | |
| assert(a == [1,2,3,4]); | |
| assert(a != [5,6,7,8]); | |
| assert(+a[0] == 1); | |
| version (LittleEndian) | |
| assert(cast(ulong[]) a == [0x0000_0002_0000_0001, 0x0000_0004_0000_0003]); | |
| else | |
| assert(cast(ulong[]) a == [0x0000_0001_0000_0002, 0x0000_0003_0000_0004]); | |
| assert(a ~ [10,11] == [1,2,3,4,10,11]); | |
| assert(a[0] == 1); | |
| assert(a[] == [1,2,3,4]); | |
| assert(a[2 .. 4] == [3,4]); | |
| static if (is(T == MyArray)) // mutable | |
| { | |
| a = a; | |
| a = [5,6,7,8]; assert(a == [5,6,7,8]); | |
| a[0] = 0; assert(a == [0,6,7,8]); | |
| a[] = 1; assert(a == [1,1,1,1]); | |
| a[0 .. 3] = 2; assert(a == [2,2,2,1]); | |
| a[0] += 2; assert(a == [4,2,2,1]); | |
| a[] *= 2; assert(a == [8,4,4,2]); | |
| a[0 .. 2] /= 2; assert(a == [4,2,4,2]); | |
| } | |
| }} | |
| } | |
| @system unittest | |
| { | |
| class Foo | |
| { | |
| int field; | |
| @property int val1() const { return field; } | |
| @property void val1(int n) { field = n; } | |
| @property ref int val2() { return field; } | |
| int func(int x, int y) const { return x; } | |
| void func1(ref int a) { a = 9; } | |
| T ifti1(T)(T t) { return t; } | |
| void ifti2(Args...)(Args args) { } | |
| void ifti3(T, Args...)(Args args) { } | |
| T opCast(T)(){ return T.init; } | |
| T tempfunc(T)() { return T.init; } | |
| } | |
| class Hoge | |
| { | |
| Foo foo; | |
| mixin Proxy!foo; | |
| this(Foo f) { foo = f; } | |
| } | |
| auto h = new Hoge(new Foo()); | |
| int n; | |
| static assert(!__traits(compiles, { Foo f = h; })); | |
| // field | |
| h.field = 1; // lhs of assign | |
| n = h.field; // rhs of assign | |
| assert(h.field == 1); // lhs of BinExp | |
| assert(1 == h.field); // rhs of BinExp | |
| assert(n == 1); | |
| // getter/setter property function | |
| h.val1 = 4; | |
| n = h.val1; | |
| assert(h.val1 == 4); | |
| assert(4 == h.val1); | |
| assert(n == 4); | |
| // ref getter property function | |
| h.val2 = 8; | |
| n = h.val2; | |
| assert(h.val2 == 8); | |
| assert(8 == h.val2); | |
| assert(n == 8); | |
| // member function | |
| assert(h.func(2,4) == 2); | |
| h.func1(n); | |
| assert(n == 9); | |
| // IFTI | |
| assert(h.ifti1(4) == 4); | |
| h.ifti2(4); | |
| h.ifti3!int(4, 3); | |
| // https://issues.dlang.org/show_bug.cgi?id=5896 test | |
| assert(h.opCast!int() == 0); | |
| assert(cast(int) h == 0); | |
| const ih = new const Hoge(new Foo()); | |
| static assert(!__traits(compiles, ih.opCast!int())); | |
| static assert(!__traits(compiles, cast(int) ih)); | |
| // template member function | |
| assert(h.tempfunc!int() == 0); | |
| } | |
| @system unittest // about Proxy inside a class | |
| { | |
| class MyClass | |
| { | |
| int payload; | |
| mixin Proxy!payload; | |
| this(int i){ payload = i; } | |
| string opCall(string msg){ return msg; } | |
| int pow(int i){ return payload ^^ i; } | |
| } | |
| class MyClass2 | |
| { | |
| MyClass payload; | |
| mixin Proxy!payload; | |
| this(int i){ payload = new MyClass(i); } | |
| } | |
| class MyClass3 | |
| { | |
| int payload; | |
| mixin Proxy!payload; | |
| this(int i){ payload = i; } | |
| } | |
| // opEquals | |
| Object a = new MyClass(5); | |
| Object b = new MyClass(5); | |
| Object c = new MyClass2(5); | |
| Object d = new MyClass3(5); | |
| assert(a == b); | |
| assert((cast(MyClass) a) == 5); | |
| assert(5 == (cast(MyClass) b)); | |
| assert(5 == cast(MyClass2) c); | |
| assert(a != d); | |
| assert(c != a); | |
| // oops! above line is unexpected, isn't it? | |
| // the reason is below. | |
| // MyClass2.opEquals knows MyClass but, | |
| // MyClass.opEquals doesn't know MyClass2. | |
| // so, c.opEquals(a) is true, but a.opEquals(c) is false. | |
| // furthermore, opEquals(T) couldn't be invoked. | |
| assert((cast(MyClass2) c) != (cast(MyClass) a)); | |
| // opCmp | |
| Object e = new MyClass2(7); | |
| assert(a < cast(MyClass2) e); // OK. and | |
| assert(e > a); // OK, but... | |
| // assert(a < e); // RUNTIME ERROR! | |
| // assert((cast(MyClass) a) < e); // RUNTIME ERROR! | |
| assert(3 < cast(MyClass) a); | |
| assert((cast(MyClass2) e) < 11); | |
| // opCall | |
| assert((cast(MyClass2) e)("hello") == "hello"); | |
| // opCast | |
| assert((cast(MyClass)(cast(MyClass2) c)) == a); | |
| assert((cast(int)(cast(MyClass2) c)) == 5); | |
| // opIndex | |
| class MyClass4 | |
| { | |
| string payload; | |
| mixin Proxy!payload; | |
| this(string s){ payload = s; } | |
| } | |
| class MyClass5 | |
| { | |
| MyClass4 payload; | |
| mixin Proxy!payload; | |
| this(string s){ payload = new MyClass4(s); } | |
| } | |
| auto f = new MyClass4("hello"); | |
| assert(f[1] == 'e'); | |
| auto g = new MyClass5("hello"); | |
| assert(f[1] == 'e'); | |
| // opSlice | |
| assert(f[2 .. 4] == "ll"); | |
| // opUnary | |
| assert(-(cast(MyClass2) c) == -5); | |
| // opBinary | |
| assert((cast(MyClass) a) + (cast(MyClass2) c) == 10); | |
| assert(5 + cast(MyClass) a == 10); | |
| // opAssign | |
| (cast(MyClass2) c) = 11; | |
| assert((cast(MyClass2) c) == 11); | |
| (cast(MyClass2) c) = new MyClass(13); | |
| assert((cast(MyClass2) c) == 13); | |
| // opOpAssign | |
| assert((cast(MyClass2) c) += 4); | |
| assert((cast(MyClass2) c) == 17); | |
| // opDispatch | |
| assert((cast(MyClass2) c).pow(2) == 289); | |
| // opDollar | |
| assert(f[2..$-1] == "ll"); | |
| // toHash | |
| int[Object] hash; | |
| hash[a] = 19; | |
| hash[c] = 21; | |
| assert(hash[b] == 19); | |
| assert(hash[c] == 21); | |
| } | |
| @safe unittest | |
| { | |
| struct MyInt | |
| { | |
| int payload; | |
| mixin Proxy!payload; | |
| } | |
| MyInt v; | |
| v = v; | |
| struct Foo | |
| { | |
| @disable void opAssign(typeof(this)); | |
| } | |
| struct MyFoo | |
| { | |
| Foo payload; | |
| mixin Proxy!payload; | |
| } | |
| MyFoo f; | |
| static assert(!__traits(compiles, f = f)); | |
| struct MyFoo2 | |
| { | |
| Foo payload; | |
| mixin Proxy!payload; | |
| // override default Proxy behavior | |
| void opAssign(typeof(this) rhs){} | |
| } | |
| MyFoo2 f2; | |
| f2 = f2; | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=8613 | |
| @safe unittest | |
| { | |
| static struct Name | |
| { | |
| mixin Proxy!val; | |
| private string val; | |
| this(string s) { val = s; } | |
| } | |
| bool[Name] names; | |
| names[Name("a")] = true; | |
| bool* b = Name("a") in names; | |
| } | |
| // workaround for https://issues.dlang.org/show_bug.cgi?id=19669 | |
| private enum isDIP1000 = __traits(compiles, () @safe { | |
| int x; | |
| int* p; | |
| p = &x; | |
| }); | |
| // excludes struct S; it's 'mixin Proxy!foo' doesn't compile with -dip1000 | |
| static if (isDIP1000) {} else | |
| @system unittest | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=14213 | |
| // using function for the payload | |
| static struct S | |
| { | |
| int foo() { return 12; } | |
| mixin Proxy!foo; | |
| } | |
| S s; | |
| assert(s + 1 == 13); | |
| assert(s * 2 == 24); | |
| } | |
| @system unittest | |
| { | |
| static class C | |
| { | |
| int foo() { return 12; } | |
| mixin Proxy!foo; | |
| } | |
| C c = new C(); | |
| } | |
| // Check all floating point comparisons for both Proxy and Typedef, | |
| // also against int and a Typedef!int, to be as regression-proof | |
| // as possible. https://issues.dlang.org/show_bug.cgi?id=15561 | |
| @safe unittest | |
| { | |
| static struct MyFloatImpl | |
| { | |
| float value; | |
| mixin Proxy!value; | |
| } | |
| static void allFail(T0, T1)(T0 a, T1 b) | |
| { | |
| assert(!(a == b)); | |
| assert(!(a<b)); | |
| assert(!(a <= b)); | |
| assert(!(a>b)); | |
| assert(!(a >= b)); | |
| } | |
| static foreach (T1; AliasSeq!(MyFloatImpl, Typedef!float, Typedef!double, | |
| float, real, Typedef!int, int)) | |
| { | |
| static foreach (T2; AliasSeq!(MyFloatImpl, Typedef!float)) | |
| {{ | |
| T1 a; | |
| T2 b; | |
| static if (isFloatingPoint!T1 || isFloatingPoint!(TypedefType!T1)) | |
| allFail(a, b); | |
| a = 3; | |
| allFail(a, b); | |
| b = 4; | |
| assert(a != b); | |
| assert(a<b); | |
| assert(a <= b); | |
| assert(!(a>b)); | |
| assert(!(a >= b)); | |
| a = 4; | |
| assert(a == b); | |
| assert(!(a<b)); | |
| assert(a <= b); | |
| assert(!(a>b)); | |
| assert(a >= b); | |
| }} | |
| } | |
| } | |
| /** | |
| $(B Typedef) allows the creation of a unique type which is | |
| based on an existing type. Unlike the `alias` feature, | |
| $(B Typedef) ensures the two types are not considered as equals. | |
| Params: | |
| init = Optional initial value for the new type. | |
| cookie = Optional, used to create multiple unique types which are | |
| based on the same origin type `T` | |
| Note: If a library routine cannot handle the Typedef type, | |
| you can use the `TypedefType` template to extract the | |
| type which the Typedef wraps. | |
| */ | |
| struct Typedef(T, T init = T.init, string cookie=null) | |
| { | |
| private T Typedef_payload = init; | |
| // https://issues.dlang.org/show_bug.cgi?id=18415 | |
| // prevent default construction if original type does too. | |
| static if ((is(T == struct) || is(T == union)) && !is(typeof({T t;}))) | |
| { | |
| @disable this(); | |
| } | |
| this(T init) | |
| { | |
| Typedef_payload = init; | |
| } | |
| this(Typedef tdef) | |
| { | |
| this(tdef.Typedef_payload); | |
| } | |
| // We need to add special overload for cast(Typedef!X) exp, | |
| // thus we can't simply inherit Proxy!Typedef_payload | |
| T2 opCast(T2 : Typedef!(T, Unused), this X, T, Unused...)() | |
| { | |
| return T2(cast(T) Typedef_payload); | |
| } | |
| auto ref opCast(T2, this X)() | |
| { | |
| return cast(T2) Typedef_payload; | |
| } | |
| mixin Proxy!Typedef_payload; | |
| pure nothrow @nogc @safe @property | |
| { | |
| alias TD = typeof(this); | |
| static if (isIntegral!T) | |
| { | |
| static TD min() {return TD(T.min);} | |
| static TD max() {return TD(T.max);} | |
| } | |
| else static if (isFloatingPoint!T) | |
| { | |
| static TD infinity() {return TD(T.infinity);} | |
| static TD nan() {return TD(T.nan);} | |
| static TD dig() {return TD(T.dig);} | |
| static TD epsilon() {return TD(T.epsilon);} | |
| static TD mant_dig() {return TD(T.mant_dig);} | |
| static TD max_10_exp() {return TD(T.max_10_exp);} | |
| static TD max_exp() {return TD(T.max_exp);} | |
| static TD min_10_exp() {return TD(T.min_10_exp);} | |
| static TD min_exp() {return TD(T.min_exp);} | |
| static TD max() {return TD(T.max);} | |
| static TD min_normal() {return TD(T.min_normal);} | |
| TD re() {return TD(Typedef_payload.re);} | |
| TD im() {return TD(Typedef_payload.im);} | |
| } | |
| } | |
| /** | |
| * Convert wrapped value to a human readable string | |
| */ | |
| string toString(this T)() | |
| { | |
| import std.array : appender; | |
| auto app = appender!string(); | |
| auto spec = singleSpec("%s"); | |
| toString(app, spec); | |
| return app.data; | |
| } | |
| /// ditto | |
| void toString(this T, W)(ref W writer, scope const ref FormatSpec!char fmt) | |
| if (isOutputRange!(W, char)) | |
| { | |
| formatValue(writer, Typedef_payload, fmt); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.conv : to; | |
| int i = 123; | |
| auto td = Typedef!int(i); | |
| assert(i.to!string == td.to!string); | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| alias MyInt = Typedef!int; | |
| MyInt foo = 10; | |
| foo++; | |
| assert(foo == 11); | |
| } | |
| /// custom initialization values | |
| @safe unittest | |
| { | |
| alias MyIntInit = Typedef!(int, 42); | |
| static assert(is(TypedefType!MyIntInit == int)); | |
| static assert(MyIntInit() == 42); | |
| } | |
| /// Typedef creates a new type | |
| @safe unittest | |
| { | |
| alias MyInt = Typedef!int; | |
| static void takeInt(int) {} | |
| static void takeMyInt(MyInt) {} | |
| int i; | |
| takeInt(i); // ok | |
| static assert(!__traits(compiles, takeMyInt(i))); | |
| MyInt myInt; | |
| static assert(!__traits(compiles, takeInt(myInt))); | |
| takeMyInt(myInt); // ok | |
| } | |
| /// Use the optional `cookie` argument to create different types of the same base type | |
| @safe unittest | |
| { | |
| alias TypeInt1 = Typedef!int; | |
| alias TypeInt2 = Typedef!int; | |
| // The two Typedefs are the same type. | |
| static assert(is(TypeInt1 == TypeInt2)); | |
| alias MoneyEuros = Typedef!(float, float.init, "euros"); | |
| alias MoneyDollars = Typedef!(float, float.init, "dollars"); | |
| // The two Typedefs are _not_ the same type. | |
| static assert(!is(MoneyEuros == MoneyDollars)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=12461 | |
| @safe unittest | |
| { | |
| alias Int = Typedef!int; | |
| Int a, b; | |
| a += b; | |
| assert(a == 0); | |
| } | |
| /** | |
| Get the underlying type which a `Typedef` wraps. | |
| If `T` is not a `Typedef` it will alias itself to `T`. | |
| */ | |
| template TypedefType(T) | |
| { | |
| static if (is(T : Typedef!Arg, Arg)) | |
| alias TypedefType = Arg; | |
| else | |
| alias TypedefType = T; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.conv : to; | |
| alias MyInt = Typedef!int; | |
| static assert(is(TypedefType!MyInt == int)); | |
| /// Instantiating with a non-Typedef will return that type | |
| static assert(is(TypedefType!int == int)); | |
| string num = "5"; | |
| // extract the needed type | |
| MyInt myInt = MyInt( num.to!(TypedefType!MyInt) ); | |
| assert(myInt == 5); | |
| // cast to the underlying type to get the value that's being wrapped | |
| int x = cast(TypedefType!MyInt) myInt; | |
| alias MyIntInit = Typedef!(int, 42); | |
| static assert(is(TypedefType!MyIntInit == int)); | |
| static assert(MyIntInit() == 42); | |
| } | |
| @safe unittest | |
| { | |
| Typedef!int x = 10; | |
| static assert(!__traits(compiles, { int y = x; })); | |
| static assert(!__traits(compiles, { long z = x; })); | |
| Typedef!int y = 10; | |
| assert(x == y); | |
| static assert(Typedef!int.init == int.init); | |
| Typedef!(float, 1.0) z; // specifies the init | |
| assert(z == 1.0); | |
| static assert(typeof(z).init == 1.0); | |
| alias Dollar = Typedef!(int, 0, "dollar"); | |
| alias Yen = Typedef!(int, 0, "yen"); | |
| static assert(!is(Dollar == Yen)); | |
| Typedef!(int[3]) sa; | |
| static assert(sa.length == 3); | |
| static assert(typeof(sa).length == 3); | |
| Typedef!(int[3]) dollar1; | |
| assert(dollar1[0..$] is dollar1[0 .. 3]); | |
| Typedef!(int[]) dollar2; | |
| dollar2.length = 3; | |
| assert(dollar2[0..$] is dollar2[0 .. 3]); | |
| static struct Dollar1 | |
| { | |
| static struct DollarToken {} | |
| enum opDollar = DollarToken.init; | |
| auto opSlice(size_t, DollarToken) { return 1; } | |
| auto opSlice(size_t, size_t) { return 2; } | |
| } | |
| Typedef!Dollar1 drange1; | |
| assert(drange1[0..$] == 1); | |
| assert(drange1[0 .. 1] == 2); | |
| static struct Dollar2 | |
| { | |
| size_t opDollar(size_t pos)() { return pos == 0 ? 1 : 100; } | |
| size_t opIndex(size_t i, size_t j) { return i + j; } | |
| } | |
| Typedef!Dollar2 drange2; | |
| assert(drange2[$, $] == 101); | |
| static struct Dollar3 | |
| { | |
| size_t opDollar() { return 123; } | |
| size_t opIndex(size_t i) { return i; } | |
| } | |
| Typedef!Dollar3 drange3; | |
| assert(drange3[$] == 123); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=18415 | |
| @safe @nogc pure nothrow unittest | |
| { | |
| struct NoDefCtorS{@disable this();} | |
| union NoDefCtorU{@disable this();} | |
| static assert(!is(typeof({Typedef!NoDefCtorS s;}))); | |
| static assert(!is(typeof({Typedef!NoDefCtorU u;}))); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=11703 | |
| @safe @nogc pure nothrow unittest | |
| { | |
| alias I = Typedef!int; | |
| static assert(is(typeof(I.min) == I)); | |
| static assert(is(typeof(I.max) == I)); | |
| alias F = Typedef!double; | |
| static assert(is(typeof(F.infinity) == F)); | |
| static assert(is(typeof(F.epsilon) == F)); | |
| F f; | |
| assert(!is(typeof(F.re).stringof == double)); | |
| assert(!is(typeof(F.im).stringof == double)); | |
| } | |
| @safe unittest | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=8655 | |
| import std.typecons; | |
| import std.bitmanip; | |
| static import core.stdc.config; | |
| alias c_ulong = Typedef!(core.stdc.config.c_ulong); | |
| static struct Foo | |
| { | |
| mixin(bitfields!( | |
| c_ulong, "NameOffset", 31, | |
| c_ulong, "NameIsString", 1 | |
| )); | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=12596 | |
| @safe unittest | |
| { | |
| import std.typecons; | |
| alias TD = Typedef!int; | |
| TD x = TD(1); | |
| TD y = TD(x); | |
| assert(x == y); | |
| } | |
| @safe unittest // about toHash | |
| { | |
| import std.typecons; | |
| { | |
| alias TD = Typedef!int; | |
| int[TD] td; | |
| td[TD(1)] = 1; | |
| assert(td[TD(1)] == 1); | |
| } | |
| { | |
| alias TD = Typedef!(int[]); | |
| int[TD] td; | |
| td[TD([1,2,3,4])] = 2; | |
| assert(td[TD([1,2,3,4])] == 2); | |
| } | |
| { | |
| alias TD = Typedef!(int[][]); | |
| int[TD] td; | |
| td[TD([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]])] = 3; | |
| assert(td[TD([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]])] == 3); | |
| } | |
| { | |
| struct MyStruct{ int x; } | |
| alias TD = Typedef!MyStruct; | |
| int[TD] td; | |
| td[TD(MyStruct(10))] = 4; | |
| assert(TD(MyStruct(20)) !in td); | |
| assert(td[TD(MyStruct(10))] == 4); | |
| } | |
| { | |
| static struct MyStruct2 | |
| { | |
| int x; | |
| size_t toHash() const nothrow @safe { return x; } | |
| bool opEquals(ref const MyStruct2 r) const { return r.x == x; } | |
| } | |
| alias TD = Typedef!MyStruct2; | |
| int[TD] td; | |
| td[TD(MyStruct2(50))] = 5; | |
| assert(td[TD(MyStruct2(50))] == 5); | |
| } | |
| { | |
| class MyClass{} | |
| alias TD = Typedef!MyClass; | |
| int[TD] td; | |
| auto c = new MyClass; | |
| td[TD(c)] = 6; | |
| assert(TD(new MyClass) !in td); | |
| assert(td[TD(c)] == 6); | |
| } | |
| } | |
| @system unittest | |
| { | |
| alias String = Typedef!(char[]); | |
| alias CString = Typedef!(const(char)[]); | |
| CString cs = "fubar"; | |
| String s = cast(String) cs; | |
| assert(cs == s); | |
| char[] s2 = cast(char[]) cs; | |
| const(char)[] cs2 = cast(const(char)[])s; | |
| assert(s2 == cs2); | |
| } | |
| @system unittest // toString | |
| { | |
| import std.meta : AliasSeq; | |
| import std.conv : to; | |
| struct TestS {} | |
| class TestC {} | |
| static foreach (T; AliasSeq!(int, bool, float, double, real, | |
| char, dchar, wchar, | |
| TestS, TestC, | |
| int*, int[], int[2], int[int])) | |
| {{ | |
| T t; | |
| Typedef!T td; | |
| Typedef!(const T) ctd; | |
| Typedef!(immutable T) itd; | |
| assert(t.to!string() == td.to!string()); | |
| static if (!(is(T == TestS) || is(T == TestC))) | |
| { | |
| assert(t.to!string() == ctd.to!string()); | |
| assert(t.to!string() == itd.to!string()); | |
| } | |
| }} | |
| } | |
| @safe @nogc unittest // typedef'ed type with custom operators | |
| { | |
| static struct MyInt | |
| { | |
| int value; | |
| int opCmp(MyInt other) | |
| { | |
| if (value < other.value) | |
| return -1; | |
| return !(value == other.value); | |
| } | |
| } | |
| auto m1 = Typedef!MyInt(MyInt(1)); | |
| auto m2 = Typedef!MyInt(MyInt(2)); | |
| assert(m1 < m2); | |
| } | |
| /** | |
| Allocates a `class` object right inside the current scope, | |
| therefore avoiding the overhead of `new`. This facility is unsafe; | |
| it is the responsibility of the user to not escape a reference to the | |
| object outside the scope. | |
| The class destructor will be called when the result of `scoped()` is | |
| itself destroyed. | |
| Scoped class instances can be embedded in a parent `class` or `struct`, | |
| just like a child struct instance. Scoped member variables must have | |
| type `typeof(scoped!Class(args))`, and be initialized with a call to | |
| scoped. See below for an example. | |
| Note: | |
| It's illegal to move a class instance even if you are sure there | |
| are no pointers to it. As such, it is illegal to move a scoped object. | |
| */ | |
| template scoped(T) | |
| if (is(T == class)) | |
| { | |
| // _d_newclass now use default GC alignment (looks like (void*).sizeof * 2 for | |
| // small objects). We will just use the maximum of filed alignments. | |
| enum alignment = __traits(classInstanceAlignment, T); | |
| alias aligned = _alignUp!alignment; | |
| static struct Scoped | |
| { | |
| // Addition of `alignment` is required as `Scoped_store` can be misaligned in memory. | |
| private void[aligned(__traits(classInstanceSize, T) + size_t.sizeof) + alignment] Scoped_store = void; | |
| @property inout(T) Scoped_payload() inout | |
| { | |
| void* alignedStore = cast(void*) aligned(cast(size_t) Scoped_store.ptr); | |
| // As `Scoped` can be unaligned moved in memory class instance should be moved accordingly. | |
| immutable size_t d = alignedStore - Scoped_store.ptr; | |
| size_t* currD = cast(size_t*) &Scoped_store[$ - size_t.sizeof]; | |
| if (d != *currD) | |
| { | |
| import core.stdc.string : memmove; | |
| memmove(alignedStore, Scoped_store.ptr + *currD, __traits(classInstanceSize, T)); | |
| *currD = d; | |
| } | |
| return cast(inout(T)) alignedStore; | |
| } | |
| alias Scoped_payload this; | |
| @disable this(); | |
| @disable this(this); | |
| ~this() | |
| { | |
| // `destroy` will also write .init but we have no functions in druntime | |
| // for deterministic finalization and memory releasing for now. | |
| .destroy(Scoped_payload); | |
| } | |
| } | |
| /** Returns the _scoped object. | |
| Params: args = Arguments to pass to `T`'s constructor. | |
| */ | |
| @system auto scoped(Args...)(auto ref Args args) | |
| { | |
| import core.lifetime : emplace, forward; | |
| Scoped result = void; | |
| void* alignedStore = cast(void*) aligned(cast(size_t) result.Scoped_store.ptr); | |
| immutable size_t d = alignedStore - result.Scoped_store.ptr; | |
| *cast(size_t*) &result.Scoped_store[$ - size_t.sizeof] = d; | |
| emplace!(Unqual!T)(result.Scoped_store[d .. $ - size_t.sizeof], forward!args); | |
| return result; | |
| } | |
| } | |
| /// | |
| @system unittest | |
| { | |
| class A | |
| { | |
| int x; | |
| this() {x = 0;} | |
| this(int i){x = i;} | |
| ~this() {} | |
| } | |
| // Standard usage, constructing A on the stack | |
| auto a1 = scoped!A(); | |
| a1.x = 42; | |
| // Result of `scoped` call implicitly converts to a class reference | |
| A aRef = a1; | |
| assert(aRef.x == 42); | |
| // Scoped destruction | |
| { | |
| auto a2 = scoped!A(1); | |
| assert(a2.x == 1); | |
| aRef = a2; | |
| // a2 is destroyed here, calling A's destructor | |
| } | |
| // aRef is now an invalid reference | |
| // Here the temporary scoped A is immediately destroyed. | |
| // This means the reference is then invalid. | |
| version (Bug) | |
| { | |
| // Wrong, should use `auto` | |
| A invalid = scoped!A(); | |
| } | |
| // Restrictions | |
| version (Bug) | |
| { | |
| import std.algorithm.mutation : move; | |
| auto invalid = a1.move; // illegal, scoped objects can't be moved | |
| } | |
| static assert(!is(typeof({ | |
| auto e1 = a1; // illegal, scoped objects can't be copied | |
| assert([a1][0].x == 42); // ditto | |
| }))); | |
| static assert(!is(typeof({ | |
| alias ScopedObject = typeof(a1); | |
| auto e2 = ScopedObject(); // illegal, must be built via scoped!A | |
| auto e3 = ScopedObject(1); // ditto | |
| }))); | |
| // Use with alias | |
| alias makeScopedA = scoped!A; | |
| auto a3 = makeScopedA(); | |
| auto a4 = makeScopedA(1); | |
| // Use as member variable | |
| struct B | |
| { | |
| typeof(scoped!A()) a; // note the trailing parentheses | |
| this(int i) | |
| { | |
| // construct member | |
| a = scoped!A(i); | |
| } | |
| } | |
| // Stack-allocate | |
| auto b1 = B(5); | |
| aRef = b1.a; | |
| assert(aRef.x == 5); | |
| destroy(b1); // calls A's destructor for b1.a | |
| // aRef is now an invalid reference | |
| // Heap-allocate | |
| auto b2 = new B(6); | |
| assert(b2.a.x == 6); | |
| destroy(*b2); // calls A's destructor for b2.a | |
| } | |
| private size_t _alignUp(size_t alignment)(size_t n) | |
| if (alignment > 0 && !((alignment - 1) & alignment)) | |
| { | |
| enum badEnd = alignment - 1; // 0b11, 0b111, ... | |
| return (n + badEnd) & ~badEnd; | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=6580 testcase | |
| @system unittest | |
| { | |
| enum alignment = (void*).alignof; | |
| static class C0 { } | |
| static class C1 { byte b; } | |
| static class C2 { byte[2] b; } | |
| static class C3 { byte[3] b; } | |
| static class C7 { byte[7] b; } | |
| static assert(scoped!C0().sizeof % alignment == 0); | |
| static assert(scoped!C1().sizeof % alignment == 0); | |
| static assert(scoped!C2().sizeof % alignment == 0); | |
| static assert(scoped!C3().sizeof % alignment == 0); | |
| static assert(scoped!C7().sizeof % alignment == 0); | |
| enum longAlignment = long.alignof; | |
| static class C1long | |
| { | |
| long long_; byte byte_ = 4; | |
| this() { } | |
| this(long _long, ref int i) { long_ = _long; ++i; } | |
| } | |
| static class C2long { byte[2] byte_ = [5, 6]; long long_ = 7; } | |
| static assert(scoped!C1long().sizeof % longAlignment == 0); | |
| static assert(scoped!C2long().sizeof % longAlignment == 0); | |
| void alignmentTest() | |
| { | |
| int var = 5; | |
| auto c1long = scoped!C1long(3, var); | |
| assert(var == 6); | |
| auto c2long = scoped!C2long(); | |
| assert(cast(uint)&c1long.long_ % longAlignment == 0); | |
| assert(cast(uint)&c2long.long_ % longAlignment == 0); | |
| assert(c1long.long_ == 3 && c1long.byte_ == 4); | |
| assert(c2long.byte_ == [5, 6] && c2long.long_ == 7); | |
| } | |
| alignmentTest(); | |
| version (DigitalMars) | |
| { | |
| void test(size_t size) | |
| { | |
| import core.stdc.stdlib : alloca; | |
| cast(void) alloca(size); | |
| alignmentTest(); | |
| } | |
| foreach (i; 0 .. 10) | |
| test(i); | |
| } | |
| else | |
| { | |
| void test(size_t size)() | |
| { | |
| byte[size] arr; | |
| alignmentTest(); | |
| } | |
| static foreach (i; 0 .. 11) | |
| test!i(); | |
| } | |
| } | |
| // Original https://issues.dlang.org/show_bug.cgi?id=6580 testcase | |
| @system unittest | |
| { | |
| class C { int i; byte b; } | |
| auto sa = [scoped!C(), scoped!C()]; | |
| assert(cast(uint)&sa[0].i % int.alignof == 0); | |
| assert(cast(uint)&sa[1].i % int.alignof == 0); // fails | |
| } | |
| @system unittest | |
| { | |
| class A { int x = 1; } | |
| auto a1 = scoped!A(); | |
| assert(a1.x == 1); | |
| auto a2 = scoped!A(); | |
| a1.x = 42; | |
| a2.x = 53; | |
| assert(a1.x == 42); | |
| } | |
| @system unittest | |
| { | |
| class A { int x = 1; this() { x = 2; } } | |
| auto a1 = scoped!A(); | |
| assert(a1.x == 2); | |
| auto a2 = scoped!A(); | |
| a1.x = 42; | |
| a2.x = 53; | |
| assert(a1.x == 42); | |
| } | |
| @system unittest | |
| { | |
| class A { int x = 1; this(int y) { x = y; } ~this() {} } | |
| auto a1 = scoped!A(5); | |
| assert(a1.x == 5); | |
| auto a2 = scoped!A(42); | |
| a1.x = 42; | |
| a2.x = 53; | |
| assert(a1.x == 42); | |
| } | |
| @system unittest | |
| { | |
| class A { static bool dead; ~this() { dead = true; } } | |
| class B : A { static bool dead; ~this() { dead = true; } } | |
| { | |
| auto b = scoped!B(); | |
| } | |
| assert(B.dead, "asdasd"); | |
| assert(A.dead, "asdasd"); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=8039 testcase | |
| @system unittest | |
| { | |
| static int dels; | |
| static struct S { ~this(){ ++dels; } } | |
| static class A { S s; } | |
| dels = 0; { scoped!A(); } | |
| assert(dels == 1); | |
| static class B { S[2] s; } | |
| dels = 0; { scoped!B(); } | |
| assert(dels == 2); | |
| static struct S2 { S[3] s; } | |
| static class C { S2[2] s; } | |
| dels = 0; { scoped!C(); } | |
| assert(dels == 6); | |
| static class D: A { S2[2] s; } | |
| dels = 0; { scoped!D(); } | |
| assert(dels == 1+6); | |
| } | |
| @system unittest | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=4500 | |
| class A | |
| { | |
| this() { a = this; } | |
| this(int i) { a = this; } | |
| A a; | |
| bool check() { return this is a; } | |
| } | |
| auto a1 = scoped!A(); | |
| assert(a1.check()); | |
| auto a2 = scoped!A(1); | |
| assert(a2.check()); | |
| a1.a = a1; | |
| assert(a1.check()); | |
| } | |
| @system unittest | |
| { | |
| static class A | |
| { | |
| static int sdtor; | |
| this() { ++sdtor; assert(sdtor == 1); } | |
| ~this() { assert(sdtor == 1); --sdtor; } | |
| } | |
| interface Bob {} | |
| static class ABob : A, Bob | |
| { | |
| this() { ++sdtor; assert(sdtor == 2); } | |
| ~this() { assert(sdtor == 2); --sdtor; } | |
| } | |
| A.sdtor = 0; | |
| scope(exit) assert(A.sdtor == 0); | |
| auto abob = scoped!ABob(); | |
| } | |
| @safe unittest | |
| { | |
| static class A { this(int) {} } | |
| static assert(!__traits(compiles, scoped!A())); | |
| } | |
| @system unittest | |
| { | |
| static class A { @property inout(int) foo() inout { return 1; } } | |
| auto a1 = scoped!A(); | |
| assert(a1.foo == 1); | |
| static assert(is(typeof(a1.foo) == int)); | |
| auto a2 = scoped!(const(A))(); | |
| assert(a2.foo == 1); | |
| static assert(is(typeof(a2.foo) == const(int))); | |
| auto a3 = scoped!(immutable(A))(); | |
| assert(a3.foo == 1); | |
| static assert(is(typeof(a3.foo) == immutable(int))); | |
| const c1 = scoped!A(); | |
| assert(c1.foo == 1); | |
| static assert(is(typeof(c1.foo) == const(int))); | |
| const c2 = scoped!(const(A))(); | |
| assert(c2.foo == 1); | |
| static assert(is(typeof(c2.foo) == const(int))); | |
| const c3 = scoped!(immutable(A))(); | |
| assert(c3.foo == 1); | |
| static assert(is(typeof(c3.foo) == immutable(int))); | |
| } | |
| @system unittest | |
| { | |
| class C | |
| { | |
| this(int rval) { assert(rval == 1); } | |
| this(ref int lval) { assert(lval == 3); ++lval; } | |
| } | |
| auto c1 = scoped!C(1); | |
| int lval = 3; | |
| auto c2 = scoped!C(lval); | |
| assert(lval == 4); | |
| } | |
| @system unittest | |
| { | |
| class C | |
| { | |
| this(){} | |
| this(int){} | |
| this(int, int){} | |
| } | |
| alias makeScopedC = scoped!C; | |
| auto a = makeScopedC(); | |
| auto b = makeScopedC(1); | |
| auto c = makeScopedC(1, 1); | |
| static assert(is(typeof(a) == typeof(b))); | |
| static assert(is(typeof(b) == typeof(c))); | |
| } | |
| /** | |
| Defines a simple, self-documenting yes/no flag. This makes it easy for | |
| APIs to define functions accepting flags without resorting to $(D | |
| bool), which is opaque in calls, and without needing to define an | |
| enumerated type separately. Using `Flag!"Name"` instead of $(D | |
| bool) makes the flag's meaning visible in calls. Each yes/no flag has | |
| its own type, which makes confusions and mix-ups impossible. | |
| Example: | |
| Code calling `getLine` (usually far away from its definition) can't be | |
| understood without looking at the documentation, even by users familiar with | |
| the API: | |
| ---- | |
| string getLine(bool keepTerminator) | |
| { | |
| ... | |
| if (keepTerminator) ... | |
| ... | |
| } | |
| ... | |
| auto line = getLine(false); | |
| ---- | |
| Assuming the reverse meaning (i.e. "ignoreTerminator") and inserting the wrong | |
| code compiles and runs with erroneous results. | |
| After replacing the boolean parameter with an instantiation of `Flag`, code | |
| calling `getLine` can be easily read and understood even by people not | |
| fluent with the API: | |
| ---- | |
| string getLine(Flag!"keepTerminator" keepTerminator) | |
| { | |
| ... | |
| if (keepTerminator) ... | |
| ... | |
| } | |
| ... | |
| auto line = getLine(Yes.keepTerminator); | |
| ---- | |
| The structs `Yes` and `No` are provided as shorthand for | |
| `Flag!"Name".yes` and `Flag!"Name".no` and are preferred for brevity and | |
| readability. These convenience structs mean it is usually unnecessary and | |
| counterproductive to create an alias of a `Flag` as a way of avoiding typing | |
| out the full type while specifying the affirmative or negative options. | |
| Passing categorical data by means of unstructured `bool` | |
| parameters is classified under "simple-data coupling" by Steve | |
| McConnell in the $(LUCKY Code Complete) book, along with three other | |
| kinds of coupling. The author argues citing several studies that | |
| coupling has a negative effect on code quality. `Flag` offers a | |
| simple structuring method for passing yes/no flags to APIs. | |
| */ | |
| template Flag(string name) { | |
| /// | |
| enum Flag : bool | |
| { | |
| /** | |
| When creating a value of type `Flag!"Name"`, use $(D | |
| Flag!"Name".no) for the negative option. When using a value | |
| of type `Flag!"Name"`, compare it against $(D | |
| Flag!"Name".no) or just `false` or `0`. */ | |
| no = false, | |
| /** When creating a value of type `Flag!"Name"`, use $(D | |
| Flag!"Name".yes) for the affirmative option. When using a | |
| value of type `Flag!"Name"`, compare it against $(D | |
| Flag!"Name".yes). | |
| */ | |
| yes = true | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| Flag!"abc" flag; | |
| assert(flag == Flag!"abc".no); | |
| assert(flag == No.abc); | |
| assert(!flag); | |
| if (flag) assert(0); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| auto flag = Yes.abc; | |
| assert(flag); | |
| assert(flag == Yes.abc); | |
| if (!flag) assert(0); | |
| if (flag) {} else assert(0); | |
| } | |
| /** | |
| Convenience names that allow using e.g. `Yes.encryption` instead of | |
| `Flag!"encryption".yes` and `No.encryption` instead of $(D | |
| Flag!"encryption".no). | |
| */ | |
| struct Yes | |
| { | |
| /// | |
| template opDispatch(string name) | |
| { | |
| enum opDispatch = Flag!name.yes; | |
| } | |
| } | |
| //template yes(string name) { enum Flag!name yes = Flag!name.yes; } | |
| /// Ditto | |
| struct No | |
| { | |
| /// | |
| template opDispatch(string name) | |
| { | |
| enum opDispatch = Flag!name.no; | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| Flag!"abc" flag; | |
| assert(flag == Flag!"abc".no); | |
| assert(flag == No.abc); | |
| assert(!flag); | |
| if (flag) assert(0); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| auto flag = Yes.abc; | |
| assert(flag); | |
| assert(flag == Yes.abc); | |
| if (!flag) assert(0); | |
| if (flag) {} else assert(0); | |
| } | |
| /** | |
| Detect whether an enum is of integral type and has only "flag" values | |
| (i.e. values with a bit count of exactly 1). | |
| Additionally, a zero value is allowed for compatibility with enums including | |
| a "None" value. | |
| */ | |
| template isBitFlagEnum(E) | |
| { | |
| static if (is(E Base == enum) && isIntegral!Base) | |
| { | |
| enum isBitFlagEnum = (E.min >= 0) && | |
| { | |
| static foreach (immutable flag; EnumMembers!E) | |
| {{ | |
| Base value = flag; | |
| value &= value - 1; | |
| if (value != 0) return false; | |
| }} | |
| return true; | |
| }(); | |
| } | |
| else | |
| { | |
| enum isBitFlagEnum = false; | |
| } | |
| } | |
| /// | |
| @safe pure nothrow unittest | |
| { | |
| enum A | |
| { | |
| None, | |
| A = 1 << 0, | |
| B = 1 << 1, | |
| C = 1 << 2, | |
| D = 1 << 3, | |
| } | |
| static assert(isBitFlagEnum!A); | |
| } | |
| /// Test an enum with default (consecutive) values | |
| @safe pure nothrow unittest | |
| { | |
| enum B | |
| { | |
| A, | |
| B, | |
| C, | |
| D // D == 3 | |
| } | |
| static assert(!isBitFlagEnum!B); | |
| } | |
| /// Test an enum with non-integral values | |
| @safe pure nothrow unittest | |
| { | |
| enum C: double | |
| { | |
| A = 1 << 0, | |
| B = 1 << 1 | |
| } | |
| static assert(!isBitFlagEnum!C); | |
| } | |
| /** | |
| A typesafe structure for storing combinations of enum values. | |
| This template defines a simple struct to represent bitwise OR combinations of | |
| enum values. It can be used if all the enum values are integral constants with | |
| a bit count of at most 1, or if the `unsafe` parameter is explicitly set to | |
| Yes. | |
| This is much safer than using the enum itself to store | |
| the OR combination, which can produce surprising effects like this: | |
| ---- | |
| enum E | |
| { | |
| A = 1 << 0, | |
| B = 1 << 1 | |
| } | |
| E e = E.A | E.B; | |
| // will throw SwitchError | |
| final switch (e) | |
| { | |
| case E.A: | |
| return; | |
| case E.B: | |
| return; | |
| } | |
| ---- | |
| */ | |
| struct BitFlags(E, Flag!"unsafe" unsafe = No.unsafe) | |
| if (unsafe || isBitFlagEnum!(E)) | |
| { | |
| @safe @nogc pure nothrow: | |
| private: | |
| enum isBaseEnumType(T) = is(E == T); | |
| alias Base = OriginalType!E; | |
| Base mValue; | |
| public: | |
| this(E flag) | |
| { | |
| this = flag; | |
| } | |
| this(T...)(T flags) | |
| if (allSatisfy!(isBaseEnumType, T)) | |
| { | |
| this = flags; | |
| } | |
| bool opCast(B: bool)() const | |
| { | |
| return mValue != 0; | |
| } | |
| Base opCast(B)() const | |
| if (is(Base : B)) | |
| { | |
| return mValue; | |
| } | |
| auto opUnary(string op)() const | |
| if (op == "~") | |
| { | |
| return BitFlags(cast(E) cast(Base) ~mValue); | |
| } | |
| auto ref opAssign(T...)(T flags) | |
| if (allSatisfy!(isBaseEnumType, T)) | |
| { | |
| mValue = 0; | |
| foreach (E flag; flags) | |
| { | |
| mValue |= flag; | |
| } | |
| return this; | |
| } | |
| auto ref opAssign(E flag) | |
| { | |
| mValue = flag; | |
| return this; | |
| } | |
| auto ref opOpAssign(string op: "|")(BitFlags flags) | |
| { | |
| mValue |= flags.mValue; | |
| return this; | |
| } | |
| auto ref opOpAssign(string op: "&")(BitFlags flags) | |
| { | |
| mValue &= flags.mValue; | |
| return this; | |
| } | |
| auto ref opOpAssign(string op: "|")(E flag) | |
| { | |
| mValue |= flag; | |
| return this; | |
| } | |
| auto ref opOpAssign(string op: "&")(E flag) | |
| { | |
| mValue &= flag; | |
| return this; | |
| } | |
| auto opBinary(string op)(BitFlags flags) const | |
| if (op == "|" || op == "&") | |
| { | |
| BitFlags result = this; | |
| result.opOpAssign!op(flags); | |
| return result; | |
| } | |
| auto opBinary(string op)(E flag) const | |
| if (op == "|" || op == "&") | |
| { | |
| BitFlags result = this; | |
| result.opOpAssign!op(flag); | |
| return result; | |
| } | |
| auto opBinaryRight(string op)(E flag) const | |
| if (op == "|" || op == "&") | |
| { | |
| return opBinary!op(flag); | |
| } | |
| bool opDispatch(string name)() const | |
| if (__traits(hasMember, E, name)) | |
| { | |
| enum e = __traits(getMember, E, name); | |
| return (mValue & e) == e; | |
| } | |
| void opDispatch(string name)(bool set) | |
| if (__traits(hasMember, E, name)) | |
| { | |
| enum e = __traits(getMember, E, name); | |
| if (set) | |
| mValue |= e; | |
| else | |
| mValue &= ~e; | |
| } | |
| } | |
| /// Set values with the | operator and test with & | |
| @safe @nogc pure nothrow unittest | |
| { | |
| enum Enum | |
| { | |
| A = 1 << 0, | |
| } | |
| // A default constructed BitFlags has no value set | |
| immutable BitFlags!Enum flags_empty; | |
| assert(!flags_empty.A); | |
| // Value can be set with the | operator | |
| immutable flags_A = flags_empty | Enum.A; | |
| // and tested using property access | |
| assert(flags_A.A); | |
| // or the & operator | |
| assert(flags_A & Enum.A); | |
| // which commutes. | |
| assert(Enum.A & flags_A); | |
| } | |
| /// A default constructed BitFlags has no value set | |
| @safe @nogc pure nothrow unittest | |
| { | |
| enum Enum | |
| { | |
| None, | |
| A = 1 << 0, | |
| B = 1 << 1, | |
| C = 1 << 2 | |
| } | |
| immutable BitFlags!Enum flags_empty; | |
| assert(!(flags_empty & (Enum.A | Enum.B | Enum.C))); | |
| assert(!(flags_empty & Enum.A) && !(flags_empty & Enum.B) && !(flags_empty & Enum.C)); | |
| } | |
| // BitFlags can be variadically initialized | |
| @safe @nogc pure nothrow unittest | |
| { | |
| import std.traits : EnumMembers; | |
| enum Enum | |
| { | |
| A = 1 << 0, | |
| B = 1 << 1, | |
| C = 1 << 2 | |
| } | |
| // Values can also be set using property access | |
| BitFlags!Enum flags; | |
| flags.A = true; | |
| assert(flags & Enum.A); | |
| flags.A = false; | |
| assert(!(flags & Enum.A)); | |
| // BitFlags can be variadically initialized | |
| immutable BitFlags!Enum flags_AB = BitFlags!Enum(Enum.A, Enum.B); | |
| assert(flags_AB.A && flags_AB.B && !flags_AB.C); | |
| // You can use the EnumMembers template to set all flags | |
| immutable BitFlags!Enum flags_all = EnumMembers!Enum; | |
| assert(flags_all.A && flags_all.B && flags_all.C); | |
| } | |
| /// Binary operations: subtracting and intersecting flags | |
| @safe @nogc pure nothrow unittest | |
| { | |
| enum Enum | |
| { | |
| A = 1 << 0, | |
| B = 1 << 1, | |
| C = 1 << 2, | |
| } | |
| immutable BitFlags!Enum flags_AB = BitFlags!Enum(Enum.A, Enum.B); | |
| immutable BitFlags!Enum flags_BC = BitFlags!Enum(Enum.B, Enum.C); | |
| // Use the ~ operator for subtracting flags | |
| immutable BitFlags!Enum flags_B = flags_AB & ~BitFlags!Enum(Enum.A); | |
| assert(!flags_B.A && flags_B.B && !flags_B.C); | |
| // use & between BitFlags for intersection | |
| assert(flags_B == (flags_BC & flags_AB)); | |
| } | |
| /// All the binary operators work in their assignment version | |
| @safe @nogc pure nothrow unittest | |
| { | |
| enum Enum | |
| { | |
| A = 1 << 0, | |
| B = 1 << 1, | |
| } | |
| BitFlags!Enum flags_empty, temp, flags_AB; | |
| flags_AB = Enum.A | Enum.B; | |
| temp |= flags_AB; | |
| assert(temp == (flags_empty | flags_AB)); | |
| temp = flags_empty; | |
| temp |= Enum.B; | |
| assert(temp == (flags_empty | Enum.B)); | |
| temp = flags_empty; | |
| temp &= flags_AB; | |
| assert(temp == (flags_empty & flags_AB)); | |
| temp = flags_empty; | |
| temp &= Enum.A; | |
| assert(temp == (flags_empty & Enum.A)); | |
| } | |
| /// Conversion to bool and int | |
| @safe @nogc pure nothrow unittest | |
| { | |
| enum Enum | |
| { | |
| A = 1 << 0, | |
| B = 1 << 1, | |
| } | |
| BitFlags!Enum flags; | |
| // BitFlags with no value set evaluate to false | |
| assert(!flags); | |
| // BitFlags with at least one value set evaluate to true | |
| flags |= Enum.A; | |
| assert(flags); | |
| // This can be useful to check intersection between BitFlags | |
| BitFlags!Enum flags_AB = Enum.A | Enum.B; | |
| assert(flags & flags_AB); | |
| assert(flags & Enum.A); | |
| // You can of course get you raw value out of flags | |
| auto value = cast(int) flags; | |
| assert(value == Enum.A); | |
| } | |
| /// You need to specify the `unsafe` parameter for enums with custom values | |
| @safe @nogc pure nothrow unittest | |
| { | |
| enum UnsafeEnum | |
| { | |
| A = 1, | |
| B = 2, | |
| C = 4, | |
| BC = B|C | |
| } | |
| static assert(!__traits(compiles, { BitFlags!UnsafeEnum flags; })); | |
| BitFlags!(UnsafeEnum, Yes.unsafe) flags; | |
| // property access tests for exact match of unsafe enums | |
| flags.B = true; | |
| assert(!flags.BC); // only B | |
| flags.C = true; | |
| assert(flags.BC); // both B and C | |
| flags.B = false; | |
| assert(!flags.BC); // only C | |
| // property access sets all bits of unsafe enum group | |
| flags = flags.init; | |
| flags.BC = true; | |
| assert(!flags.A && flags.B && flags.C); | |
| flags.A = true; | |
| flags.BC = false; | |
| assert(flags.A && !flags.B && !flags.C); | |
| } | |
| // Negation of BitFlags should work with any base type. | |
| // Double-negation of BitFlags should work. | |
| @safe @nogc pure nothrow unittest | |
| { | |
| static foreach (alias Base; AliasSeq!( | |
| byte, | |
| ubyte, | |
| short, | |
| ushort, | |
| int, | |
| uint, | |
| long, | |
| ulong, | |
| )) | |
| {{ | |
| enum Enum : Base | |
| { | |
| A = 1 << 0, | |
| B = 1 << 1, | |
| C = 1 << 2, | |
| } | |
| auto flags = BitFlags!Enum(Enum.A); | |
| assert(flags == ~~flags); | |
| }} | |
| } | |
| private enum false_(T) = false; | |
| // ReplaceType | |
| /** | |
| Replaces all occurrences of `From` into `To`, in one or more types `T`. For | |
| example, `ReplaceType!(int, uint, Tuple!(int, float)[string])` yields | |
| `Tuple!(uint, float)[string]`. The types in which replacement is performed | |
| may be arbitrarily complex, including qualifiers, built-in type constructors | |
| (pointers, arrays, associative arrays, functions, and delegates), and template | |
| instantiations; replacement proceeds transitively through the type definition. | |
| However, member types in `struct`s or `class`es are not replaced because there | |
| are no ways to express the types resulting after replacement. | |
| This is an advanced type manipulation necessary e.g. for replacing the | |
| placeholder type `This` in $(REF Algebraic, std,variant). | |
| Returns: `ReplaceType` aliases itself to the type(s) that result after | |
| replacement. | |
| */ | |
| alias ReplaceType(From, To, T...) = ReplaceTypeUnless!(false_, From, To, T); | |
| /// | |
| @safe unittest | |
| { | |
| static assert( | |
| is(ReplaceType!(int, string, int[]) == string[]) && | |
| is(ReplaceType!(int, string, int[int]) == string[string]) && | |
| is(ReplaceType!(int, string, const(int)[]) == const(string)[]) && | |
| is(ReplaceType!(int, string, Tuple!(int[], float)) | |
| == Tuple!(string[], float)) | |
| ); | |
| } | |
| /** | |
| Like $(LREF ReplaceType), but does not perform replacement in types for which | |
| `pred` evaluates to `true`. | |
| */ | |
| template ReplaceTypeUnless(alias pred, From, To, T...) | |
| { | |
| import std.meta; | |
| static if (T.length == 1) | |
| { | |
| static if (pred!(T[0])) | |
| alias ReplaceTypeUnless = T[0]; | |
| else static if (is(T[0] == From)) | |
| alias ReplaceTypeUnless = To; | |
| else static if (is(T[0] == const(U), U)) | |
| alias ReplaceTypeUnless = const(ReplaceTypeUnless!(pred, From, To, U)); | |
| else static if (is(T[0] == immutable(U), U)) | |
| alias ReplaceTypeUnless = immutable(ReplaceTypeUnless!(pred, From, To, U)); | |
| else static if (is(T[0] == shared(U), U)) | |
| alias ReplaceTypeUnless = shared(ReplaceTypeUnless!(pred, From, To, U)); | |
| else static if (is(T[0] == U*, U)) | |
| { | |
| static if (is(U == function)) | |
| alias ReplaceTypeUnless = replaceTypeInFunctionTypeUnless!(pred, From, To, T[0]); | |
| else | |
| alias ReplaceTypeUnless = ReplaceTypeUnless!(pred, From, To, U)*; | |
| } | |
| else static if (is(T[0] == delegate)) | |
| { | |
| alias ReplaceTypeUnless = replaceTypeInFunctionTypeUnless!(pred, From, To, T[0]); | |
| } | |
| else static if (is(T[0] == function)) | |
| { | |
| static assert(0, "Function types not supported," ~ | |
| " use a function pointer type instead of " ~ T[0].stringof); | |
| } | |
| else static if (is(T[0] == U!V, alias U, V...)) | |
| { | |
| template replaceTemplateArgs(T...) | |
| { | |
| static if (is(typeof(T[0]))) { // template argument is value or symbol | |
| static if (__traits(compiles, { alias _ = T[0]; })) | |
| // it's a symbol | |
| alias replaceTemplateArgs = T[0]; | |
| else | |
| // it's a value | |
| enum replaceTemplateArgs = T[0]; | |
| } else | |
| alias replaceTemplateArgs = ReplaceTypeUnless!(pred, From, To, T[0]); | |
| } | |
| alias ReplaceTypeUnless = U!(staticMap!(replaceTemplateArgs, V)); | |
| } | |
| else static if (is(T[0] == struct)) | |
| // don't match with alias this struct below | |
| // https://issues.dlang.org/show_bug.cgi?id=15168 | |
| alias ReplaceTypeUnless = T[0]; | |
| else static if (is(T[0] == U[], U)) | |
| alias ReplaceTypeUnless = ReplaceTypeUnless!(pred, From, To, U)[]; | |
| else static if (is(T[0] == U[n], U, size_t n)) | |
| alias ReplaceTypeUnless = ReplaceTypeUnless!(pred, From, To, U)[n]; | |
| else static if (is(T[0] == U[V], U, V)) | |
| alias ReplaceTypeUnless = | |
| ReplaceTypeUnless!(pred, From, To, U)[ReplaceTypeUnless!(pred, From, To, V)]; | |
| else | |
| alias ReplaceTypeUnless = T[0]; | |
| } | |
| else static if (T.length > 1) | |
| { | |
| alias ReplaceTypeUnless = AliasSeq!(ReplaceTypeUnless!(pred, From, To, T[0]), | |
| ReplaceTypeUnless!(pred, From, To, T[1 .. $])); | |
| } | |
| else | |
| { | |
| alias ReplaceTypeUnless = AliasSeq!(); | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.traits : isArray; | |
| static assert( | |
| is(ReplaceTypeUnless!(isArray, int, string, int*) == string*) && | |
| is(ReplaceTypeUnless!(isArray, int, string, int[]) == int[]) && | |
| is(ReplaceTypeUnless!(isArray, int, string, Tuple!(int, int[])) | |
| == Tuple!(string, int[])) | |
| ); | |
| } | |
| private template replaceTypeInFunctionTypeUnless(alias pred, From, To, fun) | |
| { | |
| alias RX = ReplaceTypeUnless!(pred, From, To, ReturnType!fun); | |
| alias PX = AliasSeq!(ReplaceTypeUnless!(pred, From, To, Parameters!fun)); | |
| // Wrapping with AliasSeq is neccesary because ReplaceType doesn't return | |
| // tuple if Parameters!fun.length == 1 | |
| string gen() | |
| { | |
| enum linkage = functionLinkage!fun; | |
| alias attributes = functionAttributes!fun; | |
| enum variadicStyle = variadicFunctionStyle!fun; | |
| alias storageClasses = ParameterStorageClassTuple!fun; | |
| string result; | |
| result ~= "extern(" ~ linkage ~ ") "; | |
| static if (attributes & FunctionAttribute.ref_) | |
| { | |
| result ~= "ref "; | |
| } | |
| result ~= "RX"; | |
| static if (is(fun == delegate)) | |
| result ~= " delegate"; | |
| else | |
| result ~= " function"; | |
| result ~= "("; | |
| static foreach (i; 0 .. PX.length) | |
| { | |
| if (i) | |
| result ~= ", "; | |
| if (storageClasses[i] & ParameterStorageClass.scope_) | |
| result ~= "scope "; | |
| if (storageClasses[i] & ParameterStorageClass.in_) | |
| result ~= "in "; | |
| if (storageClasses[i] & ParameterStorageClass.out_) | |
| result ~= "out "; | |
| if (storageClasses[i] & ParameterStorageClass.ref_) | |
| result ~= "ref "; | |
| if (storageClasses[i] & ParameterStorageClass.lazy_) | |
| result ~= "lazy "; | |
| if (storageClasses[i] & ParameterStorageClass.return_) | |
| result ~= "return "; | |
| result ~= "PX[" ~ i.stringof ~ "]"; | |
| } | |
| static if (variadicStyle == Variadic.typesafe) | |
| result ~= " ..."; | |
| else static if (variadicStyle != Variadic.no) | |
| result ~= ", ..."; | |
| result ~= ")"; | |
| static if (attributes & FunctionAttribute.pure_) | |
| result ~= " pure"; | |
| static if (attributes & FunctionAttribute.nothrow_) | |
| result ~= " nothrow"; | |
| static if (attributes & FunctionAttribute.property) | |
| result ~= " @property"; | |
| static if (attributes & FunctionAttribute.trusted) | |
| result ~= " @trusted"; | |
| static if (attributes & FunctionAttribute.safe) | |
| result ~= " @safe"; | |
| static if (attributes & FunctionAttribute.nogc) | |
| result ~= " @nogc"; | |
| static if (attributes & FunctionAttribute.system) | |
| result ~= " @system"; | |
| static if (attributes & FunctionAttribute.const_) | |
| result ~= " const"; | |
| static if (attributes & FunctionAttribute.immutable_) | |
| result ~= " immutable"; | |
| static if (attributes & FunctionAttribute.inout_) | |
| result ~= " inout"; | |
| static if (attributes & FunctionAttribute.shared_) | |
| result ~= " shared"; | |
| static if (attributes & FunctionAttribute.return_) | |
| result ~= " return"; | |
| static if (attributes & FunctionAttribute.live) | |
| result ~= " @live"; | |
| return result; | |
| } | |
| mixin("alias replaceTypeInFunctionTypeUnless = " ~ gen() ~ ";"); | |
| } | |
| @safe unittest | |
| { | |
| template Test(Ts...) | |
| { | |
| static if (Ts.length) | |
| { | |
| //pragma(msg, "Testing: ReplaceType!("~Ts[0].stringof~", " | |
| // ~Ts[1].stringof~", "~Ts[2].stringof~")"); | |
| static assert(is(ReplaceType!(Ts[0], Ts[1], Ts[2]) == Ts[3]), | |
| "ReplaceType!("~Ts[0].stringof~", "~Ts[1].stringof~", " | |
| ~Ts[2].stringof~") == " | |
| ~ReplaceType!(Ts[0], Ts[1], Ts[2]).stringof); | |
| alias Test = Test!(Ts[4 .. $]); | |
| } | |
| else alias Test = void; | |
| } | |
| //import core.stdc.stdio; | |
| alias RefFun1 = ref int function(float, long); | |
| alias RefFun2 = ref float function(float, long); | |
| extern(C) int printf(const char*, ...) nothrow @nogc @system; | |
| extern(C) float floatPrintf(const char*, ...) nothrow @nogc @system; | |
| int func(float); | |
| int x; | |
| struct S1 { void foo() { x = 1; } } | |
| struct S2 { void bar() { x = 2; } } | |
| alias Pass = Test!( | |
| int, float, typeof(&func), float delegate(float), | |
| int, float, typeof(&printf), typeof(&floatPrintf), | |
| int, float, int function(out long, ...), | |
| float function(out long, ...), | |
| int, float, int function(ref float, long), | |
| float function(ref float, long), | |
| int, float, int function(ref int, long), | |
| float function(ref float, long), | |
| int, float, int function(out int, long), | |
| float function(out float, long), | |
| int, float, int function(lazy int, long), | |
| float function(lazy float, long), | |
| int, float, int function(out long, ref const int), | |
| float function(out long, ref const float), | |
| int, float, int function(in long, ref const int), | |
| float function(in long, ref const float), | |
| int, float, int function(long, in int), | |
| float function(long, in float), | |
| int, int, int, int, | |
| int, float, int, float, | |
| int, float, const int, const float, | |
| int, float, immutable int, immutable float, | |
| int, float, shared int, shared float, | |
| int, float, int*, float*, | |
| int, float, const(int)*, const(float)*, | |
| int, float, const(int*), const(float*), | |
| const(int)*, float, const(int*), const(float), | |
| int*, float, const(int)*, const(int)*, | |
| int, float, int[], float[], | |
| int, float, int[42], float[42], | |
| int, float, const(int)[42], const(float)[42], | |
| int, float, const(int[42]), const(float[42]), | |
| int, float, int[int], float[float], | |
| int, float, int[double], float[double], | |
| int, float, double[int], double[float], | |
| int, float, int function(float, long), float function(float, long), | |
| int, float, int function(float), float function(float), | |
| int, float, int function(float, int), float function(float, float), | |
| int, float, int delegate(float, long), float delegate(float, long), | |
| int, float, int delegate(float), float delegate(float), | |
| int, float, int delegate(float, int), float delegate(float, float), | |
| int, float, Unique!int, Unique!float, | |
| int, float, Tuple!(float, int), Tuple!(float, float), | |
| int, float, RefFun1, RefFun2, | |
| S1, S2, | |
| S1[1][][S1]* function(), | |
| S2[1][][S2]* function(), | |
| int, string, | |
| int[3] function( int[] arr, int[2] ...) pure @trusted, | |
| string[3] function(string[] arr, string[2] ...) pure @trusted, | |
| ); | |
| // https://issues.dlang.org/show_bug.cgi?id=15168 | |
| static struct T1 { string s; alias s this; } | |
| static struct T2 { char[10] s; alias s this; } | |
| static struct T3 { string[string] s; alias s this; } | |
| alias Pass2 = Test!( | |
| ubyte, ubyte, T1, T1, | |
| ubyte, ubyte, T2, T2, | |
| ubyte, ubyte, T3, T3, | |
| ); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=17116 | |
| @safe unittest | |
| { | |
| alias ConstDg = void delegate(float) const; | |
| alias B = void delegate(int) const; | |
| alias A = ReplaceType!(float, int, ConstDg); | |
| static assert(is(B == A)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=19696 | |
| @safe unittest | |
| { | |
| static struct T(U) {} | |
| static struct S { T!int t; alias t this; } | |
| static assert(is(ReplaceType!(float, float, S) == S)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=19697 | |
| @safe unittest | |
| { | |
| class D(T) {} | |
| class C : D!C {} | |
| static assert(is(ReplaceType!(float, float, C))); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=16132 | |
| @safe unittest | |
| { | |
| interface I(T) {} | |
| class C : I!int {} | |
| static assert(is(ReplaceType!(int, string, C) == C)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=22325 | |
| @safe unittest | |
| { | |
| static struct Foo(alias f) {} | |
| static void bar() {} | |
| alias _ = ReplaceType!(int, int, Foo!bar); | |
| } | |
| /** | |
| Ternary type with three truth values: | |
| $(UL | |
| $(LI `Ternary.yes` for `true`) | |
| $(LI `Ternary.no` for `false`) | |
| $(LI `Ternary.unknown` as an unknown state) | |
| ) | |
| Also known as trinary, trivalent, or trilean. | |
| See_Also: | |
| $(HTTP en.wikipedia.org/wiki/Three-valued_logic, | |
| Three Valued Logic on Wikipedia) | |
| */ | |
| struct Ternary | |
| { | |
| @safe @nogc nothrow pure: | |
| private ubyte value = 6; | |
| private static Ternary make(ubyte b) | |
| { | |
| Ternary r = void; | |
| r.value = b; | |
| return r; | |
| } | |
| /** | |
| The possible states of the `Ternary` | |
| */ | |
| enum no = make(0); | |
| /// ditto | |
| enum yes = make(2); | |
| /// ditto | |
| enum unknown = make(6); | |
| /** | |
| Construct and assign from a `bool`, receiving `no` for `false` and `yes` | |
| for `true`. | |
| */ | |
| this(bool b) { value = b << 1; } | |
| /// ditto | |
| void opAssign(bool b) { value = b << 1; } | |
| /** | |
| Construct a ternary value from another ternary value | |
| */ | |
| this(const Ternary b) { value = b.value; } | |
| /** | |
| $(TABLE Truth table for logical operations, | |
| $(TR $(TH `a`) $(TH `b`) $(TH `$(TILDE)a`) $(TH `a | b`) $(TH `a & b`) $(TH `a ^ b`)) | |
| $(TR $(TD `no`) $(TD `no`) $(TD `yes`) $(TD `no`) $(TD `no`) $(TD `no`)) | |
| $(TR $(TD `no`) $(TD `yes`) $(TD) $(TD `yes`) $(TD `no`) $(TD `yes`)) | |
| $(TR $(TD `no`) $(TD `unknown`) $(TD) $(TD `unknown`) $(TD `no`) $(TD `unknown`)) | |
| $(TR $(TD `yes`) $(TD `no`) $(TD `no`) $(TD `yes`) $(TD `no`) $(TD `yes`)) | |
| $(TR $(TD `yes`) $(TD `yes`) $(TD) $(TD `yes`) $(TD `yes`) $(TD `no`)) | |
| $(TR $(TD `yes`) $(TD `unknown`) $(TD) $(TD `yes`) $(TD `unknown`) $(TD `unknown`)) | |
| $(TR $(TD `unknown`) $(TD `no`) $(TD `unknown`) $(TD `unknown`) $(TD `no`) $(TD `unknown`)) | |
| $(TR $(TD `unknown`) $(TD `yes`) $(TD) $(TD `yes`) $(TD `unknown`) $(TD `unknown`)) | |
| $(TR $(TD `unknown`) $(TD `unknown`) $(TD) $(TD `unknown`) $(TD `unknown`) $(TD `unknown`)) | |
| ) | |
| */ | |
| Ternary opUnary(string s)() | |
| if (s == "~") | |
| { | |
| return make((386 >> value) & 6); | |
| } | |
| /// ditto | |
| Ternary opBinary(string s)(Ternary rhs) | |
| if (s == "|") | |
| { | |
| return make((25_512 >> (value + rhs.value)) & 6); | |
| } | |
| /// ditto | |
| Ternary opBinary(string s)(Ternary rhs) | |
| if (s == "&") | |
| { | |
| return make((26_144 >> (value + rhs.value)) & 6); | |
| } | |
| /// ditto | |
| Ternary opBinary(string s)(Ternary rhs) | |
| if (s == "^") | |
| { | |
| return make((26_504 >> (value + rhs.value)) & 6); | |
| } | |
| /// ditto | |
| Ternary opBinary(string s)(bool rhs) | |
| if (s == "|" || s == "&" || s == "^") | |
| { | |
| return this.opBinary!s(Ternary(rhs)); | |
| } | |
| } | |
| /// | |
| @safe @nogc nothrow pure | |
| unittest | |
| { | |
| Ternary a; | |
| assert(a == Ternary.unknown); | |
| assert(~Ternary.yes == Ternary.no); | |
| assert(~Ternary.no == Ternary.yes); | |
| assert(~Ternary.unknown == Ternary.unknown); | |
| } | |
| @safe @nogc nothrow pure | |
| unittest | |
| { | |
| alias f = Ternary.no, t = Ternary.yes, u = Ternary.unknown; | |
| Ternary[27] truthTableAnd = | |
| [ | |
| t, t, t, | |
| t, u, u, | |
| t, f, f, | |
| u, t, u, | |
| u, u, u, | |
| u, f, f, | |
| f, t, f, | |
| f, u, f, | |
| f, f, f, | |
| ]; | |
| Ternary[27] truthTableOr = | |
| [ | |
| t, t, t, | |
| t, u, t, | |
| t, f, t, | |
| u, t, t, | |
| u, u, u, | |
| u, f, u, | |
| f, t, t, | |
| f, u, u, | |
| f, f, f, | |
| ]; | |
| Ternary[27] truthTableXor = | |
| [ | |
| t, t, f, | |
| t, u, u, | |
| t, f, t, | |
| u, t, u, | |
| u, u, u, | |
| u, f, u, | |
| f, t, t, | |
| f, u, u, | |
| f, f, f, | |
| ]; | |
| for (auto i = 0; i != truthTableAnd.length; i += 3) | |
| { | |
| assert((truthTableAnd[i] & truthTableAnd[i + 1]) | |
| == truthTableAnd[i + 2]); | |
| assert((truthTableOr[i] | truthTableOr[i + 1]) | |
| == truthTableOr[i + 2]); | |
| assert((truthTableXor[i] ^ truthTableXor[i + 1]) | |
| == truthTableXor[i + 2]); | |
| } | |
| Ternary a; | |
| assert(a == Ternary.unknown); | |
| static assert(!is(typeof({ if (a) {} }))); | |
| assert(!is(typeof({ auto b = Ternary(3); }))); | |
| a = true; | |
| assert(a == Ternary.yes); | |
| a = false; | |
| assert(a == Ternary.no); | |
| a = Ternary.unknown; | |
| assert(a == Ternary.unknown); | |
| Ternary b; | |
| b = a; | |
| assert(b == a); | |
| assert(~Ternary.yes == Ternary.no); | |
| assert(~Ternary.no == Ternary.yes); | |
| assert(~Ternary.unknown == Ternary.unknown); | |
| } | |
| @safe @nogc nothrow pure | |
| unittest | |
| { | |
| Ternary a = Ternary(true); | |
| assert(a == Ternary.yes); | |
| assert((a & false) == Ternary.no); | |
| assert((a | false) == Ternary.yes); | |
| assert((a ^ true) == Ternary.no); | |
| assert((a ^ false) == Ternary.yes); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=22511 | |
| @safe unittest | |
| { | |
| static struct S | |
| { | |
| int b; | |
| @disable this(this); | |
| this(ref return scope inout S rhs) inout | |
| { | |
| this.b = rhs.b + 1; | |
| } | |
| } | |
| Nullable!S s1 = S(1); | |
| assert(s1.get().b == 2); | |
| Nullable!S s2 = s1; | |
| assert(s2.get().b == 3); | |
| } | |
| @safe unittest | |
| { | |
| static struct S | |
| { | |
| int b; | |
| this(this) { ++b; } | |
| } | |
| Nullable!S s1 = S(1); | |
| assert(s1.get().b == 2); | |
| Nullable!S s2 = s1; | |
| assert(s2.get().b == 3); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=24318 | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| @disable this(this); | |
| int i; | |
| } | |
| Nullable!S s = S(1); | |
| assert(s.get().i == 1); | |
| s = S(2); | |
| assert(s.get().i == 2); | |
| } | |
| /// The old version of $(LREF SafeRefCounted), before $(LREF borrow) existed. | |
| /// Old code may be relying on `@safe`ty of some of the member functions which | |
| /// cannot be safe in the new scheme, and | |
| /// can avoid breakage by continuing to use this. `SafeRefCounted` should be | |
| /// preferred, as this type is outdated and unrecommended for new code. | |
| struct RefCounted(T, RefCountedAutoInitialize autoInit = | |
| RefCountedAutoInitialize.yes) | |
| { | |
| version (D_BetterC) | |
| { | |
| private enum enableGCScan = false; | |
| } | |
| else | |
| { | |
| private enum enableGCScan = hasIndirections!T; | |
| } | |
| extern(C) private pure nothrow @nogc static | |
| { | |
| pragma(mangle, "free") void pureFree( void *ptr ); | |
| static if (enableGCScan) | |
| import core.memory : GC; | |
| } | |
| struct RefCountedStore | |
| { | |
| private struct Impl | |
| { | |
| T _payload; | |
| size_t _count; | |
| } | |
| private Impl* _store; | |
| private void initialize(A...)(auto ref A args) | |
| { | |
| import core.lifetime : emplace, forward; | |
| allocateStore(); | |
| version (D_Exceptions) scope(failure) deallocateStore(); | |
| emplace(&_store._payload, forward!args); | |
| _store._count = 1; | |
| } | |
| private void move(ref T source) nothrow pure | |
| { | |
| import std.algorithm.mutation : moveEmplace; | |
| allocateStore(); | |
| moveEmplace(source, _store._payload); | |
| _store._count = 1; | |
| } | |
| // 'nothrow': can only generate an Error | |
| private void allocateStore() nothrow pure | |
| { | |
| static if (enableGCScan) | |
| { | |
| import std.internal.memory : enforceCalloc; | |
| _store = cast(Impl*) enforceCalloc(1, Impl.sizeof); | |
| GC.addRange(&_store._payload, T.sizeof); | |
| } | |
| else | |
| { | |
| import std.internal.memory : enforceMalloc; | |
| _store = cast(Impl*) enforceMalloc(Impl.sizeof); | |
| } | |
| } | |
| private void deallocateStore() nothrow pure | |
| { | |
| static if (enableGCScan) | |
| { | |
| GC.removeRange(&this._store._payload); | |
| } | |
| pureFree(_store); | |
| _store = null; | |
| } | |
| @property nothrow @safe pure @nogc | |
| bool isInitialized() const | |
| { | |
| return _store !is null; | |
| } | |
| @property nothrow @safe pure @nogc | |
| size_t refCount() const | |
| { | |
| return isInitialized ? _store._count : 0; | |
| } | |
| void ensureInitialized()() | |
| { | |
| // By checking for `@disable this()` and failing early we can | |
| // produce a clearer error message. | |
| static assert(__traits(compiles, { static T t; }), | |
| "Cannot automatically initialize `" ~ fullyQualifiedName!T ~ | |
| "` because `" ~ fullyQualifiedName!T ~ | |
| ".this()` is annotated with `@disable`."); | |
| if (!isInitialized) initialize(); | |
| } | |
| } | |
| RefCountedStore _refCounted; | |
| @property nothrow @safe | |
| ref inout(RefCountedStore) refCountedStore() inout | |
| { | |
| return _refCounted; | |
| } | |
| this(A...)(auto ref A args) | |
| if (A.length > 0) | |
| out | |
| { | |
| assert(refCountedStore.isInitialized); | |
| } | |
| do | |
| { | |
| import core.lifetime : forward; | |
| _refCounted.initialize(forward!args); | |
| } | |
| this(T val) | |
| { | |
| _refCounted.move(val); | |
| } | |
| this(this) @safe pure nothrow @nogc | |
| { | |
| if (!_refCounted.isInitialized) return; | |
| ++_refCounted._store._count; | |
| } | |
| ~this() | |
| { | |
| if (!_refCounted.isInitialized) return; | |
| assert(_refCounted._store._count > 0); | |
| if (--_refCounted._store._count) | |
| return; | |
| // Done, destroy and deallocate | |
| .destroy(_refCounted._store._payload); | |
| _refCounted.deallocateStore(); | |
| } | |
| void opAssign(typeof(this) rhs) | |
| { | |
| import std.algorithm.mutation : swap; | |
| swap(_refCounted._store, rhs._refCounted._store); | |
| } | |
| static if (__traits(compiles, lvalueOf!T = T.init)) | |
| { | |
| void opAssign(T rhs) | |
| { | |
| import std.algorithm.mutation : move; | |
| static if (autoInit == RefCountedAutoInitialize.yes) | |
| { | |
| _refCounted.ensureInitialized(); | |
| } | |
| else | |
| { | |
| assert(_refCounted.isInitialized); | |
| } | |
| move(rhs, _refCounted._store._payload); | |
| } | |
| } | |
| static if (autoInit == RefCountedAutoInitialize.yes) | |
| { | |
| //Can't use inout here because of potential mutation | |
| @property | |
| ref T refCountedPayload() return | |
| { | |
| _refCounted.ensureInitialized(); | |
| return _refCounted._store._payload; | |
| } | |
| } | |
| @property nothrow @safe pure @nogc | |
| ref inout(T) refCountedPayload() inout return | |
| { | |
| assert(_refCounted.isInitialized, "Attempted to access an uninitialized payload."); | |
| return _refCounted._store._payload; | |
| } | |
| alias refCountedPayload this; | |
| static if (is(T == struct) && !is(typeof((ref T t) => t.toString()))) | |
| { | |
| string toString(this This)() | |
| { | |
| import std.conv : to; | |
| static if (autoInit) | |
| return to!string(refCountedPayload); | |
| else | |
| { | |
| if (!_refCounted.isInitialized) | |
| return This.stringof ~ "(RefCountedStore(null))"; | |
| else | |
| return to!string(_refCounted._store._payload); | |
| } | |
| } | |
| } | |
| } | |
| /// | |
| @betterC pure @system nothrow @nogc unittest | |
| { | |
| auto rc1 = RefCounted!int(5); | |
| assert(rc1 == 5); | |
| auto rc2 = rc1; | |
| rc2 = 42; | |
| assert(rc1 == 42); | |
| } | |
| // More unit tests below SafeRefCounted | |
| /** | |
| * Like $(LREF safeRefCounted) but used to initialize $(LREF RefCounted) | |
| * instead. Intended for backwards compatibility, otherwise it is preferable | |
| * to use `safeRefCounted`. | |
| */ | |
| RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val) | |
| { | |
| typeof(return) res; | |
| res._refCounted.move(val); | |
| return res; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| static struct File | |
| { | |
| static size_t nDestroyed; | |
| string name; | |
| @disable this(this); // not copyable | |
| ~this() { name = null; ++nDestroyed; } | |
| } | |
| auto file = File("name"); | |
| assert(file.name == "name"); | |
| static assert(!__traits(compiles, {auto file2 = file;})); | |
| assert(File.nDestroyed == 0); | |
| { | |
| import std.algorithm.mutation : move; | |
| auto rcFile = refCounted(move(file)); | |
| assert(rcFile.name == "name"); | |
| assert(File.nDestroyed == 1); | |
| assert(file.name == null); | |
| auto rcFile2 = rcFile; | |
| assert(rcFile.refCountedStore.refCount == 2); | |
| assert(File.nDestroyed == 1); | |
| } | |
| assert(File.nDestroyed == 2); | |
| } | |
| // More unit tests below safeRefCounted |