This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Propagate CodeActions in addition to Fixes for diagnostics
AbandonedPublic

Authored by kadircet on Feb 7 2021, 11:09 PM.

Details

Reviewers
sammccall
Summary

Fixes only allow modifications to local file containing the diagnostics,
with CodeActions we'll be able to provide changes that can fix the issue
outside of the local file (e.g. build file/header modificiations).

As an implementation detail, we now covert all the fixes to codeactions
directly at publish time, rather than onCodeAction requests. So this
implies some extra copies on each diag release.

Diff Detail

Event Timeline

kadircet created this revision.Feb 7 2021, 11:09 PM
kadircet requested review of this revision.Feb 7 2021, 11:09 PM

Emitting fixes for a diagnostic in another file seems dangerous, what's the intended use case for this?

Emitting fixes for a diagnostic in another file seems dangerous, what's the intended use case for this?

one particular example is fixing layering violations in more strict build systems like bazel/blaze. for example if you #include "a.h" in a file, but build target of that particular file doesn't depend on a target exporting a.h clangd can generate an edit to update the relevant build target to have a dependency on a.h.

clang-tools-extra/clangd/Diagnostics.h
113

we keep broadcasting fixes and actions separately to keep embedders that can't handle codeactions happy.

we can start emitting just codeactions here by converting fixes to codeactions in this layer (all we need is fileuri, and we have it). then embedders can still iterate over all code actions and pick only the edits for mainfile while ignoring the rest.

kadircet updated this revision to Diff 326686.Feb 26 2021, 7:18 AM
  • Update header

Will the fixes/actions this enables really be available synchronously?

AFAIK the only reasons we attach fixes to diagnostics are:

  • because clang fixes are generated at parse time and need to be stored somewhere
  • for the benefit of embedders that need the fix content synchronously with the diagnostic
  • to trigger the "fix available" text

The first two points don't apply if the fix content is too slow to compute synchronously. (And the third can be more simply solved with a boolean flag instead)

The alternative is just to discover these fixes through the code action flow (and allow modules to contribute code actions, which we should do anyway)

Will the fixes/actions this enables really be available synchronously?

The fixes stay as is today, but the action might be just a command that needs to be invoked by the client (so it is still synchronous, in a sense, but instead of an edit embedder just initiates another action on the clangdserver, which will synchronously do "something")

AFAIK the only reasons we attach fixes to diagnostics are:

  • because clang fixes are generated at parse time and need to be stored somewhere
  • for the benefit of embedders that need the fix content synchronously with the diagnostic
  • to trigger the "fix available" text

The first two points don't apply if the fix content is too slow to compute synchronously. (And the third can be more simply solved with a boolean flag instead)
The alternative is just to discover these fixes through the code action flow (and allow modules to contribute code actions, which we should do anyway)

Agreed, I was going this way because currently there was no way to provide a code action to clangd::diagnostic apart from storing it in the fixes map while generating it. I suppose it is more sensible to just go with a new hook on modules that can provide a codeaction given a clangd::diagnostic. (and when the diagnostic is seen, the module can just set a boolean flag or increment some fix counter as you suggested).

Does std::vector<CodeAction> contributeActions(const CodeActionParams&); look like a good signature for the module hook?

One little curveball I'm still kind of hoping we can integrate clang-format into our codeAction workflow, in the hopes of addressing https://github.com/clangd/clangd/issues/429.
While we compute the edits eagerly we don't actually send them until the client request for codeActions so it may be possible to pipe them into clang-format then.

Agreed, I was going this way because currently there was no way to provide a code action to clangd::diagnostic apart from storing it in the fixes map while generating it. I suppose it is more sensible to just go with a new hook on modules that can provide a codeaction given a clangd::diagnostic.

I think this is what you meant, but just to be sure... there's no fundamental need in LSP to make any association between code actions and diagnostics until a codeAction request comes in.
There's no need to put anything into FixesMap. (We do have an extension/C++ API that exposes a different model, but as discussed offline I think we should just not expose this through the extension for now)

Does std::vector<CodeAction> contributeActions(const CodeActionParams&); look like a good signature for the module hook?

A couple of issues:

  • this will require modules to duplicate protocol details (e.g. CodeActionContext.only)
  • many modules that want to provide code actions will want an AST, and we build one for the codeAction request, we should be able to share it
  • it's not obvious how to use these without ClangdLSPServer if the CodeAction wraps a Command whose handler must be bound

We have the Tweak infrastructure that's basically designed to solve all these problems. It has some issues (like you can only return one fix per "class") but these are fixable and would be nice to have fixed in any case.
This means we'd be using a fixed LSP applyTweak command rather than binding a custom command for this action as planned, but it might work well?

Interface would just look like contributeTweaks(vector<unique_ptr<Tweak>>&) or something

WDYT

kadircet abandoned this revision.Mar 23 2021, 4:31 AM

abandoning in favor of D98498

Herald added a project: Restricted Project. · View Herald TranscriptMar 23 2021, 4:31 AM