Add ExtentRef; merge symbol2refcnt and outline2refcnt

Fix hierarchical document symbol for namespaces when there are multiple declarations.
This commit is contained in:
Fangrui Song 2018-10-14 23:33:24 -07:00
parent bc4dc6720b
commit 5fbe4eac83
7 changed files with 136 additions and 156 deletions

View File

@ -51,6 +51,15 @@ struct SymbolRef {
}; };
MAKE_HASHABLE(SymbolRef, t.range, t.usr, t.kind, t.role); MAKE_HASHABLE(SymbolRef, t.range, t.usr, t.kind, t.role);
struct ExtentRef : SymbolRef {
Range extent;
std::tuple<Range, Usr, SymbolKind, Role, Range> ToTuple() const {
return std::make_tuple(range, usr, kind, role, extent);
}
bool operator==(const ExtentRef &o) const { return ToTuple() == o.ToTuple(); }
};
MAKE_HASHABLE(ExtentRef, t.range, t.usr, t.kind, t.role, t.extent);
struct Ref { struct Ref {
Range range; Range range;
Role role; Role role;

View File

@ -102,15 +102,16 @@ bool Expand(MessageHandler *m, Out_cclsCall *entry, bool callee,
} else { } else {
for (Use use : func.uses) { for (Use use : func.uses) {
const QueryFile &file1 = m->db->files[use.file_id]; const QueryFile &file1 = m->db->files[use.file_id];
Maybe<SymbolRef> best_sym; Maybe<ExtentRef> best;
for (auto [sym, refcnt] : file1.outline2refcnt) for (auto [sym, refcnt] : file1.symbol2refcnt)
if (refcnt > 0 && sym.kind == SymbolKind::Func && if (refcnt > 0 && sym.extent.Valid() &&
sym.range.start <= use.range.start && sym.kind == SymbolKind::Func &&
use.range.end <= sym.range.end && sym.extent.start <= use.range.start &&
(!best_sym || best_sym->range.start < sym.range.start)) use.range.end <= sym.extent.end &&
best_sym = sym; (!best || best->extent.start < sym.extent.start))
if (best_sym) best = sym;
handle(*best_sym, use.file_id, call_type); if (best)
handle(*best, use.file_id, call_type);
} }
} }
}; };

View File

@ -43,49 +43,49 @@ struct Handler_CclsNavigate : BaseMessageHandler<In_CclsNavigate> {
switch (params.direction[0]) { switch (params.direction[0]) {
case 'D': { case 'D': {
Maybe<Range> parent; Maybe<Range> parent;
for (auto [sym, refcnt] : file->outline2refcnt) for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.range.start <= pos && pos < sym.range.end && if (refcnt > 0 && sym.extent.Valid() && sym.extent.start <= pos &&
(!parent || parent->start < sym.range.start)) pos < sym.extent.end && (!parent || parent->start < sym.extent.start))
parent = sym.range; parent = sym.extent;
for (auto [sym, refcnt] : file->outline2refcnt) for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && pos < sym.range.start && if (refcnt > 0 && pos < sym.extent.start &&
(!parent || sym.range.end <= parent->end) && (!parent || sym.extent.end <= parent->end) &&
(!res || sym.range.start < res->start)) (!res || sym.extent.start < res->start))
res = sym.range; res = sym.extent;
break; break;
} }
case 'L': case 'L':
for (auto [sym, refcnt] : file->outline2refcnt) for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.range.end <= pos && if (refcnt > 0 && sym.extent.Valid() && sym.extent.end <= pos &&
(!res || (res->end == sym.range.end ? sym.range.start < res->start (!res || (res->end == sym.extent.end ? sym.extent.start < res->start
: res->end < sym.range.end))) : res->end < sym.extent.end)))
res = sym.range; res = sym.extent;
break; break;
case 'R': { case 'R': {
Maybe<Range> parent; Maybe<Range> parent;
for (auto [sym, refcnt] : file->outline2refcnt) for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.range.start <= pos && pos < sym.range.end && if (refcnt > 0 && sym.extent.Valid() && sym.extent.start <= pos &&
(!parent || parent->start < sym.range.start)) pos < sym.extent.end && (!parent || parent->start < sym.extent.start))
parent = sym.range; parent = sym.extent;
if (parent && parent->start.line == pos.line && pos < parent->end) { if (parent && parent->start.line == pos.line && pos < parent->end) {
pos = parent->end; pos = parent->end;
if (pos.column) if (pos.column)
pos.column--; pos.column--;
} }
for (auto [sym, refcnt] : file->outline2refcnt) for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && pos < sym.range.start && if (refcnt > 0 && sym.extent.Valid() && pos < sym.extent.start &&
(!res || (!res ||
(sym.range.start == res->start ? res->end < sym.range.end (sym.extent.start == res->start ? res->end < sym.extent.end
: sym.range.start < res->start))) : sym.extent.start < res->start)))
res = sym.range; res = sym.extent;
break; break;
} }
case 'U': case 'U':
default: default:
for (auto [sym, refcnt] : file->outline2refcnt) for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.range.start < pos && pos < sym.range.end && if (refcnt > 0 && sym.extent.Valid() && sym.extent.start < pos &&
(!res || res->start < sym.range.start)) pos < sym.extent.end && (!res || res->start < sym.extent.start))
res = sym.range; res = sym.extent;
break; break;
} }
std::vector<lsLocation> result; std::vector<lsLocation> result;

View File

@ -74,15 +74,15 @@ struct Handler_TextDocumentCodeLens
return; return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path); WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
auto Add = [&](const char *singular, Cmd_xref show, Use use, int num, auto Add = [&](const char *singular, Cmd_xref show, Range range, int num,
bool force_display = false) { bool force_display = false) {
if (!num && !force_display) if (!num && !force_display)
return; return;
std::optional<lsRange> range = GetLsRange(wfile, use.range); std::optional<lsRange> ls_range = GetLsRange(wfile, range);
if (!range) if (!ls_range)
return; return;
lsCodeLens &code_lens = result.emplace_back(); lsCodeLens &code_lens = result.emplace_back();
code_lens.range = *range; code_lens.range = *ls_range;
code_lens.command = lsCommand(); code_lens.command = lsCommand();
code_lens.command->command = std::string(ccls_xref); code_lens.command->command = std::string(ccls_xref);
bool plural = num > 1 && singular[strlen(singular) - 1] != 'd'; bool plural = num > 1 && singular[strlen(singular) - 1] != 'd';
@ -91,19 +91,10 @@ struct Handler_TextDocumentCodeLens
code_lens.command->arguments.push_back(ToString(show)); code_lens.command->arguments.push_back(ToString(show));
}; };
auto ToSpell = [&](SymbolRef sym, int file_id) -> Use {
Maybe<DeclRef> def = GetDefinitionSpell(db, sym);
if (def && def->file_id == file_id &&
def->range.start.line == sym.range.start.line)
return *def;
return {{sym.range, sym.role}, file_id};
};
std::unordered_set<Range> seen; std::unordered_set<Range> seen;
for (auto [sym, refcnt] : file->outline2refcnt) { for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !seen.insert(sym.range).second) if (refcnt <= 0 || !sym.extent.Valid() || !seen.insert(sym.range).second)
continue; continue;
Use use = ToSpell(sym, file->id);
switch (sym.kind) { switch (sym.kind) {
case SymbolKind::Func: { case SymbolKind::Func: {
QueryFunc &func = db->GetFunc(sym); QueryFunc &func = db->GetFunc(sym);
@ -112,28 +103,28 @@ struct Handler_TextDocumentCodeLens
continue; continue;
std::vector<Use> base_uses = GetUsesForAllBases(db, func); std::vector<Use> base_uses = GetUsesForAllBases(db, func);
std::vector<Use> derived_uses = GetUsesForAllDerived(db, func); std::vector<Use> derived_uses = GetUsesForAllDerived(db, func);
Add("ref", {sym.usr, SymbolKind::Func, "uses"}, use, func.uses.size(), Add("ref", {sym.usr, SymbolKind::Func, "uses"}, sym.range,
base_uses.empty()); func.uses.size(), base_uses.empty());
if (base_uses.size()) if (base_uses.size())
Add("b.ref", {sym.usr, SymbolKind::Func, "bases uses"}, use, Add("b.ref", {sym.usr, SymbolKind::Func, "bases uses"}, sym.range,
base_uses.size()); base_uses.size());
if (derived_uses.size()) if (derived_uses.size())
Add("d.ref", {sym.usr, SymbolKind::Func, "derived uses"}, use, Add("d.ref", {sym.usr, SymbolKind::Func, "derived uses"}, sym.range,
derived_uses.size()); derived_uses.size());
if (base_uses.empty()) if (base_uses.empty())
Add("base", {sym.usr, SymbolKind::Func, "bases"}, use, Add("base", {sym.usr, SymbolKind::Func, "bases"}, sym.range,
def->bases.size()); def->bases.size());
Add("derived", {sym.usr, SymbolKind::Func, "derived"}, use, Add("derived", {sym.usr, SymbolKind::Func, "derived"}, sym.range,
func.derived.size()); func.derived.size());
break; break;
} }
case SymbolKind::Type: { case SymbolKind::Type: {
QueryType &type = db->GetType(sym); QueryType &type = db->GetType(sym);
Add("ref", {sym.usr, SymbolKind::Type, "uses"}, use, type.uses.size(), Add("ref", {sym.usr, SymbolKind::Type, "uses"}, sym.range, type.uses.size(),
true); true);
Add("derived", {sym.usr, SymbolKind::Type, "derived"}, use, Add("derived", {sym.usr, SymbolKind::Type, "derived"}, sym.range,
type.derived.size()); type.derived.size());
Add("var", {sym.usr, SymbolKind::Type, "instances"}, use, Add("var", {sym.usr, SymbolKind::Type, "instances"}, sym.range,
type.instances.size()); type.instances.size());
break; break;
} }
@ -142,7 +133,7 @@ struct Handler_TextDocumentCodeLens
const QueryVar::Def *def = var.AnyDef(); const QueryVar::Def *def = var.AnyDef();
if (!def || (def->is_local() && !g_config->codeLens.localVariables)) if (!def || (def->is_local() && !g_config->codeLens.localVariables))
continue; continue;
Add("ref", {sym.usr, SymbolKind::Var, "uses"}, use, var.uses.size(), Add("ref", {sym.usr, SymbolKind::Var, "uses"}, sym.range, var.uses.size(),
def->kind != lsSymbolKind::Macro); def->kind != lsSymbolKind::Macro);
break; break;
} }

View File

@ -71,12 +71,11 @@ struct Handler_TextDocumentDocumentSymbol
if (!wfile) if (!wfile)
return; return;
const auto &symbol2refcnt =
params.all ? file->symbol2refcnt : file->outline2refcnt;
if (params.startLine >= 0) { if (params.startLine >= 0) {
std::vector<lsRange> result; std::vector<lsRange> result;
for (auto [sym, refcnt] : symbol2refcnt) for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && params.startLine <= sym.range.start.line && if (refcnt > 0 && (params.all || sym.extent.Valid()) &&
params.startLine <= sym.range.start.line &&
sym.range.start.line <= params.endLine) sym.range.start.line <= params.endLine)
if (auto loc = GetLsLocation(db, working_files, sym, file_id)) if (auto loc = GetLsLocation(db, working_files, sym, file_id))
result.push_back(loc->range); result.push_back(loc->range);
@ -84,95 +83,75 @@ struct Handler_TextDocumentDocumentSymbol
pipeline::Reply(request->id, result); pipeline::Reply(request->id, result);
} else if (g_config->client.hierarchicalDocumentSymbolSupport) { } else if (g_config->client.hierarchicalDocumentSymbolSupport) {
std::unordered_map<SymbolIdx, std::unique_ptr<lsDocumentSymbol>> sym2ds; std::unordered_map<SymbolIdx, std::unique_ptr<lsDocumentSymbol>> sym2ds;
std::vector<std::pair<const QueryFunc::Def *, lsDocumentSymbol *>> funcs; std::vector<std::pair<std::vector<const void *>, lsDocumentSymbol *>>
std::vector<std::pair<const QueryType::Def *, lsDocumentSymbol *>> types; funcs, types;
for (auto [sym, refcnt] : symbol2refcnt) { for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0) if (refcnt <= 0 || !sym.extent.Valid())
continue; continue;
auto r = sym2ds.try_emplace(SymbolIdx{sym.usr, sym.kind}); auto r = sym2ds.try_emplace(SymbolIdx{sym.usr, sym.kind});
if (!r.second) if (!r.second)
continue; continue;
auto &ds = r.first->second; auto &ds = r.first->second;
ds = std::make_unique<lsDocumentSymbol>(); ds = std::make_unique<lsDocumentSymbol>();
const void *def_ptr = nullptr; std::vector<const void *> def_ptrs;
WithEntity(db, sym, [&](const auto &entity) { WithEntity(db, sym, [&, sym = sym](const auto &entity) {
auto *def = entity.AnyDef(); auto *def = entity.AnyDef();
if (!def) if (!def)
return; return;
ds->name = def->Name(false); ds->name = def->Name(false);
ds->detail = def->Name(true); ds->detail = def->Name(true);
if (auto ls_range = GetLsRange(wfile, sym.range)) {
ds->selectionRange = *ls_range;
ds->range = ds->selectionRange;
if (sym.extent.Valid())
if (auto ls_range1 = GetLsRange(wfile, sym.extent))
ds->range = *ls_range1;
}
// Try to find a definition with spell first.
const void *candidate_def_ptr = nullptr;
for (auto &def : entity.def) for (auto &def : entity.def)
if (def.file_id == file_id && !Ignore(&def)) { if (def.file_id == file_id && !Ignore(&def)) {
ds->kind = def.kind; ds->kind = def.kind;
if (def.kind == lsSymbolKind::Namespace) if (def.spell || def.kind == lsSymbolKind::Namespace)
candidate_def_ptr = &def; def_ptrs.push_back(&def);
if (!def.spell)
continue;
if (auto ls_range = GetLsRange(wfile, def.spell->extent))
ds->range = *ls_range;
else
continue;
if (auto ls_range = GetLsRange(wfile, def.spell->range))
ds->selectionRange = *ls_range;
else
continue;
def_ptr = &def;
break;
} }
// Try to find a declaration.
if (!def_ptr && candidate_def_ptr)
for (auto &decl : entity.declarations)
if (decl.file_id == file_id) {
if (auto ls_range = GetLsRange(wfile, decl.extent))
ds->range = *ls_range;
else
continue;
if (auto ls_range = GetLsRange(wfile, decl.range))
ds->selectionRange = *ls_range;
else
continue;
def_ptr = candidate_def_ptr;
break;
}
}); });
if (!def_ptr) { if (def_ptrs.empty() || !(params.all || sym.role & Role::Definition ||
ds->kind == lsSymbolKind::Namespace)) {
ds.reset(); ds.reset();
continue; continue;
} }
if (sym.kind == SymbolKind::Func) if (sym.kind == SymbolKind::Func)
funcs.emplace_back((const QueryFunc::Def *)def_ptr, ds.get()); funcs.emplace_back(std::move(def_ptrs), ds.get());
else if (sym.kind == SymbolKind::Type) else if (sym.kind == SymbolKind::Type)
types.emplace_back((const QueryType::Def *)def_ptr, ds.get()); types.emplace_back(std::move(def_ptrs), ds.get());
} }
for (auto &[def, ds] : funcs) for (auto &[def_ptrs, ds] : funcs)
for (Usr usr1 : def->vars) { for (const void *def_ptr : def_ptrs)
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var}); for (Usr usr1 : ((const QueryFunc::Def *)def_ptr)->vars) {
if (it != sym2ds.end() && it->second) auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var});
ds->children.push_back(std::move(it->second)); if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (auto &[def_ptrs, ds] : types)
for (const void *def_ptr : def_ptrs) {
auto *def = (const QueryType::Def *)def_ptr;
for (Usr usr1 : def->funcs) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Func});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (Usr usr1 : def->types) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Type});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (auto [usr1, _] : def->vars) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
} }
for (auto &[def, ds] : types) {
for (Usr usr1 : def->funcs) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Func});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (Usr usr1 : def->types) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Type});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (auto [usr1, _] : def->vars) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
}
std::vector<std::unique_ptr<lsDocumentSymbol>> result; std::vector<std::unique_ptr<lsDocumentSymbol>> result;
for (auto &[_, ds] : sym2ds) for (auto &[_, ds] : sym2ds)
if (ds) if (ds)
@ -180,8 +159,10 @@ struct Handler_TextDocumentDocumentSymbol
pipeline::Reply(request->id, result); pipeline::Reply(request->id, result);
} else { } else {
std::vector<lsSymbolInformation> result; std::vector<lsSymbolInformation> result;
for (auto [sym, refcnt] : symbol2refcnt) { for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0) continue; if (refcnt <= 0 || !sym.extent.Valid() ||
!(params.all || sym.role & Role::Definition))
continue;
if (std::optional<lsSymbolInformation> info = if (std::optional<lsSymbolInformation> info =
GetSymbolInfo(db, sym, false)) { GetSymbolInfo(db, sym, false)) {
if ((sym.kind == SymbolKind::Type && if ((sym.kind == SymbolKind::Type &&

View File

@ -224,7 +224,7 @@ void DB::ApplyIndexUpdate(IndexUpdate *u) {
SymbolKind kind, Use &use, int delta) { SymbolKind kind, Use &use, int delta) {
use.file_id = use.file_id =
use.file_id == -1 ? u->file_id : lid2fid.find(use.file_id)->second; use.file_id == -1 ? u->file_id : lid2fid.find(use.file_id)->second;
SymbolRef sym{use.range, usr, kind, use.role}; ExtentRef sym{{use.range, usr, kind, use.role}};
int &v = files[use.file_id].symbol2refcnt[sym]; int &v = files[use.file_id].symbol2refcnt[sym];
v += delta; v += delta;
assert(v >= 0); assert(v >= 0);
@ -233,9 +233,14 @@ void DB::ApplyIndexUpdate(IndexUpdate *u) {
}; };
auto RefDecl = [&](std::unordered_map<int, int> &lid2fid, Usr usr, auto RefDecl = [&](std::unordered_map<int, int> &lid2fid, Usr usr,
SymbolKind kind, DeclRef &dr, int delta) { SymbolKind kind, DeclRef &dr, int delta) {
Ref(lid2fid, usr, kind, dr, delta); dr.file_id =
files[dr.file_id] dr.file_id == -1 ? u->file_id : lid2fid.find(dr.file_id)->second;
.outline2refcnt[SymbolRef{dr.extent, usr, kind, dr.role}] += delta; ExtentRef sym{{dr.range, usr, kind, dr.role}, dr.extent};
int &v = files[dr.file_id].symbol2refcnt[sym];
v += delta;
assert(v >= 0);
if (!v)
files[dr.file_id].symbol2refcnt.erase(sym);
}; };
auto UpdateUses = auto UpdateUses =
@ -369,9 +374,8 @@ void DB::Update(const Lid2file_id &lid2file_id, int file_id,
if (def.spell) { if (def.spell) {
AssignFileId(lid2file_id, file_id, *def.spell); AssignFileId(lid2file_id, file_id, *def.spell);
files[def.spell->file_id].symbol2refcnt[{ files[def.spell->file_id].symbol2refcnt[{
def.spell->range, u.first, SymbolKind::Func, def.spell->role}]++; {def.spell->range, u.first, SymbolKind::Func, def.spell->role},
files[def.spell->file_id].outline2refcnt[{ def.spell->extent}]++;
def.spell->extent, u.first, SymbolKind::Func, def.spell->role}]++;
} }
auto R = func_usr.try_emplace({u.first}, func_usr.size()); auto R = func_usr.try_emplace({u.first}, func_usr.size());
@ -393,9 +397,8 @@ void DB::Update(const Lid2file_id &lid2file_id, int file_id,
if (def.spell) { if (def.spell) {
AssignFileId(lid2file_id, file_id, *def.spell); AssignFileId(lid2file_id, file_id, *def.spell);
files[def.spell->file_id].symbol2refcnt[{ files[def.spell->file_id].symbol2refcnt[{
def.spell->range, u.first, SymbolKind::Type, def.spell->role}]++; {def.spell->range, u.first, SymbolKind::Type, def.spell->role},
files[def.spell->file_id].outline2refcnt[{ def.spell->extent}]++;
def.spell->extent, u.first, SymbolKind::Type, def.spell->role}]++;
} }
auto R = type_usr.try_emplace({u.first}, type_usr.size()); auto R = type_usr.try_emplace({u.first}, type_usr.size());
if (R.second) if (R.second)
@ -416,9 +419,8 @@ void DB::Update(const Lid2file_id &lid2file_id, int file_id,
if (def.spell) { if (def.spell) {
AssignFileId(lid2file_id, file_id, *def.spell); AssignFileId(lid2file_id, file_id, *def.spell);
files[def.spell->file_id].symbol2refcnt[{ files[def.spell->file_id].symbol2refcnt[{
def.spell->range, u.first, SymbolKind::Var, def.spell->role}]++; {def.spell->range, u.first, SymbolKind::Var, def.spell->role},
files[def.spell->file_id].outline2refcnt[{ def.spell->extent}]++;
def.spell->extent, u.first, SymbolKind::Var, def.spell->role}]++;
} }
auto R = var_usr.try_emplace({u.first}, var_usr.size()); auto R = var_usr.try_emplace({u.first}, var_usr.size());
if (R.second) if (R.second)

View File

@ -11,17 +11,13 @@
#include <llvm/ADT/StringMap.h> #include <llvm/ADT/StringMap.h>
namespace llvm { namespace llvm {
template <> struct DenseMapInfo<SymbolRef> { template <> struct DenseMapInfo<ExtentRef> {
static inline SymbolRef getEmptyKey() { return {}; } static inline ExtentRef getEmptyKey() { return {}; }
static inline SymbolRef getTombstoneKey() { static inline ExtentRef getTombstoneKey() { return {{Range(), Usr(-1)}}; }
SymbolRef ret{}; static unsigned getHashValue(ExtentRef sym) {
ret.usr = -1; return std::hash<ExtentRef>()(sym);
return ret;
} }
static unsigned getHashValue(SymbolRef sym) { static bool isEqual(ExtentRef l, ExtentRef r) { return l == r; }
return std::hash<SymbolRef>()(sym);
}
static bool isEqual(SymbolRef l, SymbolRef r) { return l == r; }
}; };
} }
@ -42,8 +38,8 @@ struct QueryFile {
int id = -1; int id = -1;
std::optional<Def> def; std::optional<Def> def;
llvm::DenseMap<SymbolRef, int> symbol2refcnt; // `extent` is valid => declaration; invalid => regular reference
llvm::DenseMap<SymbolRef, int> outline2refcnt; llvm::DenseMap<ExtentRef, int> symbol2refcnt;
}; };
template <typename Q, typename QDef> struct QueryEntity { template <typename Q, typename QDef> struct QueryEntity {