//===- unittest/AST/ASTImporterTest.cpp - AST node import test ------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Type-parameterized tests for the correct import of Decls with different
// visibility.
//
//===----------------------------------------------------------------------===//

// Define this to have ::testing::Combine available.
// FIXME: Better solution for this?
#define GTEST_HAS_COMBINE 1

#include "ASTImporterFixtures.h"

namespace clang {
namespace ast_matchers {

using internal::BindableMatcher;

// Type parameters for type-parameterized test fixtures.
struct GetFunPattern {
  using DeclTy = FunctionDecl;
  BindableMatcher<Decl> operator()() { return functionDecl(hasName("f")); }
};
struct GetVarPattern {
  using DeclTy = VarDecl;
  BindableMatcher<Decl> operator()() { return varDecl(hasName("v")); }
};
struct GetClassPattern {
  using DeclTy = CXXRecordDecl;
  BindableMatcher<Decl> operator()() { return cxxRecordDecl(hasName("X")); }
};
struct GetEnumPattern {
  using DeclTy = EnumDecl;
  BindableMatcher<Decl> operator()() { return enumDecl(hasName("E")); }
};
struct GetTypedefNamePattern {
  using DeclTy = TypedefNameDecl;
  BindableMatcher<Decl> operator()() { return typedefNameDecl(hasName("T")); }
};
struct GetFunTemplPattern {
  using DeclTy = FunctionTemplateDecl;
  BindableMatcher<Decl> operator()() {
    return functionTemplateDecl(hasName("f"));
  }
};
struct GetClassTemplPattern {
  using DeclTy = ClassTemplateDecl;
  BindableMatcher<Decl> operator()() { return classTemplateDecl(hasName("X")); }
};

// Values for the value-parameterized test fixtures.
// FunctionDecl:
const auto *ExternF = "void f();";
const auto *StaticF = "static void f();";
const auto *AnonF = "namespace { void f(); }";
// VarDecl:
const auto *ExternV = "extern int v;";
const auto *StaticV = "static int v;";
const auto *AnonV = "namespace { extern int v; }";
// CXXRecordDecl:
const auto *ExternC = "class X;";
const auto *AnonC = "namespace { class X; }";
// EnumDecl:
const auto *ExternE = "enum E {};";
const auto *AnonE = "namespace { enum E {}; }";
// TypedefNameDecl:
const auto *ExternTypedef = "typedef int T;";
const auto *AnonTypedef = "namespace { typedef int T; }";
const auto *ExternUsing = "using T = int;";
const auto *AnonUsing = "namespace { using T = int; }";
// FunctionTemplateDecl:
const auto *ExternFT = "template <class> void f();";
const auto *StaticFT = "template <class> static void f();";
const auto *AnonFT = "namespace { template <class> void f(); }";
// ClassTemplateDecl:
const auto *ExternCT = "template <class> class X;";
const auto *AnonCT = "namespace { template <class> class X; }";

// First value in tuple: Compile options.
// Second value in tuple: Source code to be used in the test.
using ImportVisibilityChainParams =
    ::testing::WithParamInterface<std::tuple<ArgVector, const char *>>;
// Fixture to test the redecl chain of Decls with the same visibility. Gtest
// makes it possible to have either value-parameterized or type-parameterized
// fixtures. However, we cannot have both value- and type-parameterized test
// fixtures. This is a value-parameterized test fixture in the gtest sense. We
// intend to mimic gtest's type-parameters via the PatternFactory template
// parameter. We manually instantiate the different tests with the each types.
template <typename PatternFactory>
class ImportVisibilityChain
    : public ASTImporterTestBase, public ImportVisibilityChainParams {
protected:
  using DeclTy = typename PatternFactory::DeclTy;
  ArgVector getExtraArgs() const override { return std::get<0>(GetParam()); }
  std::string getCode() const { return std::get<1>(GetParam()); }
  BindableMatcher<Decl> getPattern() const { return PatternFactory()(); }

  // Type-parameterized test.
  void TypedTest_ImportChain() {
    std::string Code = getCode() + getCode();
    auto Pattern = getPattern();

    TranslationUnitDecl *FromTu = getTuDecl(Code, Lang_CXX14, "input0.cc");

    auto *FromD0 = FirstDeclMatcher<DeclTy>().match(FromTu, Pattern);
    auto *FromD1 = LastDeclMatcher<DeclTy>().match(FromTu, Pattern);

    auto *ToD0 = Import(FromD0, Lang_CXX14);
    auto *ToD1 = Import(FromD1, Lang_CXX14);

    EXPECT_TRUE(ToD0);
    ASSERT_TRUE(ToD1);
    EXPECT_NE(ToD0, ToD1);
    EXPECT_EQ(ToD1->getPreviousDecl(), ToD0);
  }
};

// Manual instantiation of the fixture with each type.
using ImportFunctionsVisibilityChain = ImportVisibilityChain<GetFunPattern>;
using ImportVariablesVisibilityChain = ImportVisibilityChain<GetVarPattern>;
using ImportClassesVisibilityChain = ImportVisibilityChain<GetClassPattern>;
using ImportFunctionTemplatesVisibilityChain =
    ImportVisibilityChain<GetFunTemplPattern>;
using ImportClassTemplatesVisibilityChain =
    ImportVisibilityChain<GetClassTemplPattern>;

// Value-parameterized test for functions.
TEST_P(ImportFunctionsVisibilityChain, ImportChain) {
  TypedTest_ImportChain();
}
// Value-parameterized test for variables.
TEST_P(ImportVariablesVisibilityChain, ImportChain) {
  TypedTest_ImportChain();
}
// Value-parameterized test for classes.
TEST_P(ImportClassesVisibilityChain, ImportChain) {
  TypedTest_ImportChain();
}
// Value-parameterized test for function templates.
TEST_P(ImportFunctionTemplatesVisibilityChain, ImportChain) {
  TypedTest_ImportChain();
}
// Value-parameterized test for class templates.
TEST_P(ImportClassTemplatesVisibilityChain, ImportChain) {
  TypedTest_ImportChain();
}

// Automatic instantiation of the value-parameterized tests.
INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFunctionsVisibilityChain,
                        ::testing::Combine(
                           DefaultTestValuesForRunOptions,
                           ::testing::Values(ExternF, StaticF, AnonF)), );
INSTANTIATE_TEST_CASE_P(
    ParameterizedTests, ImportVariablesVisibilityChain,
    ::testing::Combine(
        DefaultTestValuesForRunOptions,
        // There is no point to instantiate with StaticV, because in C++ we can
        // forward declare a variable only with the 'extern' keyword.
        // Consequently, each fwd declared variable has external linkage.  This
        // is different in the C language where any declaration without an
        // initializer is a tentative definition, subsequent definitions may be
        // provided but they must have the same linkage.  See also the test
        // ImportVariableChainInC which test for this special C Lang case.
        ::testing::Values(ExternV, AnonV)), );
INSTANTIATE_TEST_CASE_P(
    ParameterizedTests, ImportClassesVisibilityChain,
    ::testing::Combine(
        DefaultTestValuesForRunOptions,
        ::testing::Values(ExternC, AnonC)), );
INSTANTIATE_TEST_CASE_P(ParameterizedTests,
                        ImportFunctionTemplatesVisibilityChain,
                        ::testing::Combine(DefaultTestValuesForRunOptions,
                                           ::testing::Values(ExternFT, StaticFT,
                                                             AnonFT)), );
INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportClassTemplatesVisibilityChain,
                        ::testing::Combine(DefaultTestValuesForRunOptions,
                                           ::testing::Values(ExternCT,
                                                             AnonCT)), );

// First value in tuple: Compile options.
// Second value in tuple: Tuple with informations for the test.
// Code for first import (or initial code), code to import, whether the `f`
// functions are expected to be linked in a declaration chain.
// One value of this tuple is combined with every value of compile options.
// The test can have a single tuple as parameter only.
using ImportVisibilityParams = ::testing::WithParamInterface<
    std::tuple<ArgVector, std::tuple<const char *, const char *, bool>>>;

template <typename PatternFactory>
class ImportVisibility
    : public ASTImporterTestBase,
      public ImportVisibilityParams {
protected:
  using DeclTy = typename PatternFactory::DeclTy;
  ArgVector getExtraArgs() const override { return std::get<0>(GetParam()); }
  std::string getCode0() const { return std::get<0>(std::get<1>(GetParam())); }
  std::string getCode1() const { return std::get<1>(std::get<1>(GetParam())); }
  bool shouldBeLinked() const { return std::get<2>(std::get<1>(GetParam())); }
  BindableMatcher<Decl> getPattern() const { return PatternFactory()(); }

  void TypedTest_ImportAfter() {
    TranslationUnitDecl *ToTu = getToTuDecl(getCode0(), Lang_CXX14);
    TranslationUnitDecl *FromTu =
        getTuDecl(getCode1(), Lang_CXX14, "input1.cc");

    auto *ToD0 = FirstDeclMatcher<DeclTy>().match(ToTu, getPattern());
    auto *FromD1 = FirstDeclMatcher<DeclTy>().match(FromTu, getPattern());

    auto *ToD1 = Import(FromD1, Lang_CXX14);

    ASSERT_TRUE(ToD0);
    ASSERT_TRUE(ToD1);
    EXPECT_NE(ToD0, ToD1);

    if (shouldBeLinked())
      EXPECT_EQ(ToD1->getPreviousDecl(), ToD0);
    else
      EXPECT_FALSE(ToD1->getPreviousDecl());
  }

  void TypedTest_ImportAfterImport() {
    TranslationUnitDecl *FromTu0 =
        getTuDecl(getCode0(), Lang_CXX14, "input0.cc");
    TranslationUnitDecl *FromTu1 =
        getTuDecl(getCode1(), Lang_CXX14, "input1.cc");
    auto *FromD0 = FirstDeclMatcher<DeclTy>().match(FromTu0, getPattern());
    auto *FromD1 = FirstDeclMatcher<DeclTy>().match(FromTu1, getPattern());
    auto *ToD0 = Import(FromD0, Lang_CXX14);
    auto *ToD1 = Import(FromD1, Lang_CXX14);
    ASSERT_TRUE(ToD0);
    ASSERT_TRUE(ToD1);
    EXPECT_NE(ToD0, ToD1);
    if (shouldBeLinked())
      EXPECT_EQ(ToD1->getPreviousDecl(), ToD0);
    else
      EXPECT_FALSE(ToD1->getPreviousDecl());
  }

  void TypedTest_ImportAfterWithMerge() {
    TranslationUnitDecl *ToTu = getToTuDecl(getCode0(), Lang_CXX14);
    TranslationUnitDecl *FromTu =
        getTuDecl(getCode1(), Lang_CXX14, "input1.cc");

    auto *ToF0 = FirstDeclMatcher<DeclTy>().match(ToTu, getPattern());
    auto *FromF1 = FirstDeclMatcher<DeclTy>().match(FromTu, getPattern());

    auto *ToF1 = Import(FromF1, Lang_CXX14);

    ASSERT_TRUE(ToF0);
    ASSERT_TRUE(ToF1);

    if (shouldBeLinked())
      EXPECT_EQ(ToF0, ToF1);
    else
      EXPECT_NE(ToF0, ToF1);

    // We expect no (ODR) warning during the import.
    EXPECT_EQ(0u, ToTu->getASTContext().getDiagnostics().getNumWarnings());
  }

  void TypedTest_ImportAfterImportWithMerge() {
    TranslationUnitDecl *FromTu0 =
        getTuDecl(getCode0(), Lang_CXX14, "input0.cc");
    TranslationUnitDecl *FromTu1 =
        getTuDecl(getCode1(), Lang_CXX14, "input1.cc");
    auto *FromF0 = FirstDeclMatcher<DeclTy>().match(FromTu0, getPattern());
    auto *FromF1 = FirstDeclMatcher<DeclTy>().match(FromTu1, getPattern());
    auto *ToF0 = Import(FromF0, Lang_CXX14);
    auto *ToF1 = Import(FromF1, Lang_CXX14);
    ASSERT_TRUE(ToF0);
    ASSERT_TRUE(ToF1);
    if (shouldBeLinked())
      EXPECT_EQ(ToF0, ToF1);
    else
      EXPECT_NE(ToF0, ToF1);

    // We expect no (ODR) warning during the import.
    EXPECT_EQ(0u, ToF0->getTranslationUnitDecl()
                      ->getASTContext()
                      .getDiagnostics()
                      .getNumWarnings());
  }
};
using ImportFunctionsVisibility = ImportVisibility<GetFunPattern>;
using ImportVariablesVisibility = ImportVisibility<GetVarPattern>;
using ImportClassesVisibility = ImportVisibility<GetClassPattern>;
using ImportEnumsVisibility = ImportVisibility<GetEnumPattern>;
using ImportTypedefNameVisibility = ImportVisibility<GetTypedefNamePattern>;
using ImportFunctionTemplatesVisibility = ImportVisibility<GetFunTemplPattern>;
using ImportClassTemplatesVisibility = ImportVisibility<GetClassTemplPattern>;

// FunctionDecl.
TEST_P(ImportFunctionsVisibility, ImportAfter) {
  TypedTest_ImportAfter();
}
TEST_P(ImportFunctionsVisibility, ImportAfterImport) {
  TypedTest_ImportAfterImport();
}
// VarDecl.
TEST_P(ImportVariablesVisibility, ImportAfter) {
  TypedTest_ImportAfter();
}
TEST_P(ImportVariablesVisibility, ImportAfterImport) {
  TypedTest_ImportAfterImport();
}
// CXXRecordDecl.
TEST_P(ImportClassesVisibility, ImportAfter) {
  TypedTest_ImportAfter();
}
TEST_P(ImportClassesVisibility, ImportAfterImport) {
  TypedTest_ImportAfterImport();
}
// EnumDecl.
TEST_P(ImportEnumsVisibility, ImportAfter) {
  TypedTest_ImportAfterWithMerge();
}
TEST_P(ImportEnumsVisibility, ImportAfterImport) {
  TypedTest_ImportAfterImportWithMerge();
}
// TypedefNameDecl.
TEST_P(ImportTypedefNameVisibility, ImportAfter) {
  TypedTest_ImportAfterWithMerge();
}
TEST_P(ImportTypedefNameVisibility, ImportAfterImport) {
  TypedTest_ImportAfterImportWithMerge();
}
// FunctionTemplateDecl.
TEST_P(ImportFunctionTemplatesVisibility, ImportAfter) {
  TypedTest_ImportAfter();
}
TEST_P(ImportFunctionTemplatesVisibility, ImportAfterImport) {
  TypedTest_ImportAfterImport();
}
// ClassTemplateDecl.
TEST_P(ImportClassTemplatesVisibility, ImportAfter) { TypedTest_ImportAfter(); }
TEST_P(ImportClassTemplatesVisibility, ImportAfterImport) {
  TypedTest_ImportAfterImport();
}

const bool ExpectLinkedDeclChain = true;
const bool ExpectUnlinkedDeclChain = false;

INSTANTIATE_TEST_CASE_P(
    ParameterizedTests, ImportFunctionsVisibility,
    ::testing::Combine(
        DefaultTestValuesForRunOptions,
        ::testing::Values(
            std::make_tuple(ExternF, ExternF, ExpectLinkedDeclChain),
            std::make_tuple(ExternF, StaticF, ExpectUnlinkedDeclChain),
            std::make_tuple(ExternF, AnonF, ExpectUnlinkedDeclChain),
            std::make_tuple(StaticF, ExternF, ExpectUnlinkedDeclChain),
            std::make_tuple(StaticF, StaticF, ExpectUnlinkedDeclChain),
            std::make_tuple(StaticF, AnonF, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonF, ExternF, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonF, StaticF, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonF, AnonF, ExpectUnlinkedDeclChain))), );
INSTANTIATE_TEST_CASE_P(
    ParameterizedTests, ImportVariablesVisibility,
    ::testing::Combine(
        DefaultTestValuesForRunOptions,
        ::testing::Values(
            std::make_tuple(ExternV, ExternV, ExpectLinkedDeclChain),
            std::make_tuple(ExternV, StaticV, ExpectUnlinkedDeclChain),
            std::make_tuple(ExternV, AnonV, ExpectUnlinkedDeclChain),
            std::make_tuple(StaticV, ExternV, ExpectUnlinkedDeclChain),
            std::make_tuple(StaticV, StaticV, ExpectUnlinkedDeclChain),
            std::make_tuple(StaticV, AnonV, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonV, ExternV, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonV, StaticV, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonV, AnonV, ExpectUnlinkedDeclChain))), );
INSTANTIATE_TEST_CASE_P(
    ParameterizedTests, ImportClassesVisibility,
    ::testing::Combine(
        DefaultTestValuesForRunOptions,
        ::testing::Values(
            std::make_tuple(ExternC, ExternC, ExpectLinkedDeclChain),
            std::make_tuple(ExternC, AnonC, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonC, ExternC, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonC, AnonC, ExpectUnlinkedDeclChain))), );
INSTANTIATE_TEST_CASE_P(
    ParameterizedTests, ImportEnumsVisibility,
    ::testing::Combine(
        DefaultTestValuesForRunOptions,
        ::testing::Values(
            std::make_tuple(ExternE, ExternE, ExpectLinkedDeclChain),
            std::make_tuple(ExternE, AnonE, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonE, ExternE, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonE, AnonE, ExpectUnlinkedDeclChain))), );
INSTANTIATE_TEST_CASE_P(
    ParameterizedTests, ImportTypedefNameVisibility,
    ::testing::Combine(
        DefaultTestValuesForRunOptions,
        ::testing::Values(
            std::make_tuple(ExternTypedef, ExternTypedef,
                            ExpectLinkedDeclChain),
            std::make_tuple(ExternTypedef, AnonTypedef,
                            ExpectUnlinkedDeclChain),
            std::make_tuple(AnonTypedef, ExternTypedef,
                            ExpectUnlinkedDeclChain),
            std::make_tuple(AnonTypedef, AnonTypedef, ExpectUnlinkedDeclChain),

            std::make_tuple(ExternUsing, ExternUsing, ExpectLinkedDeclChain),
            std::make_tuple(ExternUsing, AnonUsing, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonUsing, ExternUsing, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonUsing, AnonUsing, ExpectUnlinkedDeclChain),

            std::make_tuple(ExternUsing, ExternTypedef, ExpectLinkedDeclChain),
            std::make_tuple(ExternUsing, AnonTypedef, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonUsing, ExternTypedef, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonUsing, AnonTypedef, ExpectUnlinkedDeclChain),

            std::make_tuple(ExternTypedef, ExternUsing, ExpectLinkedDeclChain),
            std::make_tuple(ExternTypedef, AnonUsing, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonTypedef, ExternUsing, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonTypedef, AnonUsing,
                            ExpectUnlinkedDeclChain))), );
INSTANTIATE_TEST_CASE_P(
    ParameterizedTests, ImportFunctionTemplatesVisibility,
    ::testing::Combine(
        DefaultTestValuesForRunOptions,
        ::testing::Values(
            std::make_tuple(ExternFT, ExternFT, ExpectLinkedDeclChain),
            std::make_tuple(ExternFT, StaticFT, ExpectUnlinkedDeclChain),
            std::make_tuple(ExternFT, AnonFT, ExpectUnlinkedDeclChain),
            std::make_tuple(StaticFT, ExternFT, ExpectUnlinkedDeclChain),
            std::make_tuple(StaticFT, StaticFT, ExpectUnlinkedDeclChain),
            std::make_tuple(StaticFT, AnonFT, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonFT, ExternFT, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonFT, StaticFT, ExpectUnlinkedDeclChain),
            std::make_tuple(AnonFT, AnonFT, ExpectUnlinkedDeclChain))), );
INSTANTIATE_TEST_CASE_P(
    ParameterizedTests, ImportClassTemplatesVisibility,
    ::testing::Combine(
        DefaultTestValuesForRunOptions,
        ::testing::Values(std::make_tuple(ExternCT, ExternCT, ExpectLinkedDeclChain),
                          std::make_tuple(ExternCT, AnonCT, ExpectUnlinkedDeclChain),
                          std::make_tuple(AnonCT, ExternCT, ExpectUnlinkedDeclChain),
                          std::make_tuple(AnonCT, AnonCT, ExpectUnlinkedDeclChain))), );
} // end namespace ast_matchers
} // end namespace clang
