1 // Written in the D programming language.
4 This module implements experimental additions/modifications to $(MREF std, _typecons).
6 Use this module to test out new functionality for $(REF wrap, std, _typecons)
7 which allows for a struct to be wrapped against an interface; the
8 implementation in $(MREF std, _typecons) only allows for classes to use the wrap
11 Source: $(PHOBOSSRC std/experimental/_typecons.d)
13 Copyright: Copyright the respective authors, 2008-
14 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
15 Authors: $(HTTP erdani.org, Andrei Alexandrescu),
16 $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski),
21 module std.experimental.typecons;
23 import std.meta; // : AliasSeq, allSatisfy;
26 import std.typecons : Tuple, tuple, Bind, DerivedFunctionType,
27 isImplicitlyConvertible, mixinAll, staticIota,
32 pragma(mangle, "_d_toObject")
33 extern(C) pure nothrow Object typecons_d_toObject(void* p);
37 * Avoids opCast operator overloading.
39 private template dynamicCast(T)
40 if (is(T == class) || is(T == interface))
43 T dynamicCast(S)(inout S source)
44 if (is(S == class) || is(S == interface))
46 static if (is(Unqual!S : Unqual!T))
48 import std.traits : QualifierOf;
49 alias Qual = QualifierOf!S; // SharedOf or MutableOf
50 alias TmpT = Qual!(Unqual!T);
51 inout(TmpT) tmp = source; // bypass opCast by implicit conversion
52 return *cast(T*)(&tmp); // + variable pointer cast + dereference
56 return cast(T) typecons_d_toObject(*cast(void**)(&source));
63 class C { @disable opCast(T)() {} }
65 static assert(!__traits(compiles, cast(Object) c));
66 auto o = dynamicCast!Object(c);
69 interface I { @disable opCast(T)() {} Object instance(); }
70 interface J { @disable opCast(T)() {} Object instance(); }
71 class D : I, J { Object instance() { return this; } }
73 static assert(!__traits(compiles, cast(J) i));
74 J j = dynamicCast!J(i);
75 assert(i.instance() is j.instance());
79 * Determines if the `Source` type satisfies all interface requirements of
82 private template implementsInterface(Source, Targets...)
83 if (Targets.length >= 1 && allSatisfy!(isMutable, Targets))
85 import std.meta : staticMap;
88 bool implementsInterface()()
89 if (Targets.length == 1 && is(Source : Targets[0]))
94 template implementsInterface()
95 if (!allSatisfy!(Bind!(isImplicitlyConvertible, Source), Targets))
97 auto implementsInterface()
99 return hasRequiredMethods!();
103 alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!Targets);
104 // list of function symbols
105 alias SourceMembers = GetOverloadedMethods!Source;
107 // Check whether all of SourceMembers satisfy covariance target in
109 template hasRequiredMethods(size_t i = 0)
111 static if (i >= TargetMembers.length)
112 enum hasRequiredMethods = true;
115 enum foundFunc = findCovariantFunction!(TargetMembers[i], Source, SourceMembers);
118 static if (foundFunc == -1)
119 pragma(msg, "Could not locate matching function for: ",
120 TargetMembers[i].stringof);
122 enum hasRequiredMethods =
124 hasRequiredMethods!(i + 1);
130 private template implementsInterface(Source, Targets...)
131 if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets))
133 import std.meta : staticMap;
135 alias implementsInterface = .implementsInterface!(Source, staticMap!(Unqual, Targets));
146 interface FooBar : Foo, Bar {
166 // Implements interface
167 static assert(implementsInterface!(A, Foo));
168 static assert(implementsInterface!(A, const(Foo)));
169 static assert(implementsInterface!(A, immutable(Foo)));
170 // Doesn't implement interface
171 static assert(!implementsInterface!(B, Foo));
172 static assert(implementsInterface!(B, Bar));
173 // Implements both interfaces
174 static assert(implementsInterface!(C, Foo));
175 static assert(implementsInterface!(C, Bar));
176 static assert(implementsInterface!(C, Foo, Bar));
177 static assert(implementsInterface!(C, Foo, const(Bar)));
178 static assert(!implementsInterface!(A, Foo, Bar));
179 static assert(!implementsInterface!(A, Foo, immutable(Bar)));
180 // Implements inherited
181 static assert(implementsInterface!(D, FooBar));
182 static assert(!implementsInterface!(B, FooBar));
185 private enum isInterface(ConceptType) = is(ConceptType == interface);
188 template wrap(Targets...)
189 if (Targets.length >= 1 && allSatisfy!(isInterface, Targets))
191 import std.meta : ApplyLeft, staticMap;
196 * Wrap src in an anonymous class implementing $(D_PARAM Targets).
198 * wrap creates an internal wrapper class which implements the
199 * interfaces in `Targets` using the methods of `src`, then returns a
200 * GC-allocated instance of it.
202 * $(D_PARAM Source) can be either a `class` or a `struct`, but it must
203 * $(I structurally conform) with all the $(D_PARAM Targets)
204 * interfaces; i.e. it must provide concrete methods with compatible
205 * signatures of those in $(D_PARAM Targets).
207 * If $(D_PARAM Source) is a `struct` then wrapping/unwrapping will
208 * create a copy; it is not possible to affect the original `struct`
209 * through the wrapper.
211 * The returned object additionally supports $(LREF unwrap).
214 * If $(D_PARAM Targets) has only one entry and $(D_PARAM Source) is a
215 * class which explicitly implements it, wrap simply returns src
216 * upcasted to `Targets[0]`.
219 * wrap does not support interfaces which take their own type as either
220 * a parameter type or return type in any of its methods.
222 * See_Also: $(LREF unwrap) for examples
224 auto wrap(Source)(inout Source src)
225 if (implementsInterface!(Source, Targets));
228 static if (!allSatisfy!(isMutable, Targets))
229 alias wrap = .wrap!(staticMap!(Unqual, Targets));
233 auto wrap(Source)(inout Source src)
234 if (Targets.length == 1 && is(Source : Targets[0]))
236 alias T = Select!(is(Source == shared), shared Targets[0], Targets[0]);
237 return dynamicCast!(inout T)(src);
241 template wrap(Source)
242 if (!allSatisfy!(ApplyLeft!(isImplicitlyConvertible, Source), Targets))
244 auto wrap(inout Source src)
246 static assert(implementsInterface!(Source, Targets),
247 "Source "~Source.stringof~
248 " does not have structural conformance to "~
251 alias T = Select!(is(Source == shared), shared Impl, Impl);
252 return new inout T(src);
256 alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!(Targets));
257 // list of function symbols
258 alias SourceMembers = GetOverloadedMethods!Source;
260 static if (is(Source == class) || is(Source == interface))
261 alias StructuralType = Object;
262 else static if (is(Source == struct))
263 alias StructuralType = Source;
265 // Check whether all of SourceMembers satisfy covariance target in TargetMembers
266 // Internal wrapper class
267 final class Impl : Structural!StructuralType, Targets
272 this( inout Source s) inout @safe pure nothrow { _wrap_source = s; }
273 this(shared inout Source s) shared inout @safe pure nothrow { _wrap_source = s; }
275 static if (is(Source == class) || is(Source == interface))
277 // BUG: making private should work with NVI.
278 protected inout(Object) _wrap_getSource() inout @safe
280 return dynamicCast!(inout Object)(_wrap_source);
285 // BUG: making private should work with NVI.
286 protected inout(Source) _wrap_getSource() inout @safe
292 import std.conv : to;
293 import std.functional : forward;
294 template generateFun(size_t i)
296 enum name = TargetMembers[i].name;
297 enum fa = functionAttributes!(TargetMembers[i].type);
298 static args(int num)()
302 foreach (i; staticIota!(0, num))
304 import std.conv : to;
305 r ~= (first ? "" : ", ") ~ " a" ~ (i+1).to!string;
310 static if (fa & FunctionAttribute.property)
312 static if (Parameters!(TargetMembers[i].type).length == 0)
313 enum fbody = "_wrap_source."~name;
315 enum fbody = "_wrap_source."~name~" = a1";
319 enum fbody = "_wrap_source."~name~"("~args!(Parameters!(TargetMembers[i].type).length)~")";
322 "override "~wrapperSignature!(TargetMembers[i]) ~
323 "{ return "~fbody~"; }";
328 staticMap!(generateFun, staticIota!(0, TargetMembers.length)));
334 // Build a signature that matches the provided function
335 // Each argument will be provided a name in the form a#
336 private template wrapperSignature(alias fun)
338 enum name = fun.name;
339 enum fa = functionAttributes!(fun.type);
340 static @property stc()
343 if (fa & FunctionAttribute.property) r ~= "@property ";
344 if (fa & FunctionAttribute.ref_) r ~= "ref ";
345 if (fa & FunctionAttribute.pure_) r ~= "pure ";
346 if (fa & FunctionAttribute.nothrow_) r ~= "nothrow ";
347 if (fa & FunctionAttribute.trusted) r ~= "@trusted ";
348 if (fa & FunctionAttribute.safe) r ~= "@safe ";
351 static @property mod()
353 alias type = AliasSeq!(fun.type)[0];
355 static if (is(type == immutable)) r ~= " immutable";
358 static if (is(type == shared)) r ~= " shared";
359 static if (is(type == const)) r ~= " const";
360 else static if (is(type == inout)) r ~= " inout";
365 alias param = Parameters!(fun.type);
366 static @property wrapperParameters()
370 foreach (i, p; param)
372 import std.conv : to;
373 r ~= (first ? "" : ", ") ~ p.stringof ~ " a" ~ (i+1).to!string;
379 enum wrapperSignature =
380 stc~ReturnType!(fun.type).stringof ~ " "
381 ~ name~"("~wrapperParameters~")"~mod;
389 void f2(string[] args, int count);
390 void f3(string[] args, int count) pure const;
393 alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!M);
394 static assert(wrapperSignature!(TargetMembers[0]) == "void f1()"
395 , wrapperSignature!(TargetMembers[0]));
397 static assert(wrapperSignature!(TargetMembers[1]) == "void f2(string[] a1, int a2)"
398 , wrapperSignature!(TargetMembers[1]));
400 static assert(wrapperSignature!(TargetMembers[2]) == "pure void f3(string[] a1, int a2) const"
401 , wrapperSignature!(TargetMembers[2]));
404 // Internal class to support dynamic cross-casting
405 private interface Structural(T)
407 inout(T) _wrap_getSource() inout @safe pure nothrow;
410 private string unwrapExceptionText(Source, Target)()
412 return Target.stringof~ " not wrapped into "~ Source.stringof;
418 * Extract object previously wrapped by $(LREF wrap).
421 * Target = type of wrapped object
422 * src = wrapper object returned by $(LREF wrap)
424 * Returns: the wrapped object, or null if src is not a wrapper created
425 * by $(LREF wrap) and $(D_PARAM Target) is a class
427 * Throws: $(REF ConvException, std, conv) when attempting to extract a
428 * struct which is not the wrapped type
430 * See_also: $(LREF wrap)
432 public inout(Target) unwrap(Target, Source)(inout Source src);
441 @property int height();
445 @property int height();
449 int quack() { return 1; }
450 @property int height() { return 10; }
454 int quack() { return 2; }
455 @property int height() { return 20; }
457 struct HumanStructure
459 int quack() { return 3; }
460 @property int height() { return 30; }
463 Duck d1 = new Duck();
464 Human h1 = new Human();
467 interface Refreshable
471 // does not have structural conformance
472 static assert(!__traits(compiles, d1.wrap!Refreshable));
473 static assert(!__traits(compiles, h1.wrap!Refreshable));
474 static assert(!__traits(compiles, hs1.wrap!Refreshable));
477 Quack qd = d1.wrap!Quack;
479 assert(qd.quack() == 1); // calls Duck.quack
481 Duck d2 = qd.unwrap!Duck;
485 Quack qh = h1.wrap!Quack;
486 Quack qhs = hs1.wrap!Quack;
487 assert(qh.quack() == 2); // calls Human.quack
488 assert(qhs.quack() == 3); // calls HumanStructure.quack
489 // structural downcast
490 Human h2 = qh.unwrap!Human;
491 HumanStructure hs2 = qhs.unwrap!HumanStructure;
495 // structural upcast (two steps)
496 Quack qx = h1.wrap!Quack; // Human -> Quack
497 Quack qxs = hs1.wrap!Quack; // HumanStructure -> Quack
498 Flyer fx = qx.wrap!Flyer; // Quack -> Flyer
499 Flyer fxs = qxs.wrap!Flyer; // Quack -> Flyer
500 assert(fx.height == 20); // calls Human.height
501 assert(fxs.height == 30); // calls HumanStructure.height
502 // strucural downcast (two steps)
503 Quack qy = fx.unwrap!Quack; // Flyer -> Quack
504 Quack qys = fxs.unwrap!Quack; // Flyer -> Quack
505 Human hy = qy.unwrap!Human; // Quack -> Human
506 HumanStructure hys = qys.unwrap!HumanStructure; // Quack -> HumanStructure
509 // strucural downcast (one step)
510 Human hz = fx.unwrap!Human; // Flyer -> Human
511 HumanStructure hzs = fxs.unwrap!HumanStructure; // Flyer -> HumanStructure
519 import std.traits : functionAttributes, FunctionAttribute;
520 interface A { int run(); }
521 interface B { int stop(); @property int status(); }
524 int run() { return 1; }
525 int stop() { return 2; }
526 @property int status() { return 3; }
530 auto ab = x.wrap!(A, B);
533 assert(a.run() == 1);
534 assert(b.stop() == 2);
535 assert(b.status == 3);
536 static assert(functionAttributes!(typeof(ab).status) & FunctionAttribute.property);
539 template unwrap(Target)
541 static if (!isMutable!Target)
542 alias unwrap = .unwrap!(Unqual!Target);
546 auto unwrap(Source)(inout Source src)
547 if (is(Target : Source))
549 alias T = Select!(is(Source == shared), shared Target, Target);
550 return dynamicCast!(inout T)(src);
553 // structural downcast for struct target
554 auto unwrap(Source)(inout Source src)
555 if (is(Target == struct))
557 alias T = Select!(is(Source == shared), shared Target, Target);
558 auto upCastSource = dynamicCast!Object(src); // remove qualifier
561 if (auto a = dynamicCast!(Structural!Object)(upCastSource))
563 upCastSource = a._wrap_getSource();
565 else if (auto a = dynamicCast!(Structural!T)(upCastSource))
567 return a._wrap_getSource();
571 static if (hasMember!(Source, "_wrap_getSource"))
572 return unwrap!Target(src._wrap_getSource());
576 } while (upCastSource);
577 import std.conv : ConvException;
578 throw new ConvException(unwrapExceptionText!(Source,Target));
580 // structural downcast for class target
581 auto unwrap(Source)(inout Source src)
582 if (!is(Target : Source) && !is(Target == struct))
584 alias T = Select!(is(Source == shared), shared Target, Target);
585 Object upCastSource = dynamicCast!(Object)(src); // remove qualifier
589 if (auto a = dynamicCast!(Structural!Object)(upCastSource))
591 if (auto d = dynamicCast!(inout T)(upCastSource = a._wrap_getSource()))
594 // Unwrap a structure of type T
595 else if (auto a = dynamicCast!(Structural!T)(upCastSource))
597 return a._wrap_getSource();
599 // Unwrap class that already inherited from interface
600 else if (auto d = dynamicCast!(inout T)(upCastSource))
604 // Recurse to find the struct Target within a wrapped tree
607 static if (hasMember!(Source, "_wrap_getSource"))
608 return unwrap!Target(src._wrap_getSource());
612 } while (upCastSource);
620 // Validate const/immutable
623 int draw() { return 1; }
624 int draw(int v) { return v; }
626 int draw() const { return 2; }
627 int draw() shared { return 3; }
628 int draw() shared const { return 4; }
629 int draw() immutable { return 5; }
636 int draw() shared const;
637 int draw() immutable;
645 auto sa = new shared A();
646 auto ia = new immutable A();
648 Drawable md = ma.wrap!Drawable;
649 const Drawable cd = ma.wrap!Drawable;
650 shared Drawable sd = sa.wrap!Drawable;
651 shared const Drawable scd = sa.wrap!Drawable;
652 immutable Drawable id = ia.wrap!Drawable;
653 assert( md.draw() == 1);
654 assert( cd.draw() == 2);
655 assert( sd.draw() == 3);
656 assert(scd.draw() == 4);
657 assert( id.draw() == 5);
660 Drawable2 d = ma.wrap!Drawable2;
661 static assert(!__traits(compiles, d.draw()));
662 assert(d.draw(10) == 10);
668 import std.algorithm, std.range;
670 interface MyInputRange(T)
674 @property bool empty();
677 //auto o = iota(0,10,1).inputRangeObject();
678 //pragma(msg, __traits(allMembers, typeof(o)));
679 auto r = iota(0,10,1).inputRangeObject().wrap!(MyInputRange!int)();
680 assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
691 int foo() { return 1; }
692 @disable void opCast(T, this X)(); // !
695 Interface i = new Pluggable().wrap!Interface;
696 assert(i.foo() == 1);
708 int opDispatch(string name, A...)(A args) { return 100; }
711 Interface i = wrap!Interface(new Pluggable());
712 assert(i.foo() == 100);
713 assert(i.bar(10) == 100);
716 // Concat all Targets function members into one tuple
717 private template ConcatInterfaceMembers(Targets...)
719 static if (Targets.length == 0)
720 alias ConcatInterfaceMembers = AliasSeq!();
721 else static if (Targets.length == 1)
722 alias ConcatInterfaceMembers
723 = AliasSeq!(GetOverloadedMethods!(Targets[0]));
725 alias ConcatInterfaceMembers = AliasSeq!(
726 GetOverloadedMethods!(Targets[0]),
727 ConcatInterfaceMembers!(Targets[1..$]));
729 // Remove duplicated functions based on the identifier name and function type covariance
730 private template UniqMembers(members...)
732 template FuncInfo(string s, F)
738 static if (members.length == 0)
739 alias UniqMembers = AliasSeq!();
742 alias func = members[0];
743 enum name = __traits(identifier, func);
744 alias type = FunctionTypeOf!func;
745 template check(size_t i, mem...)
747 static if (i >= mem.length)
748 enum ptrdiff_t check = -1;
750 (__traits(identifier, func) == __traits(identifier, mem[i]) &&
751 !is(DerivedFunctionType!(type, FunctionTypeOf!(mem[i])) == void))
753 enum ptrdiff_t check = i;
756 enum ptrdiff_t check = check!(i + 1, mem);
758 enum ptrdiff_t x = 1 + check!(0, members[1 .. $]);
761 alias typex = DerivedFunctionType!(type, FunctionTypeOf!(members[x]));
762 alias remain = UniqMembers!(members[1 .. x], members[x + 1 .. $]);
764 static if (remain.length >= 1 && remain[0].name == name &&
765 !is(DerivedFunctionType!(typex, remain[0].type) == void))
767 alias F = DerivedFunctionType!(typex, remain[0].type);
768 alias UniqMembers = AliasSeq!(FuncInfo!(name, F), remain[1 .. $]);
771 alias UniqMembers = AliasSeq!(FuncInfo!(name, typex), remain);
775 alias UniqMembers = AliasSeq!(FuncInfo!(name, type), UniqMembers!(members[1 .. $]));
780 // find a function from Fs that has same identifier and covariant type with f
781 private template findCovariantFunction(alias finfo, Source, Fs...)
783 template check(size_t i = 0)
785 static if (i >= Fs.length)
786 enum ptrdiff_t check = -1;
789 enum ptrdiff_t check =
790 (finfo.name == __traits(identifier, Fs[i])) &&
791 isCovariantWith!(FunctionTypeOf!(Fs[i]), finfo.type)
796 static if (x == -1 && is(typeof(Source.opDispatch)))
798 alias Params = Parameters!(finfo.type);
799 enum ptrdiff_t findCovariantFunction =
800 is(typeof(( Source).init.opDispatch!(finfo.name)(Params.init))) ||
801 is(typeof(( const Source).init.opDispatch!(finfo.name)(Params.init))) ||
802 is(typeof(( immutable Source).init.opDispatch!(finfo.name)(Params.init))) ||
803 is(typeof(( shared Source).init.opDispatch!(finfo.name)(Params.init))) ||
804 is(typeof((shared const Source).init.opDispatch!(finfo.name)(Params.init)))
805 ? ptrdiff_t.max : -1;
808 enum ptrdiff_t findCovariantFunction = x;
812 Type constructor for final (aka head-const) variables.
814 Final variables cannot be directly mutated or rebound, but references
815 reached through the variable are typed with their original mutability.
816 It is equivalent to `final` variables in D1 and Java, as well as
817 `readonly` variables in C#.
819 When `T` is a `const` or `immutable` type, `Final` aliases
824 static if (is(T == const) || is(T == immutable))
830 import std.typecons : Proxy;
832 private T final_value;
833 mixin Proxy!final_value;
836 * Construction is forwarded to the underlying type.
840 this.final_value = other;
844 this(Args...)(auto ref Args args)
845 if (__traits(compiles, T(args)))
847 static assert((!is(T == struct) && !is(T == union)) || !isNested!T,
848 "Non-static nested type " ~ fullyQualifiedName!T ~ " must be " ~
849 "constructed explicitly at the call-site (e.g. auto s = " ~
850 "makeFinal(" ~ T.stringof ~ "(...));)");
851 this.final_value = T(args);
854 // Attaching function attributes gives less noisy error messages
855 pure nothrow @safe @nogc
858 + All operators, including member access, are forwarded to the
859 + underlying value of type `T` except for these mutating operators,
860 + which are disabled.
862 void opAssign(Other)(Other other)
864 static assert(0, typeof(this).stringof ~
865 " cannot be reassigned.");
869 void opOpAssign(string op, Other)(Other other)
871 static assert(0, typeof(this).stringof ~
872 " cannot be reassigned.");
876 void opUnary(string op : "--")()
878 static assert(0, typeof(this).stringof ~
879 " cannot be mutated.");
883 void opUnary(string op : "++")()
885 static assert(0, typeof(this).stringof ~
886 " cannot be mutated.");
892 * `Final!T` implicitly converts to an rvalue of type `T` through
895 inout(T) final_get() inout
901 alias final_get this;
904 auto ref opUnary(string op)()
905 if (__traits(compiles, mixin(op ~ "T.init")))
907 return mixin(op ~ "this.final_value");
914 Final!T makeFinal(T)(T t)
919 /// `Final` can be used to create class references which cannot be rebound:
920 pure nothrow @safe unittest
926 this(int i) pure nothrow @nogc @safe
932 auto a = makeFinal(new A(42));
935 //a = new A(24); // Reassignment is illegal,
936 a.i = 24; // But fields are still mutable.
941 /// `Final` can also be used to create read-only data fields without using transitive immutability:
942 pure nothrow @safe unittest
948 this(int i) pure nothrow @nogc @safe
958 this(A a) pure nothrow @nogc @safe
960 this.a = a; // Construction, thus allowed.
964 auto b = new B(new A(42));
967 // b.a = new A(24); // Reassignment is illegal,
968 b.a.i = 24; // but `a` is still mutable.
973 pure nothrow @safe unittest
975 static class A { int i; }
976 static assert(!is(Final!A == A));
977 static assert(is(Final!(const A) == const A));
978 static assert(is(Final!(immutable A) == immutable A));
981 static assert(!__traits(compiles, a = new A));
983 static void foo(ref A a) pure nothrow @safe @nogc {}
984 static assert(!__traits(compiles, foo(a)));
991 static assert(!__traits(compiles, i = 24));
992 static assert(!__traits(compiles, --i));
993 static assert(!__traits(compiles, ++i));
997 iCopy = -i; // non-mutating unary operators must work
998 assert(iCopy == -42);
1004 pure nothrow @safe @nogc:
1007 this(int i, string s, float f){ this.i = i; }
1011 Final!S sstr = "foo";
1012 static assert(!__traits(compiles, sint = sstr));
1014 auto sboth = Final!S(42, "foo", 3.14);
1015 assert(sboth.i == 42);
1018 assert(sboth.i == 24);
1023 int get() pure nothrow @safe @nogc { return sboth.i + i; }
1026 // Nested structs must be constructed at the call-site
1027 static assert(!__traits(compiles, Final!NestedS(6)));
1028 auto s = makeFinal(NestedS(6));
1030 assert(s.get == 30);
1036 pure nothrow @safe @nogc:
1037 this(int i) { this.i = i; }
1038 int get() { return sboth.i + i; }
1041 auto c = makeFinal(new NestedC(6));
1043 assert(c.get == 30);
1046 pure nothrow @safe unittest
1048 auto arr = makeFinal([1, 2, 3]);
1049 static assert(!__traits(compiles, arr = null));
1050 static assert(!__traits(compiles, arr ~= 4));
1051 assert((arr ~ 4) == [1, 2, 3, 4]);
1055 pure nothrow @nogc @system unittest
1058 Final!(int*) fp = &i;
1060 static assert(!__traits(compiles,
1061 fp = &i // direct assignment
1063 static assert(is(typeof(*fp) == int));
1064 *fp = 2; // indirect assignment
1070 pure nothrow @system unittest
1073 // static assert(!__traits(compiles,
1074 // arr.length = 10; // bug!
1076 static assert(!__traits(compiles,
1079 static assert(!__traits(compiles,