// 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, 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 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());
}

/*
 * 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 (StdUnittest) {}
                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; 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:
                static foreach (i; 0 .. TargetMembers.length)
                    mixin(generateFun!i);
            }
        }
    }
}

// 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);
    }
}

// https://issues.dlang.org/show_bug.cgi?id=10377
@system unittest
{
    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]));
}

// 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);
}

// https://issues.dlang.org/show_bug.cgi?id=10538
@system unittest
{
    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));

    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]);
}

// https://issues.dlang.org/show_bug.cgi?id=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++
    ));
}
