diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 1aa6f972..b5a7edac 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -55,7 +55,7 @@ jobs: if: matrix.os == 'ubuntu-24.04' run: | mkdir -p ./.llvm - curl -L "https://github.com/clice-project/llvm-binary/releases/download/20.0.0/x86_64-linux-gnu-release.tar.xz" | tar -xJ -C ./.llvm + curl -L "https://github.com/clice-project/llvm-binary/releases/download/20.0.0/x86_64-linux-gnu-debug.tar.xz" | tar -xJ -C ./.llvm - name: Setup llvm binary if: matrix.os == 'windows-2022' @@ -66,8 +66,15 @@ jobs: - name: Setup msvc sysroot for cmake if: matrix.os == 'windows-2022' uses: ilammy/msvc-dev-cmd@v1 + + - name: Build clice + if: matrix.os == 'ubuntu-24.04' + run: | + cmake --preset debug + cmake --build --preset debug - name: Build clice + if: matrix.os == 'windows-2022' run: | cmake --preset release cmake --build --preset release diff --git a/.gitignore b/.gitignore index 3ac9afbb..bdeef599 100644 --- a/.gitignore +++ b/.gitignore @@ -31,14 +31,11 @@ *.out *.app -.txt -.cache/ -note.md -temp* -/build* -notes +build* +notes/ temp/ -.llvm/ +.cache/ +.llvm*/ .xmake/ - +.vscode/ diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 5f92f01a..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug", - "program": "${workspaceFolder}/build/bin/clice", - "args": [ - "--config=${workspaceFolder}/docs/clice.toml" - ], - "preLaunchTask": "Build Dev" - }, - { - "type": "lldb", - "request": "launch", - "name": "Release", - "program": "${workspaceFolder}/build-release/bin/clice", - "args": [ - "--config=${workspaceFolder}/docs/clice.toml" - ], - "preLaunchTask": "Build Release" - }, - { - "type": "lldb", - "request": "launch", - "name": "Integration Test", - "program": "${workspaceFolder}/build/bin/integration_tests", - "args": [ - "--execute=${workspaceFolder}/build/bin/clice", - "--source=${workspaceFolder}/tests", - ], - "preLaunchTask": "Build Dev" - }, - { - "type": "lldb", - "request": "launch", - "name": "Test", - "program": "${workspaceFolder}/build/bin/unit_tests", - "args": [ - "--test-dir=${workspaceFolder}/tests", - "--resource-dir=${workspaceFolder}/.llvm/lib/clang/20", - "--gtest_filter=${input:filter}" - ], - "preLaunchTask": "Build Dev Test" - } - ], - "inputs": [ - { - "id": "filter", - "type": "promptString", - "description": "Filter for test names", - "default": "" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 269e17f2..00000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Build Dev", - "type": "shell", - "command": "${workspaceFolder}/scripts/build-dev.sh && cmake --build ${workspaceFolder}/build -j", - "problemMatcher": [], - "group": "build", - }, - { - "label": "Build Release", - "type": "shell", - "command": "${workspaceFolder}/scripts/build-release.sh && cmake --build ${workspaceFolder}/build -j", - "problemMatcher": [], - "group": "build", - }, - { - "label": "Build Dev Test", - "type": "shell", - "command": "${workspaceFolder}/scripts/build-dev-test.sh && cmake --build ${workspaceFolder}/build -j", - "problemMatcher": [], - "group": "build", - } - ] -} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 15a25583..10dfaeaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,7 @@ target_link_directories(clice-core PUBLIC "${LLVM_INSTALL_PATH}/lib") if(NOT WIN32) target_link_libraries(clice-core PUBLIC LLVMSupport + LLVMFrontendOpenMP clangAST clangASTMatchers clangBasic diff --git a/CMakePresets.json b/CMakePresets.json index 98f205d6..6784264a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -28,12 +28,25 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + }, + { + "name": "debug", + "displayName": "Config Debug", + "description": "Sets debug build type", + "inherits": "config-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } } ], "buildPresets": [ { "name": "release", "configurePreset": "release" + }, + { + "name": "debug", + "configurePreset": "debug" } ] -} \ No newline at end of file +} \ No newline at end of file diff --git a/include/Basic/Document.h b/include/Basic/Document.h index d5ca37ec..03f2e9d7 100644 --- a/include/Basic/Document.h +++ b/include/Basic/Document.h @@ -47,23 +47,15 @@ struct TextDocumentPositionParams { Position position; }; - -struct MarkupKind { - std::string_view m_value; - - constexpr MarkupKind(std::string_view value) : m_value(value) {} - - constexpr static std::string_view PlainText = "plaintext"; - constexpr static std::string_view Markdown = "markdown"; -}; +using MarkupKind = string; struct MarkupContent { /// The type of the Markup. - MarkupKind kind; + MarkupKind kind = "markdown"; /// The content itself. string value; - }; +}; struct DidOpenTextDocumentParams { /// The document that was opened. diff --git a/include/Basic/Lifecycle.h b/include/Basic/Lifecycle.h index 1f3f94a4..768e48bb 100644 --- a/include/Basic/Lifecycle.h +++ b/include/Basic/Lifecycle.h @@ -14,7 +14,6 @@ #include "Feature/SignatureHelp.h" #include "Feature/CodeAction.h" #include "Feature/Formatting.h" -#include "Support/Support.h" namespace clice::proto { diff --git a/include/Basic/SourceConverter.h b/include/Basic/SourceConverter.h index 31a480eb..12405f16 100644 --- a/include/Basic/SourceConverter.h +++ b/include/Basic/SourceConverter.h @@ -1,9 +1,7 @@ #pragma once -#include "llvm/Support/Error.h" - -#include -#include +#include "Basic/Location.h" +#include "clang/Basic/SourceLocation.h" namespace clice { @@ -15,7 +13,8 @@ public: using SourceDirMapping = std::vector>; /// Construct a `SourceConverter` with the specified encoding kind and empty source map. - explicit SourceConverter(proto::PositionEncodingKind kind) : kind(kind), sourceMap() {} + explicit SourceConverter(proto::PositionEncodingKind kind = proto::PositionEncodingKind::UTF8) : + kind(kind), sourceMap() {} SourceConverter(proto::PositionEncodingKind kind, SourceDirMapping sourceMap) : kind(kind), sourceMap(std::move(sourceMap)) {} @@ -30,7 +29,8 @@ public: /// Convert a clang::SourceLocation to a proto::Position according to the /// specified encoding kind. Note that `SourceLocation` in clang is 1-based and /// is always encoded in UTF-8. - proto::Position toPosition(llvm::StringRef content, clang::SourceLocation location, + proto::Position toPosition(llvm::StringRef content, + clang::SourceLocation location, const clang::SourceManager& SM) const; /// Same as above, but content is retrieved from the `SourceManager`. @@ -54,7 +54,7 @@ public: /// Convert a real path of a file to URI. Crash if failed. static proto::DocumentUri toURI(llvm::StringRef fspath); - /// Convert a file URI to real path with `clice::fs::real_path`. Crash if failed. + /// Convert a file URI to real path with `clice::fs::real_path`. Crash if failed. static std::string toPath(llvm::StringRef uri); private: diff --git a/include/Compiler/Clang.h b/include/Compiler/Clang.h index 75a01b2d..8b8f49ae 100644 --- a/include/Compiler/Clang.h +++ b/include/Compiler/Clang.h @@ -1,14 +1,14 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include +#include "Support/Support.h" -#include +#include "clang/Lex/Preprocessor.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/Syntax/Tokens.h" +#include "clang/Sema/Sema.h" namespace std { diff --git a/include/Compiler/Compiler.h b/include/Compiler/Compiler.h index 37569a88..7967934b 100644 --- a/include/Compiler/Compiler.h +++ b/include/Compiler/Compiler.h @@ -6,8 +6,8 @@ #include "Resolver.h" #include "Directive.h" -#include "Support/Error.h" #include "Basic/Location.h" +#include "Support/Error.h" #include "llvm/ADT/StringSet.h" diff --git a/include/Compiler/Diagnostic.h b/include/Compiler/Diagnostic.h index b42c1e5b..24a4a23e 100644 --- a/include/Compiler/Diagnostic.h +++ b/include/Compiler/Diagnostic.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "Clang.h" namespace clice { @@ -8,7 +8,8 @@ class DiagnosticCollector : public clang::DiagnosticConsumer { public: void BeginSourceFile(const clang::LangOptions& Opts, const clang::Preprocessor* PP) override; - void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic& Info) override; + void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic& Info) override; void EndSourceFile() override; }; diff --git a/include/Compiler/Directive.h b/include/Compiler/Directive.h index 5dd9e7a4..ef016aa1 100644 --- a/include/Compiler/Directive.h +++ b/include/Compiler/Directive.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "Clang.h" namespace clice { diff --git a/include/Compiler/Resolver.h b/include/Compiler/Resolver.h index 005645e3..02a50d11 100644 --- a/include/Compiler/Resolver.h +++ b/include/Compiler/Resolver.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "Clang.h" namespace clice { diff --git a/include/Compiler/Selection.h b/include/Compiler/Selection.h index caad0d8d..1240f9c4 100644 --- a/include/Compiler/Selection.h +++ b/include/Compiler/Selection.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "Clang.h" namespace clice { diff --git a/include/Compiler/Utility.h b/include/Compiler/Utility.h index d3c063c2..22102189 100644 --- a/include/Compiler/Utility.h +++ b/include/Compiler/Utility.h @@ -1,4 +1,4 @@ -#include +#include "Clang.h" namespace clice { diff --git a/include/Feature/CodeCompletion.h b/include/Feature/CodeCompletion.h index 193b5179..c6c386fd 100644 --- a/include/Feature/CodeCompletion.h +++ b/include/Feature/CodeCompletion.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include "Basic/Document.h" +#include "Support/JSON.h" namespace clice { struct CompilationParams; diff --git a/include/Feature/InlayHint.h b/include/Feature/InlayHint.h index 743d17c0..19f8948c 100644 --- a/include/Feature/InlayHint.h +++ b/include/Feature/InlayHint.h @@ -137,7 +137,8 @@ namespace feature { json::Value inlayHintCapability(json::Value InlayHintClientCapabilities); /// Compute inlay hints for a document in given range and config. -proto::InlayHintsResult inlayHints(proto::InlayHintParams param, ASTInfo& info, +proto::InlayHintsResult inlayHints(proto::InlayHintParams param, + ASTInfo& info, const SourceConverter& converter, const config::InlayHintOption& config); diff --git a/include/Feature/SignatureHelp.h b/include/Feature/SignatureHelp.h index 3bc11f14..3d4aeebb 100644 --- a/include/Feature/SignatureHelp.h +++ b/include/Feature/SignatureHelp.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "Basic/Location.h" namespace clice::proto { diff --git a/include/Server/Logger.h b/include/Server/Logger.h index 621c4b04..62d9c9ae 100644 --- a/include/Server/Logger.h +++ b/include/Server/Logger.h @@ -1,8 +1,7 @@ #pragma once -#include -#include -#include +#include "Support/Format.h" +#include "Support/FileSystem.h" namespace clice::log { diff --git a/include/Support/ADT.h b/include/Support/ADT.h index d505af7c..f65f0436 100644 --- a/include/Support/ADT.h +++ b/include/Support/ADT.h @@ -6,12 +6,12 @@ #include #include -#include -#include -#include -#include -#include -#include +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringExtras.h" namespace clice { diff --git a/include/Support/Compare.h b/include/Support/Compare.h index 8833f680..af381219 100644 --- a/include/Support/Compare.h +++ b/include/Support/Compare.h @@ -1,6 +1,6 @@ #pragma once -#include "Support/ADT.h" +#include "ADT.h" #include "Struct.h" namespace clice::refl { @@ -38,7 +38,7 @@ struct Equal> { } }; -template +template requires (!requires(T lhs, T rhs) { { lhs == rhs } -> std::convertible_to; }) @@ -83,7 +83,7 @@ struct Less> { } }; -template +template requires (!requires(T lhs, T rhs) { { lhs < rhs } -> std::convertible_to; }) diff --git a/include/Support/Enum.h b/include/Support/Enum.h index 7e125799..56d0d43d 100644 --- a/include/Support/Enum.h +++ b/include/Support/Enum.h @@ -1,15 +1,13 @@ #pragma once #include -#include #include #include #include -#include #include #include -#include "TypeTraits.h" +#include "Support/TypeTraits.h" namespace clice::refl { @@ -64,7 +62,7 @@ template (m_Value); + } + /// Get the name of the enum. constexpr std::string_view name() const { using E = typename Derived::Kind; return refl::enum_name(static_cast(m_Value)); } + template ... Kinds> + constexpr bool is_one_of(Kinds... kinds) const { + return ((m_Value == underlying_value(kinds)) || ...); + } + constexpr explicit operator bool () const { return m_Value != invalid(); } @@ -138,7 +146,7 @@ template class Enum { public: /// Tag to indicate this is a special enum. - constexpr inline static bool is_special_enum_v = true; + constexpr inline static bool reflectable_enum = true; using underlying_type = underlying; @@ -250,7 +258,7 @@ template class Enum { public: /// Tag to indicate this is a special enum. - constexpr inline static bool is_special_enum_v = true; + constexpr inline static bool reflectable_enum = true; using underlying_type = underlying; @@ -259,8 +267,16 @@ public: requires { Derived::All; }, "Derived enum must define all possible enum values."); - assert(std::ranges::any_of(Derived::All, [&](auto v) { return v == value; }) && - "Invalid enum value."); + auto check = [](auto value) { + for(auto kind: Derived::All) { + if(kind == value) { + return true; + } + } + return false; + }; + + assert(check(value) && "Invalid enum value."); } constexpr Enum(const Enum&) = default; @@ -276,9 +292,9 @@ private: }; template -concept special_enum = requires { - T::is_special_enum_v; - requires T::is_special_enum_v; +concept reflectable_enum = requires { + T::reflectable_enum; + requires T::reflectable_enum; }; } // namespace clice::refl diff --git a/include/Support/Error.h b/include/Support/Error.h index 2a511b57..caeb2e39 100644 --- a/include/Support/Error.h +++ b/include/Support/Error.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" namespace clice { diff --git a/include/Support/FileSystem.h b/include/Support/FileSystem.h index be2bb62f..9ce4e747 100644 --- a/include/Support/FileSystem.h +++ b/include/Support/FileSystem.h @@ -1,9 +1,9 @@ #pragma once -#include -#include -#include -#include +#include "llvm/Support/Path.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Support/MemoryBuffer.h" namespace clice { diff --git a/include/Support/Format.h b/include/Support/Format.h index 3658ee87..c0dd8037 100644 --- a/include/Support/Format.h +++ b/include/Support/Format.h @@ -4,6 +4,7 @@ #include "Support/Error.h" #include "Support/JSON.h" +#include "Support/Ranges.h" namespace clice { @@ -12,30 +13,11 @@ void print(std::format_string fmt, Args&&... args) { llvm::outs() << std::vformat(fmt.get(), std::make_format_args(args...)); } -template -constexpr inline bool is_formattable_v = false; - -template -constexpr inline bool is_formattable_v = - std::is_convertible_v>; - -template - requires (!is_formattable_v) -void print(Args&&... args) { - ((llvm::outs() << std::format("{}", std::make_format_args(args)) << " "), ...); -} - template void println(std::format_string fmt, Args&&... args) { llvm::outs() << std::vformat(fmt.get(), std::make_format_args(args...)) << '\n'; } -template - requires (!is_formattable_v) -void println(Args&&... args) { - ((llvm::outs() << std::format("{}", std::make_format_args(args)) << " "), ...) << '\n'; -} - } // namespace clice template <> @@ -53,6 +35,21 @@ struct std::formatter : std::formatter { } }; +template +struct std::formatter> : std::formatter { + using Base = std::formatter; + + template + constexpr auto parse(ParseContext& ctx) { + return Base::parse(ctx); + } + + template + auto format(const llvm::SmallString& s, FormatContext& ctx) const { + return Base::format(llvm::StringRef(s), ctx); + } +}; + template <> struct std::formatter : std::formatter { using Base = std::formatter; @@ -71,21 +68,6 @@ struct std::formatter : std::formatter { } }; -template -struct std::formatter> : std::formatter { - using Base = std::formatter; - - template - constexpr auto parse(ParseContext& ctx) { - return Base::parse(ctx); - } - - template - auto format(const llvm::SmallString& s, FormatContext& ctx) const { - return Base::format(llvm::StringRef(s), ctx); - } -}; - template <> struct std::formatter : std::formatter { using Base = std::formatter; @@ -104,8 +86,7 @@ struct std::formatter : std::formatter { } }; -template - requires clice::refl::special_enum +template struct std::formatter : std::formatter { using Base = std::formatter; @@ -119,3 +100,66 @@ struct std::formatter : std::formatter { return Base::format(e.name(), ctx); } }; + +namespace clice { + +/// Dump object to string for debugging. Note that it is not efficient +/// and should not be used except for debugging. +template +std::string dump(const Object& object) { + if constexpr(std::is_fundamental_v) { + return std::format("{}", object); + } else if constexpr(std::is_same_v || + std::is_same_v || + std::is_same_v) { + return std::format("\"{}\"", object); + } else if constexpr(ranges::range) { + constexpr bool is_sequence = sequence_range; + std::string result = is_sequence ? "[" : "{"; + if constexpr(map_range) { + for(auto&& [key, value]: object) { + result += std::format("\"{}\": {}, ", dump(key), dump(value)); + } + } else { + for(auto&& value: object) { + result += std::format("{}, ", dump(value)); + } + } + if(!object.empty()) { + result.pop_back(); + result.pop_back(); + } + result += is_sequence ? "]" : "}"; + return result; + } else if constexpr(std::is_enum_v) { + return std::format("{}", refl::enum_name(object)); + } else if constexpr(refl::reflectable_enum) { + return std::format("\"{}\"", object); + } else if constexpr(refl::reflectable_struct) { + std::string result = "{"; + refl::foreach(object, [&](auto name, auto value) { + result += std::format("\"{}\": {}, ", name, dump(value)); + }); + if(refl::member_count() != 0) { + result.pop_back(); + result.pop_back(); + } + result += "}"; + return result; + } else { + static_assert(dependent_false, "Cannot dump object"); + } +} + +template +std::string pretty_dump(const Object& object, std::size_t indent = 2) { + auto repr = dump(object); + auto json = json::parse(repr); + if(!json) { + std::terminate(); + } + llvm::SmallString<128> buffer = {std::format("{{0:{}}}", indent)}; + return llvm::formatv(buffer.c_str(), *json); +} + +} // namespace clice diff --git a/include/Support/Hash.h b/include/Support/Hash.h index b64736eb..acc3fc5a 100644 --- a/include/Support/Hash.h +++ b/include/Support/Hash.h @@ -1,8 +1,7 @@ #pragma once -#include "llvm/Support/HashBuilder.h" - #include "Struct.h" +#include "llvm/Support/HashBuilder.h" namespace clice::refl { @@ -30,12 +29,11 @@ struct Hash> { }; }; -template +template struct Hash { static llvm::hash_code hash(const T& value) { llvm::SmallVector hashes; - foreach(value, - [&](auto, const auto& member) { hashes.emplace_back(refl::hash(member)); }); + foreach(value, [&](auto, const auto& member) { hashes.emplace_back(refl::hash(member)); }); return llvm::hash_combine_range(hashes.begin(), hashes.end()); } }; diff --git a/include/Support/JSON.h b/include/Support/JSON.h index 6549bc2b..752545cd 100644 --- a/include/Support/JSON.h +++ b/include/Support/JSON.h @@ -2,9 +2,10 @@ #include #include +#include #include -#include "ADT.h" +#include "Ranges.h" #include "TypeTraits.h" #include "Enum.h" #include "Struct.h" @@ -86,6 +87,11 @@ T deserialize(const json::Value& value, Serdes&&... serdes) { } } +template <> +struct Serde { + /// Never use json::Value as a serde. +}; + template <> struct Serde { static json::Value serialize(std::nullptr_t) { @@ -99,16 +105,14 @@ struct Serde { }; template <> -struct Serde { - using V = std::string; - - static json::Value serialize(const V& v) { - return json::Value(v); +struct Serde { + static json::Value serialize(std::nullopt_t) { + return json::Value(nullptr); } - static V deserialize(const json::Value& value) { - assert(value.kind() == json::Value::String && "Expect a string"); - return value.getAsString().value().str(); + static std::nullopt_t deserialize(const json::Value& value) { + assert(value.kind() == json::Value::Null && "Expect null"); + return std::nullopt; } }; @@ -162,14 +166,9 @@ struct Serde { }; template <> -struct Serde { - static json::Value serialize(float v) { - return json::Value(v); - } - - static float deserialize(const json::Value& value) { - assert(value.kind() == json::Value::Number && "Expect number"); - return value.getAsNumber().value(); +struct Serde { + static json::Value serialize(const char* v) { + return json::Value(llvm::StringRef(v)); } }; @@ -181,16 +180,16 @@ struct Serde { }; template <> -struct Serde { - using V = llvm::StringRef; +struct Serde { + using V = std::string; static json::Value serialize(const V& v) { - return json::Value(v.str()); + return json::Value(v); } static V deserialize(const json::Value& value) { - assert(value.kind() == json::Value::String && "Expect string"); - return value.getAsString().value(); + assert(value.kind() == json::Value::String && "Expect a string"); + return value.getAsString().value().str(); } }; @@ -208,78 +207,132 @@ struct Serde { } }; -template -struct Serde> { - using V = std::array; +template <> +struct Serde { + using V = llvm::StringRef; - constexpr inline static bool stateful = stateful_serde; - - template - static json::Value serialize(const V& v, Serdes&&... serdes) { - json::Array array; - for(const auto& element: v) { - array.push_back(json::serialize(element, std::forward(serdes)...)); - } - return array; + static json::Value serialize(const V& v) { + return json::Value(v.str()); } - template - static V deserialize(const json::Value& value, Serdes&&... serdes) { - assert(value.kind() == json::Value::Array && "Expect array"); - V array; - for(std::size_t i = 0; i < N; ++i) { - array[i] = - json::deserialize((*value.getAsArray())[i], std::forward(serdes)...); - } - return array; + static V deserialize(const json::Value& value) { + assert(value.kind() == json::Value::String && "Expect string"); + return value.getAsString().value(); } }; -template -struct Serde> { - using V = std::vector; +template +struct Serde> { + using V = llvm::SmallString; - constexpr inline static bool stateful = stateful_serde; - - template - static json::Value serialize(const V& v, Serdes&&... serdes) { - json::Array array; - for(const auto& element: v) { - array.push_back(json::serialize(element, std::forward(serdes)...)); - } - return array; + static json::Value serialize(const V& v) { + return json::Value(v.str()); } - template - static V deserialize(const json::Value& value, Serdes&&... serdes) { - assert(value.kind() == json::Value::Array && "Expect array"); - V array; - for(const auto& element: *value.getAsArray()) { - array.emplace_back(json::deserialize(element, std::forward(serdes)...)); - } - return array; + static V deserialize(const json::Value& value) { + assert(value.kind() == json::Value::String && "Expect string"); + return V{value.getAsString().value().str()}; } }; -template -struct Serde> { - using V = llvm::ArrayRef; +template +struct Serde { + using key_type = typename Range::key_type; + using mapped_type = typename Range::mapped_type; - constexpr inline static bool stateful = stateful_serde; + constexpr inline static bool stateful = stateful_serde || stateful_serde; - /// Only refl serialization. template - static json::Value serialize(const V& v, Serdes&&... serdes) { - json::Array array; - for(const auto& element: v) { - array.push_back(json::serialize(element, std::forward(serdes)...)); + static json::Value serialize(const Range& range, Serdes&&... serdes) { + json::Object object; + for(const auto& [key, value]: range) { + if constexpr(std::is_constructible_v) { + object.try_emplace(key, json::serialize(value, std::forward(serdes)...)); + } else { + object.try_emplace( + llvm::formatv("{}", json::serialize(key, std::forward(serdes)...)), + json::serialize(value, std::forward(serdes)...)); + } } - return array; + return object; + } + + template + static Range deserialize(const json::Value& value, Serdes&&... serdes) { + assert(value.kind() == json::Value::Object && "JSON must be object"); + Range range; + for(auto& [name, value]: *value.getAsObject()) { + if constexpr(std::is_constructible_v) { + range.try_emplace( + name, + json::deserialize(value, std::forward(serdes)...)); + } else { + if(auto key = json::parse(name)) { + range.try_emplace( + json::deserialize(std::move(*key), + std::forward(serdes)...), + json::deserialize(value, std::forward(serdes)...)); + } + } + } + return range; } }; -template - requires refl::special_enum +template +struct Serde { + using key_type = typename Range::key_type; + + constexpr inline static bool stateful = stateful_serde; + + template + static json::Value serialize(const Range& range, Serdes&&... serdes) { + json::Array array; + for(const auto& element: range) { + array.emplace_back(json::serialize(element, std::forward(serdes)...)); + } + return array; + } + + template + static Range deserialize(const json::Value& value, Serdes&&... serdes) { + assert(value.kind() == json::Value::Array && "JSON must be array"); + Range range; + for(auto& element: *value.getAsArray()) { + range.emplace(json::deserialize(element, std::forward(serdes)...)); + } + return range; + } +}; + +template +struct Serde { + using value_type = typename Range::value_type; + + constexpr inline static bool stateful = stateful_serde; + + template + static json::Value serialize(const Range& range, Serdes&&... serdes) { + json::Array array; + for(const auto& element: range) { + array.emplace_back(json::serialize(element, std::forward(serdes)...)); + } + return array; + } + + template + static Range deserialize(const json::Value& value, Serdes&&... serdes) { + assert(value.kind() == json::Value::Array && "JSON must be array"); + Range range; + for(auto& element: *value.getAsArray()) { + range.emplace_back( + json::deserialize(element, std::forward(serdes)...)); + } + return range; + } +}; + +template struct Serde { static json::Value serialize(const E& e) { return json::Value(e.value()); @@ -290,7 +343,7 @@ struct Serde { } }; -template +template struct Serde { constexpr inline static bool stateful = refl::member_types::apply([] { return (stateful_serde || ...); }); diff --git a/include/Support/Ranges.h b/include/Support/Ranges.h new file mode 100644 index 00000000..4b0949d0 --- /dev/null +++ b/include/Support/Ranges.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +namespace clice { + +namespace ranges = std::ranges; +namespace views = std::views; + +enum class RangeKind { + Map = 0, + Set, + Sequence, + Invalid, +}; + +template +constexpr inline RangeKind range_kind = [] { + if constexpr(std::same_as>>) { + return RangeKind::Invalid; + } else if constexpr(requires { typename Range::key_type; }) { + if constexpr(requires { typename Range::mapped_type; }) { + return RangeKind::Map; + } else { + return RangeKind::Set; + } + } else { + return RangeKind::Sequence; + } +}(); + +template +concept map_range = ranges::input_range && range_kind == RangeKind::Map; + +template +concept set_range = ranges::input_range && range_kind == RangeKind::Set; + +template +concept sequence_range = ranges::input_range && range_kind == RangeKind::Sequence; + +} // namespace clice diff --git a/include/Support/Struct.h b/include/Support/Struct.h index e04472f5..b41c080c 100644 --- a/include/Support/Struct.h +++ b/include/Support/Struct.h @@ -5,7 +5,7 @@ #include #include -#include "TypeTraits.h" +#include "Support/TypeTraits.h" namespace clice::refl { @@ -42,10 +42,18 @@ struct wrapper { }; template -struct storage { - inline static T value; +union storage_t { + char dummy; + T value; + + storage_t() {} + + ~storage_t() {} }; +template +inline storage_t storage; + template consteval auto member_name() { std::string_view name = std::source_location::current().function_name(); @@ -70,133 +78,212 @@ consteval auto member_name() { return name; } +template +constexpr inline auto to_string_literal_impl = [] { + if constexpr(N == 0) { + return std::array{'0', '\0'}; + } else { + constexpr auto length = [] { + std::size_t result = 0; + for(std::size_t n = N; n; n /= 10) { + ++result; + } + return result; + }(); + + std::array result = {}; + for(std::size_t i = length; i; i /= 10) { + result[i - 1] = '0' + N % 10; + } + result[length] = '\0'; + return result; + } +}(); + } // namespace impl +template +constexpr std::string_view to_string_literal() { + return {impl::to_string_literal_impl.data()}; +} + template -struct Struct { - constexpr inline static bool reflectable = - std::is_aggregate_v> && - std::is_default_constructible_v>; +struct Struct; - constexpr static std::size_t member_count() { - return impl::member_count(); - } +/// To check if the type is reflectable_struct. +template +concept reflectable_struct = Struct>::reflectable_struct; - constexpr static T& instance() { - return impl::storage::value; - } +/// Get the member count of the type. +template +constexpr static std::size_t member_count() { + return Struct>::member_count; +} + +/// Get the all member names of the type. +template +constexpr static auto& member_names() { + return Struct>::member_names; +} + +/// Get the member name of the type at index N. +template +constexpr static std::string_view member_name() { + return member_names()[N]; +} + +/// Get the member types of the type. +template +constexpr decltype(auto) member_value(T&& object) { + return *std::get(Struct>::collect_members(object)); +} + +template +using member_types = + tuple_to_list_t::collect_members(std::declval())), std::remove_pointer_t>; + +/// Specialize for aggregate class. +template + requires std::is_aggregate_v +struct Struct { + constexpr inline static bool reflectable_struct = true; + + constexpr inline static auto member_count = impl::member_count(); template - constexpr static auto collcet_members(Object&& object) { + constexpr static auto collect_members(Object&& object) { // clang-format off - constexpr std::size_t count = member_count(); - if constexpr(count == 0) { + if constexpr (member_count == 0) { return std::tuple{}; - } else if constexpr(count == 1) { - auto&& [a] = object; - return std::tuple{&a}; - } else if constexpr(count == 2) { - auto&& [a, b] = object; - return std::tuple{&a, &b}; - } else if constexpr(count == 3) { - auto&& [a, b, c] = object; - return std::tuple{&a, &b, &c}; - } else if constexpr(count == 4) { - auto&& [a, b, c, d] = object; - return std::tuple{&a, &b, &c, &d}; - } else if constexpr(count == 5) { - auto&& [a, b, c, d, e] = object; - return std::tuple{&a, &b, &c, &d, &e}; - } else if constexpr(count == 6) { - auto&& [a, b, c, d, e, f] = object; - return std::tuple{&a, &b, &c, &d, &e, &f}; - } else if constexpr(count == 7) { - auto&& [a, b, c, d, e, f, g] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g}; - } else if constexpr(count == 8) { - auto&& [a, b, c, d, e, f, g, h] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h}; - } else if constexpr(count == 9) { - auto&& [a, b, c, d, e, f, g, h, i] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i}; - } else if constexpr(count == 10) { - auto&& [a, b, c, d, e, f, g, h, i, j] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j}; - } else if constexpr(count == 11) { - auto&& [a, b, c, d, e, f, g, h, i, j, k] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k}; - } else if constexpr(count == 12) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l}; - } else if constexpr(count == 13) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m}; - } else if constexpr(count == 14) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n}; - } else if constexpr(count == 15) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o}; - } else if constexpr(count == 16) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p}; - } else if constexpr(count == 17) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q}; - } else if constexpr(count == 18) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r}; - } else if constexpr(count == 19) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s}; - } else if constexpr(count == 20) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t}; - } else if constexpr(count == 21) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u}; - } else if constexpr(count == 22) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v}; - } else if constexpr(count == 23) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w}; - } else if constexpr(count == 24) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x}; - } else if constexpr(count == 25) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y}; - } else if constexpr(count == 26) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z}; - } else if constexpr(count == 27) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0}; - } else if constexpr(count == 28) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0, _1] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0, &_1}; - } else if constexpr(count == 29) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0, _1, _2] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0, &_1, &_2}; - } else if constexpr(count == 30) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0, _1, _2, _3] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0, &_1, &_2, &_3}; + } else if constexpr (member_count == 1) { + auto&& [e1] = object; + return std::tuple{ &e1 }; + } else if constexpr (member_count == 2) { + auto&& [e1, e2] = object; + return std::tuple{ &e1, &e2 }; + } else if constexpr (member_count == 3) { + auto&& [e1, e2, e3] = object; + return std::tuple{ &e1, &e2, &e3 }; + } else if constexpr (member_count == 4) { + auto&& [e1, e2, e3, e4] = object; + return std::tuple{ &e1, &e2, &e3, &e4 }; + } else if constexpr (member_count == 5) { + auto&& [e1, e2, e3, e4, e5] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5 }; + } else if constexpr (member_count == 6) { + auto&& [e1, e2, e3, e4, e5, e6] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6 }; + } else if constexpr (member_count == 7) { + auto&& [e1, e2, e3, e4, e5, e6, e7] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7 }; + } else if constexpr (member_count == 8) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8 }; + } else if constexpr (member_count == 9) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9 }; + } else if constexpr (member_count == 10) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10 }; + } else if constexpr (member_count == 11) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11 }; + } else if constexpr (member_count == 12) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12 }; + } else if constexpr (member_count == 13) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13 }; + } else if constexpr (member_count == 14) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14 }; + } else if constexpr (member_count == 15) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15 }; + } else if constexpr (member_count == 16) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16 }; + } else if constexpr (member_count == 17) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17 }; + } else if constexpr (member_count == 18) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18 }; + } else if constexpr (member_count == 19) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19 }; + } else if constexpr (member_count == 20) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20 }; + } else if constexpr (member_count == 21) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21 }; + } else if constexpr (member_count == 22) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22 }; + } else if constexpr (member_count == 23) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23 }; + } else if constexpr (member_count == 24) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24 }; + } else if constexpr (member_count == 25) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25 }; + } else if constexpr (member_count == 26) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26 }; + } else if constexpr (member_count == 27) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27 }; + } else if constexpr (member_count == 28) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27, &e28 }; + } else if constexpr (member_count == 29) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27, &e28, &e29 }; + } else if constexpr (member_count == 30) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27, &e28, &e29, &e30 }; + } else if constexpr (member_count == 31) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27, &e28, &e29, &e30, &e31 }; + } else if constexpr (member_count == 32) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27, &e28, &e29, &e30, &e31, &e32 }; + } else if constexpr (member_count == 33) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32, e33] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27, &e28, &e29, &e30, &e31, &e32, &e33 }; + } else if constexpr (member_count == 34) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32, e33, e34] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27, &e28, &e29, &e30, &e31, &e32, &e33, &e34 }; + } else if constexpr (member_count == 35) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32, e33, e34, e35] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27, &e28, &e29, &e30, &e31, &e32, &e33, &e34, &e35 }; + } else if constexpr (member_count == 36) { + auto&& [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, e30, e31, e32, e33, e34, e35, e36] = object; + return std::tuple{ &e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18, &e19, &e20, &e21, &e22, &e23, &e24, &e25, &e26, &e27, &e28, &e29, &e30, &e31, &e32, &e33, &e34, &e35, &e36 }; } else { - static_assert(count <= 30, "Not supported member count"); + // For counts greater than 36, trigger a compile-time error + static_assert(member_count <= 36, "Not supported member count"); } // clang-format on } -}; -/// To check if the type is reflectable. -template -concept reflectable = Struct::reflectable; + constexpr inline static auto member_names = [](std::index_sequence) { + if constexpr(member_count == 0) { + return std::array{}; + } else { + constexpr auto members = collect_members(impl::storage.value); + return std::array{impl::member_name(members)>()...}; + } + }(std::make_index_sequence{}); +}; template struct Inheritance : Ts... {}; -/// Use to define a reflectable struct with inheritance. +/// Use to define a reflectable_struct struct with inheritance. #define inherited_struct(name, ...) \ struct name##Body; \ using name = clice::refl::Inheritance<__VA_ARGS__, name##Body>; \ @@ -204,29 +291,41 @@ struct Inheritance : Ts... {}; template struct Struct> { - constexpr inline static bool reflectable = (refl::reflectable && ...); + constexpr inline static bool reflectable_struct = (refl::reflectable_struct && ...); - constexpr static std::size_t member_count() { - return (Struct::member_count() + ...); - } - - constexpr static auto& instance() { - return impl::storage>::value; - } + constexpr static std::size_t member_count = (Struct::member_count + ...); template - constexpr static auto collcet_members(Object&& object) { + constexpr static auto collect_members(Object&& object) { if constexpr(std::is_const_v>) { - return std::tuple_cat(Struct::collcet_members(static_cast(object))...); + return std::tuple_cat(Struct::collect_members(static_cast(object))...); } else { - return std::tuple_cat(Struct::collcet_members(static_cast(object))...); + return std::tuple_cat(Struct::collect_members(static_cast(object))...); } } + + constexpr inline static auto member_names = [](std::index_sequence) { + constexpr auto members = collect_members(impl::storage>.value); + return std::array{impl::member_name(members)>()...}; + }(std::make_index_sequence{}); }; -template -using member_types = - tuple_to_list_t::collcet_members(std::declval())), std::remove_pointer_t>; +template + requires requires { std::tuple_size::value; } +struct Struct { + constexpr inline static bool reflectable_struct = true; + + constexpr inline static std::size_t member_count = std::tuple_size_v; + + template + constexpr static auto collect_members(Object&& object) { + return std::apply([](auto&&... args) { return std::tuple{&args...}; }, object); + } + + constexpr inline static auto member_names = [](std::index_sequence) { + return std::array{to_string_literal()...}; + }(std::make_index_sequence{}); +}; /// Turn the return value of the callable to bool. template @@ -242,31 +341,23 @@ constexpr auto foldable(const Callable& callable) { }; } -template +template constexpr bool foreach(Object&& object, const Callback& callback) { - using S = Struct>; - constexpr auto count = S::member_count(); - auto members = S::collcet_members(object); - constexpr auto static_members = S::collcet_members(S::instance()); + auto foldable = refl::foldable(callback); return [&](std::index_sequence) { - return (foldable(callback)(impl::member_name(static_members)>(), - *std::get(members)) && - ...); - }(std::make_index_sequence{}); + return (foldable(refl::member_name(), refl::member_value(object)) && ...); + }(std::make_index_sequence()>{}); } /// Invoke callback for each member of lhs and rhs, return false /// in callback to abort the iteration. Return true if all members are visited. -template +template constexpr bool foreach(LHS&& lhs, RHS&& rhs, const Callback& callback) { - using L = Struct>; - using R = Struct>; - static_assert(L::member_count() == R::member_count(), "Member count mismatch"); + static_assert(member_count() == member_count(), "Member count mismatch"); + auto foldable = refl::foldable(callback); return [&](std::index_sequence) { - return (foldable(callback)(*std::get(L::collcet_members(lhs)), - *std::get(R::collcet_members(rhs))) && - ...); - }(std::make_index_sequence{}); + return (foldable(refl::member_value(lhs), refl::member_value(rhs)) && ...); + }(std::make_index_sequence()>{}); } } // namespace clice::refl diff --git a/include/Test/Annotation.h b/include/Test/Annotation.h new file mode 100644 index 00000000..115e456b --- /dev/null +++ b/include/Test/Annotation.h @@ -0,0 +1,61 @@ +#pragma once + +#include "Basic/Location.h" + +namespace clice { + +class Annotation { +public: + Annotation(llvm::StringRef source) : m_source() { + m_source.reserve(source.size()); + + uint32_t line = 0; + uint32_t column = 0; + + for(uint32_t i = 0; i < source.size();) { + auto c = source[i]; + + if(c == '@') { + i += 1; + auto key = source.substr(i).take_until([](char c) { return c == ' '; }); + assert(!locations.contains(key) && "duplicate key"); + locations.try_emplace(key, line, column); + continue; + } + + if(c == '$') { + assert(i + 1 < source.size() && source[i + 1] == '(' && "expect $(name)"); + i += 2; + auto key = source.substr(i).take_until([](char c) { return c == ')'; }); + i += key.size() + 1; + assert(!locations.contains(key) && "duplicate key"); + locations.try_emplace(key, line, column); + continue; + } + + if(c == '\n') { + line += 1; + column = 0; + } else { + column += 1; + } + + i += 1; + m_source.push_back(c); + } + } + + llvm::StringRef source() const { + return m_source; + } + + proto::Position position(llvm::StringRef key) const { + return locations.lookup(key); + } + +private: + std::string m_source; + llvm::StringMap locations; +}; + +} // namespace clice diff --git a/include/Test/CTest.h b/include/Test/CTest.h new file mode 100644 index 00000000..f2260dc6 --- /dev/null +++ b/include/Test/CTest.h @@ -0,0 +1,163 @@ +#pragma once + +#include "gtest/gtest.h" +#include "Basic/Location.h" +#include "Compiler/Compiler.h" +#include "Support/Support.h" + +namespace clice::test { + +llvm::StringRef source_dir(); + +llvm::StringRef resource_dir(); + +} // namespace clice::test + +namespace clice::testing { + +#undef EXPECT_EQ +#undef EXPECT_NE + +inline void EXPECT_FAILURE(std::string msg, + std::source_location current = std::source_location::current()) { + ::testing::internal::AssertHelper(::testing ::TestPartResult ::kNonFatalFailure, + current.file_name(), + current.line(), + msg.c_str()) = ::testing ::Message(); +} + +template +inline void EXPECT_EQ(const LHS& lhs, + const RHS& rhs, + std::source_location current = std::source_location::current()) { + if(!refl::equal(lhs, rhs)) { + std::string expect; + if constexpr(requires { sizeof(json::Serde); }) { + llvm::raw_string_ostream(expect) << json::serialize(lhs); + } else { + expect = "cannot dump value"; + } + + std::string actual; + if constexpr(requires { sizeof(json::Serde); }) { + llvm::raw_string_ostream(actual) << json::serialize(rhs); + } else { + actual = "cannot dump value"; + } + + EXPECT_FAILURE(std::format("expect: {}, actual: {}\n", expect, actual), current); + } +} + +template +inline void EXPECT_NE(const LHS& lhs, + const RHS& rhs, + std::source_location current = std::source_location::current()) { + if(refl::equal(lhs, rhs)) { + std::string expect; + if constexpr(requires { sizeof(json::Serde); }) { + llvm::raw_string_ostream(expect) << json::serialize(lhs); + } else { + expect = "cannot dump value"; + } + + std::string actual; + if constexpr(requires { sizeof(json::Serde); }) { + llvm::raw_string_ostream(actual) << json::serialize(rhs); + } else { + actual = "cannot dump value"; + } + + EXPECT_FAILURE(std::format("expect: {}, actual: {}\n", expect, actual), current); + } +} + +class Tester { +public: + CompilationParams params; + std::unique_ptr vfs; + ASTInfo info; + + /// Annoated locations. + std::vector sources; + llvm::StringMap locations; + llvm::StringMap offsets; + +public: + Tester(llvm::StringRef file, llvm::StringRef content) { + params.srcPath = file; + params.content = annoate(content); + } + + void addFile(llvm::StringRef name, llvm::StringRef content) { + params.remappedFiles.emplace_back(name, content); + } + + llvm::StringRef annoate(llvm::StringRef content) { + auto& source = sources.emplace_back(); + source.reserve(content.size()); + + uint32_t line = 0; + uint32_t column = 0; + uint32_t offset = 0; + + for(uint32_t i = 0; i < content.size();) { + auto c = content[i]; + + if(c == '@') { + i += 1; + auto key = content.substr(i).take_until([](char c) { return c == ' '; }); + assert(!locations.contains(key) && "duplicate key"); + locations.try_emplace(key, line, column); + offsets.try_emplace(key, offset); + continue; + } + + if(c == '$') { + assert(i + 1 < content.size() && content[i + 1] == '(' && "expect $(name)"); + i += 2; + auto key = content.substr(i).take_until([](char c) { return c == ')'; }); + i += key.size() + 1; + assert(!locations.contains(key) && "duplicate key"); + locations.try_emplace(key, line, column); + offsets.try_emplace(key, offset); + continue; + } + + if(c == '\n') { + line += 1; + column = 0; + } else { + column += 1; + } + + i += 1; + offset += 1; + + source.push_back(c); + } + + return source; + } + + Tester& run(const char* standard = "-std=c++20") { + params.vfs = std::move(vfs); + params.command = std::format("clang++ {} {}", standard, params.srcPath); + + auto info = compile(params); + if(!info) { + llvm::errs() << "Failed to build AST\n"; + std::terminate(); + } + + this->info = std::move(*info); + return *this; + } + + proto::Position pos(llvm::StringRef key) const { + return locations.lookup(key); + } +}; + +} // namespace clice::testing + diff --git a/unittests/Index/IndexTester.h b/include/Test/IndexTester.h similarity index 93% rename from unittests/Index/IndexTester.h rename to include/Test/IndexTester.h index 94b2d8de..d024a1a1 100644 --- a/unittests/Index/IndexTester.h +++ b/include/Test/IndexTester.h @@ -1,8 +1,8 @@ -#include "Test/Test.h" +#include "Test/CTest.h" #include "Index/SymbolIndex.h" #include "Basic/SourceConverter.h" -namespace clice { +namespace clice::testing { struct IndexTester : Tester { std::vector binary; @@ -37,4 +37,4 @@ struct IndexTester : Tester { } }; -} // namespace clice +} // namespace clice::testing diff --git a/include/Test/Test.h b/include/Test/Test.h index 3240c766..22c549b4 100644 --- a/include/Test/Test.h +++ b/include/Test/Test.h @@ -1,263 +1,9 @@ #pragma once #include "gtest/gtest.h" -#include "Basic/Location.h" -#include "Compiler/Compiler.h" -#include "Support/Support.h" -namespace clice { +namespace clice::testing { -namespace test { -llvm::StringRef source_dir(); - -llvm::StringRef resource_dir(); - -} // namespace test - -class Annotation { -public: - Annotation(llvm::StringRef source) : m_source() { - m_source.reserve(source.size()); - - uint32_t line = 0; - uint32_t column = 0; - - for(uint32_t i = 0; i < source.size();) { - auto c = source[i]; - - if(c == '@') { - i += 1; - auto key = source.substr(i).take_until([](char c) { return c == ' '; }); - assert(!locations.contains(key) && "duplicate key"); - locations.try_emplace(key, line, column); - continue; - } - - if(c == '$') { - assert(i + 1 < source.size() && source[i + 1] == '(' && "expect $(name)"); - i += 2; - auto key = source.substr(i).take_until([](char c) { return c == ')'; }); - i += key.size() + 1; - assert(!locations.contains(key) && "duplicate key"); - locations.try_emplace(key, line, column); - continue; - } - - if(c == '\n') { - line += 1; - column = 0; - } else { - column += 1; - } - - i += 1; - m_source.push_back(c); - } - } - - llvm::StringRef source() const { - return m_source; - } - - proto::Position position(llvm::StringRef key) const { - return locations.lookup(key); - } - -private: - std::string m_source; - llvm::StringMap locations; -}; - -template -inline void foreachFile(std::string name, const Callback& callback) { - llvm::SmallString<128> path; - path += test::source_dir(); - path::append(path, name); - std::error_code error; - fs::directory_iterator iter(path, error); - fs::directory_iterator end; - while(!error && iter != end) { - auto file = iter->path(); - auto buffer = llvm::MemoryBuffer::getFile(file); - if(!buffer) { - llvm::outs() << "failed to open file: " << buffer.getError().message() << file << "\n"; - // TODO: - } - auto content = buffer.get()->getBuffer(); - callback(file, content); - iter.increment(error); - } -} - -#undef EXPECT_EQ -#undef EXPECT_NE - -inline void EXPECT_FAILURE(std::string msg, - std::source_location current = std::source_location::current()) { - ::testing::internal::AssertHelper(::testing ::TestPartResult ::kNonFatalFailure, - current.file_name(), - current.line(), - msg.c_str()) = ::testing ::Message(); -} - -template -inline void EXPECT_EQ(const LHS& lhs, - const RHS& rhs, - std::source_location current = std::source_location::current()) { - if(!refl::equal(lhs, rhs)) { - std::string expect; - if constexpr(requires { sizeof(json::Serde); }) { - llvm::raw_string_ostream(expect) << json::serialize(lhs); - } else { - expect = "cannot dump value"; - } - - std::string actual; - if constexpr(requires { sizeof(json::Serde); }) { - llvm::raw_string_ostream(actual) << json::serialize(rhs); - } else { - actual = "cannot dump value"; - } - - EXPECT_FAILURE(std::format("expect: {}, actual: {}\n", expect, actual), current); - } -} - -template -inline void EXPECT_NE(const LHS& lhs, - const RHS& rhs, - std::source_location current = std::source_location::current()) { - if(refl::equal(lhs, rhs)) { - std::string expect; - if constexpr(requires { sizeof(json::Serde); }) { - llvm::raw_string_ostream(expect) << json::serialize(lhs); - } else { - expect = "cannot dump value"; - } - - std::string actual; - if constexpr(requires { sizeof(json::Serde); }) { - llvm::raw_string_ostream(actual) << json::serialize(rhs); - } else { - actual = "cannot dump value"; - } - - EXPECT_FAILURE(std::format("expect: {}, actual: {}\n", expect, actual), current); - } -} - -class Tester { -public: - CompilationParams params; - std::unique_ptr vfs; - ASTInfo info; - - /// Annoated locations. - std::vector sources; - llvm::StringMap locations; - llvm::StringMap offsets; - -public: - Tester(llvm::StringRef file, llvm::StringRef content) { - params.srcPath = file; - params.content = annoate(content); - } - - void addFile(llvm::StringRef name, llvm::StringRef content) { - params.remappedFiles.emplace_back(name, content); - } - - llvm::StringRef annoate(llvm::StringRef content) { - auto& source = sources.emplace_back(); - source.reserve(content.size()); - - uint32_t line = 0; - uint32_t column = 0; - uint32_t offset = 0; - - for(uint32_t i = 0; i < content.size();) { - auto c = content[i]; - - if(c == '@') { - i += 1; - auto key = content.substr(i).take_until([](char c) { return c == ' '; }); - assert(!locations.contains(key) && "duplicate key"); - locations.try_emplace(key, line, column); - offsets.try_emplace(key, offset); - continue; - } - - if(c == '$') { - assert(i + 1 < content.size() && content[i + 1] == '(' && "expect $(name)"); - i += 2; - auto key = content.substr(i).take_until([](char c) { return c == ')'; }); - i += key.size() + 1; - assert(!locations.contains(key) && "duplicate key"); - locations.try_emplace(key, line, column); - offsets.try_emplace(key, offset); - continue; - } - - if(c == '\n') { - line += 1; - column = 0; - } else { - column += 1; - } - - i += 1; - offset += 1; - - source.push_back(c); - } - - return source; - } - - Tester& run(const char* standard = "-std=c++20") { - params.vfs = std::move(vfs); - params.command = std::format("clang++ {} {}", standard, params.srcPath); - - auto info = compile(params); - if(!info) { - llvm::errs() << "Failed to build AST\n"; - std::terminate(); - } - - this->info = std::move(*info); - return *this; - } - - Tester& fail(const auto& lhs, const auto& rhs, std::source_location loc) { - auto msg = - std::format("left : {}\nright: {}\n", json::serialize(lhs), json::serialize(rhs)); - ::testing::internal::AssertHelper(::testing ::TestPartResult ::kFatalFailure, - loc.file_name(), - loc.line(), - msg.c_str()) = ::testing ::Message(); - return *this; - } - - Tester& equal(const auto& lhs, - const auto& rhs, - std::source_location loc = std::source_location::current()) { - if(!refl::equal(lhs, rhs)) { - return fail(lhs, rhs, loc); - } - return *this; - } - - Tester& expect(llvm::StringRef name, - clang::SourceLocation loc, - std::source_location current = std::source_location::current()) { - auto pos = locations.lookup(name); - auto presumed = info.srcMgr().getPresumedLoc(loc); - /// FIXME: - equal(pos, proto::Position{presumed.getLine() - 1, presumed.getColumn() - 1}, current); - return *this; - } -}; - -} // namespace clice +} \ No newline at end of file diff --git a/src/Driver/unit_tests.cc b/src/Driver/unit_tests.cc index 4127da23..a34012b8 100644 --- a/src/Driver/unit_tests.cc +++ b/src/Driver/unit_tests.cc @@ -33,7 +33,7 @@ llvm::StringRef resource_dir() { int main(int argc, char** argv) { using namespace clice; - testing::InitGoogleTest(&argc, argv); + ::testing::InitGoogleTest(&argc, argv); llvm::cl::ParseCommandLineOptions(argc, argv, "clice test\n"); if(!cl::resource_dir.empty()) { diff --git a/src/Feature/InlayHint.cpp b/src/Feature/InlayHint.cpp index 0c27fb82..959c4e68 100644 --- a/src/Feature/InlayHint.cpp +++ b/src/Feature/InlayHint.cpp @@ -12,7 +12,6 @@ namespace { /// Create a blank markup content as a place holder. proto::MarkupContent blank() { return { - .kind = proto::MarkupKind::PlainText, .value = "", }; } @@ -83,7 +82,8 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { /// Collect hint for variable declared with `auto` keywords. /// The hint string wiil be placed at the right side of identifier, starting with ':' character. /// The `originDeclRange` will be used as the link of hint string. - void collectAutoDeclHint(clang::QualType deduced, clang::SourceRange identRange, + void collectAutoDeclHint(clang::QualType deduced, + clang::SourceRange identRange, std::optional linkDeclRange) { // For lambda expression, `getAsString` return a text like `(lambda at main.cpp:2:10)` @@ -404,7 +404,8 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { return true; } - void collectReturnTypeHint(clang::SourceLocation hintLoc, clang::QualType retType, + void collectReturnTypeHint(clang::SourceLocation hintLoc, + clang::QualType retType, clang::SourceRange retTypeDeclRange) { proto::InlayHintLablePart lable{ .value = shrinkText(std::format("-> {}", retType.getAsString(policy))), @@ -534,8 +535,10 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { return remain.ltrim(); } - void collectBlockEndHint(clang::SourceLocation location, std::string text, - clang::SourceRange linkRange, bool checkDuplicatedHint) { + void collectBlockEndHint(clang::SourceLocation location, + std::string text, + clang::SourceRange linkRange, + bool checkDuplicatedHint) { // Already has a comment in that line. if(auto remain = remainTextOfThatLine(location); remain.starts_with("/*") || remain.starts_with("//")) @@ -663,7 +666,8 @@ json::Value inlayHintCapability(json::Value InlayHintClientCapabilities) { } /// Compute inlay hints for a document in given range and config. -proto::InlayHintsResult inlayHints(proto::InlayHintParams param, ASTInfo& info, +proto::InlayHintsResult inlayHints(proto::InlayHintParams param, + ASTInfo& info, const SourceConverter& converter, const config::InlayHintOption& config) { const clang::SourceManager& src = info.srcMgr(); diff --git a/unittests/Basic/SourceConverter.cpp b/unittests/Basic/SourceConverter.cpp index c86cc134..d09dcd0c 100644 --- a/unittests/Basic/SourceConverter.cpp +++ b/unittests/Basic/SourceConverter.cpp @@ -1,91 +1,90 @@ +#include "Test/CTest.h" #include "Basic/SourceConverter.h" -#include "Test/Test.h" -namespace clice { +namespace clice::testing { namespace { TEST(SourceConverter, Remeasure) { - using SC = SourceConverter; + using SC = SourceConverter; - SourceConverter utf8{proto::PositionEncodingKind::UTF8}; + SourceConverter utf8{proto::PositionEncodingKind::UTF8}; - EXPECT_EQ(utf8.remeasure(""), 0); - EXPECT_EQ(utf8.remeasure("ascii"), 5); + EXPECT_EQ(utf8.remeasure(""), 0); + EXPECT_EQ(utf8.remeasure("ascii"), 5); - SourceConverter utf16{proto::PositionEncodingKind::UTF16}; + SourceConverter utf16{proto::PositionEncodingKind::UTF16}; - EXPECT_EQ(utf16.remeasure("↓"), 1); - EXPECT_EQ(utf16.remeasure("¥"), 1); + EXPECT_EQ(utf16.remeasure("↓"), 1); + EXPECT_EQ(utf16.remeasure("¥"), 1); - SourceConverter utf32{proto::PositionEncodingKind::UTF32}; - EXPECT_EQ(utf8.remeasure("😂"), 4); - EXPECT_EQ(utf16.remeasure("😂"), 2); - EXPECT_EQ(utf32.remeasure("😂"), 1); + SourceConverter utf32{proto::PositionEncodingKind::UTF32}; + EXPECT_EQ(utf8.remeasure("😂"), 4); + EXPECT_EQ(utf16.remeasure("😂"), 2); + EXPECT_EQ(utf32.remeasure("😂"), 1); } TEST(SourceConverter, Position) { - const char *main = "int a /*😂*/ = 1;$(eof)"; + const char* main = "int a /*😂*/ = 1;$(eof)"; - Tester txs("main.cpp", main); - txs.run("-std=c++11"); + Tester txs("main.cpp", main); + txs.run("-std=c++11"); - auto &src = txs.info.srcMgr(); - auto &tks = txs.info.tokBuf(); + auto& src = txs.info.srcMgr(); + auto& tks = txs.info.tokBuf(); - auto mainid = src.getMainFileID(); - auto tokens = tks.expandedTokens( - {src.getLocForStartOfFile(mainid), src.getLocForEndOfFile(mainid)}); + auto mainid = src.getMainFileID(); + auto tokens = + tks.expandedTokens({src.getLocForStartOfFile(mainid), src.getLocForEndOfFile(mainid)}); - auto eof = tokens.back().endLocation(); - txs.expect("eof", eof); + auto eof = tokens.back().endLocation(); - { - SourceConverter cvtr{proto::PositionEncodingKind::UTF8}; - auto pos = cvtr.toPosition(eof, src); - EXPECT_EQ(pos.line, 0); - EXPECT_EQ(pos.character, 19); - } + { + SourceConverter cvtr{proto::PositionEncodingKind::UTF8}; + auto pos = cvtr.toPosition(eof, src); + EXPECT_EQ(pos.line, 0); + EXPECT_EQ(pos.character, 19); + } - { - SourceConverter cvtr{proto::PositionEncodingKind::UTF16}; - auto pos = cvtr.toPosition(eof, src); - EXPECT_EQ(pos.line, 0); - EXPECT_EQ(pos.character, 19); - } + { + SourceConverter cvtr{proto::PositionEncodingKind::UTF16}; + auto pos = cvtr.toPosition(eof, src); + EXPECT_EQ(pos.line, 0); + EXPECT_EQ(pos.character, 19); + } - { - SourceConverter cvtr{proto::PositionEncodingKind::UTF32}; - auto pos = cvtr.toPosition(eof, src); - EXPECT_EQ(pos.line, 0); - EXPECT_EQ(pos.character, 19); - } + { + SourceConverter cvtr{proto::PositionEncodingKind::UTF32}; + auto pos = cvtr.toPosition(eof, src); + EXPECT_EQ(pos.line, 0); + EXPECT_EQ(pos.character, 19); + } } TEST(SourceConverter, UriAndFsPath) { - using SC = SourceConverter; + using SC = SourceConverter; - // It must be a existed file. + // It must be a existed file. #if defined(__unix__) - llvm::StringRef paths[] = {"/dev/null"}; - llvm::StringRef uris[] = {"file:///dev/null"}; + llvm::StringRef paths[] = {"/dev/null"}; + llvm::StringRef uris[] = {"file:///dev/null"}; #elif defined(_WIN32) - llvm::StringRef paths[] = {"C:\\Windows\\System32\\notepad.exe"}; - llvm::StringRef uris[] = {"file:///C%3A/Windows/System32/notepad.exe"}; + llvm::StringRef paths[] = {"C:\\Windows\\System32\\notepad.exe"}; + llvm::StringRef uris[] = {"file:///C%3A/Windows/System32/notepad.exe"}; #else #error "Unsupported platform" #endif - EXPECT_EQ(std::size(paths), std::size(uris)); + EXPECT_EQ(std::size(paths), std::size(uris)); - for (int i = 0; i < std::size(paths); ++i) { - llvm::outs() << ": " << SC::toURI(paths[i])<< "\n"; - llvm::outs() << ": " << SC::toPath(uris[i])<< "\n"; - EXPECT_EQ(SC::toURI(paths[i]), uris[i]); - EXPECT_EQ(paths[i], SC::toPath(uris[i])); - } + for(int i = 0; i < std::size(paths); ++i) { + llvm::outs() << ": " << SC::toURI(paths[i]) << "\n"; + llvm::outs() << ": " << SC::toPath(uris[i]) << "\n"; + EXPECT_EQ(SC::toURI(paths[i]), uris[i]); + EXPECT_EQ(paths[i], SC::toPath(uris[i])); + } } -} // namespace +} // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Compiler/Command.cpp b/unittests/Compiler/Command.cpp index 33bccb14..2cbf0a96 100644 --- a/unittests/Compiler/Command.cpp +++ b/unittests/Compiler/Command.cpp @@ -1,8 +1,7 @@ #include "Test/Test.h" - #include "Compiler/Command.h" -namespace clice { +namespace clice::testing { namespace { diff --git a/unittests/Compiler/Compiler.cpp b/unittests/Compiler/Compiler.cpp index bdf0bbf3..163207c5 100644 --- a/unittests/Compiler/Compiler.cpp +++ b/unittests/Compiler/Compiler.cpp @@ -1,8 +1,8 @@ #include "Test/Test.h" #include "Compiler/Compiler.h" -#include +#include "Support/FileSystem.h" -namespace clice { +namespace clice::testing { namespace { @@ -179,5 +179,5 @@ export int foo = 1; } // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Compiler/Diagnostic.cpp b/unittests/Compiler/Diagnostic.cpp index 2d9fd9c4..d74d4cba 100644 --- a/unittests/Compiler/Diagnostic.cpp +++ b/unittests/Compiler/Diagnostic.cpp @@ -1,23 +1,15 @@ #include "Test/Test.h" -#include -#include +#include "Compiler/Diagnostic.h" + +namespace clice::testing { namespace { using namespace clice; -TEST(clice, Diagnostic) { - // foreachFile("Diagnostic", [](std::string file, llvm::StringRef content) { - // std::vector compileArgs = { - // "clang++", - // "-std=c++20", - // file.c_str(), - // "-resource-dir", - // "/home/ykiko/C++/clice2/build/lib/clang/20", - // }; - // Compiler compiler(file, content, compileArgs, new DiagnosticCollector()); - // compiler.buildAST(); - // }); -} +TEST(clice, Diagnostic) {} } // namespace + +} // namespace clice::testing + diff --git a/unittests/Compiler/Directive.cpp b/unittests/Compiler/Directive.cpp index e8144d32..05f44196 100644 --- a/unittests/Compiler/Directive.cpp +++ b/unittests/Compiler/Directive.cpp @@ -1,7 +1,7 @@ -#include "Test/Test.h" -#include "Compiler/Compiler.h" +#include "Test/CTest.h" +#include "Basic/SourceConverter.h" -namespace clice { +namespace clice::testing { namespace { @@ -27,13 +27,19 @@ TEST(Directive, Include) { auto& info = tester.info; auto& includes = info.directive(info.srcMgr().getMainFileID()).includes; - tester.equal(includes.size(), 3) - .expect("0", includes[0].loc) - .equal("test.h", includes[0].path) - .expect("1", includes[1].loc) - .equal("test2.h", includes[1].path) - .expect("2", includes[2].loc) - .equal("test3.h", includes[2].path); + auto EXPECT_INCLUDE = [&](std::size_t index, + llvm::StringRef position, + llvm::StringRef path, + std::source_location current = std::source_location::current()) { + auto& include = includes[index]; + EXPECT_EQ(SourceConverter().toPosition(include.loc, info.srcMgr()), tester.pos(position)); + EXPECT_EQ(include.path, path); + }; + + EXPECT_EQ(includes.size(), 3); + EXPECT_INCLUDE(0, "0", "test.h"); + EXPECT_INCLUDE(1, "1", "test2.h"); + EXPECT_INCLUDE(2, "2", "test3.h"); } TEST(Directive, Condition) { @@ -60,23 +66,26 @@ TEST(Directive, Condition) { auto& info = tester.info; auto& conditions = info.directive(info.srcMgr().getMainFileID()).conditions; - tester.equal(conditions.size(), 8) - .equal(conditions[0].kind, Condition::BranchKind::If) - .expect("0", conditions[0].loc) - .equal(conditions[1].kind, Condition::BranchKind::Elif) - .expect("1", conditions[1].loc) - .equal(conditions[2].kind, Condition::BranchKind::Else) - .expect("2", conditions[2].loc) - .equal(conditions[3].kind, Condition::BranchKind::EndIf) - .expect("3", conditions[3].loc) - .equal(conditions[4].kind, Condition::BranchKind::Ifdef) - .expect("4", conditions[4].loc) - .equal(conditions[5].kind, Condition::BranchKind::Elifdef) - .expect("5", conditions[5].loc) - .equal(conditions[6].kind, Condition::BranchKind::Else) - .expect("6", conditions[6].loc) - .equal(conditions[7].kind, Condition::BranchKind::EndIf) - .expect("7", conditions[7].loc); + auto EPXECT_CON = [&](std::size_t index, + Condition::BranchKind kind, + llvm::StringRef position, + std::source_location current = std::source_location::current()) { + auto& condition = conditions[index]; + EXPECT_EQ(condition.kind, kind, current); + EXPECT_EQ(SourceConverter().toPosition(condition.loc, info.srcMgr()), + tester.pos(position), + current); + }; + + EXPECT_EQ(conditions.size(), 8); + EPXECT_CON(0, Condition::BranchKind::If, "0"); + EPXECT_CON(1, Condition::BranchKind::Elif, "1"); + EPXECT_CON(2, Condition::BranchKind::Else, "2"); + EPXECT_CON(3, Condition::BranchKind::EndIf, "3"); + EPXECT_CON(4, Condition::BranchKind::Ifdef, "4"); + EPXECT_CON(5, Condition::BranchKind::Elifdef, "5"); + EPXECT_CON(6, Condition::BranchKind::Else, "6"); + EPXECT_CON(7, Condition::BranchKind::EndIf, "7"); } TEST(Directive, Macro) { @@ -104,27 +113,29 @@ int y = $(6)expr($(7)expr(1)); auto& info = tester.info; auto& macros = info.directive(info.srcMgr().getMainFileID()).macros; - tester.equal(macros.size(), 9) - .equal(macros[0].kind, MacroRef::Kind::Def) - .expect("0", macros[0].loc) - .equal(macros[1].kind, MacroRef::Kind::Ref) - .expect("1", macros[1].loc) - .equal(macros[2].kind, MacroRef::Kind::Ref) - .expect("2", macros[2].loc) - .equal(macros[3].kind, MacroRef::Kind::Undef) - .expect("3", macros[3].loc) - .equal(macros[4].kind, MacroRef::Kind::Def) - .expect("4", macros[4].loc) - .equal(macros[5].kind, MacroRef::Kind::Ref) - .expect("5", macros[5].loc) - .equal(macros[6].kind, MacroRef::Kind::Ref) - .expect("6", macros[6].loc) - .equal(macros[7].kind, MacroRef::Kind::Ref) - .expect("7", macros[7].loc) - .equal(macros[8].kind, MacroRef::Kind::Undef) - .expect("8", macros[8].loc); + auto EXPECT_MACRO = [&](std::size_t index, + MacroRef::Kind kind, + llvm::StringRef position, + std::source_location current = std::source_location::current()) { + auto& macro = macros[index]; + EXPECT_EQ(macro.kind, kind, current); + EXPECT_EQ(SourceConverter().toPosition(macro.loc, info.srcMgr()), + tester.pos(position), + current); + }; + + EXPECT_EQ(macros.size(), 9); + EXPECT_MACRO(0, MacroRef::Kind::Def, "0"); + EXPECT_MACRO(1, MacroRef::Kind::Ref, "1"); + EXPECT_MACRO(2, MacroRef::Kind::Ref, "2"); + EXPECT_MACRO(3, MacroRef::Kind::Undef, "3"); + EXPECT_MACRO(4, MacroRef::Kind::Def, "4"); + EXPECT_MACRO(5, MacroRef::Kind::Ref, "5"); + EXPECT_MACRO(6, MacroRef::Kind::Ref, "6"); + EXPECT_MACRO(7, MacroRef::Kind::Ref, "7"); + EXPECT_MACRO(8, MacroRef::Kind::Undef, "8"); } } // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Compiler/Module.cpp b/unittests/Compiler/Module.cpp index a156d4aa..6bd04ad3 100644 --- a/unittests/Compiler/Module.cpp +++ b/unittests/Compiler/Module.cpp @@ -1,9 +1,8 @@ -#include "gtest/gtest.h" +#include "Test/Test.h" #include "Compiler/Compiler.h" - #include "llvm/Support/ToolOutputFile.h" -namespace clice { +namespace clice::testing { namespace { diff --git a/unittests/Compiler/Preamble.cpp b/unittests/Compiler/Preamble.cpp index 4e64a8ba..029add70 100644 --- a/unittests/Compiler/Preamble.cpp +++ b/unittests/Compiler/Preamble.cpp @@ -1,8 +1,9 @@ -#include "gtest/gtest.h" +#include "Test/Test.h" #include "Compiler/Preamble.h" -namespace { +namespace clice::testing { +namespace {} + +} // namespace clice::testing - -} diff --git a/unittests/Compiler/Resolver.cpp b/unittests/Compiler/Resolver.cpp index 7afcfd23..b02c8915 100644 --- a/unittests/Compiler/Resolver.cpp +++ b/unittests/Compiler/Resolver.cpp @@ -1,8 +1,6 @@ -#include "Test/Test.h" -#include -#include +#include "Test/CTest.h" -namespace clice { +namespace clice::testing { namespace { @@ -47,10 +45,10 @@ void run(llvm::StringRef code, std::source_location current = std::source_locati auto input = tester.info.resolver().resolve(run.input); auto expect = run.expect; - EXPECT_EQ(input.isNull(), false, current); - EXPECT_EQ(expect.isNull(), false, current); + EXPECT_EQ(input.isNull(), false); + EXPECT_EQ(expect.isNull(), false); - EXPECT_EQ(input.getCanonicalType(), expect.getCanonicalType(), current); + EXPECT_EQ(input.getCanonicalType(), expect.getCanonicalType()); } TEST(TemplateResolver, TypeParameterType) { @@ -470,5 +468,5 @@ struct test { } // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Compiler/SemanticVisitor.cpp b/unittests/Compiler/SemanticVisitor.cpp index 9d32cd21..c12663df 100644 --- a/unittests/Compiler/SemanticVisitor.cpp +++ b/unittests/Compiler/SemanticVisitor.cpp @@ -1,10 +1,9 @@ +#include "Test/Test.h" #include "Compiler/Semantic.h" -#include "Test/Test.h" - -namespace clice { +namespace clice::testing { namespace {} // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Feature/CodeCompletion.cpp b/unittests/Feature/CodeCompletion.cpp index a58d027b..008a2825 100644 --- a/unittests/Feature/CodeCompletion.cpp +++ b/unittests/Feature/CodeCompletion.cpp @@ -1,7 +1,7 @@ -#include "Test/Test.h" +#include "Test/CTest.h" #include "Feature/CodeCompletion.h" -namespace clice { +namespace clice::testing { namespace { @@ -32,5 +32,5 @@ int main() { } // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Feature/DocumentSymbol.cpp b/unittests/Feature/DocumentSymbol.cpp index a9f689ef..1b0d0924 100644 --- a/unittests/Feature/DocumentSymbol.cpp +++ b/unittests/Feature/DocumentSymbol.cpp @@ -1,32 +1,11 @@ -#include -#include -#include +#include "Test/CTest.h" +#include "Basic/SourceConverter.h" +#include "Feature/DocumentSymbol.h" -#include "Test/Test.h" - -namespace clice { +namespace clice::testing { namespace { -void dbg(const proto::DocumentSymbolResult& result, size_t ident = 0) { - for(auto& item: result) { - for(size_t i = 0; i < ident; ++i) - llvm::outs() << ' '; - llvm::outs() - << std::format( - "kind: {}, name:{}, detail:{}, deprecated:{}, range: {}, children_num:{}", - item.kind.name(), - item.name, - item.detail, - item.deprecated, - json::serialize(item.range), - item.children.size()) - << '\n'; - - dbg(item.children, ident + 2); - } -} - void total_size(const proto::DocumentSymbolResult& result, size_t& size) { for(auto& item: result) { ++size; @@ -40,7 +19,7 @@ size_t total_size(const proto::DocumentSymbolResult& result) { return size; } -const SourceConverter Converter{proto::PositionEncodingKind::UTF8}; +const SourceConverter converter{proto::PositionEncodingKind::UTF8}; TEST(DocumentSymbol, Namespace) { const char* main = R"cpp( @@ -67,7 +46,7 @@ namespace _1::_2{ Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(txs.info, Converter); + auto res = feature::documentSymbol(txs.info, converter); // dbg(res); ASSERT_EQ(total_size(res), 8); } @@ -96,7 +75,7 @@ int main(int argc, char* argv[]) { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(txs.info, Converter); + auto res = feature::documentSymbol(txs.info, converter); // dbg(res); ASSERT_EQ(total_size(res), 9); } @@ -121,7 +100,7 @@ struct x { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(txs.info, Converter); + auto res = feature::documentSymbol(txs.info, converter); // dbg(res); ASSERT_EQ(total_size(res), 7); } @@ -141,7 +120,7 @@ struct S { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(txs.info, Converter); + auto res = feature::documentSymbol(txs.info, converter); // dbg(res); ASSERT_EQ(total_size(res), 6); } @@ -163,7 +142,7 @@ struct _0 { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(txs.info, Converter); + auto res = feature::documentSymbol(txs.info, converter); // dbg(res); ASSERT_EQ(total_size(res), 7); } @@ -188,7 +167,7 @@ enum B { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(txs.info, Converter); + auto res = feature::documentSymbol(txs.info, converter); // dbg(res); ASSERT_EQ(total_size(res), 8); } @@ -202,7 +181,7 @@ int y = 2; Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(txs.info, Converter); + auto res = feature::documentSymbol(txs.info, converter); // dbg(res); ASSERT_EQ(total_size(res), 2); } @@ -232,7 +211,7 @@ VAR(test) Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(txs.info, Converter); + auto res = feature::documentSymbol(txs.info, converter); // dbg(res); // clang-format off @@ -250,4 +229,4 @@ VAR(test) } // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Feature/FoldingRange.cpp b/unittests/Feature/FoldingRange.cpp index 64840547..43306595 100644 --- a/unittests/Feature/FoldingRange.cpp +++ b/unittests/Feature/FoldingRange.cpp @@ -1,36 +1,40 @@ -#include -#include +#include "Test/CTest.h" +#include "Feature/FoldingRange.h" -#include "Test/Test.h" - -namespace clice { +namespace clice::testing { namespace { -void dbg(const proto::FoldingRangeResult& result) { - for(auto& item: result) { - llvm::outs() - << std::format("begin/end line: {}/{}, begin/end character: {}/{}, kind: {}, text: {}", - item.startLine, - item.endLine, - item.startCharacter, - item.endCharacter, - json::serialize(item.kind), - item.collapsedText) - << "\n"; +struct FoldingRange : public ::testing::Test { + std::optional tester; + proto::FoldingRangeResult result; + + void run(llvm::StringRef code) { + tester.emplace("main.cpp", code); + tester->run(); + auto& info = tester->info; + + FoldingRangeParams param; + SourceConverter converter; + result = feature::foldingRange(param, info, converter); } -} -// convert 0-0 based location in LSP to 1-1 based location in clang. -auto fromLspLocation(const clang::SourceManager* src, proto::FoldingRange range) - -> std::pair { - auto fileID = src->getMainFileID(); - return {src->translateLineCol(fileID, range.startLine + 1, range.startCharacter + 1), - src->translateLineCol(fileID, range.endLine + 1, range.endCharacter + 1)}; -} + void EXPECT_RANGE(std::size_t index, + llvm::StringRef begin, + llvm::StringRef end, + std::source_location current = std::source_location::current()) { + auto& folding = result[index]; + EXPECT_EQ(tester->pos(begin), + proto::Position{folding.startLine, folding.startCharacter}, + current); + EXPECT_EQ(tester->pos(end), + proto::Position{folding.endLine, folding.endCharacter}, + current); + } +}; -TEST(FoldingRange, Namespace) { - const char* main = R"cpp( +TEST_F(FoldingRange, Namespace) { + run(R"cpp( namespace single_line {$(1) //$(2) } @@ -49,39 +53,15 @@ namespace ugly //$(6) } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - txs.equal(res.size(), 3) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - .expect("5", toLoc(res[2]).first) - .expect("6", toLoc(res[2]).second) - // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); } -TEST(FoldingRange, Enum) { - auto main = R"cpp( +TEST_F(FoldingRange, Enum) { + run(R"cpp( enum _0 {$(1) A, B, @@ -96,35 +76,14 @@ enum class _2 {$(3) C$(4) }; -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 2) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); } -TEST(FoldingRange, RecordDecl) { - const char* main = R"cpp( +TEST_F(FoldingRange, RecordDecl) { + run(R"cpp( struct _2 {$(1) int x; float y;$(2) @@ -153,47 +112,18 @@ void f() {$(9) };$(10) } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 6) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - .expect("5", toLoc(res[2]).first) - .expect("6", toLoc(res[2]).second) - // - .expect("7", toLoc(res[3]).first) - .expect("8", toLoc(res[3]).second) - // - .expect("9", toLoc(res[4]).first) - .expect("10", toLoc(res[4]).second) - // - .expect("11", toLoc(res[5]).first) - .expect("12", toLoc(res[5]).second) - // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); + EXPECT_RANGE(3, "7", "8"); + EXPECT_RANGE(4, "9", "10"); + EXPECT_RANGE(5, "11", "12"); } -TEST(FoldingRange, CXXRecordDeclAndMemberMethod) { - const char* main = R"cpp( +TEST_F(FoldingRange, CXXRecordDeclAndMemberMethod) { + run(R"cpp( struct _2 {$(1) int x; float y; @@ -215,44 +145,16 @@ $(4) }; struct _4; -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 4) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - .expect("5", toLoc(res[2]).first) - .expect("6", toLoc(res[2]).second) - // - .expect("7", toLoc(res[3]).first) - .expect("8", toLoc(res[3]).second) - // - // .expect("9", toLoc(res[4].startLine, res[4].startCharacter)) - // .expect("10", toLoc(res[4].endLine, res[4].endCharacter)) - // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); + EXPECT_RANGE(3, "7", "8"); } -TEST(FoldingRange, LambdaCapture) { - const char* main = R"cpp( +TEST_F(FoldingRange, LambdaCapture) { + run(R"cpp( auto z = [$(1) x = 0, y = 1$(2) ]() {$(3) @@ -264,45 +166,17 @@ auto s = [$(5) y = 1$(6) ](){ return; }; -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); + EXPECT_EQ(result.size(), 3); - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 3) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - .expect("5", toLoc(res[2]).first) - .expect("6", toLoc(res[2]).second) - // // - // .expect("7", toLoc(res[3]).first) - // .expect("8", toLoc(res[3]).second) - // // - // .expect("9", toLoc(res[4].startLine, res[4].startCharacter)) - // .expect("10", toLoc(res[4].endLine, res[4].endCharacter)) - // // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); } -TEST(FoldingRange, LambdaExpression) { - const char* main = R"cpp( - +TEST_F(FoldingRange, LambdaExpression) { + run(R"cpp( auto _0 = [](int _) {}; auto _1 = [](int _) {$(1) @@ -319,44 +193,15 @@ auto _3 = []($(5) int _2$(6) ) {}; -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 3) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - .expect("5", toLoc(res[2]).first) - .expect("6", toLoc(res[2]).second) - // - // .expect("7", toLoc(res[3]).first) - // .expect("8", toLoc(res[3]).second) - // // - // .expect("9", toLoc(res[4].startLine, res[4].startCharacter)) - // .expect("10", toLoc(res[4].endLine, res[4].endCharacter)) - // // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); } -TEST(FoldingRange, FnParas) { - const char* main = R"cpp( +TEST_F(FoldingRange, FunctionParams) { + run(R"cpp( void e() {} void f($(1) @@ -375,39 +220,15 @@ void d($(5) int _2, ...$(6) ); -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 3) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - .expect("5", toLoc(res[2]).first) - .expect("6", toLoc(res[2]).second) - // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); } -TEST(FoldingRange, FnBody) { - const char* main = R"cpp( - +TEST_F(FoldingRange, FunctionBody) { + run(R"cpp( void f() {$(1) // //$(2) @@ -425,42 +246,16 @@ void n() {$(5) } //$(6) } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 4) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - .expect("5", toLoc(res[2]).first) - .expect("6", toLoc(res[2]).second) - // - .expect("7", toLoc(res[3]).first) - .expect("8", toLoc(res[3]).second) - // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); + EXPECT_RANGE(3, "7", "8"); } -TEST(FoldingRange, FnCall) { - const char* main = R"cpp( - +TEST_F(FoldingRange, FunctionCall) { + run(R"cpp( int f(int _1, int _2, int _3, int _4, int _5, int _6) { return _1 + _2; } int main() {$(1) @@ -472,42 +267,14 @@ int main() {$(1) 4, 5, 6$(4) );$(2) } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 2) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - // .expect("5", toLoc(res[2]).first) - // .expect("6", toLoc(res[2]).second) - // // - // .expect("7", toLoc(res[3]).first) - // .expect("8", toLoc(res[3]).second) - // // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); } -TEST(FoldingRange, CompoundStmt) { - const char* main = R"cpp( - +TEST_F(FoldingRange, CompoundStmt) { + run(R"cpp( int main () {$(1) {$(3) @@ -525,45 +292,15 @@ int main () {$(1) return 0;$(2) } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 4) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - .expect("5", toLoc(res[2]).first) - .expect("6", toLoc(res[2]).second) - // // - // .expect("7", toLoc(res[3]).first) - // .expect("8", toLoc(res[3]).second) - // // - // .expect("9", toLoc(res[4].startLine, res[4].startCharacter)) - // .expect("10", toLoc(res[4].endLine, res[4].endCharacter)) - // // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); } -TEST(FoldingRange, InitializeList) { - const char* main = R"cpp( - +TEST_F(FoldingRange, InitializeList) { + run(R"cpp( struct L { int xs[4]; }; L l1 = {$(1) @@ -575,38 +312,14 @@ L l2 = {$(3) //$(4) }; -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 2) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - // .expect("5", toLoc(res[2]).first) - // .expect("6", toLoc(res[2]).second) - // - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); } -TEST(FoldingRange, AccessControlBlock) { - const char* main = R"cpp( +TEST_F(FoldingRange, AccessControlBlock) { + run(R"cpp( struct empty { int x; }; class _0 {$(1) @@ -629,46 +342,18 @@ public: private: public:$(12) }; -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 6) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("3", toLoc(res[1]).first) - .expect("4", toLoc(res[1]).second) - // - .expect("5", toLoc(res[2]).first) - .expect("6", toLoc(res[2]).second) - // - .expect("7", toLoc(res[3]).first) - .expect("8", toLoc(res[3]).second) - // - .expect("9", toLoc(res[4]).first) - .expect("10", toLoc(res[4]).second) - // - .expect("11", toLoc(res[5]).first) - .expect("12", toLoc(res[5]).second); + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); + EXPECT_RANGE(3, "7", "8"); + EXPECT_RANGE(4, "9", "10"); + EXPECT_RANGE(5, "11", "12"); } -TEST(FoldingRange, Macro) { - const char* main = R"cpp( - +TEST_F(FoldingRange, Macro) { + run(R"cpp( #$(1)ifdef M1 $(2) #$(3)else @@ -680,46 +365,13 @@ $(2) //$(4) #endif +)cpp"); -)cpp"; - - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto toLoc = [src = &info.srcMgr()](const proto::FoldingRange& fr) { - return fromLspLocation(src, fr); - }; - - SourceConverter converter{proto::PositionEncodingKind::UTF8}; - FoldingRangeParams param; - auto res = feature::foldingRange(param, info, converter); - - // dbg(res); - - txs.equal(res.size(), 3) - // - .expect("1", toLoc(res[0]).first) - .expect("2", toLoc(res[0]).second) - // - .expect("5", toLoc(res[1]).first) - .expect("6", toLoc(res[1]).second) - // - .expect("3", toLoc(res[2]).first) - .expect("4", toLoc(res[2]).second) - // // - // .expect("7", toLoc(res[3]).first) - // .expect("8", toLoc(res[3]).second) - // // - // .expect("9", toLoc(res[4]).first) - // .expect("10", toLoc(res[4]).second) - // // - // .expect("11", toLoc(res[5]).first) - // .expect("12", toLoc(res[5]).second) - - ; + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "5", "6"); + EXPECT_RANGE(2, "3", "4"); } } // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Feature/InlayHint.cpp b/unittests/Feature/InlayHint.cpp index e3977484..23c9b763 100644 --- a/unittests/Feature/InlayHint.cpp +++ b/unittests/Feature/InlayHint.cpp @@ -1,30 +1,11 @@ -#include -#include -#include +#include "Test/CTest.h" +#include "Feature/InlayHint.h" +#include "Basic/SourceConverter.h" -#include "Test/Test.h" - -namespace clice { +namespace clice::testing { namespace { -void dbg(const std::vector& hints) { - for(auto& hint: hints) { - llvm::outs() << std::format("kind:{}, position:{}, value_size:{},", - hint.kind.name(), - json::serialize(hint.position), - hint.lable.size()); - for(auto& lable: hint.lable) { - llvm::outs() << std::format(" value:{}, link position:{}", - lable.value, - json::serialize(lable.Location)) - << '\n'; - } - } -} - -const SourceConverter Converter{proto::PositionEncodingKind::UTF8}; - const config::InlayHintOption LikeClangd{ .maxLength = 20, .maxArrayElements = 10, @@ -34,34 +15,45 @@ const config::InlayHintOption LikeClangd{ .chainCall = false, }; -TEST(InlayHint, RequestRange) { - const char* main = R"cpp( +struct InlayHint : public ::testing::Test { +protected: + void run(llvm::StringRef code, + proto::Range range = {}, + const config::InlayHintOption& option = LikeClangd) { + tester.emplace("main.cpp", code); + tester->run(); + auto& info = tester->info; + SourceConverter converter; + result = feature::inlayHints({.range = range}, info, converter, option); + } + + std::optional tester; + proto::InlayHintsResult result; +}; + +TEST_F(InlayHint, RequestRange) { + run(R"cpp( auto x1 = 1;$(request_range_start) auto x2 = 1; auto x3 = 1; auto x4 = 1;$(request_range_end) -)cpp"; - - Tester txs("main.cpp", main); - txs.run(); - auto& info = txs.info; - - proto::Range request{ - .start = {1, 12}, // $(request_range_start) - .end = {4, 12}, // $(request_range_end) - }; - auto res = feature::inlayHints({.range = request}, info, Converter, {.implicitCast = true}); - - // dbg(res); +)cpp", + { + // $(request_range_start) + .start = {1, 12}, + // $(request_range_end) + .end = {4, 12}, + }, + { + .implicitCast = true, + }); // 3: x2, x3, x4 is included in the request range. - txs.equal(res.size(), 3) - // - ; + EXPECT_EQ(result.size(), 3); } -TEST(InlayHint, AutoDecl) { - const char* main = R"cpp( +TEST_F(InlayHint, AutoDecl) { + run(R"cpp( auto$(1) x = 1; void f() { @@ -76,23 +68,13 @@ template void t() { auto z = T{}; } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 4) - // - ; + EXPECT_EQ(result.size(), 4); } -TEST(InlayHint, FreeFnArguments) { - const char* main = R"cpp( +TEST_F(InlayHint, FreeFunctionArguments) { + run(R"cpp( void f(int a, int b) {} void g(int a = 1) {} void h() { @@ -100,45 +82,25 @@ f($(1)1, $(2)2); g(); } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 2) - // - ; + EXPECT_EQ(result.size(), 2); } -TEST(InlayHint, FnArgPassedAsLValueRef) { - const char* main = R"cpp( +TEST_F(InlayHint, FnArgPassedAsLValueRef) { + run(R"cpp( void f(int& a, int& b) { } void g() { int x = 1; f($(1)x, $(2)x); } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 2) - // - ; + EXPECT_EQ(result.size(), 2); } -TEST(InlayHint, MethodArguments) { - const char* main = R"cpp( +TEST_F(InlayHint, MethodArguments) { + run(R"cpp( struct A { void f(int a, int b, int d) {} }; @@ -147,23 +109,13 @@ void f() { A a; a.f($(1)1, $(2)2, $(3)3); } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 3) - // - ; + EXPECT_EQ(result.size(), 3); } -TEST(InlayHint, OperatorCall) { - const char* main = R"cpp( +TEST_F(InlayHint, OperatorCall) { + run(R"cpp( struct A { int operator()(int a, int b) { return a + b; } }; @@ -172,23 +124,13 @@ int f() { A a; return a(1, 2); } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 2) - // - ; + EXPECT_EQ(result.size(), 2); } -TEST(InlayHint, ReturnTypeHint) { - const char* main = R"cpp( +TEST_F(InlayHint, ReturnTypeHint) { + run(R"cpp( auto f()$(1) { return 1; } @@ -203,45 +145,25 @@ void g() { }(); } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 3) - // - ; + EXPECT_EQ(result.size(), 3); } -TEST(InlayHint, StructureBinding) { - const char* main = R"cpp( +TEST_F(InlayHint, StructureBinding) { + run(R"cpp( int f() { int a[2]; auto [x$(1), y$(2)] = a; return x + y; // use x and y to avoid warning. } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 2) - // - ; + EXPECT_EQ(result.size(), 2); } -TEST(InlayHint, Constructor) { - const char* main = R"cpp( +TEST_F(InlayHint, Constructor) { + run(R"cpp( struct A { int x; int y; @@ -255,61 +177,31 @@ void f() { A c$(3) = {1, 2}; } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 6) - // - ; + EXPECT_EQ(result.size(), 6); } -TEST(InlayHint, InitializeList) { - const char* main = R"cpp( +TEST_F(InlayHint, InitializeList) { + run(R"cpp( int a[3] = {1, 2, 3}; int b[2][3] = {{1, 2, 3}, {4, 5, 6}}; -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 3 + (3 * 2 + 2)) - // - ; + EXPECT_EQ(result.size(), 3 + (3 * 2 + 2)); } -TEST(InlayHint, Designators) { - const char* main = R"cpp( +TEST_F(InlayHint, Designators) { + run(R"cpp( struct A{ int x; int y;}; A a = {.x = 1, .y = 2}; -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 0) - // - ; + EXPECT_EQ(result.size(), 0); } -TEST(InlayHint, IgnoreSimpleSetter) { - const char* main = R"cpp( +TEST_F(InlayHint, IgnoreSimpleSetter) { + run(R"cpp( struct A { void setPara(int Para); void set_para(int para); @@ -323,23 +215,13 @@ void f() { a.set_para_meter(1); } -)cpp"; +)cpp"); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, LikeClangd); - - // dbg(res); - - txs.equal(res.size(), 0) - // - ; + EXPECT_EQ(result.size(), 0); } -TEST(InlayHint, BlockEnd) { - const char* main = R"cpp( +TEST_F(InlayHint, BlockEnd) { + run(R"cpp( struct A { int x; }$(1); @@ -362,48 +244,30 @@ struct Out { struct In { };$(5)}$(6); -)cpp"; +)cpp", + {}, + { + .blockEnd = true, + .structSizeAndAlign = false, + }); - Tester txs("main.cpp", main); - txs.run(); - - config::InlayHintOption option{ - .blockEnd = true, - .structSizeAndAlign = false, - }; - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, option); - - // dbg(res); - - txs.equal(res.size(), 6) - // - ; + EXPECT_EQ(result.size(), 6); } -TEST(InlayHint, Lambda) { - const char* main = R"cpp( +TEST_F(InlayHint, Lambda) { + run(R"cpp( auto l = []$(1) { return 1; }$(2); -)cpp"; +)cpp", + {}, + {.returnType = true, .blockEnd = true}); - Tester txs("main.cpp", main); - txs.run(); - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, {.returnType = true, .blockEnd = true}); - - // dbg(res); - - txs.equal(res.size(), 3) - // - ; + EXPECT_EQ(result.size(), 3); } -TEST(InlayHint, StructAndMemberHint) { - const char* main = R"cpp( +TEST_F(InlayHint, StructAndMemberHint) { + run(R"cpp( struct A { int x; int y; @@ -412,47 +276,29 @@ struct A { int z; }; }; -)cpp"; - - Tester txs("main.cpp", main); - txs.run(); - - config::InlayHintOption option{ - .blockEnd = false, - .structSizeAndAlign = true, - .memberSizeAndOffset = true, - }; - - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, option); - - // dbg(res); +)cpp", + {}, + { + .blockEnd = false, + .structSizeAndAlign = true, + .memberSizeAndOffset = true, + }); /// TODO: /// if InlayHintOption::memberSizeAndOffset was implemented, the total hint count is 2 + 3. - txs.equal(res.size(), 2 /*+ 3*/) - // - ; + EXPECT_EQ(result.size(), 2 /*+ 3*/); } -TEST(InlayHint, ImplicitCast) { - const char* main = R"cpp( +TEST_F(InlayHint, ImplicitCast) { + run(R"cpp( int x = 1.0; -)cpp"; +)cpp", + {}, + {.implicitCast = true}); - Tester txs("main.cpp", main); - txs.run(); - auto& info = txs.info; - auto res = feature::inlayHints({}, info, Converter, {.implicitCast = true}); - - // dbg(res); - - /// FIXME: - /// Hint count should be 1. - txs.equal(res.size(), 0) - // - ; + /// FIXME: Hint count should be 1. + EXPECT_EQ(result.size(), 0); } } // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Feature/SemanticTokens.cpp b/unittests/Feature/SemanticTokens.cpp index 86bcc4e0..5641fa64 100644 --- a/unittests/Feature/SemanticTokens.cpp +++ b/unittests/Feature/SemanticTokens.cpp @@ -1,6 +1,7 @@ -#include -// #include -#include +#include "Test/CTest.h" +#include "Feature/SemanticTokens.h" + +namespace clice::testing { namespace { @@ -8,3 +9,5 @@ TEST(Feature, SemanticTokens) {} } // namespace +} // namespace clice::testing + diff --git a/unittests/Index/Template.cpp b/unittests/Index/Template.cpp index f41f6709..137c7e18 100644 --- a/unittests/Index/Template.cpp +++ b/unittests/Index/Template.cpp @@ -1,6 +1,6 @@ -#include "IndexTester.h" +#include "Test/IndexTester.h" -namespace clice { +namespace clice::testing { namespace { diff --git a/unittests/Server/Async.cpp b/unittests/Server/Async.cpp index 88bcec41..d4e5e407 100644 --- a/unittests/Server/Async.cpp +++ b/unittests/Server/Async.cpp @@ -1,7 +1,7 @@ #include "Test/Test.h" #include "Server/Async.h" -namespace clice { +namespace clice::testing { namespace {} // namespace diff --git a/unittests/Support/Compare.cpp b/unittests/Support/Compare.cpp index 680e883f..c9c4815a 100644 --- a/unittests/Support/Compare.cpp +++ b/unittests/Support/Compare.cpp @@ -1,7 +1,7 @@ -#include "gtest/gtest.h" +#include "Test/Test.h" #include "Support/Compare.h" -namespace clice { +namespace clice::testing { namespace { diff --git a/unittests/Support/Enum.cpp b/unittests/Support/Enum.cpp index c93a7a8b..2d3bceee 100644 --- a/unittests/Support/Enum.cpp +++ b/unittests/Support/Enum.cpp @@ -1,11 +1,32 @@ -#include "gtest/gtest.h" +#include "Test/Test.h" #include "Support/Enum.h" -#include "Support/JSON.h" -namespace clice { +namespace clice::testing { namespace { +TEST(Reflection, EnumName) { + enum class E { + A, + B, + C, + }; + + static_assert(refl::enum_name(E::A) == "A"); + static_assert(refl::enum_name(E::B) == "B"); + static_assert(refl::enum_name(E::C) == "C"); + + enum F { + A, + B, + C, + }; + + static_assert(refl::enum_name(F::A) == "A"); + static_assert(refl::enum_name(F::B) == "B"); + static_assert(refl::enum_name(F::C) == "C"); +} + struct Color : refl::Enum { enum Kind : uint8_t { Red, @@ -20,7 +41,7 @@ struct Color : refl::Enum { constexpr inline static auto InvalidEnum = Invalid; }; -TEST(Support, NormalEnum) { +TEST(Reflection, NormalEnum) { constexpr Color invalid = {}; static_assert(!invalid); static_assert(invalid.value() == Color::Invalid); @@ -30,25 +51,19 @@ TEST(Support, NormalEnum) { constexpr Color blue = Color::Blue; constexpr Color yellow = Color::Yellow; - static_assert(red.name() == "Red" && red.value() == 0); - static_assert(green.name() == "Green" && green.value() == 1); - static_assert(blue.name() == "Blue" && blue.value() == 2); - static_assert(yellow.name() == "Yellow" && yellow.value() == 3); + static_assert(red.name() == "Red" && red.kind() == Color::Red && red.value() == 0); + static_assert(green.name() == "Green" && green.kind() == Color::Green && green.value() == 1); + static_assert(blue.name() == "Blue" && blue.kind() == Color::Blue && blue.value() == 2); + static_assert(yellow.name() == "Yellow" && yellow.kind() == Color::Yellow && + yellow.value() == 3); static_assert(red != green && red != blue && red != yellow); constexpr Color red2 = Color(0); static_assert(red == red2); - EXPECT_EQ(json::serialize(red), json::Value(0)); - EXPECT_EQ(json::serialize(green), json::Value(1)); - EXPECT_EQ(json::serialize(blue), json::Value(2)); - EXPECT_EQ(json::serialize(yellow), json::Value(3)); - - EXPECT_EQ(json::deserialize(json::Value(0)), red); - EXPECT_EQ(json::deserialize(json::Value(1)), green); - EXPECT_EQ(json::deserialize(json::Value(2)), blue); - EXPECT_EQ(json::deserialize(json::Value(3)), yellow); + static_assert(red.is_one_of(Color::Red)); + static_assert(!red.is_one_of(Color::Blue)); } struct Mask : refl::Enum { @@ -62,7 +77,7 @@ struct Mask : refl::Enum { using Enum::Enum; }; -TEST(Support, MaskEnum) { +TEST(Reflection, MaskEnum) { constexpr Mask invalid = {}; static_assert(!invalid); static_assert(invalid.value() == 0); @@ -93,16 +108,6 @@ TEST(Support, MaskEnum) { EXPECT_TRUE(bool(mask6 & Mask::A)); EXPECT_TRUE(bool(mask6 & Mask::B)); EXPECT_TRUE(bool(mask6 & Mask::C)); - - EXPECT_EQ(json::serialize(mask), json::Value(1)); - EXPECT_EQ(json::serialize(mask2), json::Value(2)); - EXPECT_EQ(json::serialize(mask3), json::Value(4)); - EXPECT_EQ(json::serialize(mask4), json::Value(8)); - - EXPECT_EQ(json::deserialize(json::Value(1)), mask); - EXPECT_EQ(json::deserialize(json::Value(2)), mask2); - EXPECT_EQ(json::deserialize(json::Value(4)), mask3); - EXPECT_EQ(json::deserialize(json::Value(8)), mask4); } struct StringEnum : refl::Enum { @@ -116,7 +121,7 @@ struct StringEnum : refl::Enum { constexpr inline static std::array All = {A, B, C, D}; }; -TEST(Support, StringEnum) { +TEST(Reflection, StringEnum) { constexpr StringEnum a = StringEnum::A; constexpr StringEnum b = StringEnum::B; constexpr StringEnum c = StringEnum::C; @@ -128,19 +133,9 @@ TEST(Support, StringEnum) { static_assert(d.value() == "D"); static_assert(a != b && a != c && a != d); - - EXPECT_EQ(json::serialize(a), json::Value("A")); - EXPECT_EQ(json::serialize(b), json::Value("B")); - EXPECT_EQ(json::serialize(c), json::Value("C")); - EXPECT_EQ(json::serialize(d), json::Value("D")); - - EXPECT_EQ(json::deserialize(json::Value("A")), a); - EXPECT_EQ(json::deserialize(json::Value("B")), b); - EXPECT_EQ(json::deserialize(json::Value("C")), c); - EXPECT_EQ(json::deserialize(json::Value("D")), d); } } // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Support/JSON.cpp b/unittests/Support/JSON.cpp index 970f379b..4f2f5d24 100644 --- a/unittests/Support/JSON.cpp +++ b/unittests/Support/JSON.cpp @@ -1,16 +1,31 @@ -#include "gtest/gtest.h" +#include +#include +#include +#include +#include + +#include "Test/Test.h" #include "Support/JSON.h" -namespace clice { +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/SmallString.h" namespace { + struct ValueRef { std::size_t index; }; + } // namespace template <> -struct json::Serde { +struct clice::json::Serde { constexpr inline static bool stateful = true; std::vector& encoder; @@ -26,28 +41,153 @@ struct json::Serde { } }; +namespace clice::testing { + namespace { -TEST(Support, JSON) { - struct Point { - int x; - int y; - }; +TEST(JSON, String) { + json::Value expect = "hello"; - json::Value object = json::Object{ - {"x", 1}, - {"y", 2}, - }; + std::string input = "hello"; + EXPECT_EQ(json::serialize(input), expect); + EXPECT_EQ(json::deserialize(expect), input); - auto point = json::deserialize(std::move(object)); - ASSERT_EQ(point.x, 1); - ASSERT_EQ(point.y, 2); + std::string_view input2 = "hello"; + EXPECT_EQ(json::serialize(input2), expect); + EXPECT_EQ(json::deserialize(expect), input2); - auto result = json::serialize(point); - ASSERT_EQ(result, object); + llvm::StringRef input3 = "hello"; + EXPECT_EQ(json::serialize(input3), expect); + EXPECT_EQ(json::deserialize(expect), input3); + + llvm::SmallString<5> input4 = {"hello"}; + EXPECT_EQ(json::serialize(input4), expect); + EXPECT_EQ(json::deserialize>(expect), input4); } -TEST(Support, StatefulSerde) { +TEST(JSON, MapRange) { + json::Value expect = json::Object{ + {"1", 2}, + {"3", 4}, + {"5", 6} + }; + + std::map input = { + {1, 2}, + {3, 4}, + {5, 6} + }; + + EXPECT_EQ(json::serialize(input), expect); + EXPECT_EQ(input, json::deserialize(expect)); + + std::unordered_map input2 = { + {1, 2}, + {3, 4}, + {5, 6} + }; + EXPECT_EQ(json::serialize(input2), expect); + EXPECT_EQ(input2, json::deserialize(expect)); + + llvm::DenseMap input4 = { + {1, 2}, + {3, 4}, + {5, 6} + }; + EXPECT_EQ(json::serialize(input4), expect); + EXPECT_EQ(input4, json::deserialize(expect)); +} + +TEST(JSON, SetRange) { + json::Value expect = {1, 2, 3, 4, 5}; + + std::set input = {1, 2, 3, 4, 5}; + EXPECT_EQ(input, json::deserialize(expect)); + + std::unordered_set input2 = {1, 2, 3, 4, 5}; + EXPECT_EQ(input2, json::deserialize(expect)); +} + +TEST(JSON, SequenceRange) { + json::Value expect = {1, 2, 3, 4, 5}; + + std::vector input = {1, 2, 3, 4, 5}; + EXPECT_EQ(json::serialize(input), expect); + EXPECT_EQ(json::deserialize>(expect), input); + + llvm::ArrayRef input2 = input; + EXPECT_EQ(json::serialize(input2), expect); + + llvm::SmallVector input3 = {1, 2, 3, 4, 5}; + EXPECT_EQ(json::serialize(input3), expect); + EXPECT_EQ((json::deserialize>(expect)), input3); +} + +TEST(JSON, Enum) { + enum class E { A, B, C }; + + json::Value expect = json::Value(1); + + E input = E::B; + EXPECT_EQ(json::serialize(input), expect); + EXPECT_EQ(json::deserialize(expect), input); + + struct Color : refl::Enum { + enum Kind { + Red = 0, + Green, + Blue, + Yellow, + }; + + using Enum::Enum; + }; + + json::Value expect2 = json::Value(2); + + Color input2 = Color::Blue; + EXPECT_EQ(json::serialize(input2), expect2); + EXPECT_EQ(json::deserialize(expect2), input2); +} + +TEST(JSON, Struct) { + struct A { + int x; + int y; + + bool operator== (const A& other) const = default; + }; + + json::Value expect = json::Object{ + {"x", 1}, + {"y", 2} + }; + + A input = {1, 2}; + EXPECT_EQ(json::serialize(input), expect); + EXPECT_EQ(json::deserialize(expect), input); + + struct B { + A a; + std::string s; + + bool operator== (const B& other) const = default; + }; + + json::Value expect2 = json::Object{ + {"a", json::Object{{"x", 1}, {"y", 2}}}, + {"s", "hello" } + }; + + B input2 = { + {1, 2}, + "hello", + }; + EXPECT_EQ(json::serialize(input2), expect2); + EXPECT_EQ(json::deserialize(expect2), input2); +} + +TEST(JSON, StatefulSerde) { struct Refs { std::vector data; }; @@ -80,4 +220,4 @@ TEST(Support, StatefulSerde) { } // namespace -} // namespace clice +} // namespace clice::testing diff --git a/unittests/Support/Struct.cpp b/unittests/Support/Struct.cpp index cbff9cf2..a01b3de4 100644 --- a/unittests/Support/Struct.cpp +++ b/unittests/Support/Struct.cpp @@ -1,8 +1,7 @@ -#include "gtest/gtest.h" +#include "Test/Test.h" #include "Support/Struct.h" -#include "Support/JSON.h" -namespace clice { +namespace clice::testing { namespace { @@ -15,7 +14,53 @@ struct X { static_assert(std::is_same_v, type_list>); -TEST(Support, Struct) { +TEST(Reflection, FieldName) { + static struct X { + int a; + int b; + } x; + + static_assert(refl::impl::member_name<&x.a>() == "a", "Member name mismatch"); + static_assert(refl::impl::member_name<&x.b>() == "b", "Member name mismatch"); + static_assert(refl::member_names() == std::array{"a", "b"}); + + static struct Y { + X x; + } y; + + static_assert(refl::impl::member_name<&y.x>() == "x", "Member name mismatch"); + static_assert(refl::impl::member_name<&y.x.a>() == "a", "Member name mismatch"); + static_assert(refl::impl::member_name<&y.x.b>() == "b", "Member name mismatch"); + static_assert(refl::member_names() == std::array{"x"}); + + struct H { + X x; + Y y; + H() = delete; + }; + + static union Z { + char dummy; + H h; + + Z() {}; + ~Z() {}; + } z; + + static_assert(refl::impl::member_name<&z.h.x>() == "x", "Member name mismatch"); + static_assert(refl::impl::member_name<&z.h.y>() == "y", "Member name mismatch"); + static_assert(refl::impl::member_name<&z.h.y.x>() == "x", "Member name mismatch"); + + struct M { + X x; + Y y; + H h; + }; + + static_assert(refl::member_names() == std::array{"x", "y", "h"}); +} + +TEST(Reflection, Foreach) { bool x = false, y = false; refl::foreach(X{1, 2}, [&](auto name, auto value) { if(name == "x") { @@ -35,27 +80,15 @@ TEST(Support, Struct) { EXPECT_TRUE(refl::foreach(x1, x2, [](auto& lhs, auto& rhs) { return lhs = rhs; })); EXPECT_EQ(x1.x, 3); EXPECT_EQ(x1.y, 4); - - auto j1 = json::Value(json::Object{ - {"x", 3}, - {"y", 4}, - }); - EXPECT_EQ(json::serialize(x1), j1); - - auto j2 = json::Value(json::Object{ - {"x", 3}, - {"y", 4}, - }); - EXPECT_EQ(x2, json::deserialize(j2)); } -inherited_struct(Y, X) { - int z; -}; +TEST(Reflection, Inheritance) { + inherited_struct(Y, X) { + int z; + }; -static_assert(std::is_same_v, type_list>); + static_assert(std::is_same_v, type_list>); -TEST(Support, Inheritance) { bool x = false, y = false, z = false; refl::foreach(Y{1, 2, 3}, [&](auto name, auto value) { if(name == "x") { @@ -79,26 +112,41 @@ TEST(Support, Inheritance) { EXPECT_EQ(y1.x, 4); EXPECT_EQ(y1.y, 5); EXPECT_EQ(y1.z, 6); +} - auto j1 = json::Value(json::Object{ - {"x", 4}, - {"y", 5}, - {"z", 6}, - }); - EXPECT_EQ(json::serialize(y1), j1); +TEST(Reflection, TupleLike) { + std::pair p = {1, 2}; - auto j2 = json::Value(json::Object{ - {"x", 4}, - {"y", 5}, - {"z", 6}, + static_assert(refl::member_names() == std::array{"0", "1"}); + + std::tuple t = {1, 2}; + + static_assert(refl::member_names() == std::array{"0", "1"}); + + bool x = false, y = false; + refl::foreach(t, [&](auto name, auto value) { + if(name == "0") { + x = true; + EXPECT_EQ(value, 1); + } else if(name == "1") { + y = true; + EXPECT_EQ(value, 2); + } else { + EXPECT_TRUE(false); + } }); - auto y3 = json::deserialize(j2); - EXPECT_EQ(y2.x, y3.x); - EXPECT_EQ(y2.y, y3.y); - EXPECT_EQ(y2.z, y3.z); + EXPECT_TRUE(x && y); + + std::tuple t1 = {1, 2}; + std::tuple t2 = {3, 4}; + + EXPECT_TRUE(refl::foreach(t1, t2, [](auto& lhs, auto& rhs) { return lhs = rhs; })); + EXPECT_EQ(std::get<0>(t1), 3); + EXPECT_EQ(std::get<1>(t1), 4); + EXPECT_EQ(std::get<0>(t2), 3); } } // namespace -} // namespace clice +} // namespace clice::testing