From 70f29147d5031676c1d856cbc12079fb80938ae2 Mon Sep 17 00:00:00 2001 From: ykiko Date: Mon, 13 Jan 2025 23:14:10 +0800 Subject: [PATCH] Add tests for `Server` (#34) --- .github/workflows/CI.yml | 2 +- .vscode/launch.json | 13 +- CMakeLists.txt | 17 +- include/Basic/Basic.h | 4 + include/Basic/Document.h | 29 ++ include/Basic/Lifecycle.h | 91 ++++ include/Basic/SymbolKind.h | 3 + include/Basic/Workspace.h | 18 + include/Feature/SemanticTokens.def | 134 ----- include/Feature/SemanticTokens.h | 76 +-- include/Index/FeatureIndex.h | 13 +- include/Server/Async.h | 19 +- include/Server/Protocol.h | 171 +------ include/Server/Server.h | 19 +- include/Support/Enum.h | 9 + include/Support/JSON.h | 5 +- {unittests => include/Test}/Test.h | 8 +- src/Driver/clice.cc | 51 ++ src/Driver/integration_tests.cc | 61 +++ .../main.cpp => src/Driver/unit_tests.cc | 6 +- src/Feature/SemanticTokens.cpp | 133 +---- src/Server/Async.cpp | 149 ++++-- src/Server/Lifecycle.cpp | 6 +- src/Server/Server.cpp | 131 ++--- src/main.cpp | 9 - tests/ASTVisitor/macro.cpp | 17 - tests/ASTVisitor/test.cpp | 5 - tests/CodeCompletion/non-self-contain.cpp | 3 - tests/CodeCompletion/non-self-contain.h | 3 - tests/Compiler/test.cpp | 20 - tests/Diagnostic/test.cpp | 6 - tests/Diagnostic/test.h | 1 - tests/Index/test.cpp | 15 - tests/ModuleScanner/A.cppm | 5 - tests/ModuleScanner/B.cppm | 3 - tests/ModuleScanner/C.cppm | 3 - tests/ModuleScanner/D.cppm | 7 - tests/SelectionTree/test.cpp | 7 - tests/compile_commands.json | 12 - tests/initialize/input.json | 482 ++++++++++++++++++ unittests/Basic/SourceConverter.cpp | 2 +- unittests/Compiler/Command.cpp | 2 +- unittests/Compiler/Compiler.cpp | 2 +- unittests/Compiler/Diagnostic.cpp | 2 +- unittests/Compiler/Directive.cpp | 2 +- unittests/Compiler/Resolver.cpp | 2 +- unittests/Compiler/SemanticVisitor.cpp | 2 +- unittests/Feature/CodeCompletion.cpp | 2 +- unittests/Feature/FoldingRange.cpp | 2 +- unittests/Index/IndexTester.h | 2 +- unittests/Server/Async.cpp | 2 +- unittests/Support/Enum.cpp | 56 ++ unittests/Support/Struct.cpp | 43 +- 53 files changed, 1157 insertions(+), 730 deletions(-) create mode 100644 include/Basic/Lifecycle.h create mode 100644 include/Basic/Workspace.h delete mode 100644 include/Feature/SemanticTokens.def rename {unittests => include/Test}/Test.h (98%) create mode 100644 src/Driver/clice.cc create mode 100644 src/Driver/integration_tests.cc rename unittests/main.cpp => src/Driver/unit_tests.cc (88%) delete mode 100644 src/main.cpp delete mode 100644 tests/ASTVisitor/macro.cpp delete mode 100644 tests/ASTVisitor/test.cpp delete mode 100644 tests/CodeCompletion/non-self-contain.cpp delete mode 100644 tests/CodeCompletion/non-self-contain.h delete mode 100644 tests/Compiler/test.cpp delete mode 100644 tests/Diagnostic/test.cpp delete mode 100644 tests/Diagnostic/test.h delete mode 100644 tests/Index/test.cpp delete mode 100644 tests/ModuleScanner/A.cppm delete mode 100644 tests/ModuleScanner/B.cppm delete mode 100644 tests/ModuleScanner/C.cppm delete mode 100644 tests/ModuleScanner/D.cppm delete mode 100644 tests/SelectionTree/test.cpp delete mode 100644 tests/compile_commands.json create mode 100644 tests/initialize/input.json diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a999d457..8bacdad6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -79,4 +79,4 @@ jobs: bash ./scripts/build-dev-test.sh && cmake --build build - name: Run tests - run: ./build/bin/clice-tests --test-dir=./tests + run: ./build/bin/unit_tests --test-dir=./tests diff --git a/.vscode/launch.json b/.vscode/launch.json index 040eebef..da933a94 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,11 +24,22 @@ ], "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/clice-tests", + "program": "${workspaceFolder}/build/bin/unit_tests", "args": [ "--test-dir=${workspaceFolder}/tests", "--gtest_filter=${input:filter}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 50e05425..20185b00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ file(GLOB_RECURSE CLICE_CORE_SOURCES "${CMAKE_SOURCE_DIR}/src/Index/*.cpp" "${CMAKE_SOURCE_DIR}/src/Feature/*.cpp" "${CMAKE_SOURCE_DIR}/src/Support/*.cpp" + "${CMAKE_SOURCE_DIR}/src/Server/*.cpp" ) add_library(clice-core ${CLICE_LIB_TYPE} ${CLICE_CORE_SOURCES}) @@ -61,10 +62,8 @@ target_link_libraries(clice-core PUBLIC # clice executable add_subdirectory(${CMAKE_SOURCE_DIR}/deps/libuv) -set(CLICE_SERVER_SOURCES ${CMAKE_SOURCE_DIR}/src/main.cpp) -file(GLOB_RECURSE SRC_FILES "${CMAKE_SOURCE_DIR}/src/Server/*.cpp") -list(APPEND CLICE_SERVER_SOURCES ${SRC_FILES}) -add_executable(clice ${CLICE_SERVER_SOURCES}) +add_executable(clice ${SRC_FILES} ${CMAKE_SOURCE_DIR}/src/Driver/clice.cc) +add_executable(integration_tests ${SRC_FILES} ${CMAKE_SOURCE_DIR}/src/Driver/integration_tests.cc) include_directories( "${CMAKE_SOURCE_DIR}/deps/toml/include" @@ -72,20 +71,20 @@ include_directories( ) target_link_libraries(clice PRIVATE clice-core uv) - +target_link_libraries(integration_tests PRIVATE clice-core uv) # clice tests if(CLICE_ENABLE_TEST) add_subdirectory("${CMAKE_SOURCE_DIR}/deps/googletest") - set(CLICE_TEST_SOURCES ${CMAKE_SOURCE_DIR}/unittests/main.cpp) + set(CLICE_TEST_SOURCES ${CMAKE_SOURCE_DIR}/src/Driver/unit_tests.cc) file(GLOB_RECURSE TEST_SRC_FILES "${CMAKE_SOURCE_DIR}/unittests/*/*.cpp") list(APPEND CLICE_TEST_SOURCES ${TEST_SRC_FILES}) - add_executable(clice-tests ${CLICE_TEST_SOURCES} ${SRC_FILES}) - target_include_directories(clice-tests PRIVATE + add_executable(unit_tests ${CLICE_TEST_SOURCES} ${SRC_FILES}) + target_include_directories(unit_tests PRIVATE "${CMAKE_SOURCE_DIR}/deps/googletest/googletest/include" ) - target_link_libraries(clice-tests PRIVATE + target_link_libraries(unit_tests PRIVATE clice-core gtest_main uv diff --git a/include/Basic/Basic.h b/include/Basic/Basic.h index 2288e454..4a902fbd 100644 --- a/include/Basic/Basic.h +++ b/include/Basic/Basic.h @@ -21,4 +21,8 @@ using array = std::vector; using DocumentUri = std::string; +using URI = std::string; + +struct None {}; + } // namespace clice::proto diff --git a/include/Basic/Document.h b/include/Basic/Document.h index fdd62798..5ed09195 100644 --- a/include/Basic/Document.h +++ b/include/Basic/Document.h @@ -47,4 +47,33 @@ struct TextDocumentPositionParams { Position position; }; +struct DidOpenTextDocumentParams { + /// The document that was opened. + TextDocumentItem textDocument; +}; + +struct DidChangeTextDocumentParams { + /// The document that did change. The version number points + /// to the version after all provided content changes have + /// been applied. + VersionedTextDocumentIdentifier textDocument; + + /// The actual content changes. + std::vector contentChanges; +}; + +struct DidSaveTextDocumentParams { + /// The document that was saved. + TextDocumentIdentifier textDocument; + + /// Optional the content when saved. Depends on the includeText value + /// when the save notifcation was requested. + string text; +}; + +struct DidCloseTextDocumentParams { + /// The document that was closed. + TextDocumentIdentifier textDocument; +}; + } // namespace clice::proto diff --git a/include/Basic/Lifecycle.h b/include/Basic/Lifecycle.h new file mode 100644 index 00000000..1f3f94a4 --- /dev/null +++ b/include/Basic/Lifecycle.h @@ -0,0 +1,91 @@ +#pragma once + +#include "Workspace.h" +#include "Feature/Lookup.h" +#include "Feature/DocumentHighlight.h" +#include "Feature/DocumentLink.h" +#include "Feature/Hover.h" +#include "Feature/CodeLens.h" +#include "Feature/FoldingRange.h" +#include "Feature/DocumentSymbol.h" +#include "Feature/SemanticTokens.h" +#include "Feature/InlayHint.h" +#include "Feature/CodeCompletion.h" +#include "Feature/SignatureHelp.h" +#include "Feature/CodeAction.h" +#include "Feature/Formatting.h" +#include "Support/Support.h" + +namespace clice::proto { + +struct ClientCapabilities { + /// General client capabilities. + struct { + /// The position encodings supported by the client. Client and server + /// have to agree on the same position encoding to ensure that offsets + /// (e.g. character position in a line) are interpreted the same on both + /// side. + /// + /// To keep the protocol backwards compatible the following applies: if + /// the value 'utf-16' is missing from the array of position encodings + /// servers can assume that the client supports UTF-16. UTF-16 is + /// therefore a mandatory encoding. + /// + /// If omitted it defaults to ['utf-16']. + /// + /// Implementation considerations: since the conversion from one encoding + /// into another requires the content of the file / line the conversion + /// is best done where the file is read which is usually on the server + /// side. + std::vector positionEncodings = {PositionEncodingKind::UTF16}; + } general; +}; + +struct InitializeParams { + /// Information about the client. + struct { + /// The name of the client as defined by the client. + std::string name; + + /// The client's version as defined by the client. + std::string version; + } clientInfo; + + /// The capabilities provided by the client (editor or tool). + ClientCapabilities capabilities; + + /// The workspace folders configured in the client when the server starts. + /// This property is only available if the client supports workspace folders. + /// It can be `null` if the client supports workspace folders but none are + /// configured. + std::vector workspaceFolders; +}; + +struct ServerCapabilities { + /// The position encoding the server picked from the encodings offered + /// by the client via the client capability `general.positionEncodings`. + /// + /// If the client didn't provide any position encodings the only valid + /// value that a server can return is 'utf-16'. + /// + /// If omitted it defaults to 'utf-16'. + PositionEncodingKind positionEncoding = PositionEncodingKind::UTF16; +}; + +struct InitializeResult { + /// The capabilities the language server provides. + ServerCapabilities capabilities; + + /// Information about the server. + struct { + /// The name of the server as defined by the server. + std::string name; + + /// The server's version as defined by the server. + std::string version; + } serverInfo; +}; + +struct InitializedParams {}; + +} // namespace clice::proto diff --git a/include/Basic/SymbolKind.h b/include/Basic/SymbolKind.h index 70b9fe49..e6b0d65d 100644 --- a/include/Basic/SymbolKind.h +++ b/include/Basic/SymbolKind.h @@ -1,6 +1,7 @@ #pragma once #include "Support/Enum.h" +#include "clang/AST/Decl.h" namespace clice { @@ -56,5 +57,7 @@ struct SymbolKind : refl::Enum { static SymbolKind from(const clang::tok::TokenKind kind); }; +struct SymbolModifiers : refl::Enum {}; + } // namespace clice diff --git a/include/Basic/Workspace.h b/include/Basic/Workspace.h new file mode 100644 index 00000000..bd59faf0 --- /dev/null +++ b/include/Basic/Workspace.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Basic.h" + +namespace clice::proto { + +struct WorkspaceFolder { + /// The associated URI for this workspace folder. + URI uri; + + /// The name of the workspace folder. Used to refer to this workspace folder + /// in the user interface. + std::string name; +}; + +struct DidChangeWatchedFilesParams {}; + +} // namespace clice::proto diff --git a/include/Feature/SemanticTokens.def b/include/Feature/SemanticTokens.def deleted file mode 100644 index fca7db5c..00000000 --- a/include/Feature/SemanticTokens.def +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef SEMANTIC_TOKEN_TYPE -#define SEMANTIC_TOKEN_TYPE(...) -#endif - -#ifndef SEMANTIC_TOKEN_MODIFIER -#define SEMANTIC_TOKEN_MODIFIER(...) -#endif - -/// comment. -SEMANTIC_TOKEN_TYPE(Comment, "comment") - -/// character literal. -SEMANTIC_TOKEN_TYPE(Character, "character") - -/// number literal(int, float, hex, binary). -SEMANTIC_TOKEN_TYPE(Number, "number") - -/// string literal. -SEMANTIC_TOKEN_TYPE(String, "string") - -/// C/C++ keyword (e.g. `if`, `else`, `while`, `for`). -SEMANTIC_TOKEN_TYPE(Keyword, "keyword") - -/// compiler builtin macro, function or keyword (e.g. `__FILE__`, `__LINE__`, `__attribute__`). -SEMANTIC_TOKEN_TYPE(Builtin, "builtin") - -/// preprocessor directive (e.g. `#include`, `#define`, `#ifdef`). -SEMANTIC_TOKEN_TYPE(Directives, "directive") - -/// header file (e.g. `"foo.h"` and ``). -SEMANTIC_TOKEN_TYPE(Header, "header") - -/// C/C++ macro name, both in definition and invocation. -SEMANTIC_TOKEN_TYPE(Macro, "macro") - -/// C/C++ macro parameter, both in definition and invocation. -SEMANTIC_TOKEN_TYPE(MacroParameter, "macroParameter") - -/// C++ namespace. -SEMANTIC_TOKEN_TYPE(Namespace, "namespace") - -/// C/C++ type name (e.g. `std::string`). -SEMANTIC_TOKEN_TYPE(Type, "type") - -/// C/C++ enum name. -SEMANTIC_TOKEN_TYPE(Enum, "enum") - -/// C/C++ enum member name. -SEMANTIC_TOKEN_TYPE(EnumMember, "enumMember") - -/// C/C++ struct name. -SEMANTIC_TOKEN_TYPE(Struct, "struct") - -/// C/C++ union name. -SEMANTIC_TOKEN_TYPE(Union, "union") - -/// C/C++ class name. -SEMANTIC_TOKEN_TYPE(Class, "class") - -/// C/C++ field name. -SEMANTIC_TOKEN_TYPE(Field, "field") - -/// C/C++ variable name. -SEMANTIC_TOKEN_TYPE(Variable, "variable") - -/// C/C++ function/method parameter name. -SEMANTIC_TOKEN_TYPE(Parameter, "parameter") - -/// C/C++ function name. -SEMANTIC_TOKEN_TYPE(Function, "function") - -/// C/C++ method name. -SEMANTIC_TOKEN_TYPE(Method, "method") - -/// C/C++ label name. -SEMANTIC_TOKEN_TYPE(Label, "label") - -/// C++ attribute name(e.g., `nodiscard`, `likely`, `fallthrough`). -SEMANTIC_TOKEN_TYPE(Attribute, "attribute") - -/// C++20 module name. -SEMANTIC_TOKEN_TYPE(Module, "module") - -/// C++20 concept name. -SEMANTIC_TOKEN_TYPE(Concept, "concept") - -/// parentheses `()`. -SEMANTIC_TOKEN_TYPE(Paren, "paren") - -/// brackets `[]`. -SEMANTIC_TOKEN_TYPE(Bracket, "bracket") - -/// braces `{}`. -SEMANTIC_TOKEN_TYPE(Brace, "brace") - -/// angle brackets `<>`. -SEMANTIC_TOKEN_TYPE(Angle, "angle") - -/// operators (e.g. `+`, `==`, `<<`). -SEMANTIC_TOKEN_TYPE(Operator, "operator") - -/// for name in declaration. -SEMANTIC_TOKEN_MODIFIER(Declaration, "declaration") - -/// for name in definition. -SEMANTIC_TOKEN_MODIFIER(Definition, "definition") - -/// for `(`, `[`, `{`, `<`. -SEMANTIC_TOKEN_MODIFIER(Left, "left") - -/// for `)`, `]`, `}`, `>`. -SEMANTIC_TOKEN_MODIFIER(Right, "right") - -/// for punctuation in type, e.g `*` in `int*` and `&` in `int&`. -SEMANTIC_TOKEN_MODIFIER(InType, "inType") - -/// for overloaded function or operator. -SEMANTIC_TOKEN_MODIFIER(Overloaded, "overloaded") - -/// for name around template arguments(e.g., `foo` in `foo`). -SEMANTIC_TOKEN_MODIFIER(Templated, "templated") - -/// C++ dependent name in a template context (e.g., `name` in `auto y = T::name` -/// where T is a template parameter). -/// Note: This includes `T::template name(...)`; it's not possible to distinguish whether a name -/// is a function or a functor in a dependent context. - -/// C++ dependent type name (e.g., `type` in `typename std::vector::type` where -/// T is a template parameter). -/// Note: This includes `typename T::template type<...>`. -SEMANTIC_TOKEN_MODIFIER(Dependent, "dependent") - -#undef SEMANTIC_TOKEN_TYPE -#undef SEMANTIC_TOKEN_MODIFIER diff --git a/include/Feature/SemanticTokens.h b/include/Feature/SemanticTokens.h index a82a92da..5bfbb988 100644 --- a/include/Feature/SemanticTokens.h +++ b/include/Feature/SemanticTokens.h @@ -1,46 +1,37 @@ #pragma once -#include +#include "Basic/Document.h" +#include "Basic/SymbolKind.h" +#include "Index/FeatureIndex.h" -namespace clice::proto { +namespace clice { -// clang-format off -enum class SemanticTokenType : uint8_t { - #define SEMANTIC_TOKEN_TYPE(name, ...) name, - #include "SemanticTokens.def" - #undef SEMANTIC_TOKEN_TYPE - Invalid, - LAST_TYPE -}; +class ASTInfo; -enum class SemanticTokenModifier : uint8_t { - #define SEMANTIC_TOKEN_MODIFIER(name, ...) name, - #include "SemanticTokens.def" - #undef SEMANTIC_TOKEN_MODIFIER - LAST_MODIFIER -}; +namespace config { -struct SemanticTokensLegend { - std::array(SemanticTokenType::LAST_TYPE)> tokenTypes = { - #define SEMANTIC_TOKEN_TYPE(name, value) value, - #include "SemanticTokens.def" - }; +struct SemanticTokensOption {}; - std::array(SemanticTokenModifier::LAST_MODIFIER)> tokenModifiers = { - #define SEMANTIC_TOKEN_MODIFIER(name, value) value, - #include "SemanticTokens.def" - }; -}; // clang-format on +}; // namespace config + +namespace proto { /// Server Capability. struct SemanticTokensOptions { /// The legend used by the server. - SemanticTokensLegend legend = {}; + struct SemanticTokensLegend { + /// The token types a server uses. + std::vector tokenTypes; - /// The grammar of C++ is highly context-sensitive, so we only provide semantic tokens for - /// the whole document. + /// The token modifiers a server uses. + std::vector tokenModifiers; + } legend; + + /// Server supports providing semantic tokens for a specific range + /// of a document. bool range = false; + /// Server supports providing semantic tokens for a full document. bool full = true; }; @@ -54,18 +45,29 @@ struct SemanticTokens { std::vector data; }; -} // namespace clice::proto +} // namespace proto -namespace clice { +namespace feature { -class ASTInfo; +struct SemanticToken { + uint32_t line; + uint32_t column; + uint32_t length; + SymbolKind kind; + SymbolModifiers modifiers; +}; -} +/// Generate semantic tokens for all files. +index::SharedIndex> semanticTokens(ASTInfo& info); -namespace clice::feature { +/// Translate semantic tokens to LSP format. +proto::SemanticTokens toSemanticTokens(llvm::ArrayRef tokens, + const config::SemanticTokensOption& option); -/// FIXME: -proto::SemanticTokens semanticTokens(class ASTInfo& info, llvm::StringRef filename); +/// Generate semantic tokens for main file and translate to LSP format. +proto::SemanticTokens semanticTokens(ASTInfo& info, const config::SemanticTokensOption& option); -} // namespace clice::feature +} // namespace feature + +} // namespace clice diff --git a/include/Index/FeatureIndex.h b/include/Index/FeatureIndex.h index f0b9aa5e..57985d7d 100644 --- a/include/Index/FeatureIndex.h +++ b/include/Index/FeatureIndex.h @@ -1,8 +1,13 @@ -#include "SymbolIndex.h" +#pragma once + +#include + +#include "llvm/ADT/DenseMap.h" +#include "clang/Basic/SourceLocation.h" namespace clice::index { +template +using SharedIndex = llvm::DenseMap; - - -} \ No newline at end of file +} diff --git a/include/Server/Async.h b/include/Server/Async.h index c973272e..98198796 100644 --- a/include/Server/Async.h +++ b/include/Server/Async.h @@ -95,11 +95,6 @@ struct promise_type : promise_base { /// In the final suspend point, this coroutine is already done. /// So try to resume the waiting coroutine if it exists. async::schedule(waiting); - } else { - /// If waiting is empty, this is a top-level coroutine. - /// We decide to destroy it here. For non-top-level coroutines, - /// they are destroyed in the destructor of Task. - core.destroy(); } } @@ -448,16 +443,10 @@ void listen(Callback callback); /// Listen on the given ip and port, callback is called when there is a LSP message available. void listen(Callback callback, const char* ip, unsigned int port); -/// Send a request to the client. -Task<> request(llvm::StringRef method, json::Value params); +/// Spawn a new process and listen on its stdin/stdout. +void spawn(Callback callback, llvm::StringRef path, llvm::ArrayRef args); -/// Send a notification to the client. -Task<> notify(llvm::StringRef method, json::Value params); - -/// Send a response to the client. -Task<> response(json::Value id, json::Value result); - -/// Send an register capability to the client. -Task<> registerCapacity(llvm::StringRef id, llvm::StringRef method, json::Value registerOptions); +/// Write a JSON value to the client. +Task<> write(json::Value value); } // namespace clice::async diff --git a/include/Server/Protocol.h b/include/Server/Protocol.h index 5461c255..05c16ba6 100644 --- a/include/Server/Protocol.h +++ b/include/Server/Protocol.h @@ -1,172 +1,3 @@ #pragma once -#include "Feature/Lookup.h" -#include "Feature/DocumentHighlight.h" -#include "Feature/DocumentLink.h" -#include "Feature/Hover.h" -#include "Feature/CodeLens.h" -#include "Feature/FoldingRange.h" -#include "Feature/DocumentSymbol.h" -#include "Feature/SemanticTokens.h" -#include "Feature/InlayHint.h" -#include "Feature/CodeCompletion.h" -#include "Feature/SignatureHelp.h" -#include "Feature/CodeAction.h" -#include "Feature/Formatting.h" - -#include "Support/Support.h" - -namespace clice::proto { - -enum class TextDocumentSyncKind { - None = 0, - Full = 1, - Incremental = 2, -}; - -struct ClientInfo { - /// The name of the client as defined by the client. - string name; - /// The client's version as defined by the client. - string version; -}; - -struct DidChangeWatchedFilesClientCapabilities { - /// Did change watched files notification supports dynamic registration. - /// Please note that the current protocol doesn't support static - /// configuration for file changes from the server side. - bool dynamicRegistration = false; -}; - -struct ClientCapabilities { - /// Workspace specific client capabilities. - struct { - /// Capabilities specific to the `workspace/didChangeWatchedFiles` - /// notification. - DidChangeWatchedFilesClientCapabilities didChangeWatchedFiles; - } workspace; -}; - -struct FileSystemWatcher { - /// The glob pattern to watch. - std::string globPattern; - - enum WatchKind { - Create = 1, - Change = 2, - Delete = 4, - }; - - /// The kind of events of interest. - int kind = WatchKind::Create | WatchKind::Change | WatchKind::Delete; -}; - -struct DidChangeWatchedFilesRegistrationOptions { - /// The watchers to register. - std::vector watchers; -}; - -enum class FileChangeType { - /// The file got created. - Created = 1, - - /// The file got changed. - Changed = 2, - - /// The file got deleted. - Deleted = 3, -}; - -struct FileEvent { - /// The file's URI. - string uri; - - /// The change type. - FileChangeType type; -}; - -struct DidChangeWatchedFilesParams { - /// The actual file events. - std::vector changes; -}; - -struct Workplace { - /// The associated URI for this workspace folder. - string uri; - - /// The name of the workspace folder. Used to refer to this - /// workspace folder in the user interface. - string name; -}; - -struct None {}; - -struct InitializeParams { - /// Information about the client - ClientInfo clientInfo; - - /// The locale the client is currently showing the user interface - /// in. This must not necessarily be the locale of the operating - /// system. - /// - /// Uses IETF language tags as the value's syntax. - /// (See https://en.wikipedia.org/wiki/IETF_language_tag) - string locale; - - /// The capabilities provided by the client (editor or tool). - ClientCapabilities capabilities; - - /// The workspace folders configured in the client when the server starts. - /// This property is only available if the client supports workspace folders. - /// It can be `null` if the client supports workspace folders but none are - /// configured. - std::vector workspaceFolders; -}; - -struct ServerCapabilities { - std::string_view positionEncoding = "utf-16"; - TextDocumentSyncKind textDocumentSync = TextDocumentSyncKind::Full; - SemanticTokensOptions semanticTokensProvider; -}; - -struct InitializeResult { - ServerCapabilities capabilities; - - struct { - std::string_view name = "clice"; - std::string_view version = "0.0.1"; - } serverInfo; -}; - -struct InitializedParams {}; - -struct DidOpenTextDocumentParams { - /// The document that was opened. - TextDocumentItem textDocument; -}; - -struct DidChangeTextDocumentParams { - /// The document that did change. The version number points - /// to the version after all provided content changes have - /// been applied. - VersionedTextDocumentIdentifier textDocument; - - /// The actual content changes. - std::vector contentChanges; -}; - -struct DidSaveTextDocumentParams { - /// The document that was saved. - TextDocumentIdentifier textDocument; - - /// Optional the content when saved. Depends on the includeText value - /// when the save notifcation was requested. - string text; -}; - -struct DidCloseTextDocumentParams { - /// The document that was closed. - TextDocumentIdentifier textDocument; -}; - -} // namespace clice::proto +#include "Basic/Lifecycle.h" \ No newline at end of file diff --git a/include/Server/Server.h b/include/Server/Server.h index 4a3308e5..6eea47af 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -9,7 +9,22 @@ class Server { public: Server(); - int run(int argc, const char** argv); + async::Task<> onReceive(json::Value value); + + /// Send a request to the client. + async::Task<> request(llvm::StringRef method, json::Value params); + + /// Send a notification to the client. + async::Task<> notify(llvm::StringRef method, json::Value params); + + /// Send a response to the client. + async::Task<> response(json::Value id, json::Value result); + + /// Send an register capability to the client. + async::Task<> registerCapacity(llvm::StringRef id, + llvm::StringRef method, + json::Value registerOptions); + std::uint32_t id = 0; private: using onRequest = llvm::unique_function(json::Value, json::Value)>; @@ -37,7 +52,7 @@ private: private: /// ============================================================================ - /// Lifestyle Message + /// Lifecycle Message /// ============================================================================ async::Task<> onInitialize(json::Value id, const proto::InitializeParams& params); diff --git a/include/Support/Enum.h b/include/Support/Enum.h index 9b17281a..7e125799 100644 --- a/include/Support/Enum.h +++ b/include/Support/Enum.h @@ -66,6 +66,8 @@ public: /// Tag to indicate this is a special enum. constexpr inline static bool is_special_enum_v = true; + using underlying_type = underlying; + constexpr Enum() : m_Value(invalid()) {} /// A integral must explicitly convert to the enum. @@ -138,6 +140,8 @@ public: /// Tag to indicate this is a special enum. constexpr inline static bool is_special_enum_v = true; + using underlying_type = underlying; + /// FIXME: Enum() = default; @@ -245,6 +249,11 @@ template requires (!integral) class Enum { public: + /// Tag to indicate this is a special enum. + constexpr inline static bool is_special_enum_v = true; + + using underlying_type = underlying; + constexpr Enum(underlying value) : m_Value(value) { static_assert( requires { Derived::All; }, diff --git a/include/Support/JSON.h b/include/Support/JSON.h index 921acfba..6549bc2b 100644 --- a/include/Support/JSON.h +++ b/include/Support/JSON.h @@ -286,8 +286,7 @@ struct Serde { } static E deserialize(const json::Value& value) { - assert(value.kind() == json::Value::Number && "Expect a number"); - return E(value.getAsNumber().value()); + return E(json::deserialize(value)); } }; @@ -299,7 +298,7 @@ struct Serde { template static json::Value serialize(const T& t, Serdes&&... serdes) { json::Object object; - refl::foreach(t, [&](std::string_view name, auto&& member) { + refl::foreach(t, [&](std::string_view name, auto& member) { object.try_emplace(llvm::StringRef(name), json::serialize(member, std::forward(serdes)...)); }); diff --git a/unittests/Test.h b/include/Test/Test.h similarity index 98% rename from unittests/Test.h rename to include/Test/Test.h index 013c2d2f..3240c766 100644 --- a/unittests/Test.h +++ b/include/Test/Test.h @@ -1,9 +1,9 @@ #pragma once -#include -#include -#include -#include +#include "gtest/gtest.h" +#include "Basic/Location.h" +#include "Compiler/Compiler.h" +#include "Support/Support.h" namespace clice { diff --git a/src/Driver/clice.cc b/src/Driver/clice.cc new file mode 100644 index 00000000..6b326a69 --- /dev/null +++ b/src/Driver/clice.cc @@ -0,0 +1,51 @@ +#include "Server/Logger.h" +#include "Server/Server.h" +#include "llvm/Support/CommandLine.h" + +using namespace clice; + +namespace cl { + +llvm::cl::opt config("config", + llvm::cl::desc("The path of the config file"), + llvm::cl::value_desc("path")); + +llvm::cl::opt pipe("pipe", llvm::cl::desc("Use pipe mode")); + +} // namespace cl + +int main(int argc, const char** argv) { + for(int i = 0; i < argc; ++i) { + log::warn("argv[{0}] = {1}", i, argv[i]); + } + + llvm::cl::SetVersionPrinter([](llvm::raw_ostream& os) { os << "clice version: 0.0.1\n"; }); + llvm::cl::ParseCommandLineOptions(argc, argv, "clice language server"); + + if(cl::config.empty()) { + log::warn("No config file specified; using default configuration."); + } else { + /// config::load(argv[0], cl::config.getValue()); + log::info("Successfully loaded configuration file from {0}.", cl::config.getValue()); + } + + /// Get the resource directory. + if(auto error = fs::init_resource_dir(argv[0])) { + log::fatal("Failed to get resource directory, because {0}", error); + return 1; + } + + static Server server; + auto loop = [](json::Value value) -> async::Task<> { + co_await server.onReceive(value); + }; + + if(cl::pipe && cl::pipe.getValue()) { + async::listen(loop); + } else { + async::listen(loop, "127.0.0.1", 50051); + } + + async::run(); +} + diff --git a/src/Driver/integration_tests.cc b/src/Driver/integration_tests.cc new file mode 100644 index 00000000..b04d78d7 --- /dev/null +++ b/src/Driver/integration_tests.cc @@ -0,0 +1,61 @@ +#include "Server/Async.h" +#include "Server/Logger.h" +#include "Support/Support.h" +#include "llvm/Support/CommandLine.h" + +using namespace clice; + +namespace cl { + +llvm::cl::opt execute("execute", llvm::cl::desc("The execute path")); +llvm::cl::opt source("source", llvm::cl::desc("The test source path")); + +} // namespace cl + +static uint32_t id = 0; + +async::Task request(llvm::StringRef dir, llvm::StringRef file, llvm::StringRef method) { + llvm::SmallString<128> path; + path::append(path, cl::source, dir, file); + + auto content = llvm::MemoryBuffer::getFile(path); + if(!content) { + log::fatal("Failed to read file: {0}", path); + } + + auto params = json::parse(content.get()->getBuffer()); + if(!params) { + log::fatal("Failed to parse file: {0}", path); + } + + json::Object request = json::Object{ + {"jsonrpc", "2.0" }, + {"id", id++ }, + {"method", method }, + {"params", std::move(*params)}, + }; + + log::info("Send Request: {0}", method); + + co_await async::write(std::move(request)); + + co_return 1; +} + +int main(int argc, const char** argv) { + llvm::cl::SetVersionPrinter([](llvm::raw_ostream& os) { os << "clice version: 0.0.1\n"; }); + llvm::cl::ParseCommandLineOptions(argc, argv, "clice language server"); + + async::spawn( + [](json::Value value) -> async::Task<> { + print("Receive: {0}", value); + co_return; + }, + cl::execute.getValue(), + {"--pipe=true", "--config=/home/ykiko/C++/clice2/docs/clice.toml"}); + + auto p = request("initialize", "input.json", "initialize"); + async::run(p); + p.release(); + return 0; +} diff --git a/unittests/main.cpp b/src/Driver/unit_tests.cc similarity index 88% rename from unittests/main.cpp rename to src/Driver/unit_tests.cc index 0fe49540..4af1a8f2 100644 --- a/unittests/main.cpp +++ b/src/Driver/unit_tests.cc @@ -1,5 +1,7 @@ -#include "Test.h" -#include +#include "Test/Test.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/ADT/SmallString.h" +#include "Support/Support.h" namespace clice { diff --git a/src/Feature/SemanticTokens.cpp b/src/Feature/SemanticTokens.cpp index f0179385..c23b1653 100644 --- a/src/Feature/SemanticTokens.cpp +++ b/src/Feature/SemanticTokens.cpp @@ -6,119 +6,20 @@ namespace clice::feature { namespace { -struct SemanticToken { - std::uint32_t line; - std::uint32_t column; - std::uint32_t length; - proto::SemanticTokenType type; - std::uint32_t modifiers = 0; - - SemanticToken& addModifier(proto::SemanticTokenModifier modifier) { - modifiers |= 1 << static_cast(modifier); - return *this; - } -}; - class HighlightBuilder : public SemanticVisitor { public: HighlightBuilder(ASTInfo& compiler) : SemanticVisitor(compiler, true) {} - void handleOccurrence(const clang::Decl* decl, clang::SourceRange range, RelationKind kind) { - proto::SemanticTokenType type = proto::SemanticTokenType::Invalid; - if(llvm::isa(decl)) { - type = proto::SemanticTokenType::Namespace; - } else if(llvm::isa(decl)) { - type = proto::SemanticTokenType::Type; - } else if(llvm::isa(decl)) { - type = proto::SemanticTokenType::Enum; - } else if(llvm::isa(decl)) { - type = proto::SemanticTokenType::EnumMember; - } else if(auto TD = llvm::dyn_cast(decl)) { - type = TD->isStruct() ? proto::SemanticTokenType::Struct - : TD->isUnion() ? proto::SemanticTokenType::Union - : proto::SemanticTokenType::Class; - } else if(llvm::isa(decl)) { - type = proto::SemanticTokenType::Field; - } else if(llvm::isa(decl)) { - type = proto::SemanticTokenType::Parameter; - } else if(llvm::isa( - decl)) { - type = proto::SemanticTokenType::Variable; - } else if(llvm::isa(decl)) { - type = proto::SemanticTokenType::Method; - } else if(llvm::isa(decl)) { - type = proto::SemanticTokenType::Function; - } else if(llvm::isa(decl)) { - type = proto::SemanticTokenType::Label; - } else if(llvm::isa(decl)) { - type = proto::SemanticTokenType::Concept; - } - - if(type != proto::SemanticTokenType::Invalid) { - addToken(range.getBegin(), type); - } - } - - void handleOccurrence(const clang::BuiltinType* type, clang::SourceRange range) { - // llvm::outs() << type->getName(clang::PrintingPolicy({})) << "\n"; - // dump(range.getBegin()); - // dump(range.getEnd()); - } - - void handleOccurrence(const clang::Attr* attr, clang::SourceRange range) { - auto tokens = tokBuf.expandedTokens(range); - if(auto first = tokens.begin()) { - auto second = first + 1; - switch(first->kind()) { - case clang::tok::identifier: { - bool isFirstTokenNamespace = second && second->kind() == clang::tok::coloncolon; - if(isFirstTokenNamespace) { - addToken(first->location(), proto::SemanticTokenType::Namespace); - first += 2; - } - - assert(first && first->kind() == clang::tok::identifier && - "Expecting an identifier token"); - addToken(first->location(), proto::SemanticTokenType::Attribute); - break; - } - /// [[using CC: opt(1), debug]] - case clang::tok::kw_using: { - assert(second && second->kind() == clang::tok::identifier); - addToken(second->location(), proto::SemanticTokenType::Namespace); - break; - } - default: { - std::terminate(); - } - } - } - } - void run() { TraverseAST(sema.getASTContext()); } - HighlightBuilder& addToken(clang::SourceLocation location, proto::SemanticTokenType type) { - auto loc = srcMgr.getPresumedLoc(location); - tokens.emplace_back(SemanticToken{ - .line = loc.getLine() - 1, - .column = loc.getColumn() - 1, - .length = clang::Lexer::MeasureTokenLength(location, srcMgr, sema.getLangOpts()), - .type = type, - .modifiers = 0, - }); - return *this; - } - proto::SemanticTokens build() { /// Collect semantic from spelled tokens. auto mainFileID = srcMgr.getMainFileID(); auto spelledTokens = tokBuf.spelledTokens(mainFileID); for(auto& token: spelledTokens) { - proto::SemanticTokenType type = proto::SemanticTokenType::Invalid; + SymbolKind type = SymbolKind::Invalid; // llvm::outs() << clang::tok::getTokenName(token.kind()) << " " // << pp.getIdentifierInfo(token.text(srcMgr))->isKeyword(pp.getLangOpts()) // << "\n"; @@ -126,7 +27,7 @@ public: auto kind = token.kind(); switch(kind) { case clang::tok::numeric_constant: { - type = proto::SemanticTokenType::Number; + type = SymbolKind::Number; break; } @@ -135,7 +36,7 @@ public: case clang::tok::utf8_char_constant: case clang::tok::utf16_char_constant: case clang::tok::utf32_char_constant: { - type = proto::SemanticTokenType::Character; + type = SymbolKind::Character; break; } @@ -144,7 +45,7 @@ public: case clang::tok::utf8_string_literal: case clang::tok::utf16_string_literal: case clang::tok::utf32_string_literal: { - type = proto::SemanticTokenType::String; + type = SymbolKind::String; break; } @@ -163,21 +64,19 @@ public: /// highlight them as keywords. So check whether their text is same as the /// operator spelling. If not, it indicates that they are keywords. if(token.text(srcMgr) != clang::tok::getPunctuatorSpelling(kind)) { - type = proto::SemanticTokenType::Operator; + type = SymbolKind::Operator; } } default: { if(pp.getIdentifierInfo(token.text(srcMgr))->isKeyword(pp.getLangOpts())) { - type = proto::SemanticTokenType::Keyword; + type = SymbolKind::Keyword; break; } } } - if(type != proto::SemanticTokenType::Invalid) { - addToken(token.location(), type); - } + if(type != SymbolKind::Invalid) {} } /// Collect semantic tokens from AST. @@ -196,8 +95,8 @@ public: result.data.push_back(token.line == lastLine ? token.column - lastColumn : token.column); result.data.push_back(token.length); - result.data.push_back(llvm::to_underlying(token.type)); - result.data.push_back(token.modifiers); + // result.data.push_back(llvm::to_underlying(token.column)); + // result.data.push_back(0); lastLine = token.line; lastColumn = token.column; @@ -211,9 +110,17 @@ public: } // namespace -proto::SemanticTokens semanticTokens(ASTInfo& info, llvm::StringRef filename) { - HighlightBuilder builder(info); - return builder.build(); +index::SharedIndex> semanticTokens(ASTInfo& info) { + return index::SharedIndex>{}; +} + +proto::SemanticTokens toSemanticTokens(llvm::ArrayRef tokens, + const config::SemanticTokensOption& option) { + return {}; +} + +proto::SemanticTokens semanticTokens(ASTInfo& info, const config::SemanticTokensOption& option) { + return {}; } } // namespace clice::feature diff --git a/src/Server/Async.cpp b/src/Server/Async.cpp index b6f1195c..ad7f8dc6 100644 --- a/src/Server/Async.cpp +++ b/src/Server/Async.cpp @@ -17,6 +17,9 @@ Callback callback = {}; uv_stream_t* writer = {}; +/// Whether the server is listening. +bool listened = false; + } // namespace /// This function is called by the event loop to resume the tasks. @@ -29,7 +32,7 @@ static void event_loop(uv_idle_t* handle) { tasks.pop_front(); task.resume(); - if(tasks.empty()) { + if(tasks.empty() && !listened) { uv_stop(loop); } } @@ -134,7 +137,7 @@ void listen(Callback callback) { uv_check_call(uv_read_start, (uv_stream_t*)&in, async::on_alloc, async::on_read); log::info("Server started in pipe mode"); - uv_check_call(uv_run, async::loop, UV_RUN_DEFAULT); + async::listened = true; } void listen(Callback callback, const char* ip, unsigned int port) { @@ -164,11 +167,109 @@ void listen(Callback callback, const char* ip, unsigned int port) { uv_check_call(uv_listen, (uv_stream_t*)&server, 128, on_connection); log::info("Server started in socket mode at {0}:{1}", ip, port); - uv_check_call(uv_run, async::loop, UV_RUN_DEFAULT); + async::listened = true; +} + +void spawn(Callback callback, llvm::StringRef path, llvm::ArrayRef args) { + static uv_pipe_t in; + static uv_pipe_t out; + static uv_pipe_t err; + + async::callback = std::move(callback); + writer = reinterpret_cast(&in); + + uv_check_call(uv_pipe_init, async::loop, &in, 0); + uv_check_call(uv_pipe_init, async::loop, &out, 0); + uv_check_call(uv_pipe_init, async::loop, &err, 0); + + static uv_process_t process; + static uv_process_options_t options; + + static uv_stdio_container_t stdio[3]; + stdio[0].flags = static_cast(UV_CREATE_PIPE | UV_READABLE_PIPE); + stdio[0].data.stream = (uv_stream_t*)∈ + + stdio[1].flags = static_cast(UV_CREATE_PIPE | UV_WRITABLE_PIPE); + stdio[1].data.stream = (uv_stream_t*)&out; + + stdio[2].flags = static_cast(UV_CREATE_PIPE | UV_WRITABLE_PIPE); + stdio[2].data.stream = (uv_stream_t*)&err; + + options = {[](uv_process_t* req, int64_t exit_status, int term_signal) { + printf("Child process exited with status %ld, signal %d\n", exit_status, term_signal); + uv_close((uv_handle_t*)req, NULL); + }}; + options.stdio = stdio; + options.stdio_count = 3; + + static llvm::SmallString<128> file = path; + options.file = file.c_str(); + + static llvm::SmallString<1024> buffer; + static llvm::SmallVector argv; + std::size_t size = 0; + size += path.size() + 1; + for(auto& arg: args) { + size += arg.size() + 1; + } + buffer.resize_for_overwrite(size); + argv.push_back(buffer.end()); + buffer.append(path); + buffer.push_back('\0'); + for(auto& arg: args) { + argv.push_back(buffer.end()); + buffer.append(arg); + buffer.push_back('\0'); + } + options.args = argv.data(); + + uv_check_call(uv_spawn, async::loop, &process, &options); + uv_check_call(uv_read_start, (uv_stream_t*)&out, async::on_alloc, async::on_read); + uv_check_call( + uv_read_start, + (uv_stream_t*)&err, + async::on_alloc, + [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + if(nread > 0) { + log::warn("{0}", llvm::StringRef{buf->base, static_cast(nread)}); + } else if(nread < 0) { + if(nread != UV_EOF) { + log::fatal("An error occurred while reading: {0}", uv_strerror(nread)); + } + uv_close((uv_handle_t*)stream, NULL); + } + }); + + log::info("Process spawned: {0}", path); + async::listened = true; + + auto on_signal = +[](uv_signal_t* handle, int signum) { + fprintf(stderr, "Signal received: %d, killing child process\n", signum); + uv_process_kill(&process, SIGKILL); + uv_signal_stop(handle); + uv_close((uv_handle_t*)handle, NULL); + uv_stop(loop); + }; + + static uv_signal_t signal; + uv_signal_init(loop, &signal); + uv_signal_start(&signal, on_signal, SIGINT); + + static uv_signal_t signal2; + uv_signal_init(loop, &signal2); + uv_signal_start(&signal2, on_signal, SIGTERM); + + static uv_signal_t signal3; + uv_signal_init(loop, &signal3); + uv_signal_start(&signal3, on_signal, SIGQUIT); + + static uv_signal_t signal4; + uv_signal_init(loop, &signal4); + uv_signal_start(&signal4, on_signal, SIGKILL); } /// Write a JSON value to the client. -static auto write(json::Value value) { +Task<> write(json::Value value) { struct awaiter { uv_write_t write; uv_buf_t buf[2]; @@ -204,45 +305,7 @@ static auto write(json::Value value) { llvm::raw_svector_ostream(awaiter.header) << "Content-Length: " << awaiter.message.size() << "\r\n\r\n"; - return awaiter; -} - -Task<> request(llvm::StringRef method, json::Value params) { - static std::uint32_t id = 0; - co_await write(json::Object{ - {"jsonrpc", "2.0" }, - {"id", id += 1 }, - {"method", method }, - {"params", std::move(params)}, - }); -} - -Task<> notify(llvm::StringRef method, json::Value params) { - co_await write(json::Object{ - {"jsonrpc", "2.0" }, - {"method", method }, - {"params", std::move(params)}, - }); -} - -Task<> response(json::Value id, json::Value result) { - co_await write(json::Object{ - {"jsonrpc", "2.0" }, - {"id", id }, - {"result", std::move(result)}, - }); -} - -Task<> registerCapacity(llvm::StringRef id, llvm::StringRef method, json::Value registerOptions) { - co_await request("client/registerCapability", - json::Object{ - {"registrations", - json::Array{json::Object{ - {"id", id}, - {"method", method}, - {"registerOptions", std::move(registerOptions)}, - }}}, - }); + co_await awaiter; } } // namespace clice::async diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index a8b5eea8..ee9daa1c 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -4,7 +4,11 @@ namespace clice { async::Task<> Server::onInitialize(json::Value id, const proto::InitializeParams& params) { - co_return; + proto::InitializeResult result = {}; + result.serverInfo.name = "clice"; + result.serverInfo.version = "0.0.1"; + + co_await response(std::move(id), json::serialize(result)); } async::Task<> Server::onInitialized(const proto::InitializedParams& params) { diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index a1f76639..ed10887e 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -1,70 +1,8 @@ #include "Server/Logger.h" #include "Server/Server.h" -#include "llvm/Support/CommandLine.h" namespace clice { -namespace cl { - -llvm::cl::opt config("config", - llvm::cl::desc("The path of the config file"), - llvm::cl::value_desc("path")); - -} // namespace cl - -int Server::run(int argc, const char** argv) { - llvm::cl::SetVersionPrinter([](llvm::raw_ostream& os) { os << "clice version: 0.0.1\n"; }); - llvm::cl::ParseCommandLineOptions(argc, argv, "clice language server"); - - if(cl::config.empty()) { - log::warn("No config file specified; using default configuration."); - } else { - /// config::load(argv[0], cl::config.getValue()); - log::info("Successfully loaded configuration file from {0}.", cl::config.getValue()); - } - - /// Get the resource directory. - if(auto error = fs::init_resource_dir(argv[0])) { - log::fatal("Failed to get resource directory, because {0}", error); - return 1; - } - - auto dispatch = [this](json::Value value) -> async::Task<> { - assert(value.kind() == json::Value::Object); - auto object = value.getAsObject(); - assert(object && "value is not an object"); - if(auto method = object->get("method")) { - auto name = *method->getAsString(); - auto params = object->get("params"); - if(auto id = object->get("id")) { - if(auto iter = requests.find(name); iter != requests.end()) { - /// auto tracer = Tracer(); - log::info("Receive request: {0}", name); - co_await iter->second(std::move(*id), - params ? std::move(*params) : json::Value(nullptr)); - log::info("Request {0} is done, elapsed {1}", name, 0); - - } else { - log::warn("Unknown request: {0}", name); - } - } else { - if(auto iter = notifications.find(name); iter != notifications.end()) { - log::info("Notification: {0}", name); - co_await iter->second(params ? std::move(*params) : json::Value(nullptr)); - } else { - log::warn("Unknown notification: {0}", name); - } - } - } - - co_return; - }; - - async::listen(dispatch, "127.0.0.1", 50051); - - return 0; -} - Server::Server() { addMethod("initialize", &Server::onInitialize); addMethod("initialized", &Server::onInitialized); @@ -108,4 +46,73 @@ Server::Server() { addMethod("context/all", &Server::onContextAll); } +async::Task<> Server::onReceive(json::Value value) { + assert(value.kind() == json::Value::Object); + auto object = value.getAsObject(); + assert(object && "value is not an object"); + if(auto method = object->get("method")) { + auto name = *method->getAsString(); + auto params = object->get("params"); + if(auto id = object->get("id")) { + if(auto iter = requests.find(name); iter != requests.end()) { + /// auto tracer = Tracer(); + log::info("Receive request: {0}", name); + co_await iter->second(std::move(*id), + params ? std::move(*params) : json::Value(nullptr)); + log::info("Request {0} is done, elapsed {1}", name, 0); + + } else { + log::warn("Unknown request: {0}", name); + } + } else { + if(auto iter = notifications.find(name); iter != notifications.end()) { + log::info("Notification: {0}", name); + co_await iter->second(params ? std::move(*params) : json::Value(nullptr)); + } else { + log::warn("Unknown notification: {0}", name); + } + } + } + co_return; +} + +async::Task<> Server::request(llvm::StringRef method, json::Value params) { + co_await async::write(json::Object{ + {"jsonrpc", "2.0" }, + {"id", id += 1 }, + {"method", method }, + {"params", std::move(params)}, + }); +} + +async::Task<> Server::notify(llvm::StringRef method, json::Value params) { + co_await async::write(json::Object{ + {"jsonrpc", "2.0" }, + {"method", method }, + {"params", std::move(params)}, + }); +} + +async::Task<> Server::response(json::Value id, json::Value result) { + co_await async::write(json::Object{ + {"jsonrpc", "2.0" }, + {"id", id }, + {"result", std::move(result)}, + }); +} + +async::Task<> Server::registerCapacity(llvm::StringRef id, + llvm::StringRef method, + json::Value registerOptions) { + co_await request("client/registerCapability", + json::Object{ + {"registrations", + json::Array{json::Object{ + {"id", id}, + {"method", method}, + {"registerOptions", std::move(registerOptions)}, + }}}, + }); +} + } // namespace clice diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 6515c01f..00000000 --- a/src/main.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "Server/Server.h" - -using namespace clice; - -int main(int argc, const char** argv) { - Server server; - return server.run(argc, argv); -} - diff --git a/tests/ASTVisitor/macro.cpp b/tests/ASTVisitor/macro.cpp deleted file mode 100644 index 24761444..00000000 --- a/tests/ASTVisitor/macro.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#define TEST(name) \ - struct name {}; \ - struct name2 {}; \ - struct name##3 {}; \ - struct name name; - -TEST(name); - -int main() { - struct name x; - struct name2 y; - struct name3 z; -} - -#define Name(x) x - -int Name(x) = 1; \ No newline at end of file diff --git a/tests/ASTVisitor/test.cpp b/tests/ASTVisitor/test.cpp deleted file mode 100644 index ebb2ccf9..00000000 --- a/tests/ASTVisitor/test.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include - -int main() { - -} diff --git a/tests/CodeCompletion/non-self-contain.cpp b/tests/CodeCompletion/non-self-contain.cpp deleted file mode 100644 index 3bbe72da..00000000 --- a/tests/CodeCompletion/non-self-contain.cpp +++ /dev/null @@ -1,3 +0,0 @@ -struct Foo {}; - -#include "non-self-contain.h" diff --git a/tests/CodeCompletion/non-self-contain.h b/tests/CodeCompletion/non-self-contain.h deleted file mode 100644 index 69a868ca..00000000 --- a/tests/CodeCompletion/non-self-contain.h +++ /dev/null @@ -1,3 +0,0 @@ -inline void bar() { - Foo f; -} diff --git a/tests/Compiler/test.cpp b/tests/Compiler/test.cpp deleted file mode 100644 index 0a1f09af..00000000 --- a/tests/Compiler/test.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#define name 1 - -#if name - - - -int x = 1; - -#elif [[$s]]name - -int x = 1; - -#else - -int x = 1; - -#endif - -#undef name - diff --git a/tests/Diagnostic/test.cpp b/tests/Diagnostic/test.cpp deleted file mode 100644 index b5780001..00000000 --- a/tests/Diagnostic/test.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// #include "test.h" -#include - -#if __has_include("test.h") - -#endif diff --git a/tests/Diagnostic/test.h b/tests/Diagnostic/test.h deleted file mode 100644 index 4c2f4555..00000000 --- a/tests/Diagnostic/test.h +++ /dev/null @@ -1 +0,0 @@ -int x; \ No newline at end of file diff --git a/tests/Index/test.cpp b/tests/Index/test.cpp deleted file mode 100644 index 7d7fe2f3..00000000 --- a/tests/Index/test.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#define expr(v) v - -#ifdef expr -int x = expr(1); -#endif - -#undef expr - -#define expr(v) v - -#ifdef expr -int y = expr(expr(1)); -#endif - -#undef expr diff --git a/tests/ModuleScanner/A.cppm b/tests/ModuleScanner/A.cppm deleted file mode 100644 index 0db46de1..00000000 --- a/tests/ModuleScanner/A.cppm +++ /dev/null @@ -1,5 +0,0 @@ -export module A; - -export int f() { - return 42; -} diff --git a/tests/ModuleScanner/B.cppm b/tests/ModuleScanner/B.cppm deleted file mode 100644 index 8866f49d..00000000 --- a/tests/ModuleScanner/B.cppm +++ /dev/null @@ -1,3 +0,0 @@ -export module B; - -import A; \ No newline at end of file diff --git a/tests/ModuleScanner/C.cppm b/tests/ModuleScanner/C.cppm deleted file mode 100644 index 652cbd44..00000000 --- a/tests/ModuleScanner/C.cppm +++ /dev/null @@ -1,3 +0,0 @@ -export module C; - -import A; \ No newline at end of file diff --git a/tests/ModuleScanner/D.cppm b/tests/ModuleScanner/D.cppm deleted file mode 100644 index 080d121a..00000000 --- a/tests/ModuleScanner/D.cppm +++ /dev/null @@ -1,7 +0,0 @@ -import B; -import C; - -int main() { - int x = 42; - return 42; -} diff --git a/tests/SelectionTree/test.cpp b/tests/SelectionTree/test.cpp deleted file mode 100644 index 58af3e2f..00000000 --- a/tests/SelectionTree/test.cpp +++ /dev/null @@ -1,7 +0,0 @@ -namespace XX { -namespace YY { -int x; -}; -}; // namespace XX - -int y = XX::YY::x; diff --git a/tests/compile_commands.json b/tests/compile_commands.json deleted file mode 100644 index a2370836..00000000 --- a/tests/compile_commands.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "directory": "/path/to/project", - "command": "clang++ -Iinclude -std=c++17 -o file.o -c file.cpp", - "file": "file.cpp" - }, - { - "directory": "/path/to/project", - "command": "clang++ -Iinclude -std=c++17 -o another_file.o -c another_file.cpp", - "file": "another_file.cpp" - } -] \ No newline at end of file diff --git a/tests/initialize/input.json b/tests/initialize/input.json new file mode 100644 index 00000000..66a8ac25 --- /dev/null +++ b/tests/initialize/input.json @@ -0,0 +1,482 @@ +{ + "processId": 312637, + "clientInfo": { + "name": "Visual Studio Code", + "version": "1.96.2" + }, + "locale": "en", + "rootPath": "/home/ykiko/C++/module", + "rootUri": "file:///home/ykiko/C%2B%2B/module", + "capabilities": { + "workspace": { + "applyEdit": true, + "workspaceEdit": { + "documentChanges": true, + "resourceOperations": [ + "create", + "rename", + "delete" + ], + "failureHandling": "textOnlyTransactional", + "normalizesLineEndings": true, + "changeAnnotationSupport": { + "groupsOnLabel": true + } + }, + "configuration": true, + "didChangeWatchedFiles": { + "dynamicRegistration": true, + "relativePatternSupport": true + }, + "symbol": { + "dynamicRegistration": true, + "symbolKind": { + "valueSet": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ] + }, + "tagSupport": { + "valueSet": [ + 1 + ] + }, + "resolveSupport": { + "properties": [ + "location.range" + ] + } + }, + "codeLens": { + "refreshSupport": true + }, + "executeCommand": { + "dynamicRegistration": true + }, + "didChangeConfiguration": { + "dynamicRegistration": true + }, + "workspaceFolders": true, + "foldingRange": { + "refreshSupport": true + }, + "semanticTokens": { + "refreshSupport": true + }, + "fileOperations": { + "dynamicRegistration": true, + "didCreate": true, + "didRename": true, + "didDelete": true, + "willCreate": true, + "willRename": true, + "willDelete": true + }, + "inlineValue": { + "refreshSupport": true + }, + "inlayHint": { + "refreshSupport": true + }, + "diagnostics": { + "refreshSupport": true + } + }, + "textDocument": { + "publishDiagnostics": { + "relatedInformation": true, + "versionSupport": false, + "tagSupport": { + "valueSet": [ + 1, + 2 + ] + }, + "codeDescriptionSupport": true, + "dataSupport": true + }, + "synchronization": { + "dynamicRegistration": true, + "willSave": true, + "willSaveWaitUntil": true, + "didSave": true + }, + "completion": { + "dynamicRegistration": true, + "contextSupport": true, + "completionItem": { + "snippetSupport": true, + "commitCharactersSupport": true, + "documentationFormat": [ + "markdown", + "plaintext" + ], + "deprecatedSupport": true, + "preselectSupport": true, + "tagSupport": { + "valueSet": [ + 1 + ] + }, + "insertReplaceSupport": true, + "resolveSupport": { + "properties": [ + "documentation", + "detail", + "additionalTextEdits" + ] + }, + "insertTextModeSupport": { + "valueSet": [ + 1, + 2 + ] + }, + "labelDetailsSupport": true + }, + "insertTextMode": 2, + "completionItemKind": { + "valueSet": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25 + ] + }, + "completionList": { + "itemDefaults": [ + "commitCharacters", + "editRange", + "insertTextFormat", + "insertTextMode", + "data" + ] + } + }, + "hover": { + "dynamicRegistration": true, + "contentFormat": [ + "markdown", + "plaintext" + ] + }, + "signatureHelp": { + "dynamicRegistration": true, + "signatureInformation": { + "documentationFormat": [ + "markdown", + "plaintext" + ], + "parameterInformation": { + "labelOffsetSupport": true + }, + "activeParameterSupport": true + }, + "contextSupport": true + }, + "definition": { + "dynamicRegistration": true, + "linkSupport": true + }, + "references": { + "dynamicRegistration": true + }, + "documentHighlight": { + "dynamicRegistration": true + }, + "documentSymbol": { + "dynamicRegistration": true, + "symbolKind": { + "valueSet": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ] + }, + "hierarchicalDocumentSymbolSupport": true, + "tagSupport": { + "valueSet": [ + 1 + ] + }, + "labelSupport": true + }, + "codeAction": { + "dynamicRegistration": true, + "isPreferredSupport": true, + "disabledSupport": true, + "dataSupport": true, + "resolveSupport": { + "properties": [ + "edit" + ] + }, + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "", + "quickfix", + "refactor", + "refactor.extract", + "refactor.inline", + "refactor.rewrite", + "source", + "source.organizeImports" + ] + } + }, + "honorsChangeAnnotations": true + }, + "codeLens": { + "dynamicRegistration": true + }, + "formatting": { + "dynamicRegistration": true + }, + "rangeFormatting": { + "dynamicRegistration": true, + "rangesSupport": true + }, + "onTypeFormatting": { + "dynamicRegistration": true + }, + "rename": { + "dynamicRegistration": true, + "prepareSupport": true, + "prepareSupportDefaultBehavior": 1, + "honorsChangeAnnotations": true + }, + "documentLink": { + "dynamicRegistration": true, + "tooltipSupport": true + }, + "typeDefinition": { + "dynamicRegistration": true, + "linkSupport": true + }, + "implementation": { + "dynamicRegistration": true, + "linkSupport": true + }, + "colorProvider": { + "dynamicRegistration": true + }, + "foldingRange": { + "dynamicRegistration": true, + "rangeLimit": 5000, + "lineFoldingOnly": true, + "foldingRangeKind": { + "valueSet": [ + "comment", + "imports", + "region" + ] + }, + "foldingRange": { + "collapsedText": false + } + }, + "declaration": { + "dynamicRegistration": true, + "linkSupport": true + }, + "selectionRange": { + "dynamicRegistration": true + }, + "callHierarchy": { + "dynamicRegistration": true + }, + "semanticTokens": { + "dynamicRegistration": true, + "tokenTypes": [ + "namespace", + "type", + "class", + "enum", + "interface", + "struct", + "typeParameter", + "parameter", + "variable", + "property", + "enumMember", + "event", + "function", + "method", + "macro", + "keyword", + "modifier", + "comment", + "string", + "number", + "regexp", + "operator", + "decorator" + ], + "tokenModifiers": [ + "declaration", + "definition", + "readonly", + "static", + "deprecated", + "abstract", + "async", + "modification", + "documentation", + "defaultLibrary" + ], + "formats": [ + "relative" + ], + "requests": { + "range": true, + "full": { + "delta": true + } + }, + "multilineTokenSupport": false, + "overlappingTokenSupport": false, + "serverCancelSupport": true, + "augmentsSyntaxTokens": true + }, + "linkedEditingRange": { + "dynamicRegistration": true + }, + "typeHierarchy": { + "dynamicRegistration": true + }, + "inlineValue": { + "dynamicRegistration": true + }, + "inlayHint": { + "dynamicRegistration": true, + "resolveSupport": { + "properties": [ + "tooltip", + "textEdits", + "label.tooltip", + "label.location", + "label.command" + ] + } + }, + "diagnostic": { + "dynamicRegistration": true, + "relatedDocumentSupport": false + } + }, + "window": { + "showMessage": { + "messageActionItem": { + "additionalPropertiesSupport": true + } + }, + "showDocument": { + "support": true + }, + "workDoneProgress": true + }, + "general": { + "staleRequestSupport": { + "cancel": true, + "retryOnContentModified": [ + "textDocument/semanticTokens/full", + "textDocument/semanticTokens/range", + "textDocument/semanticTokens/full/delta" + ] + }, + "regularExpressions": { + "engine": "ECMAScript", + "version": "ES2020" + }, + "markdown": { + "parser": "marked", + "version": "1.1.0" + }, + "positionEncodings": [ + "utf-16" + ] + }, + "notebookDocument": { + "synchronization": { + "dynamicRegistration": true, + "executionSummarySupport": true + } + } + }, + "trace": "verbose", + "workspaceFolders": [ + { + "uri": "file:///home/ykiko/C%2B%2B/module", + "name": "module" + } + ] +} \ No newline at end of file diff --git a/unittests/Basic/SourceConverter.cpp b/unittests/Basic/SourceConverter.cpp index 70854461..9e5702ed 100644 --- a/unittests/Basic/SourceConverter.cpp +++ b/unittests/Basic/SourceConverter.cpp @@ -1,4 +1,4 @@ -#include "../Test.h" +#include "Test/Test.h" #include "Basic/SourceConverter.h" namespace clice { diff --git a/unittests/Compiler/Command.cpp b/unittests/Compiler/Command.cpp index 77c1c4d1..33bccb14 100644 --- a/unittests/Compiler/Command.cpp +++ b/unittests/Compiler/Command.cpp @@ -1,4 +1,4 @@ -#include "../Test.h" +#include "Test/Test.h" #include "Compiler/Command.h" diff --git a/unittests/Compiler/Compiler.cpp b/unittests/Compiler/Compiler.cpp index 384d9c95..bdf0bbf3 100644 --- a/unittests/Compiler/Compiler.cpp +++ b/unittests/Compiler/Compiler.cpp @@ -1,4 +1,4 @@ -#include "../Test.h" +#include "Test/Test.h" #include "Compiler/Compiler.h" #include diff --git a/unittests/Compiler/Diagnostic.cpp b/unittests/Compiler/Diagnostic.cpp index 56003fde..2d9fd9c4 100644 --- a/unittests/Compiler/Diagnostic.cpp +++ b/unittests/Compiler/Diagnostic.cpp @@ -1,4 +1,4 @@ -#include "../Test.h" +#include "Test/Test.h" #include #include diff --git a/unittests/Compiler/Directive.cpp b/unittests/Compiler/Directive.cpp index b0dbab3b..e8144d32 100644 --- a/unittests/Compiler/Directive.cpp +++ b/unittests/Compiler/Directive.cpp @@ -1,4 +1,4 @@ -#include "../Test.h" +#include "Test/Test.h" #include "Compiler/Compiler.h" namespace clice { diff --git a/unittests/Compiler/Resolver.cpp b/unittests/Compiler/Resolver.cpp index 2d23b089..7afcfd23 100644 --- a/unittests/Compiler/Resolver.cpp +++ b/unittests/Compiler/Resolver.cpp @@ -1,4 +1,4 @@ -#include "../Test.h" +#include "Test/Test.h" #include #include diff --git a/unittests/Compiler/SemanticVisitor.cpp b/unittests/Compiler/SemanticVisitor.cpp index a7d79367..9d32cd21 100644 --- a/unittests/Compiler/SemanticVisitor.cpp +++ b/unittests/Compiler/SemanticVisitor.cpp @@ -1,6 +1,6 @@ #include "Compiler/Semantic.h" -#include "../Test.h" +#include "Test/Test.h" namespace clice { diff --git a/unittests/Feature/CodeCompletion.cpp b/unittests/Feature/CodeCompletion.cpp index d341d8ec..a58d027b 100644 --- a/unittests/Feature/CodeCompletion.cpp +++ b/unittests/Feature/CodeCompletion.cpp @@ -1,4 +1,4 @@ -#include "../Test.h" +#include "Test/Test.h" #include "Feature/CodeCompletion.h" namespace clice { diff --git a/unittests/Feature/FoldingRange.cpp b/unittests/Feature/FoldingRange.cpp index a5a172d5..64840547 100644 --- a/unittests/Feature/FoldingRange.cpp +++ b/unittests/Feature/FoldingRange.cpp @@ -1,7 +1,7 @@ #include #include -#include "../Test.h" +#include "Test/Test.h" namespace clice { diff --git a/unittests/Index/IndexTester.h b/unittests/Index/IndexTester.h index 4343120d..94b2d8de 100644 --- a/unittests/Index/IndexTester.h +++ b/unittests/Index/IndexTester.h @@ -1,4 +1,4 @@ -#include "../Test.h" +#include "Test/Test.h" #include "Index/SymbolIndex.h" #include "Basic/SourceConverter.h" diff --git a/unittests/Server/Async.cpp b/unittests/Server/Async.cpp index d89ceaf2..88bcec41 100644 --- a/unittests/Server/Async.cpp +++ b/unittests/Server/Async.cpp @@ -1,4 +1,4 @@ -#include "../Test.h" +#include "Test/Test.h" #include "Server/Async.h" namespace clice { diff --git a/unittests/Support/Enum.cpp b/unittests/Support/Enum.cpp index dd370a95..c93a7a8b 100644 --- a/unittests/Support/Enum.cpp +++ b/unittests/Support/Enum.cpp @@ -1,5 +1,6 @@ #include "gtest/gtest.h" #include "Support/Enum.h" +#include "Support/JSON.h" namespace clice { @@ -38,6 +39,16 @@ TEST(Support, NormalEnum) { 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); } struct Mask : refl::Enum { @@ -82,6 +93,51 @@ 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 { + using Enum::Enum; + + constexpr inline static std::string_view A = "A"; + constexpr inline static std::string_view B = "B"; + constexpr inline static std::string_view C = "C"; + constexpr inline static std::string_view D = "D"; + + constexpr inline static std::array All = {A, B, C, D}; +}; + +TEST(Support, StringEnum) { + constexpr StringEnum a = StringEnum::A; + constexpr StringEnum b = StringEnum::B; + constexpr StringEnum c = StringEnum::C; + constexpr StringEnum d = StringEnum::D; + + static_assert(a.value() == "A"); + static_assert(b.value() == "B"); + static_assert(c.value() == "C"); + 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 diff --git a/unittests/Support/Struct.cpp b/unittests/Support/Struct.cpp index 7ac81319..cbff9cf2 100644 --- a/unittests/Support/Struct.cpp +++ b/unittests/Support/Struct.cpp @@ -1,5 +1,6 @@ #include "gtest/gtest.h" #include "Support/Struct.h" +#include "Support/JSON.h" namespace clice { @@ -8,6 +9,8 @@ namespace { struct X { int x; int y; + + friend constexpr bool operator== (const X& lhs, const X& rhs) noexcept = default; }; static_assert(std::is_same_v, type_list>); @@ -27,11 +30,23 @@ TEST(Support, Struct) { }); EXPECT_TRUE(x && y); - struct X x1 = {1, 2}; - struct X x2 = {3, 4}; + X x1 = {1, 2}; + X x2 = {3, 4}; 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) { @@ -57,6 +72,30 @@ TEST(Support, Inheritance) { } }); EXPECT_TRUE(x && y && z); + + Y y1 = {1, 2, 3}; + Y y2 = {4, 5, 6}; + EXPECT_TRUE(refl::foreach(y1, y2, [](auto& lhs, auto& rhs) { return lhs = rhs; })); + 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); + + auto j2 = json::Value(json::Object{ + {"x", 4}, + {"y", 5}, + {"z", 6}, + }); + auto y3 = json::deserialize(j2); + EXPECT_EQ(y2.x, y3.x); + EXPECT_EQ(y2.y, y3.y); + EXPECT_EQ(y2.z, y3.z); } } // namespace