This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Some nitpicking around the new split (preamble/main) dynamic index
ClosedPublic

Authored by sammccall on Aug 24 2018, 9:46 AM.

Details

Summary
  • DynamicIndex doesn't implement ParsingCallbacks, to make its role clearer. ParsingCallbacks is a separate object owned by the receiving TUScheduler. (I tried to get rid of the "index-like-object that doesn't implement index" but it was too messy).
  • Clarified(?) docs around DynamicIndex - fewer details up front, more details inside.
  • Exposed dynamic index from ClangdServer for memory monitoring and more direct testing of its contents (actual tests not added here, wanted to get this out for review)
  • Removed a redundant and sligthly confusing filename param in a callback

Diff Detail

Repository
rL LLVM

Event Timeline

sammccall created this revision.Aug 24 2018, 9:46 AM
sammccall updated this revision to Diff 162401.Aug 24 2018, 9:50 AM

Add note about the overlap between preamble index across files.

Thanks for the cleanups, mostly NITs from my side.

clangd/ClangdServer.cpp
81 ↗(On Diff #162401)

Maybe return const SymbolIndex& and keep the method const?

101 ↗(On Diff #162401)

Maybe simplify to make_unique<CB>(this)? This should work, right?

122 ↗(On Diff #162401)

I thought it was not true, but now I notice we actually don't index those symbols.
I think we should index them for workspaceSymbols, but not for completion. Any pointers to the code that filters them out?

152 ↗(On Diff #162401)

Any reason to prefer nullptr over the no-op callbacks?
Might be a personal preference, my reasoning is: having an extra check for null before on each of the call sites both adds unnecessary boilerplate and adds an extra branch before the virtual call (which is, technically, another form of a branch).

Not opposed to doing it either way, though.

clangd/ClangdServer.h
195 ↗(On Diff #162401)

Maybe make it const?

clangd/TUScheduler.cpp
632 ↗(On Diff #162401)

Why not ParsingCallbacks* or ParsingCallbacks&? This restricts the lifetime of the passed values.
Our particular use-case LG this way, but it seems there is no reason why TUScheduler should own the callbacks.

sammccall marked 3 inline comments as done.Aug 28 2018, 11:33 AM
sammccall added inline comments.
clangd/ClangdServer.cpp
122 ↗(On Diff #162401)

https://reviews.llvm.org/source/clang-tools-extra/browse/clang-tools-extra/trunk/clangd/index/SymbolCollector.cpp;340828$272

isExpansionInMainFile()

(having this buried so deep hurts readability and probably performance).

But I think this would be the behavior we want for code complete, to keep the indexes tiny and incremental updates fast?
WorkspaceSymbols is indeed a problem here :-( Tradeoffs...

152 ↗(On Diff #162401)

Basically I prefer the old behavior here (when it was std::function).
Having to keep the callbacks object alive is a pain, and the shared instance of the no-op implementation for this purpose seems a little silly.

We don't have the std::function copyability stuff which is a mixed bag but nice for cases like this. But passing the reference from TUScheduler to its ASTWorkers is internal enough that it doesn't bother me, WDYT?

extra check for null before on each of the call sites

Note that the check is actually in the constructor, supporting nullptr is just for brevity (particularly in tests).

sammccall marked an inline comment as done.

Address review comments, add missing dynamicIndex() implementation.

clangd/TUScheduler.cpp
632 ↗(On Diff #162401)

As noted above, this is just like std::function.

(forgot some changes)

ilya-biryukov accepted this revision.Aug 29 2018, 5:54 AM

LGTM

clangd/ClangdServer.cpp
122 ↗(On Diff #162401)

I would assume the number of symbols in the main files is not very large, compared to the ones from the preamble.
We could try indexing those and set a flag they're not for code completion. Should give us a better workspaceSymbol without hurting anything else.

152 ↗(On Diff #162401)

Having to keep the callbacks object alive is a pain, and the shared instance of the no-op implementation for this purpose seems a little silly.

OTOH, this gives flexibility to the callers wrt to the lifetime, i.e. they don't have to create a separate object for the callbacks if they don't want to (one example of this pattern is ClangdLSPServer inheriting DiagnosticsConsumer and ProtocolCallbacks).

But happy to do it either way.

clangd/TUScheduler.h
158 ↗(On Diff #163023)

NIT: maybe mention that this is never null in a comment?

unittests/clangd/TUSchedulerTests.cpp
48 ↗(On Diff #163023)

NIT: maybe add a name of the parameter here for better readability (nullptr can mean many things)?

This revision is now accepted and ready to land.Aug 29 2018, 5:54 AM
sammccall added inline comments.Sep 3 2018, 9:28 AM
clangd/ClangdServer.cpp
152 ↗(On Diff #162401)

I don't think there's any flexibility here, callers *can't* in general create an object (unless they know enough about the lifetimes to store the object appropriately).

For dependencies of the callback (e.g. lambda ref captures), either way works well.
For resources owned by the callback itself, the callee knows better than the caller when they should be freed.

ClangdLSPServer inheriting DiagnosticsConsumer and ProtocolCallbacks

FWIW, the former seems like an antipattern that should be std::function, to me.
The latter is indeed different - interfaces with many methods no longer feel like they fit the lightweight callback pattern, to me...

sammccall updated this revision to Diff 163730.Sep 3 2018, 9:30 AM
sammccall marked 2 inline comments as done.

Address review comments

This revision was automatically updated to reflect the committed changes.
ilya-biryukov added inline comments.Sep 4 2018, 3:47 AM
clangd/ClangdServer.cpp
152 ↗(On Diff #162401)

I don't think there's any flexibility here, callers *can't* in general create an object (unless they know enough about the lifetimes to store the object appropriately).

The only limitation is that their object must outlive the ClangdServer, they have flexibility in making it a separate object, a subobject of some other object, a global static object in case it does not have state.
Any option other than separate object requires extra code when unique_ptr is accepted.
OTOH, I agree that keeping it alive is a pain, and maybe it's the most common use-case anyway.

But again, not opposed to either of these approaches.

FWIW, the former seems like an antipattern that should be std::function, to me.

Agreed, I would also prefer for it to be an std::function or a separate class that implements the interface. To separate the implementation of different concerns (LSP managing vs handling ClangdServer responses) better.
In that sense, making the callee responsible for the lifetime of the object forces this separation on the callers, which actually looks like a good thing.

The latter is indeed different - interfaces with many methods no longer feel like they fit the lightweight callback pattern, to me...

It's hard to draw a line between "lightweight callback pattern" and a non-lightweight one, it feels like this shouldn't affect the API choices (the only clear separation I see is between one callback function vs two and more, since we have nice syntax with lambdas for single-callback case).