#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%new-style
%enable-all-warnings
%require-types
%strict-args

%requires ../../../../qlib/QUnit.qm

%exec-class CastingTest

class Base {
    public {}

    string ok() {
        return "ok";
    }
}

class Child inherits private Base;

class OtherChild inherits Base {
    public {
        int i = 0;
    }
}

class CastingTest inherits QUnit::Test {
    constructor() : QUnit::Test("Casting test", "1.0") {
        addTestCase("Test casting", \testCasting());
        set_return_value(main());
    }

    testCasting() {
        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(),
                ("list<string> sub t() { return cast<list<string>>(('1', 1)); }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(),
                ("list<string> sub t() { auto x = '1', return cast<list<string>>(('1', x, 1)); }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("list<string> sub t() { auto x = 1; return cast<list<string>>(('1', x)); }", "");
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("*list<string> sub t() { auto x = 1; return cast<*list<string>>(('1', x)); }", "");
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(),
                ("hash<string, string> sub t() { return cast<hash<string, string>>({'a': '1', 'b': 1}); }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(),
                ("hash<string, string> sub t() { auto x = '1', "
                    "return cast<hash<string, string>>({'a': '1', 'b': x, 'c': 1}); }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("hash<string, string> sub t() { auto x = 1; "
                "return cast<hash<string, string>>({'a': '1', 'b': x}); }", "");
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("*hash<string, string> sub t() { auto x = 1; "
                "return cast<*hash<string, string>>({'a': '1', 'b': x}); }", "");
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), "t");
        }

        {
            OtherChild c();
            Base b = c;
            cast<OtherChild>(b).i = 1;
            assertEq(1, c.i);
        }

        {
            list l0 = ();
            auto l = l0;
            cast<list>(l)[0] = 1;
            assertEq("list", l.fullType());
        }

        {
            list<int> l0 = ();
            auto l = l0;
            cast<list<int>>(l)[0] = 1;
            assertEq("list<int>", l.fullType());
        }

        {
            hash<auto> h0 = {};
            auto h = h0;
            cast<hash<auto>>(h).a = 1;
            assertEq("hash<auto>", h.fullType());
        }

        {
            hash<StatInfo> h0 = <StatInfo>{};
            auto h = h0;
            cast<hash<StatInfo>>(h).size = 1;
            assertEq("hash<StatInfo>", h.fullType());
        }

        {
            hash h0 = {};
            auto h = h0;
            cast<hash>(h).size = 1;
            assertEq("hash", h.fullType());
        }

        {
            ListReverseIterator li();
            AbstractIterator i = li;
            assertFalse(cast<ListIterator>(i).next());
            assertFalse(cast<ListReverseIterator>(i).next());
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() {auto l = (); cast<list>(l)[0] = 1;}", "");
            assertThrows("RUNTIME-CAST-ERROR", "cannot modify lvalue type", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() {auto l = (); cast<list<int>>(l)[0] = 1;}", "");
            assertThrows("RUNTIME-CAST-ERROR", "cannot modify lvalue type", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() {list<int> l0 = (); auto l = l0; cast<list>(l)[0] = 1;}", "");
            assertThrows("RUNTIME-CAST-ERROR", "cannot modify lvalue type", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() {list<int> l0 = (); auto l = l0; cast<list<softstring>>(l)[0] = 1;}", "");
            assertThrows("RUNTIME-CAST-ERROR", "cannot modify lvalue type", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() {hash<auto> l0 = {}; auto l = l0; cast<hash>(l).a = 1;}", "");
            assertThrows("RUNTIME-CAST-ERROR", "cannot modify lvalue type", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() {ListIterator li(); AbstractIterator i = li; cast<ListReverseIterator>(i).next();}", "");
            assertThrows("RUNTIME-CAST-ERROR", "cannot cast from class", \p.callFunction(), "t");
        }

        Child c();

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(),
                ("sub t() {code func = sub() { Base b = cast<Base>(c); remove b; };}", ""));
        }

        assertEq("hash<StatInfo>", cast<hash<StatInfo>>({}).fullType());
        assertEq("nothing", cast<*hash<StatInfo>>(NOTHING).fullType());

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { *hash<auto> h = {}; auto x = cast<*hash<string, list<auto>>>(h); return remove x; }", "");
            assertEq({}, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { *hash<auto> h = {}; auto x = cast<*hash<string, *list<auto>>>(h); return remove x; }", "");
            assertEq({}, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { *hash<auto> h; auto x = cast<*hash<string, list<auto>>>(h); return remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { *hash<auto> h; auto x = cast<*hash<string, *list<auto>>>(h); return remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *hash h; auto x = cast<hash<StatInfo>>(h); remove x; }", "");
            assertThrows("RUNTIME-CAST-ERROR", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *hash h; auto x = cast<*hash<StatInfo>>(h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<hash<StatInfo>>(*hash h); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*hash<StatInfo>>(*hash h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<hash<StatInfo>>(NOTHING); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*hash<StatInfo>>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *hash h; auto x = cast<hash<StatInfo>>(h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *hash h; auto x = cast<*hash<StatInfo>>(h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<hash<StatInfo>>(*hash h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*hash<StatInfo>>(*hash h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<hash<StatInfo>>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*hash<StatInfo>>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        assertEq("hash<string, int>", cast<hash<string, int>>({}).fullType());
        assertEq("nothing", cast<*hash<string, int>>(NOTHING).fullType());

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *hash h; auto x = cast<hash<string, int>>(h); remove x; }", "");
            assertThrows("RUNTIME-CAST-ERROR", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *hash h; auto x = cast<*hash<string, int>>(h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<hash<string, int>>(*hash h); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*hash<string, int>>(*hash h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<hash<string, int>>(NOTHING); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*hash<string, int>>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *hash h; auto x = cast<hash<string, int>>(h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *hash h; auto x = cast<*hash<string, int>>(h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<hash<string, int>>(*hash h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*hash<string, int>>(*hash h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<hash<string, int>>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*hash<string, int>>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        assertEq("hash", cast<hash>({}).fullType());
        assertEq("nothing", cast<*hash>(NOTHING).fullType());

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *hash h; auto x = cast<hash>(h); remove x; }", "");
            assertThrows("RUNTIME-CAST-ERROR", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *hash h; auto x = cast<*hash>(h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<hash>(*hash h); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*hash>(*hash h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<hash>(NOTHING); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*hash>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *hash h; auto x = cast<hash>(h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *hash h; auto x = cast<*hash>(h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<hash>(*hash h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*hash>(*hash h); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<hash>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*hash>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        assertEq("object<Mutex>", cast<Mutex>(new Mutex()).fullType());
        assertEq("nothing", cast<*Mutex>(NOTHING).fullType());

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *Mutex m; auto x = cast<Mutex>(m); remove x; }", "");
            assertThrows("RUNTIME-CAST-ERROR", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *Mutex m; auto x = cast<*Mutex>(m); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<Mutex>(*Mutex m); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<Mutex>(new Sequence()); remove x; }", "");
            assertTrue(True);
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*Mutex>(*Mutex m); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<Mutex>(NOTHING); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*Mutex>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *Mutex m; auto x = cast<Mutex>(m); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *Mutex m; auto x = cast<*Mutex>(m); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<Mutex>(*Mutex m); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*Mutex>(*Mutex m); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<Mutex>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*Mutex>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        assertEq("list<int>", cast<list<int>>(()).fullType());
        assertEq("nothing", cast<*list<int>>(NOTHING).fullType());

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *list l; auto x = cast<list<int>>(l); remove x; }", "");
            assertThrows("RUNTIME-CAST-ERROR", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *list l; auto x = cast<*list<int>>(l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<list<int>>(*list l); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*list<int>>(*list l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<list<int>>(NOTHING); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*list<int>>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *list l; auto x = cast<list<int>>(l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *list l; auto x = cast<*list<int>>(l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<list<int>>(*list l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*list<int>>(*list l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<list<int>>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*list<int>>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        assertEq("list", cast<list>(()).fullType());
        assertEq("nothing", cast<*list>(NOTHING).fullType());

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *list l; auto x = cast<list>(l); remove x; }", "");
            assertThrows("RUNTIME-CAST-ERROR", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { *list l; auto x = cast<*list>(l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<list>(*list l); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*list>(*list l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto x = cast<list>(NOTHING); remove x; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { auto x = cast<*list>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *list l; auto x = cast<list>(l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { *list l; auto x = cast<*list>(l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<list>(*list l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*list>(*list l); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<list>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_CAST);
            p.parse("sub t() { auto x = cast<*list>(NOTHING); remove x; }", "");
            assertNothing(p.callFunction("t"));
        }
    }
}
