//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14

// FIXME: Building this in MSVC mode fails when instantiating two cases of
// std::function that only differ in constness of the return type, with this
// error:
// include/c++/v1/__functional/function.h:254:31: error: definition with same mangled name '??0?$__base@$$A6AXXZ@__function@__1@std@@QEAA@XZ' as another definition
//     _LIBCPP_HIDE_FROM_ABI __base() {}
// include/c++/v1/__functional/function.h:254:31: note: previous definition is here
// XFAIL: msvc

// <functional>

#include <functional>
#include <cassert>

#include "test_macros.h"

// Prevent warning on the `const NonCopyable()` function type.
TEST_CLANG_DIAGNOSTIC_IGNORED("-Wignored-qualifiers")
TEST_GCC_DIAGNOSTIC_IGNORED("-Wignored-qualifiers")

struct NonCopyable {
    NonCopyable() = default;
    NonCopyable(NonCopyable&&) = delete;
    friend bool operator==(NonCopyable, NonCopyable) { return true; }
};

struct LargeLambda {
    int a[100];
    NonCopyable operator()() const { return NonCopyable(); }
    NonCopyable operator()(int) const { return NonCopyable(); }
    NonCopyable f() const { return NonCopyable(); }
};

void test()
{
    std::function<NonCopyable()> f1a = []() { return NonCopyable(); };
    std::function<NonCopyable()> f2a = +[]() { return NonCopyable(); };
    std::function<NonCopyable()> f3a = LargeLambda();
    std::function<NonCopyable()> f4a = std::ref(f1a);
    std::function<NonCopyable(int)> f1b = [](int) { return NonCopyable(); };
    std::function<NonCopyable(int)> f2b = +[](int) { return NonCopyable(); };
    std::function<NonCopyable(int)> f3b = LargeLambda();
    std::function<NonCopyable(int)> f4b = std::ref(f1b);

    assert(f1a() == f2a());
    assert(f3a() == f4a());
    assert(f1b(1) == f2b(1));
    assert(f3b(1) == f4b(1));
}

void const_test()
{
    std::function<const NonCopyable()> f1a = []() { return NonCopyable(); };
    std::function<const NonCopyable()> f2a = +[]() { return NonCopyable(); };
    std::function<const NonCopyable()> f3a = LargeLambda();
    std::function<const NonCopyable()> f4a = std::ref(f1a);
    std::function<const NonCopyable(int)> f1b = [](int) { return NonCopyable(); };
    std::function<const NonCopyable(int)> f2b = +[](int) { return NonCopyable(); };
    std::function<const NonCopyable(int)> f3b = LargeLambda();
    std::function<const NonCopyable(int)> f4b = std::ref(f1b);

    assert(f1a() == f2a());
    assert(f3a() == f4a());
    assert(f1b(1) == f2b(1));
    assert(f3b(1) == f4b(1));
}

void void_test()
{
    std::function<void()> f1a = []() { return NonCopyable(); };
    std::function<void()> f2a = +[]() { return NonCopyable(); };
    std::function<void()> f3a = LargeLambda();
    std::function<void()> f4a = std::ref(f1a);
    std::function<void(int)> f1b = [](int) { return NonCopyable(); };
    std::function<void(int)> f2b = +[](int) { return NonCopyable(); };
    std::function<void(int)> f3b = LargeLambda();
    std::function<void(int)> f4b = std::ref(f1b);
}

void const_void_test()
{
    std::function<const void()> f1a = []() { return NonCopyable(); };
    std::function<const void()> f2a = +[]() { return NonCopyable(); };
    std::function<const void()> f3a = LargeLambda();
    std::function<const void()> f4a = std::ref(f1a);
    std::function<const void(int)> f1b = [](int) { return NonCopyable(); };
    std::function<const void(int)> f2b = +[](int) { return NonCopyable(); };
    std::function<const void(int)> f3b = LargeLambda();
    std::function<const void(int)> f4b = std::ref(f1b);
}

void member_pointer_test()
{
    std::function<NonCopyable(LargeLambda*)> f1a = &LargeLambda::f;
    std::function<NonCopyable(LargeLambda&)> f2a = &LargeLambda::f;
    LargeLambda ll;
    assert(f1a(&ll) == f2a(ll));

    static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)(), std::function<NonCopyable(LargeLambda*)>>);
    static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)(), std::function<NonCopyable(LargeLambda&)>>);
    static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)() const, std::function<NonCopyable(LargeLambda*)>>);
    static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)() const, std::function<NonCopyable(LargeLambda&)>>);
    static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)() const, std::function<NonCopyable(const LargeLambda*)>>);
    static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)() const, std::function<NonCopyable(const LargeLambda&)>>);

    // Verify we have SFINAE against invoking a pointer-to-data-member in a way that would have to copy the NonCopyable.
    static_assert(!std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable(LargeLambda*)>>);
    static_assert(!std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable(LargeLambda&)>>);
    static_assert(!std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable&(const LargeLambda&)>>);
    static_assert(std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable&(LargeLambda*)>>);
    static_assert(std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable&(LargeLambda&)>>);
    static_assert(std::is_convertible_v<NonCopyable LargeLambda::*, std::function<const NonCopyable&(const LargeLambda&)>>);
}

void ctad_test()
{
    std::function f1a = []() { return NonCopyable(); };
    std::function f2a = +[]() { return NonCopyable(); };
    std::function f1b = [](int) { return NonCopyable(); };
    std::function f2b = +[](int) { return NonCopyable(); };
    static_assert(std::is_same_v<decltype(f1a), std::function<NonCopyable()>>);
    static_assert(std::is_same_v<decltype(f2a), std::function<NonCopyable()>>);
    static_assert(std::is_same_v<decltype(f1b), std::function<NonCopyable(int)>>);
    static_assert(std::is_same_v<decltype(f2b), std::function<NonCopyable(int)>>);
}

int main(int, char**)
{
    test();
    const_test();
    void_test();
    const_void_test();
    member_pointer_test();
    ctad_test();
    return 0;
}
