blob: 6906f05a4a86defaf7323bc3c2aa24e2efaf8f0e [file] [log] [blame]
// 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,
isImplicitlyConvertible, 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);
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++
));
}