Simplify Position & Range; prettify Maybe; remove file_contents.{h,cc}

This commit is contained in:
Fangrui Song 2018-04-07 23:32:35 -07:00
parent a632f97a2d
commit 5fb88749a9
19 changed files with 175 additions and 520 deletions

View File

@ -210,7 +210,6 @@ target_sources(ccls PRIVATE
src/config.cc
src/diagnostics_engine.cc
src/file_consumer.cc
src/file_contents.cc
src/filesystem.cc
src/fuzzy_match.cc
src/iindexer.cc

View File

@ -17,8 +17,8 @@ Range ResolveCXSourceRange(const CXSourceRange& range, CXFile* cx_file) {
unsigned int end_line, end_column;
clang_getSpellingLocation(end, nullptr, &end_line, &end_column, nullptr);
return Range(Position((int16_t)start_line - 1, (int16_t)start_column - 1),
Position((int16_t)end_line - 1, (int16_t)end_column - 1));
return Range{{int16_t(start_line - 1), (int16_t)(start_column - 1)},
{int16_t(end_line - 1), int16_t(end_column - 1)}};
}
ClangCursor ClangType::get_declaration() const {

View File

@ -9,6 +9,7 @@
#include <loguru.hpp>
#include <inttypes.h>
#include <limits.h>
#include <algorithm>
#include <cassert>
@ -280,7 +281,7 @@ struct ConstructorCache {
struct IndexParam {
std::unordered_set<CXFile> seen_cx_files;
std::vector<std::string> seen_files;
FileContentsMap file_contents;
std::unordered_map<std::string, FileContents> file_contents;
std::unordered_map<std::string, int64_t> file_modification_times;
// Only use this when strictly needed (ie, primary translation unit is
@ -729,7 +730,10 @@ IndexTypeId IndexFile::ToTypeId(Usr usr) {
return it->second;
IndexTypeId id(types.size());
types.push_back(IndexType(id, usr));
IndexType type;
type.usr = usr;
type.id = id;
types.push_back(type);
id_cache.usr_to_type_id[usr] = id;
id_cache.type_id_to_usr[id] = usr;
return id;
@ -740,7 +744,10 @@ IndexFuncId IndexFile::ToFuncId(Usr usr) {
return it->second;
IndexFuncId id(funcs.size());
funcs.push_back(IndexFunc(id, usr));
IndexFunc func;
func.usr = usr;
func.id = id;
funcs.push_back(std::move(func));
id_cache.usr_to_func_id[usr] = id;
id_cache.func_id_to_usr[id] = usr;
return id;
@ -751,7 +758,10 @@ IndexVarId IndexFile::ToVarId(Usr usr) {
return it->second;
IndexVarId id(vars.size());
vars.push_back(IndexVar(id, usr));
IndexVar var;
var.usr = usr;
var.id = id;
vars.push_back(std::move(var));
id_cache.usr_to_var_id[usr] = id;
id_cache.var_id_to_usr[id] = usr;
return id;
@ -783,8 +793,6 @@ std::string IndexFile::ToString() {
return Serialize(SerializeFormat::Json, *this);
}
IndexType::IndexType(IndexTypeId id, Usr usr) : usr(usr), id(id) {}
template <typename T>
void Uniquify(std::vector<Id<T>>& ids) {
std::unordered_set<Id<T>> seen;
@ -796,11 +804,19 @@ void Uniquify(std::vector<Id<T>>& ids) {
}
void Uniquify(std::vector<Use>& uses) {
std::unordered_set<Range> seen;
union U {
Range range = {};
uint64_t u64;
};
static_assert(sizeof(Range) == 8);
std::unordered_set<uint64_t> seen;
size_t n = 0;
for (size_t i = 0; i < uses.size(); i++)
if (seen.insert(uses[i].range).second)
for (size_t i = 0; i < uses.size(); i++) {
U u;
u.range = uses[i].range;
if (seen.insert(u.u64).second)
uses[n++] = uses[i];
}
uses.resize(n);
}
@ -2343,10 +2359,6 @@ void IndexInit() {
clang_toggleCrashRecovery(1);
}
std::string GetClangVersion() {
return ToString(clang_getClangVersion());
}
// |SymbolRef| is serialized this way.
// |Use| also uses this though it has an extra field |file|,
// which is not used by Index* so it does not need to be serialized.
@ -2354,7 +2366,7 @@ void Reflect(Reader& visitor, Reference& value) {
if (visitor.Format() == SerializeFormat::Json) {
std::string t = visitor.GetString();
char* s = const_cast<char*>(t.c_str());
value.range = Range(s);
value.range = Range::FromString(s);
s = strchr(s, '|');
value.id.id = RawId(strtol(s + 1, &s, 10));
value.kind = static_cast<SymbolKind>(strtol(s + 1, &s, 10));
@ -2368,12 +2380,13 @@ void Reflect(Reader& visitor, Reference& value) {
}
void Reflect(Writer& visitor, Reference& value) {
if (visitor.Format() == SerializeFormat::Json) {
std::string s = value.range.ToString();
// RawId(-1) -> "-1"
s += '|' + std::to_string(
static_cast<std::make_signed<RawId>::type>(value.id.id));
s += '|' + std::to_string(int(value.kind));
s += '|' + std::to_string(int(value.role));
char buf[99];
snprintf(buf, sizeof buf, "%s|%" PRId32 "|%d|%d",
value.range.ToString().c_str(),
static_cast<std::make_signed<RawId>::type>(value.id.id),
int(value.kind), int(value.role));
std::string s(buf);
Reflect(visitor, s);
} else {
Reflect(visitor, value.range);

View File

@ -9,8 +9,9 @@
namespace {
std::optional<std::string> GetFileContents(const std::string& path,
FileContentsMap* file_contents) {
std::optional<std::string> GetFileContents(
const std::string& path,
std::unordered_map<std::string, FileContents>* file_contents) {
auto it = file_contents->find(path);
if (it == file_contents->end()) {
std::optional<std::string> content = ReadContent(path);
@ -28,6 +29,34 @@ bool operator==(const CXFileUniqueID& a, const CXFileUniqueID& b) {
a.data[2] == b.data[2];
}
FileContents::FileContents() : line_offsets_{0} {}
FileContents::FileContents(const std::string& path, const std::string& content)
: path(path), content(content) {
line_offsets_.push_back(0);
for (size_t i = 0; i < content.size(); i++) {
if (content[i] == '\n')
line_offsets_.push_back(i + 1);
}
}
std::optional<int> FileContents::ToOffset(Position p) const {
if (0 <= p.line && size_t(p.line) < line_offsets_.size()) {
int ret = line_offsets_[p.line] + p.column;
if (size_t(ret) < content.size())
return ret;
}
return std::nullopt;
}
std::optional<std::string> FileContents::ContentsInRange(Range range) const {
std::optional<int> start_offset = ToOffset(range.start),
end_offset = ToOffset(range.end);
if (start_offset && end_offset && *start_offset < *end_offset)
return content.substr(*start_offset, *end_offset - *start_offset);
return std::nullopt;
}
bool FileConsumerSharedState::Mark(const std::string& file) {
std::lock_guard<std::mutex> lock(mutex);
return used_files.insert(file).second;
@ -44,9 +73,10 @@ FileConsumer::FileConsumer(FileConsumerSharedState* shared_state,
const std::string& parse_file)
: shared_(shared_state), parse_file_(parse_file) {}
IndexFile* FileConsumer::TryConsumeFile(CXFile file,
bool* is_first_ownership,
FileContentsMap* file_contents_map) {
IndexFile* FileConsumer::TryConsumeFile(
CXFile file,
bool* is_first_ownership,
std::unordered_map<std::string, FileContents>* file_contents_map) {
assert(is_first_ownership);
CXFileUniqueID file_id;

View File

@ -1,6 +1,6 @@
#pragma once
#include "file_contents.h"
#include "position.h"
#include "utils.h"
#include <clang-c/Index.h>
@ -9,6 +9,7 @@
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include <vector>
struct IndexFile;
@ -16,6 +17,19 @@ struct IndexFile;
MAKE_HASHABLE(CXFileUniqueID, t.data[0], t.data[1], t.data[2]);
bool operator==(const CXFileUniqueID& a, const CXFileUniqueID& b);
struct FileContents {
FileContents();
FileContents(const std::string& path, const std::string& content);
std::optional<int> ToOffset(Position p) const;
std::optional<std::string> ContentsInRange(Range range) const;
std::string path;
std::string content;
// {0, 1 + position of first newline, 1 + position of second newline, ...}
std::vector<int> line_offsets_;
};
struct FileConsumerSharedState {
mutable std::unordered_set<std::string> used_files;
mutable std::mutex mutex;
@ -48,7 +62,7 @@ struct FileConsumer {
// variable since it is large and we do not want to copy it.
IndexFile* TryConsumeFile(CXFile file,
bool* is_first_ownership,
FileContentsMap* file_contents);
std::unordered_map<std::string, FileContents>* file_contents);
// Returns and passes ownership of all local state.
std::vector<std::unique_ptr<IndexFile>> TakeLocalState();

View File

@ -1,29 +0,0 @@
#include "file_contents.h"
FileContents::FileContents() : line_offsets_{0} {}
FileContents::FileContents(const std::string& path, const std::string& content)
: path(path), content(content) {
line_offsets_.push_back(0);
for (size_t i = 0; i < content.size(); i++) {
if (content[i] == '\n')
line_offsets_.push_back(i + 1);
}
}
std::optional<int> FileContents::ToOffset(Position p) const {
if (0 <= p.line && size_t(p.line) < line_offsets_.size()) {
int ret = line_offsets_[p.line] + p.column;
if (size_t(ret) < content.size())
return ret;
}
return std::nullopt;
}
std::optional<std::string> FileContents::ContentsInRange(Range range) const {
std::optional<int> start_offset = ToOffset(range.start),
end_offset = ToOffset(range.end);
if (start_offset && end_offset && *start_offset < *end_offset)
return content.substr(*start_offset, *end_offset - *start_offset);
return std::nullopt;
}

View File

@ -1,23 +0,0 @@
#pragma once
#include "position.h"
#include <string>
#include <optional>
#include <unordered_map>
#include <vector>
struct FileContents {
FileContents();
FileContents(const std::string& path, const std::string& content);
std::optional<int> ToOffset(Position p) const;
std::optional<std::string> ContentsInRange(Range range) const;
std::string path;
std::string content;
// {0, 1 + position of first newline, 1 + position of second newline, ...}
std::vector<int> line_offsets_;
};
using FileContentsMap = std::unordered_map<std::string, FileContents>;

View File

@ -4,7 +4,6 @@
#include "clang_translation_unit.h"
#include "clang_utils.h"
#include "file_consumer.h"
#include "file_contents.h"
#include "language.h"
#include "lsp.h"
#include "maybe.h"
@ -54,7 +53,7 @@ struct Id {
// Needed for google::dense_hash_map.
explicit operator RawId() const { return id; }
bool HasValueForMaybe_() const { return id != RawId(-1); }
bool Valid() const { return id != RawId(-1); }
bool operator==(const Id& o) const { return id == o.id; }
bool operator!=(const Id& o) const { return id != o.id; }
@ -101,7 +100,7 @@ struct Reference {
SymbolKind kind;
Role role;
bool HasValueForMaybe_() const { return range.HasValueForMaybe_(); }
bool Valid() const { return range.Valid(); }
operator SymbolIdx() const { return {id, kind}; }
std::tuple<Range, Id<void>, SymbolKind, Role> ToTuple() const {
return std::make_tuple(range, id, kind, role);
@ -126,8 +125,6 @@ struct Use : Reference {
Use(Range range, Id<void> id, SymbolKind kind, Role role, Id<QueryFile> file)
: Reference{range, id, kind, role}, file(file) {}
};
// Used by |HANDLE_MERGEABLE| so only |range| is needed.
MAKE_HASHABLE(Use, t.range);
void Reflect(Reader& visitor, Reference& value);
void Reflect(Writer& visitor, Reference& value);
@ -241,9 +238,6 @@ struct IndexType {
// NOTE: Do not insert directly! Use AddUsage instead.
std::vector<Use> uses;
IndexType() {} // For serialization.
IndexType(IndexTypeId id, Usr usr);
bool operator<(const IndexType& other) const { return id < other.id; }
};
MAKE_HASHABLE(IndexType, t.id);
@ -336,9 +330,6 @@ struct IndexFunc : NameMixin<IndexFunc> {
// def.spell.
std::vector<Use> uses;
IndexFunc() {} // For serialization.
IndexFunc(IndexFuncId id, Usr usr) : usr(usr), id(id) {}
bool operator<(const IndexFunc& other) const { return id < other.id; }
};
MAKE_HASHABLE(IndexFunc, t.id);
@ -408,9 +399,6 @@ struct IndexVar {
std::vector<Use> declarations;
std::vector<Use> uses;
IndexVar() {} // For serialization.
IndexVar(IndexVarId id, Usr usr) : usr(usr), id(id) {}
bool operator<(const IndexVar& other) const { return id < other.id; }
};
MAKE_HASHABLE(IndexVar, t.id);
@ -519,7 +507,3 @@ std::vector<std::unique_ptr<IndexFile>> ParseWithTu(
bool ConcatTypeAndName(std::string& type, const std::string& name);
void IndexInit();
void ClangSanityCheck();
std::string GetClangVersion();

View File

@ -5,7 +5,7 @@
#include <utility>
// Like std::optional, but the stored data is responsible for containing the empty
// state. T should define a function `bool T::HasValueForMaybe_()`.
// state. T should define a function `bool T::Valid()`.
template <typename T>
class Maybe {
T storage;
@ -28,10 +28,10 @@ class Maybe {
const T& operator*() const { return storage; }
T& operator*() { return storage; }
bool HasValue() const { return storage.HasValueForMaybe_(); }
explicit operator bool() const { return HasValue(); }
bool Valid() const { return storage.Valid(); }
explicit operator bool() const { return Valid(); }
operator std::optional<T>() const {
if (HasValue())
if (Valid())
return storage;
return std::nullopt;
}

View File

@ -105,7 +105,8 @@ struct Handler_TextDocumentDefinition
if (uses.empty() && on_def)
uses.push_back(*on_def);
}
AddRange(&out.result, GetLsLocationExs(db, working_files, uses));
auto locs = GetLsLocationExs(db, working_files, uses);
out.result.insert(out.result.end(), locs.begin(), locs.end());
if (!out.result.empty())
break;
}

View File

@ -5,13 +5,3 @@ MethodType kMethodType_Exit = "exit";
MethodType kMethodType_TextDocumentPublishDiagnostics = "textDocument/publishDiagnostics";
MethodType kMethodType_CclsPublishInactiveRegions = "$ccls/publishInactiveRegions";
MethodType kMethodType_CclsPublishSemanticHighlighting = "$ccls/publishSemanticHighlighting";
InMessage::~InMessage() = default;
lsRequestId RequestInMessage::GetRequestId() const {
return id;
}
lsRequestId NotificationInMessage::GetRequestId() const {
return std::monostate();
}

View File

@ -15,7 +15,7 @@ extern MethodType kMethodType_CclsPublishSemanticHighlighting;
using lsRequestId = std::variant<std::monostate, int64_t, std::string>;
struct InMessage {
virtual ~InMessage();
virtual ~InMessage() = default;
virtual MethodType GetMethodType() const = 0;
virtual lsRequestId GetRequestId() const = 0;
@ -24,10 +24,14 @@ struct InMessage {
struct RequestInMessage : public InMessage {
// number or string, actually no null
lsRequestId id;
lsRequestId GetRequestId() const override;
lsRequestId GetRequestId() const override {
return id;
}
};
// NotificationInMessage does not have |id|.
struct NotificationInMessage : public InMessage {
lsRequestId GetRequestId() const override;
lsRequestId GetRequestId() const override {
return std::monostate();
}
};

View File

@ -1,74 +1,29 @@
#include "position.h"
#include "serializer.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
Position::Position() : line(-1), column(-1) {}
Position::Position(int16_t line, int16_t column) : line(line), column(column) {}
Position::Position(const char* encoded) {
char* p = const_cast<char*>(encoded);
line = int16_t(strtol(p, &p, 10)) - 1;
Position Position::FromString(const std::string& encoded) {
char* p = const_cast<char*>(encoded.c_str());
int16_t line = int16_t(strtol(p, &p, 10)) - 1;
assert(*p == ':');
p++;
column = int16_t(strtol(p, &p, 10)) - 1;
int16_t column = int16_t(strtol(p, &p, 10)) - 1;
return {line, column};
}
std::string Position::ToString() {
// Output looks like this:
//
// 1:2
//
// 1 => line
// 2 => column
std::string result;
result += std::to_string(line + 1);
result += ':';
result += std::to_string(column + 1);
return result;
char buf[99];
snprintf(buf, sizeof buf, "%d:%d", line + 1, column + 1);
return buf;
}
std::string Position::ToPrettyString(const std::string& filename) {
// Output looks like this:
//
// 1:2:3
//
// 1 => filename
// 2 => line
// 3 => column
std::string result;
result += filename;
result += ':';
result += std::to_string(line + 1);
result += ':';
result += std::to_string(column + 1);
return result;
}
bool Position::operator==(const Position& that) const {
return line == that.line && column == that.column;
}
bool Position::operator!=(const Position& that) const {
return !(*this == that);
}
bool Position::operator<(const Position& that) const {
if (line != that.line)
return line < that.line;
return column < that.column;
}
Range::Range() {}
Range::Range(Position position) : Range(position, position) {}
Range::Range(Position start, Position end) : start(start), end(end) {}
Range::Range(const char* encoded) {
char* p = const_cast<char*>(encoded);
Range Range::FromString(const std::string& encoded) {
Position start, end;
char* p = const_cast<char*>(encoded.c_str());
start.line = int16_t(strtol(p, &p, 10)) - 1;
assert(*p == ':');
p++;
@ -80,18 +35,14 @@ Range::Range(const char* encoded) {
assert(*p == ':');
p++;
end.column = int16_t(strtol(p, nullptr, 10)) - 1;
return {start, end};
}
bool Range::Contains(int line, int column) const {
if (line == start.line && line == end.line)
return column >= start.column && column < end.column;
if (line == start.line)
return column >= start.column;
if (line == end.line)
return column < end.column;
if (line > start.line && line < end.line)
return true;
return false;
if (line > INT16_MAX)
return false;
Position p{int16_t(line), int16_t(std::min(column, INT16_MAX))};
return !(p < start) && p < end;
}
Range Range::RemovePrefix(Position position) const {
@ -99,47 +50,16 @@ Range Range::RemovePrefix(Position position) const {
}
std::string Range::ToString() {
// Output looks like this:
//
// 1:2-3:4
//
// 1 => start line
// 2 => start column
// 3 => end line
// 4 => end column
std::string output;
output += std::to_string(start.line + 1);
output += ':';
output += std::to_string(start.column + 1);
output += '-';
output += std::to_string(end.line + 1);
output += ':';
output += std::to_string(end.column + 1);
return output;
}
bool Range::operator==(const Range& that) const {
return start == that.start && end == that.end;
}
bool Range::operator!=(const Range& that) const {
return !(*this == that);
}
bool Range::operator<(const Range& that) const {
if (start != that.start)
return start < that.start;
return end < that.end;
char buf[99];
snprintf(buf, sizeof buf, "%d:%d-%d:%d", start.line + 1, start.column + 1,
end.line + 1, end.column + 1);
return buf;
}
// Position
void Reflect(Reader& visitor, Position& value) {
if (visitor.Format() == SerializeFormat::Json) {
std::string s = visitor.GetString();
value = Position(s.c_str());
value = Position::FromString(visitor.GetString());
} else {
Reflect(visitor, value.line);
Reflect(visitor, value.column);
@ -158,8 +78,7 @@ void Reflect(Writer& visitor, Position& value) {
// Range
void Reflect(Reader& visitor, Range& value) {
if (visitor.Format() == SerializeFormat::Json) {
std::string s = visitor.GetString();
value = Range(s.c_str());
value = Range::FromString(visitor.GetString());
} else {
Reflect(visitor, value.start.line);
Reflect(visitor, value.start.column);

View File

@ -1,57 +1,56 @@
#pragma once
#include "maybe.h"
#include "serializer.h"
#include "utils.h"
#include <stdint.h>
#include <string>
struct Position {
int16_t line;
int16_t column;
int16_t line = -1;
int16_t column = -1;
Position();
Position(int16_t line, int16_t column);
explicit Position(const char* encoded);
static Position FromString(const std::string& encoded);
bool HasValueForMaybe_() const { return line >= 0; }
bool Valid() const { return line >= 0; }
std::string ToString();
std::string ToPrettyString(const std::string& filename);
// Compare two Positions and check if they are equal. Ignores the value of
// |interesting|.
bool operator==(const Position& that) const;
bool operator!=(const Position& that) const;
bool operator<(const Position& that) const;
bool operator==(const Position& o) const {
return line == o.line && column == o.column;
}
bool operator<(const Position& o) const {
if (line != o.line)
return line < o.line;
return column < o.column;
}
};
static_assert(
sizeof(Position) == 4,
"Investigate, Position should be 32-bits for indexer size reasons");
MAKE_HASHABLE(Position, t.line, t.column);
struct Range {
Position start;
Position end;
Range();
explicit Range(Position position);
Range(Position start, Position end);
explicit Range(const char* encoded);
static Range FromString(const std::string& encoded);
bool HasValueForMaybe_() const { return start.HasValueForMaybe_(); }
bool Valid() const { return start.Valid(); }
bool Contains(int line, int column) const;
Range RemovePrefix(Position position) const;
std::string ToString();
bool operator==(const Range& that) const;
bool operator!=(const Range& that) const;
bool operator<(const Range& that) const;
bool operator==(const Range& o) const {
return start == o.start && end == o.end;
}
bool operator<(const Range& o) const {
return !(start == o.start) ? start < o.start : end < o.end;
}
};
MAKE_HASHABLE(Range, t.start, t.end);
// Reflection
class Reader;
class Writer;
void Reflect(Reader& visitor, Position& value);
void Reflect(Writer& visitor, Position& value);
void Reflect(Reader& visitor, Range& value);

View File

@ -11,14 +11,30 @@
#include <cassert>
#include <cstdint>
#include <functional>
#include <iterator>
#include <string>
#include <unordered_map>
#include <unordered_set>
// TODO: Make all copy constructors explicit.
// Used by |HANDLE_MERGEABLE| so only |range| is needed.
MAKE_HASHABLE(Range, t.start, t.end);
MAKE_HASHABLE(Use, t.range);
namespace {
template <typename T>
void AddRange(std::vector<T>* dest, const std::vector<T>& to_add) {
dest->insert(dest->end(), to_add.begin(), to_add.end());
}
template <typename T>
void AddRange(std::vector<T>* dest, std::vector<T>&& to_add) {
dest->insert(dest->end(), std::make_move_iterator(to_add.begin()),
std::make_move_iterator(to_add.end()));
}
template <typename T>
void RemoveRange(std::vector<T>* dest, const std::vector<T>& to_remove) {
std::unordered_set<T> to_remove_set(to_remove.begin(), to_remove.end());
@ -897,7 +913,7 @@ void QueryDatabase::ImportOrUpdate(std::vector<QueryVar::DefUpdate>&& updates) {
void QueryDatabase::UpdateSymbols(Maybe<Id<void>>* symbol_idx,
SymbolKind kind,
Id<void> idx) {
if (!symbol_idx->HasValue()) {
if (!symbol_idx->Valid()) {
*symbol_idx = Id<void>(symbols.size());
symbols.push_back(SymbolIdx{idx, kind});
}
@ -928,255 +944,3 @@ std::string_view QueryDatabase::GetSymbolName(RawId symbol_idx,
}
return "";
}
TEST_SUITE("query") {
IndexUpdate GetDelta(IndexFile previous, IndexFile current) {
QueryDatabase db;
IdMap previous_map(&db, previous.id_cache);
IdMap current_map(&db, current.id_cache);
return IndexUpdate::CreateDelta(&previous_map, &current_map, &previous,
&current);
}
TEST_CASE("remove defs") {
IndexFile previous("foo.cc", "<empty>");
IndexFile current("foo.cc", "<empty>");
previous.Resolve(previous.ToTypeId(HashUsr("usr1")))->def.spell =
Use(Range(Position(1, 0)), {}, {}, {}, {});
previous.Resolve(previous.ToFuncId(HashUsr("usr2")))->def.spell =
Use(Range(Position(2, 0)), {}, {}, {}, {});
previous.Resolve(previous.ToVarId(HashUsr("usr3")))->def.spell =
Use(Range(Position(3, 0)), {}, {}, {}, {});
IndexUpdate update = GetDelta(previous, current);
REQUIRE(update.types_removed == std::vector<Usr>{HashUsr("usr1")});
REQUIRE(update.funcs_removed.size() == 1);
REQUIRE(update.funcs_removed[0].usr == HashUsr("usr2"));
REQUIRE(update.vars_removed.size() == 1);
REQUIRE(update.vars_removed[0].usr == HashUsr("usr3"));
}
TEST_CASE("do not remove ref-only defs") {
IndexFile previous("foo.cc", "<empty>");
IndexFile current("foo.cc", "<empty>");
previous.Resolve(previous.ToTypeId(HashUsr("usr1")))
->uses.push_back(Use{Range(Position(1, 0)), {}, {}, {}, {}});
previous.Resolve(previous.ToFuncId(HashUsr("usr2")))
->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {}));
previous.Resolve(previous.ToVarId(HashUsr("usr3")))
->uses.push_back(Use(Range(Position(3, 0)), {}, {}, {}, {}));
IndexUpdate update = GetDelta(previous, current);
REQUIRE(update.types_removed == std::vector<Usr>{});
REQUIRE(update.funcs_removed.empty());
REQUIRE(update.vars_removed.empty());
}
TEST_CASE("func callers") {
IndexFile previous("foo.cc", "<empty>");
IndexFile current("foo.cc", "<empty>");
IndexFunc* pf = previous.Resolve(previous.ToFuncId(HashUsr("usr")));
IndexFunc* cf = current.Resolve(current.ToFuncId(HashUsr("usr")));
pf->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {}));
cf->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {}));
IndexUpdate update = GetDelta(previous, current);
REQUIRE(update.funcs_removed.empty());
REQUIRE(update.funcs_uses.size() == 1);
REQUIRE(update.funcs_uses[0].id == QueryFuncId(0));
REQUIRE(update.funcs_uses[0].to_remove.size() == 1);
REQUIRE(update.funcs_uses[0].to_remove[0].range == Range(Position(1, 0)));
REQUIRE(update.funcs_uses[0].to_add.size() == 1);
REQUIRE(update.funcs_uses[0].to_add[0].range == Range(Position(2, 0)));
}
TEST_CASE("type usages") {
IndexFile previous("foo.cc", "<empty>");
IndexFile current("foo.cc", "<empty>");
IndexType* pt = previous.Resolve(previous.ToTypeId(HashUsr("usr")));
IndexType* ct = current.Resolve(current.ToTypeId(HashUsr("usr")));
pt->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {}));
ct->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {}));
IndexUpdate update = GetDelta(previous, current);
REQUIRE(update.types_removed == std::vector<Usr>{});
REQUIRE(update.types_def_update.empty());
REQUIRE(update.types_uses.size() == 1);
REQUIRE(update.types_uses[0].to_remove.size() == 1);
REQUIRE(update.types_uses[0].to_remove[0].range == Range(Position(1, 0)));
REQUIRE(update.types_uses[0].to_add.size() == 1);
REQUIRE(update.types_uses[0].to_add[0].range == Range(Position(2, 0)));
}
TEST_CASE("apply delta") {
IndexFile previous("foo.cc", "<empty>");
IndexFile current("foo.cc", "<empty>");
IndexFunc* pf = previous.Resolve(previous.ToFuncId(HashUsr("usr")));
IndexFunc* cf = current.Resolve(current.ToFuncId(HashUsr("usr")));
pf->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {}));
pf->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {}));
cf->uses.push_back(Use(Range(Position(4, 0)), {}, {}, {}, {}));
cf->uses.push_back(Use(Range(Position(5, 0)), {}, {}, {}, {}));
QueryDatabase db;
IdMap previous_map(&db, previous.id_cache);
IdMap current_map(&db, current.id_cache);
REQUIRE(db.funcs.size() == 1);
IndexUpdate import_update =
IndexUpdate::CreateDelta(nullptr, &previous_map, nullptr, &previous);
IndexUpdate delta_update = IndexUpdate::CreateDelta(
&previous_map, &current_map, &previous, &current);
db.ApplyIndexUpdate(&import_update);
REQUIRE(db.funcs[0].uses.size() == 2);
REQUIRE(db.funcs[0].uses[0].range == Range(Position(1, 0)));
REQUIRE(db.funcs[0].uses[1].range == Range(Position(2, 0)));
db.ApplyIndexUpdate(&delta_update);
REQUIRE(db.funcs[0].uses.size() == 2);
REQUIRE(db.funcs[0].uses[0].range == Range(Position(4, 0)));
REQUIRE(db.funcs[0].uses[1].range == Range(Position(5, 0)));
}
TEST_CASE("Remove variable with usage") {
auto load_index_from_json = [](const char* json) {
return Deserialize(SerializeFormat::Json, "foo.cc", json, "<empty>",
std::nullopt);
};
auto previous = load_index_from_json(R"RAW(
{
"types": [
{
"id": 0,
"usr": 17,
"detailed_name": "",
"short_name_offset": 0,
"short_name_size": 0,
"kind": 0,
"hover": "",
"comments": "",
"parents": [],
"derived": [],
"types": [],
"funcs": [],
"vars": [],
"instances": [
0
],
"uses": []
}
],
"funcs": [
{
"id": 0,
"usr": 4259594751088586730,
"detailed_name": "void foo()",
"short_name_offset": 5,
"short_name_size": 3,
"kind": 12,
"storage": 1,
"hover": "",
"comments": "",
"declarations": [],
"spell": "1:6-1:9|-1|1|2",
"extent": "1:1-4:2|-1|1|0",
"base": [],
"derived": [],
"locals": [],
"uses": [],
"callees": []
}
],
"vars": [
{
"id": 0,
"usr": 16837348799350457167,
"detailed_name": "int a",
"short_name_offset": 4,
"short_name_size": 1,
"hover": "",
"comments": "",
"declarations": [],
"spell": "2:7-2:8|0|3|2",
"extent": "2:3-2:8|0|3|2",
"type": 0,
"uses": [
"3:3-3:4|0|3|4"
],
"kind": 13,
"storage": 1
}
]
}
)RAW");
auto current = load_index_from_json(R"RAW(
{
"types": [],
"funcs": [
{
"id": 0,
"usr": 4259594751088586730,
"detailed_name": "void foo()",
"short_name_offset": 5,
"short_name_size": 3,
"kind": 12,
"storage": 1,
"hover": "",
"comments": "",
"declarations": [],
"spell": "1:6-1:9|-1|1|2",
"extent": "1:1-5:2|-1|1|0",
"base": [],
"derived": [],
"locals": [],
"uses": [],
"callees": []
}
],
"vars": []
}
)RAW");
// Validate previous/current were parsed.
REQUIRE(previous->vars.size() == 1);
REQUIRE(current->vars.size() == 0);
QueryDatabase db;
// Apply initial file.
{
IdMap previous_map(&db, previous->id_cache);
IndexUpdate import_update = IndexUpdate::CreateDelta(
nullptr, &previous_map, nullptr, previous.get());
db.ApplyIndexUpdate(&import_update);
}
REQUIRE(db.vars.size() == 1);
REQUIRE(db.vars[0].uses.size() == 1);
// Apply change.
{
IdMap previous_map(&db, previous->id_cache);
IdMap current_map(&db, current->id_cache);
IndexUpdate delta_update = IndexUpdate::CreateDelta(
&previous_map, &current_map, previous.get(), current.get());
db.ApplyIndexUpdate(&delta_update);
}
REQUIRE(db.vars.size() == 1);
REQUIRE(db.vars[0].uses.size() == 0);
}
}

View File

@ -47,8 +47,8 @@ Maybe<Use> GetDefinitionSpell(QueryDatabase* db, SymbolIdx sym) {
Maybe<Use> GetDefinitionExtent(QueryDatabase* db, SymbolIdx sym) {
// Used to jump to file.
if (sym.kind == SymbolKind::File)
return Use(Range(Position(0, 0), Position(0, 0)), sym.id, sym.kind,
Role::None, QueryFileId(sym.id));
return Use(Range{{0, 0}, {0, 0}}, sym.id, sym.kind, Role::None,
QueryFileId(sym.id));
Maybe<Use> ret;
EachEntityDef(db, sym, [&](const auto& def) { return !(ret = def.extent); });
return ret;
@ -126,7 +126,7 @@ std::vector<Use> GetUsesForAllBases(QueryDatabase* db, QueryFunc& root) {
if (!seen.count(func1.usr)) {
seen.insert(func1.usr);
stack.push_back(&func1);
AddRange(&ret, func1.uses);
ret.insert(ret.end(), func1.uses.begin(), func1.uses.end());
}
});
}
@ -147,7 +147,7 @@ std::vector<Use> GetUsesForAllDerived(QueryDatabase* db, QueryFunc& root) {
if (!seen.count(func1.usr)) {
seen.insert(func1.usr);
stack.push_back(&func1);
AddRange(&ret, func1.uses);
ret.insert(ret.end(), func1.uses.begin(), func1.uses.end());
}
});
}

View File

@ -231,7 +231,7 @@ void ReflectMember(Writer& visitor, const char* name, std::optional<T>& value) {
// The same as std::optional
template <typename T>
void ReflectMember(Writer& visitor, const char* name, Maybe<T>& value) {
if (value.HasValue() || visitor.Format() != SerializeFormat::Json) {
if (value.Valid() || visitor.Format() != SerializeFormat::Json) {
visitor.Key(name);
Reflect(visitor, value);
}

View File

@ -229,16 +229,17 @@ IndexFile* FindDbForPathEnding(
bool RunIndexTests(const std::string& filter_path, bool enable_update) {
gTestOutputMode = true;
std::string version = ToString(clang_getClangVersion());
// Index tests change based on the version of clang used.
static const char kRequiredClangVersion[] =
"clang version 6.0.0 (tags/RELEASE_600/final)";
if (GetClangVersion() != kRequiredClangVersion &&
GetClangVersion().find("trunk") == std::string::npos) {
if (version != kRequiredClangVersion &&
version.find("trunk") == std::string::npos) {
fprintf(stderr,
"Index tests must be run using clang version %s, ccls is running "
"with %s\n",
kRequiredClangVersion, GetClangVersion().c_str());
kRequiredClangVersion, version.c_str());
return false;
}

View File

@ -65,17 +65,6 @@ std::optional<std::string> ReadContent(const std::string& filename);
void WriteToFile(const std::string& filename, const std::string& content);
template <typename T>
void AddRange(std::vector<T>* dest, const std::vector<T>& to_add) {
dest->insert(dest->end(), to_add.begin(), to_add.end());
}
template <typename T>
void AddRange(std::vector<T>* dest, std::vector<T>&& to_add) {
dest->insert(dest->end(), std::make_move_iterator(to_add.begin()),
std::make_move_iterator(to_add.end()));
}
// http://stackoverflow.com/a/38140932
//
// struct SomeHashKey {