| // Written in the D programming language. |
| |
| /** |
| This module implements experimental additions/modifications to $(MREF std, _typecons). |
| |
| Use this module to test out new functionality for $(REF wrap, std, _typecons) |
| which allows for a struct to be wrapped against an interface; the |
| implementation in $(MREF std, _typecons) only allows for classes to use the wrap |
| functionality. |
| |
| Source: $(PHOBOSSRC std/experimental/_typecons.d) |
| |
| Copyright: Copyright the respective authors, 2008- |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| Authors: $(HTTP erdani.org, Andrei Alexandrescu), |
| $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski), |
| Don Clugston, |
| Shin Fujishiro, |
| Kenji Hara |
| */ |
| module std.experimental.typecons; |
| |
| import std.meta; // : AliasSeq, allSatisfy; |
| import std.traits; |
| |
| import std.typecons : Tuple, tuple, Bind, DerivedFunctionType, mixinAll, staticIota, |
| GetOverloadedMethods; |
| |
| 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 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 opCast(T)() {} Object instance(); } |
| interface J { @disable 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()); |
| } |
| |
| /* |
| * Determines if the `Source` type satisfies all interface requirements of |
| * `Targets`. |
| */ |
| private template implementsInterface(Source, Targets...) |
| if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) |
| { |
| import std.meta : staticMap; |
| |
| // strict upcast |
| bool implementsInterface()() |
| if (Targets.length == 1 && is(Source : Targets[0])) |
| { |
| return true; |
| } |
| // structural upcast |
| template implementsInterface() |
| if (!allSatisfy!(Bind!(isImplicitlyConvertible, Source), Targets)) |
| { |
| auto implementsInterface() |
| { |
| return hasRequiredMethods!(); |
| } |
| |
| // list of FuncInfo |
| alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!Targets); |
| // list of function symbols |
| alias SourceMembers = GetOverloadedMethods!Source; |
| |
| // Check whether all of SourceMembers satisfy covariance target in |
| // TargetMembers |
| template hasRequiredMethods(size_t i = 0) |
| { |
| static if (i >= TargetMembers.length) |
| enum hasRequiredMethods = true; |
| else |
| { |
| enum foundFunc = findCovariantFunction!(TargetMembers[i], Source, SourceMembers); |
| version (unittest) {} |
| else debug |
| { |
| static if (foundFunc == -1) |
| pragma(msg, "Could not locate matching function for: ", |
| TargetMembers[i].stringof); |
| } |
| enum hasRequiredMethods = |
| foundFunc != -1 && |
| hasRequiredMethods!(i + 1); |
| } |
| } |
| } |
| } |
| // ditto |
| private template implementsInterface(Source, Targets...) |
| if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets)) |
| { |
| import std.meta : staticMap; |
| |
| alias implementsInterface = .implementsInterface!(Source, staticMap!(Unqual, Targets)); |
| } |
| |
| @safe unittest |
| { |
| interface Foo { |
| void foo(); |
| } |
| interface Bar { |
| void bar(); |
| } |
| interface FooBar : Foo, Bar { |
| void foobar(); |
| } |
| |
| struct A { |
| void foo() {} |
| } |
| struct B { |
| void bar() {} |
| void foobar() {} |
| } |
| class C { |
| void foo() {} |
| void bar() {} |
| } |
| struct D { |
| void foo() {} |
| void bar() {} |
| void foobar() {} |
| } |
| // Implements interface |
| static assert(implementsInterface!(A, Foo)); |
| static assert(implementsInterface!(A, const(Foo))); |
| static assert(implementsInterface!(A, immutable(Foo))); |
| // Doesn't implement interface |
| static assert(!implementsInterface!(B, Foo)); |
| static assert(implementsInterface!(B, Bar)); |
| // Implements both interfaces |
| static assert(implementsInterface!(C, Foo)); |
| static assert(implementsInterface!(C, Bar)); |
| static assert(implementsInterface!(C, Foo, Bar)); |
| static assert(implementsInterface!(C, Foo, const(Bar))); |
| static assert(!implementsInterface!(A, Foo, Bar)); |
| static assert(!implementsInterface!(A, Foo, immutable(Bar))); |
| // Implements inherited |
| static assert(implementsInterface!(D, FooBar)); |
| static assert(!implementsInterface!(B, FooBar)); |
| } |
| |
| private enum isInterface(ConceptType) = is(ConceptType == interface); |
| |
| /// |
| template wrap(Targets...) |
| if (Targets.length >= 1 && allSatisfy!(isInterface, Targets)) |
| { |
| import std.meta : ApplyLeft, staticMap; |
| |
| version (StdDdoc) |
| { |
| /** |
| * Wrap src in an anonymous class implementing $(D_PARAM Targets). |
| * |
| * wrap creates an internal wrapper class which implements the |
| * interfaces in `Targets` using the methods of `src`, then returns a |
| * GC-allocated instance of it. |
| * |
| * $(D_PARAM Source) can be either a `class` or a `struct`, but it must |
| * $(I structurally conform) with all the $(D_PARAM Targets) |
| * interfaces; i.e. it must provide concrete methods with compatible |
| * signatures of those in $(D_PARAM Targets). |
| * |
| * If $(D_PARAM Source) is a `struct` then wrapping/unwrapping will |
| * create a copy; it is not possible to affect the original `struct` |
| * through the wrapper. |
| * |
| * The returned object additionally supports $(LREF unwrap). |
| * |
| * Note: |
| * If $(D_PARAM Targets) has only one entry and $(D_PARAM Source) is a |
| * class which explicitly implements it, wrap simply returns src |
| * upcasted to `Targets[0]`. |
| * |
| * Bugs: |
| * wrap does not support interfaces which take their own type as either |
| * a parameter type or return type in any of its methods. |
| * |
| * See_Also: $(LREF unwrap) for examples |
| */ |
| auto wrap(Source)(inout Source src) |
| if (implementsInterface!(Source, Targets)); |
| } |
| |
| static if (!allSatisfy!(isMutable, Targets)) |
| alias wrap = .wrap!(staticMap!(Unqual, Targets)); |
| else |
| { |
| // strict upcast |
| auto wrap(Source)(inout Source src) |
| 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!(ApplyLeft!(isImplicitlyConvertible, Source), Targets)) |
| { |
| auto wrap(inout Source src) |
| { |
| static assert(implementsInterface!(Source, Targets), |
| "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); |
| } |
| |
| // list of FuncInfo |
| alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!(Targets)); |
| // list of function symbols |
| alias SourceMembers = GetOverloadedMethods!Source; |
| |
| static if (is(Source == class) || is(Source == interface)) |
| alias StructuralType = Object; |
| else static if (is(Source == struct)) |
| alias StructuralType = Source; |
| |
| // Check whether all of SourceMembers satisfy covariance target in TargetMembers |
| // Internal wrapper class |
| final class Impl : Structural!StructuralType, 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; } |
| |
| static if (is(Source == class) || is(Source == interface)) |
| { |
| // BUG: making private should work with NVI. |
| protected inout(Object) _wrap_getSource() inout @safe |
| { |
| return dynamicCast!(inout Object)(_wrap_source); |
| } |
| } |
| else |
| { |
| // BUG: making private should work with NVI. |
| protected inout(Source) _wrap_getSource() inout @safe |
| { |
| return _wrap_source; |
| } |
| } |
| |
| import std.conv : to; |
| import std.functional : forward; |
| template generateFun(size_t i) |
| { |
| enum name = TargetMembers[i].name; |
| enum fa = functionAttributes!(TargetMembers[i].type); |
| static args(int num)() |
| { |
| string r; |
| bool first = true; |
| foreach (i; staticIota!(0, num)) |
| { |
| import std.conv : to; |
| r ~= (first ? "" : ", ") ~ " a" ~ (i+1).to!string; |
| first = false; |
| } |
| return r; |
| } |
| static if (fa & FunctionAttribute.property) |
| { |
| static if (Parameters!(TargetMembers[i].type).length == 0) |
| enum fbody = "_wrap_source."~name; |
| else |
| enum fbody = "_wrap_source."~name~" = a1"; |
| } |
| else |
| { |
| enum fbody = "_wrap_source."~name~"("~args!(Parameters!(TargetMembers[i].type).length)~")"; |
| } |
| enum generateFun = |
| "override "~wrapperSignature!(TargetMembers[i]) ~ |
| "{ return "~fbody~"; }"; |
| } |
| |
| public: |
| mixin mixinAll!( |
| staticMap!(generateFun, staticIota!(0, TargetMembers.length))); |
| } |
| } |
| } |
| } |
| |
| // Build a signature that matches the provided function |
| // Each argument will be provided a name in the form a# |
| private template wrapperSignature(alias fun) |
| { |
| enum name = fun.name; |
| enum fa = functionAttributes!(fun.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!(fun.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; |
| } |
| alias param = Parameters!(fun.type); |
| static @property wrapperParameters() |
| { |
| string r; |
| bool first = true; |
| foreach (i, p; param) |
| { |
| import std.conv : to; |
| r ~= (first ? "" : ", ") ~ p.stringof ~ " a" ~ (i+1).to!string; |
| first = false; |
| } |
| return r; |
| } |
| |
| enum wrapperSignature = |
| stc~ReturnType!(fun.type).stringof ~ " " |
| ~ name~"("~wrapperParameters~")"~mod; |
| } |
| |
| @safe unittest |
| { |
| interface M |
| { |
| void f1(); |
| void f2(string[] args, int count); |
| void f3(string[] args, int count) pure const; |
| } |
| |
| alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!M); |
| static assert(wrapperSignature!(TargetMembers[0]) == "void f1()" |
| , wrapperSignature!(TargetMembers[0])); |
| |
| static assert(wrapperSignature!(TargetMembers[1]) == "void f2(string[] a1, int a2)" |
| , wrapperSignature!(TargetMembers[1])); |
| |
| static assert(wrapperSignature!(TargetMembers[2]) == "pure void f3(string[] a1, int a2) const" |
| , wrapperSignature!(TargetMembers[2])); |
| } |
| |
| // Internal class to support dynamic cross-casting |
| private interface Structural(T) |
| { |
| inout(T) _wrap_getSource() inout @safe pure nothrow; |
| } |
| |
| private string unwrapExceptionText(Source, Target)() |
| { |
| return Target.stringof~ " not wrapped into "~ Source.stringof; |
| } |
| |
| version (StdDdoc) |
| { |
| /** |
| * Extract object previously wrapped by $(LREF wrap). |
| * |
| * Params: |
| * Target = type of wrapped object |
| * src = wrapper object returned by $(LREF wrap) |
| * |
| * Returns: the wrapped object, or null if src is not a wrapper created |
| * by $(LREF wrap) and $(D_PARAM Target) is a class |
| * |
| * Throws: $(REF ConvException, std, conv) when attempting to extract a |
| * struct which is not the wrapped type |
| * |
| * See_also: $(LREF wrap) |
| */ |
| public inout(Target) unwrap(Target, Source)(inout Source src); |
| } |
| |
| /// |
| @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; } |
| } |
| struct HumanStructure |
| { |
| int quack() { return 3; } |
| @property int height() { return 30; } |
| } |
| |
| Duck d1 = new Duck(); |
| Human h1 = new Human(); |
| HumanStructure hs1; |
| |
| interface Refreshable |
| { |
| int refresh(); |
| } |
| // does not have structural conformance |
| static assert(!__traits(compiles, d1.wrap!Refreshable)); |
| static assert(!__traits(compiles, h1.wrap!Refreshable)); |
| static assert(!__traits(compiles, hs1.wrap!Refreshable)); |
| |
| // 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; |
| Quack qhs = hs1.wrap!Quack; |
| assert(qh.quack() == 2); // calls Human.quack |
| assert(qhs.quack() == 3); // calls HumanStructure.quack |
| // structural downcast |
| Human h2 = qh.unwrap!Human; |
| HumanStructure hs2 = qhs.unwrap!HumanStructure; |
| assert(h2 is h1); |
| assert(hs2 is hs1); |
| |
| // structural upcast (two steps) |
| Quack qx = h1.wrap!Quack; // Human -> Quack |
| Quack qxs = hs1.wrap!Quack; // HumanStructure -> Quack |
| Flyer fx = qx.wrap!Flyer; // Quack -> Flyer |
| Flyer fxs = qxs.wrap!Flyer; // Quack -> Flyer |
| assert(fx.height == 20); // calls Human.height |
| assert(fxs.height == 30); // calls HumanStructure.height |
| // strucural downcast (two steps) |
| Quack qy = fx.unwrap!Quack; // Flyer -> Quack |
| Quack qys = fxs.unwrap!Quack; // Flyer -> Quack |
| Human hy = qy.unwrap!Human; // Quack -> Human |
| HumanStructure hys = qys.unwrap!HumanStructure; // Quack -> HumanStructure |
| assert(hy is h1); |
| assert(hys is hs1); |
| // strucural downcast (one step) |
| Human hz = fx.unwrap!Human; // Flyer -> Human |
| HumanStructure hzs = fxs.unwrap!HumanStructure; // Flyer -> HumanStructure |
| assert(hz is h1); |
| assert(hzs is hs1); |
| } |
| |
| /// |
| @system unittest |
| { |
| import std.traits : functionAttributes, FunctionAttribute; |
| 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); |
| } |
| |
| template unwrap(Target) |
| { |
| static if (!isMutable!Target) |
| alias unwrap = .unwrap!(Unqual!Target); |
| else |
| { |
| // strict downcast |
| auto unwrap(Source)(inout Source src) |
| if (is(Target : Source)) |
| { |
| alias T = Select!(is(Source == shared), shared Target, Target); |
| return dynamicCast!(inout T)(src); |
| } |
| |
| // structural downcast for struct target |
| auto unwrap(Source)(inout Source src) |
| if (is(Target == struct)) |
| { |
| alias T = Select!(is(Source == shared), shared Target, Target); |
| auto upCastSource = dynamicCast!Object(src); // remove qualifier |
| do |
| { |
| if (auto a = dynamicCast!(Structural!Object)(upCastSource)) |
| { |
| upCastSource = a._wrap_getSource(); |
| } |
| else if (auto a = dynamicCast!(Structural!T)(upCastSource)) |
| { |
| return a._wrap_getSource(); |
| } |
| else |
| { |
| static if (hasMember!(Source, "_wrap_getSource")) |
| return unwrap!Target(src._wrap_getSource()); |
| else |
| break; |
| } |
| } while (upCastSource); |
| import std.conv : ConvException; |
| throw new ConvException(unwrapExceptionText!(Source,Target)); |
| } |
| // structural downcast for class target |
| auto unwrap(Source)(inout Source src) |
| if (!is(Target : Source) && !is(Target == struct)) |
| { |
| alias T = Select!(is(Source == shared), shared Target, Target); |
| Object upCastSource = dynamicCast!(Object)(src); // remove qualifier |
| do |
| { |
| // Unwrap classes |
| if (auto a = dynamicCast!(Structural!Object)(upCastSource)) |
| { |
| if (auto d = dynamicCast!(inout T)(upCastSource = a._wrap_getSource())) |
| return d; |
| } |
| // Unwrap a structure of type T |
| else if (auto a = dynamicCast!(Structural!T)(upCastSource)) |
| { |
| return a._wrap_getSource(); |
| } |
| // Unwrap class that already inherited from interface |
| else if (auto d = dynamicCast!(inout T)(upCastSource)) |
| { |
| return d; |
| } |
| // Recurse to find the struct Target within a wrapped tree |
| else |
| { |
| static if (hasMember!(Source, "_wrap_getSource")) |
| return unwrap!Target(src._wrap_getSource()); |
| else |
| break; |
| } |
| } while (upCastSource); |
| return null; |
| } |
| } |
| } |
| |
| @system unittest |
| { |
| // Validate const/immutable |
| 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); |
| } |
| } |
| @system unittest |
| { |
| // Bugzilla 10377 |
| import std.algorithm, std.range; |
| |
| 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])); |
| } |
| @system unittest |
| { |
| // Bugzilla 10536 |
| 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); |
| } |
| |
| // Concat all Targets function members into one tuple |
| private template ConcatInterfaceMembers(Targets...) |
| { |
| static if (Targets.length == 0) |
| alias ConcatInterfaceMembers = AliasSeq!(); |
| else static if (Targets.length == 1) |
| alias ConcatInterfaceMembers |
| = AliasSeq!(GetOverloadedMethods!(Targets[0])); |
| else |
| alias ConcatInterfaceMembers = AliasSeq!( |
| GetOverloadedMethods!(Targets[0]), |
| ConcatInterfaceMembers!(Targets[1..$])); |
| } |
| // Remove duplicated functions based on the identifier name and function type covariance |
| private template UniqMembers(members...) |
| { |
| template FuncInfo(string s, F) |
| { |
| enum name = s; |
| alias type = F; |
| } |
| |
| static if (members.length == 0) |
| alias UniqMembers = 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 static if |
| (__traits(identifier, func) == __traits(identifier, mem[i]) && |
| !is(DerivedFunctionType!(type, FunctionTypeOf!(mem[i])) == void)) |
| { |
| enum ptrdiff_t check = i; |
| } |
| else |
| enum ptrdiff_t check = 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 = UniqMembers!(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 UniqMembers = AliasSeq!(FuncInfo!(name, F), remain[1 .. $]); |
| } |
| else |
| alias UniqMembers = AliasSeq!(FuncInfo!(name, typex), remain); |
| } |
| else |
| { |
| alias UniqMembers = AliasSeq!(FuncInfo!(name, type), UniqMembers!(members[1 .. $])); |
| } |
| } |
| } |
| |
| // 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; |
| } |
| |
| /** |
| Type constructor for final (aka head-const) variables. |
| |
| Final variables cannot be directly mutated or rebound, but references |
| reached through the variable are typed with their original mutability. |
| It is equivalent to `final` variables in D1 and Java, as well as |
| `readonly` variables in C#. |
| |
| When `T` is a `const` or `immutable` type, `Final` aliases |
| to `T`. |
| */ |
| template Final(T) |
| { |
| static if (is(T == const) || is(T == immutable)) |
| alias Final = T; |
| else |
| { |
| struct Final |
| { |
| import std.typecons : Proxy; |
| |
| private T final_value; |
| mixin Proxy!final_value; |
| |
| /** |
| * Construction is forwarded to the underlying type. |
| */ |
| this(T other) |
| { |
| this.final_value = other; |
| } |
| |
| /// Ditto |
| this(Args...)(auto ref Args args) |
| if (__traits(compiles, T(args))) |
| { |
| static assert((!is(T == struct) && !is(T == union)) || !isNested!T, |
| "Non-static nested type " ~ fullyQualifiedName!T ~ " must be " ~ |
| "constructed explicitly at the call-site (e.g. auto s = " ~ |
| "makeFinal(" ~ T.stringof ~ "(...));)"); |
| this.final_value = T(args); |
| } |
| |
| // Attaching function attributes gives less noisy error messages |
| pure nothrow @safe @nogc |
| { |
| /++ |
| + All operators, including member access, are forwarded to the |
| + underlying value of type `T` except for these mutating operators, |
| + which are disabled. |
| +/ |
| void opAssign(Other)(Other other) |
| { |
| static assert(0, typeof(this).stringof ~ |
| " cannot be reassigned."); |
| } |
| |
| /// Ditto |
| void opOpAssign(string op, Other)(Other other) |
| { |
| static assert(0, typeof(this).stringof ~ |
| " cannot be reassigned."); |
| } |
| |
| /// Ditto |
| void opUnary(string op : "--")() |
| { |
| static assert(0, typeof(this).stringof ~ |
| " cannot be mutated."); |
| } |
| |
| /// Ditto |
| void opUnary(string op : "++")() |
| { |
| static assert(0, typeof(this).stringof ~ |
| " cannot be mutated."); |
| } |
| } |
| |
| /** |
| * |
| * `Final!T` implicitly converts to an rvalue of type `T` through |
| * `AliasThis`. |
| */ |
| inout(T) final_get() inout |
| { |
| return final_value; |
| } |
| |
| /// Ditto |
| alias final_get this; |
| |
| /// Ditto |
| auto ref opUnary(string op)() |
| if (__traits(compiles, mixin(op ~ "T.init"))) |
| { |
| return mixin(op ~ "this.final_value"); |
| } |
| } |
| } |
| } |
| |
| /// Ditto |
| Final!T makeFinal(T)(T t) |
| { |
| return Final!T(t); |
| } |
| |
| /// `Final` can be used to create class references which cannot be rebound: |
| pure nothrow @safe unittest |
| { |
| static class A |
| { |
| int i; |
| |
| this(int i) pure nothrow @nogc @safe |
| { |
| this.i = i; |
| } |
| } |
| |
| auto a = makeFinal(new A(42)); |
| assert(a.i == 42); |
| |
| //a = new A(24); // Reassignment is illegal, |
| a.i = 24; // But fields are still mutable. |
| |
| assert(a.i == 24); |
| } |
| |
| /// `Final` can also be used to create read-only data fields without using transitive immutability: |
| pure nothrow @safe unittest |
| { |
| static class A |
| { |
| int i; |
| |
| this(int i) pure nothrow @nogc @safe |
| { |
| this.i = i; |
| } |
| } |
| |
| static class B |
| { |
| Final!A a; |
| |
| this(A a) pure nothrow @nogc @safe |
| { |
| this.a = a; // Construction, thus allowed. |
| } |
| } |
| |
| auto b = new B(new A(42)); |
| assert(b.a.i == 42); |
| |
| // b.a = new A(24); // Reassignment is illegal, |
| b.a.i = 24; // but `a` is still mutable. |
| |
| assert(b.a.i == 24); |
| } |
| |
| pure nothrow @safe unittest |
| { |
| static class A { int i; } |
| static assert(!is(Final!A == A)); |
| static assert(is(Final!(const A) == const A)); |
| static assert(is(Final!(immutable A) == immutable A)); |
| |
| Final!A a = new A; |
| static assert(!__traits(compiles, a = new A)); |
| |
| static void foo(ref A a) pure nothrow @safe @nogc {} |
| static assert(!__traits(compiles, foo(a))); |
| |
| assert(a.i == 0); |
| a.i = 42; |
| assert(a.i == 42); |
| |
| Final!int i = 42; |
| static assert(!__traits(compiles, i = 24)); |
| static assert(!__traits(compiles, --i)); |
| static assert(!__traits(compiles, ++i)); |
| assert(i == 42); |
| int iCopy = i; |
| assert(iCopy == 42); |
| iCopy = -i; // non-mutating unary operators must work |
| assert(iCopy == -42); |
| |
| static struct S |
| { |
| int i; |
| |
| pure nothrow @safe @nogc: |
| this(int i){} |
| this(string s){} |
| this(int i, string s, float f){ this.i = i; } |
| } |
| |
| Final!S sint = 42; |
| Final!S sstr = "foo"; |
| static assert(!__traits(compiles, sint = sstr)); |
| |
| auto sboth = Final!S(42, "foo", 3.14); |
| assert(sboth.i == 42); |
| |
| sboth.i = 24; |
| assert(sboth.i == 24); |
| |
| struct NestedS |
| { |
| int i; |
| int get() pure nothrow @safe @nogc { return sboth.i + i; } |
| } |
| |
| // Nested structs must be constructed at the call-site |
| static assert(!__traits(compiles, Final!NestedS(6))); |
| auto s = makeFinal(NestedS(6)); |
| assert(s.i == 6); |
| assert(s.get == 30); |
| |
| class NestedC |
| { |
| int i; |
| |
| pure nothrow @safe @nogc: |
| this(int i) { this.i = i; } |
| int get() { return sboth.i + i; } |
| } |
| |
| auto c = makeFinal(new NestedC(6)); |
| assert(c.i == 6); |
| assert(c.get == 30); |
| } |
| |
| pure nothrow @safe unittest |
| { |
| auto arr = makeFinal([1, 2, 3]); |
| static assert(!__traits(compiles, arr = null)); |
| static assert(!__traits(compiles, arr ~= 4)); |
| assert((arr ~ 4) == [1, 2, 3, 4]); |
| } |
| |
| // issue 17270 |
| pure nothrow @nogc @system unittest |
| { |
| int i = 1; |
| Final!(int*) fp = &i; |
| assert(*fp == 1); |
| static assert(!__traits(compiles, |
| fp = &i // direct assignment |
| )); |
| static assert(is(typeof(*fp) == int)); |
| *fp = 2; // indirect assignment |
| assert(*fp == 2); |
| int* p = fp; |
| assert(*p == 2); |
| } |
| |
| pure nothrow @system unittest |
| { |
| Final!(int[]) arr; |
| // static assert(!__traits(compiles, |
| // arr.length = 10; // bug! |
| // )); |
| static assert(!__traits(compiles, |
| arr.ptr = null |
| )); |
| static assert(!__traits(compiles, |
| arr.ptr++ |
| )); |
| } |