%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
%
% This test exercises the situations which compiler/direct_arg_in_out.m
% cannot handle, each of which should result in an error message.
%
% The .err_exp{,2,3} files are for targeting C, Java and C# respectively.
% Since the direct arg optimization does not apply when we generate code
% for Java and C#, .err_exp[23] don't contain the errors that .err_exp does.
% However, reporting no errors would mean that the test case failed (because
% it failed to detect the problem the test case is for), so we add another
% problem for these backend: duplicate foreign_procs.
%
%-----------------------------------------------------------------------------%

:- module gh72_errors.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.

:- import_module list.
:- import_module string.
:- import_module solutions.

%-----------------------------------------------------------------------------%

main(!IO) :-
    test_exports(!IO),
    test_fprocs(!IO).
    % test_modes(!IO).

%-----------------------------------------------------------------------------%

:- pred test_exports(io::di, io::uo) is det.

test_exports(!IO) :-
    A = f1(_),
    test_export_1(A, !IO),

    B = f2(_),
    C = f3(_),
    test_export_2(42, B, C, !IO).

:- pred test_export_1(t, io, io).
:- mode test_export_1(t4 >> ground, di, uo) is det.
:- pragma foreign_export("C",
    test_export_1(t4 >> ground, di, uo),
    "exported_test_export_1").
:- pragma foreign_export("Java",
    test_export_1(t4 >> ground, di, uo),
    "exported_test_export_1").
:- pragma foreign_export("C#",
    test_export_1(t4 >> ground, di, uo),
    "exported_test_export_1").

test_export_1(A, !IO) :-
    fill3("c", A),
    fill2("b", A),
    fill1("a", A),
    io.write_string(dump_t(A), !IO).

:- pred test_export_2(int, t, t, io, io).
:- mode test_export_2(in, t4 >> ground, t4 >> ground, di, uo) is det.
:- pragma foreign_export("C",
    test_export_2(in, t4 >> ground, t4 >> ground, di, uo),
    "exported_test_export_2").
:- pragma foreign_export("Java",
    test_export_2(in, t4 >> ground, t4 >> ground, di, uo),
    "exported_test_export_2").
:- pragma foreign_export("C#",
    test_export_2(in, t4 >> ground, t4 >> ground, di, uo),
    "exported_test_export_2").

test_export_2(_N, A, B, !IO) :-
    fill3("c", A),
    fill2("b", A),
    fill1("a", A),
    io.write_string(dump_t(A), !IO),
    fill3("c", B),
    fill2("b", B),
    fill1("a", B),
    io.write_string(dump_t(B), !IO).

%-----------------------------------------------------------------------------%

:- pred test_fprocs(io::di, io::uo) is det.

test_fprocs(!IO) :-
    A = f1(_),
    test_fproc_1(A, !IO),

    B = f2(_),
    C = f3(_),
    test_fproc_2(42, B, C, !IO).

:- pred test_fproc_1(t, io, io).
:- mode test_fproc_1(t4 >> ground, di, uo) is det.

:- pragma foreign_proc("C",
    test_fproc_1(A::(t4 >> ground), IO0::di, IO::uo),
    [promise_pure, may_call_mercury],
"
    // A
    IO = IO0;
").
:- pragma foreign_proc("Java",
    test_fproc_1(A::(t4 >> ground), IO0::di, IO::uo),
    [promise_pure, may_call_mercury],
"
    // A
    IO = IO0;
").
:- pragma foreign_proc("C#",
    test_fproc_1(A::(t4 >> ground), IO0::di, IO::uo),
    [promise_pure, may_call_mercury],
"
    // A
    IO = IO0;
").

:- pred test_fproc_2(int, t, t, io, io).
:- mode test_fproc_2(in, t4 >> ground, t4 >> ground, di, uo) is det.

:- pragma foreign_proc("C",
    test_fproc_2(N::in, A::(t4 >> ground), B::(t4 >> ground), IO0::di, IO::uo),
    [promise_pure, may_call_mercury],
"
    // N, A, B
    IO = IO0;
").
:- pragma foreign_proc("Java",
    test_fproc_2(N::in, A::(t4 >> ground), B::(t4 >> ground), IO0::di, IO::uo),
    [promise_pure, may_call_mercury],
"
    // N, A, B
    IO = IO0;
").
:- pragma foreign_proc("C#",
    test_fproc_2(N::in, A::(t4 >> ground), B::(t4 >> ground), IO0::di, IO::uo),
    [promise_pure, may_call_mercury],
"
    // N, A, B
    IO = IO0;
").

% These foreign_procs are the deliberate duplicates referred to at the top.
:- pragma foreign_proc("Java",
    test_fproc_2(N::in, A::(t4 >> ground), B::(t4 >> ground), IO0::di, IO::uo),
    [promise_pure, may_call_mercury],
"
    // N, A, B
    IO = IO0;
").
:- pragma foreign_proc("C#",
    test_fproc_2(N::in, A::(t4 >> ground), B::(t4 >> ground), IO0::di, IO::uo),
    [promise_pure, may_call_mercury],
"
    // N, A, B
    IO = IO0;
").

%-----------------------------------------------------------------------------%

/*
These test predicates all cause mode errors before direct_arg_in_out.m
is every invoked, and I (zs) cannot think of any simple way to fix
the mode errors without screwing up the purpose of these tests.
Proposed fixes welcome.

:- pred test_modes(io::di, io::uo) is det.

test_modes(!IO) :-
    A = f1(_),
    test_modes_1(A, !IO),

    B = f2(_),
    C = f3(_),
    test_modes_2(B, 42, C, !IO),

    D = f1(_),
    E = f2(_),
    F = f3(_),
    test_modes_3(D, 42, E, F, !IO),

    G = f1(_),
    H = f2(_),
    I = f3(_),
    J = f1(_),
    test_modes_4(G, 42, H, I, J, !IO).

:- pred test_modes_1(t, io, io).
:- mode test_modes_1(((I =< ground) >> ground), di, uo) is det.

test_modes_1(A, !IO) :-
    fill3("c", A),
    fill2("b", A),
    fill1("a", A),
    io.write_string(dump_t(A), !IO).

:- pred test_modes_2(t, int, t, io, io).
:- mode test_modes_2((I =< ground) >> ground, in, (I =< ground) >> ground,
    di, uo) is det.

test_modes_2(A, _N, B, !IO) :-
    fill3("c", A),
    fill2("b", A),
    fill1("a", A),
    io.write_string(dump_t(A), !IO),
    fill3("c", B),
    fill2("b", B),
    fill1("a", B),
    io.write_string(dump_t(B), !IO).

:- pred test_modes_3(t, int, t, t, io, io).
:- mode test_modes_3(((I =< ground) >> ground), in, (t234 >> ground),
    ((I =< ground) >> ground), di, uo) is det.

test_modes_3(A, _N, B, C, !IO) :-
    fill3("c", A),
    fill2("b", A),
    fill1("a", A),
    io.write_string(dump_t(A), !IO),
    fill3("c", B),
    fill2("b", B),
    fill1("a", B),
    io.write_string(dump_t(B), !IO),
    fill3("c", C),
    fill2("b", C),
    fill1("a", C),
    io.write_string(dump_t(C), !IO).

:- pred test_modes_4(t, int, t, t, t, io, io).
:- mode test_modes_4(((I =< ground) >> ground), in, (t234 >> ground),
    ((I =< ground) >> ground), (t234 >> ground), di, uo) is det.

test_modes_4(A, _N, B, C, D, !IO) :-
    fill3("c", A),
    fill2("b", A),
    fill1("a", A),
    io.write_string(dump_t(A), !IO),
    fill3("c", B),
    fill2("b", B),
    fill1("a", B),
    io.write_string(dump_t(B), !IO),
    fill3("c", C),
    fill2("b", C),
    fill1("a", C),
    io.write_string(dump_t(C), !IO),
    fill3("c", D),
    fill2("b", D),
    fill1("a", D),
    io.write_string(dump_t(D), !IO).
*/

%-----------------------------------------------------------------------------%

:- type t
    --->    f1(package)
    ;       f2(package)
    ;       f3(package)
    ;       f4(package).

:- inst t4 for t/0
    --->    f1(free)
    ;       f2(free)
    ;       f3(free)
    ;       f4(ground).

:- inst t34 for t/0
    --->    f1(free)
    ;       f2(free)
    ;       f3(ground)
    ;       f4(ground).

:- inst t234 for t/0
    --->    f1(free)
    ;       f2(ground)
    ;       f3(ground)
    ;       f4(ground).

:- type package
    --->    package(string, string).

%-----------------------------------------------------------------------------%

:- func dump_t(t) = string.
:- pragma no_inline(dump_t/1).

dump_t(f1(Package)) = "f1(" ++ dump_package(Package) ++ ")\n".
dump_t(f2(Package)) = "f2(" ++ dump_package(Package) ++ ")\n".
dump_t(f3(Package)) = "f3(" ++ dump_package(Package) ++ ")\n".
dump_t(f4(Package)) = "f4(" ++ dump_package(Package) ++ ")\n".

:- func dump_package(package) = string.
:- pragma no_inline(dump_t/1).

dump_package(package(A, B)) = "package(" ++ A ++ ", " ++ B ++ ")".

%-----------------------------------------------------------------------------%

:- pred fill3(string, t).
:- mode fill3(in, t4 >> t34) is det.
:- pragma no_inline(fill3/2).

fill3(S, T0) :-
    ( T0 = f1(_)
    ; T0 = f2(_)
    ; T0 = f3(package("fill3", S))
    ; T0 = f4(_)
    ).

:- pred fill23(string, t).
:- mode fill23(in, t4 >> t234) is det.
:- pragma no_inline(fill3/2).

fill23(S, T0) :-
    ( T0 = f1(_)
    ; T0 = f2(package("fill2", S))
    ; T0 = f3(package("fill3", S))
    ; T0 = f4(_)
    ).

:- pred fill2(string, t).
:- mode fill2(in, t34 >> t234) is det.
:- pragma no_inline(fill2/2).

fill2(S, T0) :-
    ( T0 = f1(_)
    ; T0 = f2(package("fill2", S))
    ; T0 = f3(_)
    ; T0 = f4(_)
    ).

:- pred fill1(string, t).
:- mode fill1(in, t234 >> ground) is det.
:- pragma no_inline(fill1/2).

fill1(S, T0) :-
    ( T0 = f1(package("fill1", S))
    ; T0 = f2(_)
    ; T0 = f3(_)
    ; T0 = f4(_)
    ).

%-----------------------------------------------------------------------------%
