FileIndex and MergedIndex for clice (#271)

This commit is contained in:
ykiko
2025-10-05 22:46:32 +08:00
committed by GitHub
parent 4a184a26ce
commit 49caa2d9ba
22 changed files with 781 additions and 813 deletions

View File

@@ -1,4 +1,5 @@
# .pre-commit-config.yaml
exclude: "\\.patch$"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks

View File

@@ -1,236 +1,101 @@
cmake_minimum_required(VERSION 3.20)
project(CLICE_PROJECT LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
include(cmake/llvm_setup.cmake)
setup_llvm()
get_filename_component(LLVM_INSTALL_PATH "${LLVM_INSTALL_PATH}" ABSOLUTE)
if(NOT EXISTS "${LLVM_INSTALL_PATH}")
message(FATAL_ERROR "Error: The specified LLVM_INSTALL_PATH does not exist: ${LLVM_INSTALL_PATH}")
endif()
message(STATUS "Found llvm-libs ${LLVM_INSTALL_PATH}")
if (EXISTS "${LLVM_CMAKE_DIR}")
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
else()
list(APPEND CMAKE_PREFIX_PATH "${LLVM_INSTALL_PATH}")
endif()
message(STATUS "CMake module path: ${CMAKE_MODULE_PATH}")
message(STATUS "CMake prefix path: ${CMAKE_PREFIX_PATH}")
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
# Make sure all third libraries are affected by ABI related options
if(CLICE_USE_LIBCXX)
string(APPEND CMAKE_CXX_FLAGS "-stdlib=libc++")
string(APPEND CMAKE_EXE_LINKER_FLAGS "-stdlib=libc++")
string(APPEND CMAKE_SHARED_LINKER_FLAGS "-stdlib=libc++")
string(APPEND CMAKE_MODULE_LINKER_FLAGS "-stdlib=libc++")
string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -stdlib=libc++")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -stdlib=libc++")
endif()
message(STATUS "Installing other dependencies, please wait...")
# install dependencies
include(FetchContent)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
# libuv option
set(ASAN ON CACHE BOOL "" FORCE)
if(NOT WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
string(APPEND CMAKE_CXX_FLAGS " -fsanitize=address")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -fsanitize=address")
endif()
message(STATUS "Fetching libuv...")
FetchContent_Declare(
libuv
GIT_REPOSITORY https://github.com/libuv/libuv.git
GIT_TAG v1.x
)
include("${PROJECT_SOURCE_DIR}/cmake/package.cmake")
set(BUILD_SHARED_LIBS OFF)
message(STATUS "Fetching spdlog...")
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.15.3
)
add_library(clice_options INTERFACE)
message(STATUS "Fetching tomlplusplus...")
FetchContent_Declare(
tomlplusplus
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
)
FetchContent_MakeAvailable(libuv spdlog tomlplusplus)
message(STATUS "Found tomlplusplus: ${tomlplusplus_SOURCE_DIR}")
message(STATUS "Found libuv: ${libuv_SOURCE_DIR}")
message(STATUS "Found spdlog: ${spdlog_SOURCE_DIR}")
if(CLICE_CI_ENVIRONMENT)
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT)
endif()
if(WIN32)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS)
target_link_options(clice_options INTERFACE -fuse-ld=lld-link)
else()
target_link_options(clice_options INTERFACE -fuse-ld=lld)
endif()
target_compile_definitions(spdlog PUBLIC SPDLOG_USE_STD_FORMAT=1 SPDLOG_NO_EXCEPTIONS=1)
if (MSVC)
# Remove any RTTI or exception enabling flags from CMAKE_CXX_FLAGS
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
# Fix MSVC Non-standard preprocessor caused error C1189
# While compiling Command.cpp, MSVC won't expand Options macro correctly
# Output: D:\Desktop\code\clice\build\.packages\l\llvm\20.1.5\cc2aa9f1d09a4b71b6fa3bf0011f6387\include\clang/Driver/Options.inc(3590): error C2365: “clang::driver::options::OPT_”: redefinition; previous definition was 'enumerator'
set(CLICE_CXX_FLAGS "/GR-;/EHsc-;/Zc:preprocessor;")
target_compile_options(clice_options INTERFACE
/GR-
/EHsc-
/Zc:preprocessor
)
else()
set(CLICE_CXX_FLAGS "-fno-rtti;-fno-exceptions;-Wno-deprecated-declarations;-Wno-undefined-inline;")
target_compile_options(clice_options INTERFACE
-fno-rtti
-fno-exceptions
-Wno-deprecated-declarations
-Wno-undefined-inline
)
endif()
set(CLICE_LINKER_FLAGS "")
# Build-type specific flags
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Debug build: Enabling -g -O0 -fsanitize=address")
list(APPEND CLICE_CXX_FLAGS "-g;-O0;-fsanitize=address")
list(APPEND CLICE_LINKER_FLAGS "-fsanitize=address")
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
if(NOT MSVC)
# MSVC only supports ASAN
message(STATUS "RelWithDebInfo build: Enabling -fsanitize=thread")
list(APPEND CLICE_CXX_FLAGS "-fsanitize=thread")
list(APPEND CLICE_LINKER_FLAGS "-fsanitize=thread")
endif()
else()
message(STATUS "Release/Default build: Enabling -O3")
list(APPEND CLICE_CXX_FLAGS "-O3")
endif()
if(CLICE_CI_ENVIRONMENT)
list(APPEND CLICE_CXX_FLAGS "-DCLICE_CI_ENVIRONMENT")
endif()
# Linker flags for LLD
if(WIN32)
list(APPEND CLICE_LINKER_FLAGS "-fuse-ld=lld-link")
else()
list(APPEND CLICE_LINKER_FLAGS "-fuse-ld=lld")
endif()
# build clice core part as library
file(GLOB_RECURSE CLICE_SOURCES CONFIGURE_DEPENDS
"${CMAKE_SOURCE_DIR}/src/AST/*.cpp"
"${CMAKE_SOURCE_DIR}/src/Async/*.cpp"
"${CMAKE_SOURCE_DIR}/src/Basic/*.cpp"
"${CMAKE_SOURCE_DIR}/src/Compiler/*.cpp"
"${CMAKE_SOURCE_DIR}/src/Index/*.cpp"
"${CMAKE_SOURCE_DIR}/src/Feature/*.cpp"
"${CMAKE_SOURCE_DIR}/src/Server/*.cpp"
"${CMAKE_SOURCE_DIR}/src/Support/*.cpp"
"${PROJECT_SOURCE_DIR}/src/AST/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Async/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Basic/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Compiler/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Index/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Feature/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Server/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Support/*.cpp"
)
add_library(clice-core STATIC "${CLICE_SOURCES}")
# set llvm include and lib path
add_library(llvm-libs INTERFACE IMPORTED)
message(STATUS "LLVM include path: ${LLVM_INSTALL_PATH}/include")
# add to include directories
target_include_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/include")
if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/include")
# private files are downloaded here
message(STATUS "Fetched private headers to include: ${CMAKE_CURRENT_BINARY_DIR}/include")
target_include_directories(llvm-libs INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/include")
endif()
if(WIN32)
target_compile_definitions(llvm-libs INTERFACE "CLANG_BUILD_STATIC")
target_link_libraries(llvm-libs INTERFACE version ntdll)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_link_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/lib")
target_link_libraries(llvm-libs INTERFACE
LLVMSupport
LLVMFrontendOpenMP
LLVMOption
clangAST
clangASTMatchers
clangBasic
clangDependencyScanning
clangDriver
clangFormat
clangFrontend
clangIndex
clangLex
clangSema
clangSerialization
clangTidy
clangTidyUtils
# ALL_CLANG_TIDY_CHECKS
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyAlteraModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule
clangTidyConcurrencyModule
clangTidyCppCoreGuidelinesModule
clangTidyDarwinModule
clangTidyFuchsiaModule
clangTidyGoogleModule
clangTidyHICPPModule
clangTidyLinuxKernelModule
clangTidyLLVMModule
clangTidyLLVMLibcModule
clangTidyMiscModule
clangTidyModernizeModule
clangTidyObjCModule
clangTidyOpenMPModule
clangTidyPerformanceModule
clangTidyPortabilityModule
clangTidyReadabilityModule
clangTidyZirconModule
clangTooling
clangToolingCore
clangToolingInclusions
clangToolingInclusionsStdlib
clangToolingSyntax
)
else()
file(GLOB LLVM_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/*${CMAKE_STATIC_LIBRARY_SUFFIX}")
target_link_libraries(llvm-libs INTERFACE ${LLVM_LIBRARIES})
endif()
target_compile_options(clice-core PUBLIC ${CLICE_CXX_FLAGS})
target_link_options(clice-core PUBLIC ${CLICE_LINKER_FLAGS})
target_include_directories(clice-core PUBLIC "${CMAKE_SOURCE_DIR}/include")
target_link_libraries(clice-core PUBLIC uv_a spdlog::spdlog tomlplusplus::tomlplusplus llvm-libs)
target_compile_definitions(clice-core PUBLIC TOML_EXCEPTIONS=0)
# clice executable
add_executable(clice "${CMAKE_SOURCE_DIR}/src/Driver/clice.cc")
target_link_libraries(clice PRIVATE clice-core)
target_compile_options(clice PUBLIC ${CLICE_CXX_FLAGS})
target_link_options(clice PUBLIC ${CLICE_LINKER_FLAGS})
# install clice executable
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# copy resource dir
message(STATUS "Copying resource directory")
file(COPY
${LLVM_INSTALL_PATH}/lib/clang/20/
DESTINATION ${PROJECT_BINARY_DIR}/lib/clang/20
target_include_directories(clice-core PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(clice-core PUBLIC
clice_options
uv_a
spdlog::spdlog
tomlplusplus::tomlplusplus
roaring::roaring
llvm-libs
)
# clice tests
if(CLICE_ENABLE_TEST)
file(GLOB_RECURSE CLICE_TEST_SOURCES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/tests/unit/*/*.cpp")
add_executable(unit_tests "${CLICE_TEST_SOURCES}" "${CMAKE_SOURCE_DIR}/src/Driver/unit_tests.cc")
target_include_directories(unit_tests PUBLIC "${CMAKE_SOURCE_DIR}")
add_executable(clice "${PROJECT_SOURCE_DIR}/bin/clice.cc")
target_link_libraries(clice PRIVATE clice-core)
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
message(STATUS "Copying resource directory for development build")
file(COPY
"${LLVM_INSTALL_PATH}/lib/clang/20/"
DESTINATION "${PROJECT_BINARY_DIR}/lib/clang/20"
)
install(DIRECTORY "${LLVM_INSTALL_PATH}/lib/clang/20/"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/clang/20"
)
if(CLICE_ENABLE_TEST)
file(GLOB_RECURSE CLICE_TEST_SOURCES CONFIGURE_DEPENDS
"${PROJECT_SOURCE_DIR}/tests/unit/*/*.cpp")
add_executable(unit_tests
"${CLICE_TEST_SOURCES}"
"${PROJECT_SOURCE_DIR}/bin/unit_tests.cc"
)
target_include_directories(unit_tests PUBLIC "${PROJECT_SOURCE_DIR}")
target_link_libraries(unit_tests PRIVATE clice-core)
target_compile_options(unit_tests PUBLIC ${CLICE_CXX_FLAGS})
target_link_options(unit_tests PUBLIC ${CLICE_LINKER_FLAGS})
endif()

30
cmake/croaring-fix.patch Normal file
View File

@@ -0,0 +1,30 @@
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index addff26d..72d4b2f8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -90,7 +90,7 @@ foreach(tree_type BUILD INSTALL)
"${build_location}/roaring-config-version.cmake"
VERSION ${ROARING_LIB_VERSION}
COMPATIBILITY SameMajorVersion)
- configure_package_config_file("${CMAKE_SOURCE_DIR}/cmake/config.cmake.in"
+ configure_package_config_file("${PROJECT_SOURCE_DIR}/cmake/config.cmake.in"
"${build_location}/roaring-config.cmake"
INSTALL_DESTINATION "${install_location}")
diff --git a/tools/cmake/FindOptions.cmake b/tools/cmake/FindOptions.cmake
index 69b40ed8..55cbece4 100644
--- a/tools/cmake/FindOptions.cmake
+++ b/tools/cmake/FindOptions.cmake
@@ -38,10 +38,11 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXXSTD_FLAGS} ${OPT_FLAGS} ${INCLUDE_F
if(MSVC)
add_definitions( "/W3 /D_CRT_SECURE_NO_WARNINGS /wd4005 /wd4996 /wd4267 /wd4244 /wd4113 /nologo")
-endif()
if(MSVC_VERSION GREATER 1910)
add_definitions("/permissive-")
endif()
+endif()
+
if(ROARING_LINK_STATIC)
if(NOT MSVC)
set(CMAKE_EXE_LINKER_FLAGS "-static")

142
cmake/package.cmake Normal file
View File

@@ -0,0 +1,142 @@
include_guard()
include(${CMAKE_CURRENT_LIST_DIR}/llvm_setup.cmake)
setup_llvm()
get_filename_component(LLVM_INSTALL_PATH "${LLVM_INSTALL_PATH}" ABSOLUTE)
if(NOT EXISTS "${LLVM_INSTALL_PATH}")
message(FATAL_ERROR "Error: The specified LLVM_INSTALL_PATH does not exist: ${LLVM_INSTALL_PATH}")
endif()
# set llvm include and lib path
add_library(llvm-libs INTERFACE IMPORTED)
message(STATUS "LLVM include path: ${LLVM_INSTALL_PATH}/include")
# add to include directories
target_include_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/include")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_link_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/lib")
target_link_libraries(llvm-libs INTERFACE
LLVMSupport
LLVMFrontendOpenMP
LLVMOption
clangAST
clangASTMatchers
clangBasic
clangDependencyScanning
clangDriver
clangFormat
clangFrontend
clangIndex
clangLex
clangSema
clangSerialization
clangTidy
clangTidyUtils
# ALL_CLANG_TIDY_CHECKS
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyAlteraModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule
clangTidyConcurrencyModule
clangTidyCppCoreGuidelinesModule
clangTidyDarwinModule
clangTidyFuchsiaModule
clangTidyGoogleModule
clangTidyHICPPModule
clangTidyLinuxKernelModule
clangTidyLLVMModule
clangTidyLLVMLibcModule
clangTidyMiscModule
clangTidyModernizeModule
clangTidyObjCModule
clangTidyOpenMPModule
clangTidyPerformanceModule
clangTidyPortabilityModule
clangTidyReadabilityModule
clangTidyZirconModule
clangTooling
clangToolingCore
clangToolingInclusions
clangToolingInclusionsStdlib
clangToolingSyntax
)
else()
file(GLOB LLVM_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/*${CMAKE_STATIC_LIBRARY_SUFFIX}")
target_link_libraries(llvm-libs INTERFACE ${LLVM_LIBRARIES})
endif()
if(WIN32)
target_compile_definitions(llvm-libs INTERFACE "CLANG_BUILD_STATIC")
target_link_libraries(llvm-libs INTERFACE version ntdll)
endif()
# install dependencies
include(FetchContent)
if(WIN32)
set(NULL_DEVICE NUL)
else()
set(NULL_DEVICE /dev/null)
endif()
# libuv
FetchContent_Declare(
libuv
GIT_REPOSITORY https://github.com/libuv/libuv.git
GIT_TAG v1.x
)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set(ASAN ON CACHE BOOL "Enable AddressSanitizer for libuv" FORCE)
endif()
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build dependencies as static libs")
# spdlog
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.15.3
)
# tomlplusplus
FetchContent_Declare(
tomlplusplus
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
)
# croaring
FetchContent_Declare(
croaring
GIT_REPOSITORY https://github.com/RoaringBitmap/CRoaring.git
GIT_TAG v4.4.0
# Workaround for https://github.com/RoaringBitmap/CRoaring/pull/750
PATCH_COMMAND git apply --reverse --check ${PROJECT_SOURCE_DIR}/cmake/croaring-fix.patch 2> ${NULL_DEVICE}
|| git apply ${PROJECT_SOURCE_DIR}/cmake/croaring-fix.patch
)
set(ENABLE_ROARING_TESTS OFF CACHE INTERNAL "")
set(CMAKE_MODULE_PATH "")
FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring)
if(WIN32)
target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS)
endif()
if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND TARGET uv_a)
target_compile_options(uv_a PRIVATE
"-Wno-unused-function"
"-Wno-unused-variable"
"-Wno-unused-but-set-variable"
"-Wno-deprecated-declarations"
"-Wno-missing-braces"
)
endif()
target_compile_definitions(spdlog PUBLIC SPDLOG_USE_STD_FORMAT=1 SPDLOG_NO_EXCEPTIONS=1)

View File

@@ -75,8 +75,10 @@ public:
}
void handleAttrOccurrence(const clang::Attr* attr, clang::SourceRange range) {
assert(attr && "Invalid attribute");
assert(range.isValid() && "Invalid range");
if(!attr || range.isInvalid()) {
return;
}
if constexpr(!std::same_as<decltype(&SemanticVisitor::handleAttrOccurrence),
decltype(&Derived::handleAttrOccurrence)>) {
getDerived().handleAttrOccurrence(attr, range);
@@ -633,6 +635,7 @@ public:
}
bool VisitAttr(clang::Attr* attr) {
getDerived().handleAttrOccurrence(attr, attr->getRange());
return true;
}

View File

@@ -1,41 +0,0 @@
#pragma once
#include <vector>
#include "Shared.h"
#include "Feature/SemanticToken.h"
#include "Feature/FoldingRange.h"
#include "Feature/DocumentLink.h"
#include "Feature/DocumentSymbol.h"
#include "llvm/ADT/DenseMap.h"
#include "clang/Basic/SourceLocation.h"
namespace clice::index {
class FeatureIndex {
public:
FeatureIndex(char* base, std::size_t size) : base(base), size(size) {}
/// The path of source file.
llvm::StringRef path();
/// The content of source file.
llvm::StringRef content();
feature::SemanticTokens semanticTokens() const;
feature::FoldingRanges foldingRanges() const;
feature::DocumentLinks documentLinks() const;
feature::DocumentSymbols documentSymbols() const;
static Shared<std::vector<char>> build(CompilationUnit& unit);
public:
char* base;
std::size_t size;
};
} // namespace clice::index

View File

@@ -1,22 +0,0 @@
#pragma once
#include <deque>
#include <bitset>
#include <vector>
#include <variant>
#include "TUIndex.h"
#include "RawIndex.h"
#include "HeaderIndex.h"
#include "IncludeGraph.h"
namespace clice::index::memory {
struct Indices {
std::unique_ptr<TUIndex> tu_index;
llvm::DenseMap<clang::FileID, std::unique_ptr<RawIndex>> header_indices;
};
Indices index(CompilationUnit& unit);
} // namespace clice::index::memory

View File

@@ -1,91 +0,0 @@
#pragma once
#include <cstdint>
#include <iterator>
#include <ranges>
#include <algorithm>
namespace clice::index {
/// When serialize index to binary, we will transform all pointer to offset
/// to base address. And data only will be deserialized when it is accessed.
struct Relative {
const void* base = nullptr;
const void* data = nullptr;
bool operator== (const Relative& other) const = default;
};
template <typename T>
class LazyArray : Relative {
public:
LazyArray(const void* base, const void* data, std::uint32_t size, std::uint32_t stride) :
Relative{base, data}, size(size), stride(stride) {}
using difference_type = std::ptrdiff_t;
using value_type = T;
class Iterator : Relative {
public:
Iterator(const void* base, const void* data, std::size_t stride) :
Relative{base, data}, stride(stride) {}
using difference_type = std::ptrdiff_t;
using value_type = T;
Iterator() = default;
Iterator(const Iterator&) = default;
decltype(auto) operator* () const {
if constexpr(std::derived_from<T, Relative>) {
return T{base, data};
} else {
return *static_cast<const T*>(data);
}
}
Iterator& operator++ () {
data = static_cast<const char*>(data) + stride;
return *this;
}
Iterator operator++ (int) {
++*this;
}
bool operator== (const Iterator& other) const = default;
private:
std::uint32_t stride;
};
Iterator begin() const {
return Iterator(base, data, stride);
}
Iterator end() const {
return Iterator(base, static_cast<const char*>(data) + size * stride, stride);
}
uint32_t length() const {
return size;
}
decltype(auto) operator[] (uint32_t index) const {
const void* data = static_cast<const char*>(this->data) + index * stride;
if constexpr(std::derived_from<T, Relative>) {
return T{base, data};
} else {
return *static_cast<const T*>(data);
}
}
bool operator== (const LazyArray& other) const = default;
private:
std::uint32_t size;
std::uint32_t stride;
};
} // namespace clice::index

138
include/Index/MergedIndex.h Normal file
View File

@@ -0,0 +1,138 @@
#pragma once
#include "TUIndex.h"
#define ROARING_EXCEPTIONS 0
#define ROARING_TERMINATE(message) std::abort()
#include "roaring/roaring.hh"
#include "llvm/Support/Allocator.h"
namespace clice::index {
/// struct CompilationContext {
/// /// The target of this compilation.
/// llvm::StringRef target;
///
/// /// The canonical compilation command.
/// llvm::StringRef command;
///
/// /// A version field for verification.
/// std::uint32_t version;
/// };
///
/// struct HeaderContext : CompilationContext {
/// /// The include location in the include graph.
/// std::uint32_t include;
///
/// /// The path of the file includes this header.
/// llvm::StringRef path;
/// };
struct HeaderContexts {
std::uint32_t version = 0;
using Context = std::pair<std::uint32_t, std::uint32_t>;
/// A array of include location and its context id.
llvm::SmallVector<Context> includes;
};
struct MergedIndex {
/// For each merged index, we will give it a canonical id.
/// The max canonical id.
std::uint32_t max_canonical_id = 0;
/// We use the value of SHA256 to judge whether two indices are same.
/// Index with same content will be given same canonical id.
llvm::DenseMap<llvm::StringRef, std::uint32_t> canonical_cache;
/// The allocator for storing sha256 hash.
llvm::BumpPtrAllocator allocator;
/// The reference count of each canonical id.
std::vector<std::uint32_t> canonical_ref_counts;
/// The canonical id set of removed index.
roaring::Roaring removed;
/// A map between source file path and its header contexts.
llvm::StringMap<HeaderContexts> contexts;
/// All merged symbol relations.
llvm::DenseMap<SymbolHash, llvm::DenseMap<Relation, roaring::Roaring>> relations;
/// All merged symbol occurrences.
llvm::DenseMap<Occurrence, roaring::Roaring> occurrences;
void remove(llvm::StringRef path);
void merge(llvm::StringRef path, std::uint32_t include, FileIndex& index);
};
} // namespace clice::index
namespace llvm {
template <typename... Ts>
unsigned dense_hash(const Ts&... ts) {
return llvm::DenseMapInfo<std::tuple<Ts...>>::getHashValue(std::tuple{ts...});
}
template <>
struct DenseMapInfo<clice::index::Occurrence> {
using R = clice::LocalSourceRange;
using V = clice::index::Occurrence;
inline static V getEmptyKey() {
return V(R(-1, 0), 0);
}
inline static V getTombstoneKey() {
return V(R(-2, 0), 0);
}
static auto getHashValue(const V& v) {
return dense_hash(v.range.begin, v.range.end, v.target);
}
static bool isEqual(const V& lhs, const V& rhs) {
return lhs.range == rhs.range && lhs.target == rhs.target;
}
};
template <>
struct DenseMapInfo<clice::index::Relation> {
using R = clice::index::Relation;
inline static R getEmptyKey() {
return R{
.kind = clice::RelationKind(),
.range = clice::LocalSourceRange(-1, 0),
.target_symbol = 0,
};
}
inline static R getTombstoneKey() {
return R{
.kind = clice::RelationKind(),
.range = clice::LocalSourceRange(-2, 0),
.target_symbol = 0,
};
}
/// Contextual doen't take part in hashing and equality.
static auto getHashValue(const R& relation) {
return dense_hash(relation.kind.value(),
relation.range.begin,
relation.range.end,
relation.target_symbol);
}
static bool isEqual(const R& lhs, const R& rhs) {
return lhs.kind == rhs.kind && lhs.range == rhs.range &&
lhs.target_symbol == rhs.target_symbol;
}
};
} // namespace llvm

View File

@@ -1,85 +0,0 @@
#pragma once
#include "Shared.h"
#include "LazyArray.h"
#include "AST/SymbolID.h"
#include "AST/SourceCode.h"
#include "AST/SymbolKind.h"
#include "AST/RelationKind.h"
#include "Support/JSON.h"
namespace clice::index {
struct Symbol;
struct Relation : Relative {
/// Return the relation kind.
RelationKind kind() const;
/// Return the range of relation.
LocalSourceRange range() const;
/// Return the definition range.
LocalSourceRange sourceRange() const;
/// The target symbol.
Symbol target() const;
};
struct Symbol : Relative {
/// Return the symbol id.
SymbolID id() const;
/// Return the hash value of symbol id.
std::uint64_t hash() const;
/// Return the symbol name.
llvm::StringRef name() const;
/// Return the symbol kind.
SymbolKind kind() const;
/// Return all relations of this symbol.
LazyArray<Relation> relations() const;
};
struct Occurrence : Relative {
/// The source range of occurrence.
LocalSourceRange range() const;
/// The target symbol of occurrence.
Symbol symbol() const;
};
class SymbolIndex {
public:
SymbolIndex(const char* data, std::uint32_t size) : data(data), size(size) {}
/// The path of source file.
llvm::StringRef path() const;
/// The content of source file.
llvm::StringRef content() const;
/// All symbols in the index.
LazyArray<Symbol> symbols() const;
/// All occurrences in the index.
LazyArray<Occurrence> occurrences() const;
/// Locate the symbols with given offset.
std::vector<Symbol> locateSymbol(uint32_t offset) const;
/// Locate the symbol with given symbol id.
std::optional<Symbol> locateSymbol(const SymbolID& id) const;
static Shared<std::vector<char>> build(CompilationUnit& unit);
json::Value toJSON(bool line = true);
private:
const char* data;
std::uint32_t size;
};
} // namespace clice::index

View File

@@ -1,19 +1,56 @@
#pragma once
#include "RawIndex.h"
#include "IncludeGraph.h"
#include "AST/SourceCode.h"
#include "AST/RelationKind.h"
namespace clice::index::memory {
namespace clice::index {
class TUIndex : public RawIndex {
public:
using Range = LocalSourceRange;
using SymbolHash = std::uint64_t;
public:
/// The time of building this index.
std::int64_t time;
struct Relation {
RelationKind kind;
/// The include graph of this index.
IncludeGraph graph;
char padding[4] = {0, 0, 0, 0};
LocalSourceRange range;
union {
LocalSourceRange definition_range;
SymbolHash target_symbol;
};
};
} // namespace clice::index::memory
struct Occurrence {
/// range of this occurrence.
Range range;
///
SymbolHash target;
};
struct FileIndex {
llvm::DenseMap<SymbolHash, std::vector<Relation>> relations;
std::vector<Occurrence> occurrences;
};
struct Symbol {
std::string name;
/// ...
};
struct TUIndex {
IncludeGraph graph;
llvm::DenseMap<SymbolHash, Symbol> symbols;
llvm::DenseMap<clang::FileID, FileIndex> file_indices;
static TUIndex build(CompilationUnit& unit);
};
} // namespace clice::index

View File

@@ -13,65 +13,6 @@ namespace clice {
class CompilationUnit;
class Indexer {
public:
/// Index an opened file, its AST is already builtin
/// and PCH is used for it.
async::Task<> index(CompilationUnit& unit);
/// Index an static file.
async::Task<> index(llvm::StringRef file);
using Path = std::string;
using PathID = std::uint32_t;
using SymbolID = std::uint64_t;
public:
Indexer(CompilationDatabase& database) : database(database) {}
PathID getPath(llvm::StringRef path) {
auto it = paths.find(path);
if(it != paths.end()) {
return it->second;
}
auto id = path_storage.size();
path_storage.emplace_back(path);
paths.try_emplace(path, id);
return id;
}
private:
struct HeaderIndices {
using RawIndex = std::pair<std::uint32_t, std::unique_ptr<index::memory::RawIndex>>;
/// The merged index.
std::unique_ptr<index::memory::HeaderIndex> merged;
llvm::DenseMap<PathID, std::vector<RawIndex>> unmergeds;
};
CompilationDatabase& database;
/// All paths of indices.
std::vector<Path> path_storage;
/// A map between path and its id.
llvm::StringMap<PathID> paths;
/// A map between symbol id and files that contains it.
llvm::DenseMap<SymbolID, llvm::DenseSet<PathID>> symbol_indices;
/// A map between source file path and its static indices.
llvm::DenseMap<PathID, Path> static_indices;
std::uint32_t unmerged_count = 0;
/// In-memory header indices.
llvm::DenseMap<PathID, std::unique_ptr<HeaderIndices>> dynamic_header_indices;
/// In-memory translation unit indices.
llvm::DenseMap<PathID, std::unique_ptr<index::memory::TUIndex>> dynamic_tu_indices;
};
class Indexer {};
} // namespace clice

View File

@@ -1,92 +0,0 @@
#include "Compiler/CompilationUnit.h"
#include "Support/Binary.h"
#include "Index/FeatureIndex.h"
namespace clice::index {
namespace memory {
struct FeatureIndex {
/// The path of source file.
std::string path;
/// The content of source file.
std::string content;
/// The index of semantic tokens.
feature::SemanticTokens tokens;
/// The index of folding ranges.
feature::FoldingRanges foldings;
/// The index of document links.
feature::DocumentLinks links;
/// The index of document symbols.
feature::DocumentSymbols symbols;
};
} // namespace memory
llvm::StringRef FeatureIndex::path() {
binary::Proxy<memory::FeatureIndex> index{base, base};
return index.get<"path">().as_string();
}
llvm::StringRef FeatureIndex::content() {
binary::Proxy<memory::FeatureIndex> index{base, base};
return index.get<"content">().as_string();
}
feature::SemanticTokens FeatureIndex::semanticTokens() const {
binary::Proxy<memory::FeatureIndex> index{base, base};
return binary::deserialize(index.get<"tokens">());
}
feature::FoldingRanges FeatureIndex::foldingRanges() const {
binary::Proxy<memory::FeatureIndex> index{base, base};
return binary::deserialize(index.get<"foldings">());
}
feature::DocumentLinks FeatureIndex::documentLinks() const {
binary::Proxy<memory::FeatureIndex> index{base, base};
return binary::deserialize(index.get<"links">());
}
feature::DocumentSymbols FeatureIndex::documentSymbols() const {
binary::Proxy<memory::FeatureIndex> index{base, base};
return binary::deserialize(index.get<"symbols">());
}
Shared<std::vector<char>> FeatureIndex::build(CompilationUnit& unit) {
Shared<memory::FeatureIndex> indices;
for(auto&& [fid, result]: feature::index_semantic_token(unit)) {
indices[fid].tokens = std::move(result);
}
for(auto&& [fid, result]: feature::index_folding_range(unit)) {
indices[fid].foldings = std::move(result);
}
for(auto&& [fid, result]: feature::index_document_link(unit)) {
indices[fid].links = std::move(result);
}
for(auto&& [fid, result]: feature::index_document_symbol(unit)) {
indices[fid].symbols = std::move(result);
}
Shared<std::vector<char>> result;
for(auto&& [fid, index]: indices) {
index.path = unit.file_path(fid);
index.content = unit.file_content(fid);
auto [buffer, _] = binary::serialize(index);
result.try_emplace(fid, std::move(buffer));
}
return result;
}
} // namespace clice::index

View File

@@ -1,145 +0,0 @@
#include "AST/Semantic.h"
#include "Index/Index.h"
#include "Index/IncludeGraph.h"
#include "Support/Format.h"
namespace clice::index::memory {
namespace {
class IndexBuilder : public Indices, public SemanticVisitor<IndexBuilder> {
public:
IndexBuilder(CompilationUnit& unit) : SemanticVisitor(unit, false) {
tu_index = std::make_unique<TUIndex>();
tu_index->path = unit.file_path(unit.interested_file());
tu_index->content = unit.file_content(unit.interested_file());
tu_index->graph = IncludeGraph::from(unit);
}
RawIndex& getIndex(clang::FileID fid) {
if(fid == unit.interested_file()) {
return *tu_index;
}
if(auto it = header_indices.find(fid); it != header_indices.end()) {
return *it->second;
}
auto [it, _] = header_indices.try_emplace(fid, new RawIndex());
auto& index = *it->second;
index.path = unit.file_path(fid);
index.content = unit.file_content(fid);
return index;
}
void handleDeclOccurrence(const clang::NamedDecl* decl,
RelationKind kind,
clang::SourceLocation location) {
assert(decl && "Invalid decl");
decl = ast::normalize(decl);
if(location.isMacroID()) {
auto spelling = unit.spelling_location(location);
auto expansion = unit.expansion_location(location);
/// FIXME: For location from macro, we only handle the case that the
/// spelling and expansion are in the same file currently.
if(unit.file_id(spelling) != unit.file_id(expansion)) {
return;
}
/// For occurrence, we always use spelling location.
location = spelling;
}
auto [fid, range] = unit.decompose_range(location);
auto& index = getIndex(fid);
auto symbol_id = unit.getSymbolID(decl);
auto& symbol = index.get_symbol(symbol_id.hash);
symbol.kind = SymbolKind::from(decl);
index.add_occurrence(range, symbol_id.hash);
}
void handleMacroOccurrence(const clang::MacroInfo* def,
RelationKind kind,
clang::SourceLocation location) {
/// FIXME: Figure out when location is MacroID.
if(location.isMacroID()) {
return;
}
auto [fid, range] = unit.decompose_range(location);
auto& index = getIndex(fid);
auto symbol_id = unit.getSymbolID(def);
auto& symbol = index.get_symbol(symbol_id.hash);
symbol.kind = SymbolKind::Macro;
symbol.name = unit.token_spelling(def->getDefinitionLoc());
index.add_occurrence(range, symbol_id.hash);
if(kind & RelationKind::Definition) {
auto begin = def->getDefinitionLoc();
auto end = def->getDefinitionEndLoc();
assert(begin.isFileID() && end.isFileID() && "Invalid location");
auto [fid2, definition_range] = unit.decompose_range(clang::SourceRange(begin, end));
assert(fid == fid2 && "Invalid macro definition location");
/// definitionLoc = builder.getLocation(range);
index.add_relation(symbol,
Relation{
.kind = RelationKind::Definition,
.range = range,
.definition_range = definition_range,
});
} else {
index.add_relation(symbol,
Relation{
.kind = RelationKind::Reference,
.range = range,
.target_symbol = 0,
});
}
}
void handleRelation(const clang::NamedDecl* decl,
RelationKind kind,
const clang::NamedDecl* target,
clang::SourceRange range) {
auto [fid, relationRange] = unit.decompose_expansion_range(range);
Relation relation{.kind = kind};
if(kind.isDeclOrDef()) {
auto [fid2, definitionRange] = unit.decompose_expansion_range(decl->getSourceRange());
assert(fid == fid2 && "Invalid definition location");
relation.range = relationRange;
relation.definition_range = definitionRange;
} else if(kind.isReference()) {
relation.range = relationRange;
relation.target_symbol = 0;
} else if(kind.isBetweenSymbol()) {
auto symbol_id = unit.getSymbolID(ast::normalize(target));
relation.target_symbol = symbol_id.hash;
} else if(kind.isCall()) {
auto symbol_id = unit.getSymbolID(ast::normalize(target));
relation.range = relationRange;
relation.target_symbol = symbol_id.hash;
} else {
std::unreachable();
}
auto& index = getIndex(fid);
auto symbol_id = unit.getSymbolID(ast::normalize(decl));
auto& symbol = index.get_symbol(symbol_id.hash);
index.add_relation(symbol, relation);
}
};
} // namespace
Indices index(CompilationUnit& unit) {
IndexBuilder builder(unit);
builder.run();
return std::move(builder);
}
} // namespace clice::index::memory

89
src/Index/MergedIndex.cpp Normal file
View File

@@ -0,0 +1,89 @@
#include "Index/MergedIndex.h"
#include "llvm/Support/SHA256.h"
namespace clice::index {
namespace {
auto sha256_hash(FileIndex& index) {
llvm::SHA256 hasher;
using u8 = std::uint8_t;
if(!index.occurrences.empty()) {
static_assert(sizeof(Occurrence) == sizeof(Range) + sizeof(SymbolHash));
static_assert(sizeof(Occurrence) % 8 == 0);
auto data = reinterpret_cast<u8*>(index.occurrences.data());
auto size = index.occurrences.size() * sizeof(Occurrence);
hasher.update(llvm::ArrayRef(data, size));
}
for(auto& [symbol_id, relations]: index.relations) {
hasher.update(std::bit_cast<std::array<u8, sizeof(symbol_id)>>(symbol_id));
static_assert(sizeof(Relation) ==
sizeof(RelationKind) + 4 + sizeof(Range) + sizeof(SymbolHash));
static_assert(sizeof(Relation) % 8 == 0);
if(!relations.empty()) {
auto data = reinterpret_cast<u8*>(relations.data());
auto size = relations.size() * sizeof(Relation);
hasher.update(llvm::ArrayRef(data, size));
}
}
return hasher.final();
}
} // namespace
void MergedIndex::remove(llvm::StringRef path) {
auto& includes = contexts[path].includes;
for(auto& [_, canonical_id]: includes) {
auto& ref_counts = canonical_ref_counts[canonical_id];
ref_counts -= 1;
if(ref_counts == 0) {
removed.add(canonical_id);
}
}
includes.clear();
}
void MergedIndex::merge(llvm::StringRef path, std::uint32_t include, FileIndex& index) {
auto& context = contexts[path];
auto hash = sha256_hash(index);
auto hash_key = llvm::StringRef(reinterpret_cast<char*>(hash.data()), hash.size());
auto [it, success] = canonical_cache.try_emplace(hash_key, max_canonical_id);
auto canonical_id = it->second;
context.includes.emplace_back(include, canonical_id);
if(!success) {
canonical_ref_counts[canonical_id] += 1;
removed.remove(canonical_id);
return;
}
auto data = allocator.Allocate<char>(32);
std::ranges::copy(hash, data);
it->first = llvm::StringRef(data, hash.size());
for(auto& occurrence: index.occurrences) {
this->occurrences[occurrence].add(canonical_id);
}
for(auto& [symbol_id, relations]: index.relations) {
auto& target = this->relations[symbol_id];
for(auto& relation: relations) {
target[relation].add(canonical_id);
}
}
canonical_ref_counts.emplace_back(1);
max_canonical_id += 1;
}
} // namespace clice::index

119
src/Index/TUIndex.cpp Normal file
View File

@@ -0,0 +1,119 @@
#include "AST/Semantic.h"
#include "Index/TUIndex.h"
#include "Support/Compare.h"
namespace clice::index {
namespace {
class Builder : public SemanticVisitor<Builder> {
public:
Builder(TUIndex& result, CompilationUnit& unit) :
SemanticVisitor<Builder>(unit, false), result(result) {
result.graph = IncludeGraph::from(unit);
}
void handleDeclOccurrence(const clang::NamedDecl* decl,
RelationKind kind,
clang::SourceLocation location) {
assert(decl && "Invalid decl");
decl = ast::normalize(decl);
if(location.isMacroID()) {
auto spelling = unit.spelling_location(location);
auto expansion = unit.expansion_location(location);
/// FIXME: For location from macro, we only handle the case that the
/// spelling and expansion are in the same file currently.
if(unit.file_id(spelling) != unit.file_id(expansion)) {
return;
}
/// For occurrence, we always use spelling location.
location = spelling;
}
auto [fid, range] = unit.decompose_range(location);
auto& index = result.file_indices[fid];
auto symbol_id = unit.getSymbolID(decl);
index.occurrences.emplace_back(range, symbol_id.hash);
}
void handleMacroOccurrence(const clang::MacroInfo* def,
RelationKind kind,
clang::SourceLocation location) {
/// FIXME: Figure out when location is MacroID.
if(location.isMacroID()) {
return;
}
auto [fid, range] = unit.decompose_range(location);
auto& index = result.file_indices[fid];
auto symbol_id = unit.getSymbolID(def);
index.occurrences.emplace_back(range, symbol_id.hash);
Relation relation{
.kind = RelationKind::Definition,
.range = range,
.target_symbol = 0,
};
index.relations[symbol_id.hash].emplace_back(relation);
}
void handleRelation(const clang::NamedDecl* decl,
RelationKind kind,
const clang::NamedDecl* target,
clang::SourceRange range) {
auto [fid, relationRange] = unit.decompose_expansion_range(range);
Relation relation{.kind = kind};
if(kind.isDeclOrDef()) {
auto [fid2, definitionRange] = unit.decompose_expansion_range(decl->getSourceRange());
assert(fid == fid2 && "Invalid definition location");
relation.range = relationRange;
relation.definition_range = definitionRange;
} else if(kind.isReference()) {
relation.range = relationRange;
relation.target_symbol = 0;
} else if(kind.isBetweenSymbol()) {
auto symbol_id = unit.getSymbolID(ast::normalize(target));
relation.target_symbol = symbol_id.hash;
} else if(kind.isCall()) {
auto symbol_id = unit.getSymbolID(ast::normalize(target));
relation.range = relationRange;
relation.target_symbol = symbol_id.hash;
} else {
std::unreachable();
}
auto& index = result.file_indices[fid];
auto symbol_id = unit.getSymbolID(ast::normalize(decl));
index.relations[symbol_id.hash].emplace_back(relation);
}
void build() {
run();
for(auto& [fid, index]: result.file_indices) {
std::ranges::sort(index.occurrences, refl::less);
}
}
private:
TUIndex& result;
};
} // namespace
TUIndex TUIndex::build(CompilationUnit& unit) {
TUIndex index;
Builder builder(index, unit);
builder.build();
return index;
}
} // namespace clice::index

View File

@@ -4,60 +4,4 @@
#include "Server/Indexer.h"
#include "Support/Logging.h"
namespace clice {
async::Task<> Indexer::index(CompilationUnit& unit) {
auto [tu_index, header_indices] =
co_await async::submit([&] { return index::memory::index(unit); });
auto tu_id = getPath(tu_index->path);
llvm::DenseSet<PathID> visited_headers;
for(auto& [fid, index]: header_indices) {
auto id = getPath(index->path);
auto it = dynamic_header_indices.find(id);
/// FIXME: If the value less than 0, it represents the file is from
/// PCH or module. How to handle such file?
if(fid < clang::FileID::getSentinel()) {
continue;
}
auto include = tu_index->graph.getInclude(fid);
if(it != dynamic_header_indices.end()) {
auto& indices = it->second;
if(!visited_headers.contains(id)) {
indices->unmergeds[tu_id].clear();
visited_headers.insert(id);
}
indices->unmergeds[tu_id].emplace_back(include, std::move(index));
} else {
auto [it, _] = dynamic_header_indices.try_emplace(id, new HeaderIndices());
it->second->unmergeds[tu_id].emplace_back(include, std::move(index));
visited_headers.insert(id);
}
}
dynamic_tu_indices[tu_id] = std::move(tu_index);
}
async::Task<> Indexer::index(llvm::StringRef file) {
CompilationParams params;
params.kind = CompilationUnit::Indexing;
params.arguments = database.get_command(file).arguments;
auto AST = co_await async::submit([&] { return compile(params); });
if(!AST) {
logging::info("Fail to index background file {}", file);
co_return;
}
co_await index(*AST);
}
} // namespace clice
namespace clice {} // namespace clice

View File

@@ -0,0 +1,67 @@
#include "Test/Tester.h"
#include "Index/MergedIndex.h"
namespace clice::testing {
namespace {
suite<"MergedIndex"> suite = [] {
Tester tester;
index::TUIndex tu_index;
auto build_index = [&](llvm::StringRef code,
std::source_location location = std::source_location::current()) {
tester.clear();
tester.add_main("main.cpp", code);
fatal / expect(tester.compile(), location);
tu_index = index::TUIndex::build(*tester.unit);
};
auto expect_select = [&](llvm::StringRef pos,
llvm::StringRef expect_range,
llvm::StringRef file = "",
std::source_location location = std::source_location::current()) {
auto offset = tester.point(pos, file);
auto range = tester.range(expect_range, file);
auto fid = file.empty() ? tester.unit->interested_file() : tester.unit->file_id(file);
auto& index = tu_index.file_indices[fid];
auto it = std::ranges::lower_bound(
index.occurrences,
offset,
{},
[](index::Occurrence& occurrence) { return occurrence.range.end; });
auto err =
std::format("Fail to find symbol for offser: {} range: range: {}", offset, dump(range));
fatal / expect(it != index.occurrences.end(), location) << err;
/// FIXME: Make eq pretty print reflectable struct.
expect(eq(dump(it->range), dump(range)), location);
};
test("Assert") = [&] {
build_index(R"(
#include <iostream>
)");
std::println("{}", tu_index.file_indices.size());
index::MergedIndex merged;
for(auto& [fid, index]: tu_index.file_indices) {
auto path = tester.unit->file_path(fid);
if(path.ends_with("stddef.h")) {
merged.merge(path, tu_index.graph.getInclude(fid), index);
}
}
};
};
} // namespace
} // namespace clice::testing

View File

@@ -0,0 +1,68 @@
#include "Test/Tester.h"
#include "Index/TUIndex.h"
namespace clice::testing {
namespace {
suite<"TUIndex"> suite = [] {
Tester tester;
index::TUIndex tu_index;
auto build_index = [&](llvm::StringRef code,
std::source_location location = std::source_location::current()) {
tester.clear();
tester.add_main("main.cpp", code);
fatal / expect(tester.compile(), location);
tu_index = index::TUIndex::build(*tester.unit);
};
auto expect_select = [&](llvm::StringRef pos,
llvm::StringRef expect_range,
llvm::StringRef file = "",
std::source_location location = std::source_location::current()) {
auto offset = tester.point(pos, file);
auto range = tester.range(expect_range, file);
auto fid = file.empty() ? tester.unit->interested_file() : tester.unit->file_id(file);
auto& index = tu_index.file_indices[fid];
auto it = std::ranges::lower_bound(
index.occurrences,
offset,
{},
[](index::Occurrence& occurrence) { return occurrence.range.end; });
auto err =
std::format("Fail to find symbol for offser: {} range: range: {}", offset, dump(range));
fatal / expect(it != index.occurrences.end(), location) << err;
/// FIXME: Make eq pretty print reflectable struct.
expect(eq(dump(it->range), dump(range)), location);
};
test("Basic") = [&] {
build_index(R"(
int @1[f$(1)oo]();
int @2[b$(2)ar]() {
return @3[fo$(3)o]() + 1;
}
)");
expect(eq(tu_index.file_indices.size(), 1));
auto& index = tu_index.file_indices.begin()->second;
expect(eq(index.relations.size(), 2));
expect(eq(index.occurrences.size(), 3));
expect_select("1", "1");
expect_select("2", "2");
expect_select("3", "3");
};
};
} // namespace
} // namespace clice::testing

View File

@@ -16,7 +16,7 @@ if has_config("dev") then
if is_plat("windows") then
set_runtimes("MD")
if is_mode("debug") then
print("Clice does not support build in debug mode with pre-compiled llvm binary on windows.\n"
print("clice does not support build in debug mode with pre-compiled llvm binary on windows.\n"
.."See https://github.com/clice-io/clice/issues/42 for more information.")
os.raise()
end
@@ -41,7 +41,7 @@ if has_config("release") then
end
add_defines("TOML_EXCEPTIONS=0")
add_requires(libuv_require, "spdlog[header_only=n,std_format,noexcept]" ,"toml++")
add_requires(libuv_require, "spdlog[header_only=n,std_format,noexcept]" ,"toml++", "croaring")
add_requires("clice-llvm", {alias = "llvm"})
add_rules("mode.release", "mode.debug", "mode.releasedbg")
@@ -53,7 +53,7 @@ target("clice-core")
add_files("src/**.cpp|Driver/*.cpp")
add_includedirs("include", {public = true})
add_packages("libuv", "spdlog", "toml++", {public = true})
add_packages("libuv", "spdlog", "toml++", "croaring", {public = true})
if is_mode("debug") then
add_packages("llvm", {
@@ -116,7 +116,7 @@ target("clice-core")
target("clice")
set_kind("binary")
add_files("src/Driver/clice.cc")
add_files("bin/clice.cc")
add_deps("clice-core")
@@ -136,7 +136,7 @@ target("clice")
target("unit_tests")
set_default(false)
set_kind("binary")
add_files("src/Driver/unit_tests.cc", "tests/unit/**.cpp")
add_files("bin/unit_tests.cc", "tests/unit/**.cpp")
add_includedirs(".", {public = true})
add_deps("clice-core")