-
Notifications
You must be signed in to change notification settings - Fork 285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add LSP extension request for retrieving macro expansions #892
Conversation
Thanks for starting the work on this @fwcd 🙏🏽 A couple of high-level thoughts, I haven’t looked at the actual code yet:
|
Those are already very helpful insights, thanks! Most of the implementation in this PR is currently just standard boilerplate for adding a request, the actual logic is yet to be implemented. I will definitely have to take a closer look at how this refactoring works and how Xcode uses it, though. Regarding 3 and 4: I've opened swiftlang/vscode-swift#621 with a super basic client implementation of the request that, admittedly, is still in dire need of a good UI. That thread also contains a short discussion of how |
Oh, nice. I didn’t see the VSCode editor extension PR. ❤️ |
b103c49
to
95b4166
Compare
95b4166
to
5cdf908
Compare
This actually ended up being a lot easier to implement than I thought thanks to the semantic refactoring! |
5cdf908
to
3a24271
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that was indeed easy.
I think the major thing to consider here, is how to support LSP functionality inside the expansion buffer itself. It doesn’t need to be part of this PR but I would like to design it in a way that allows us to add semantic functionality to the expansion buffers in the future.
//===----------------------------------------------------------------------===// | ||
|
||
/// Request the expansion of the macro at a given use site. | ||
/// **LSP Extension**. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it would be valuable to link to the rust-analyzer definition of the request? Just so we can check in the future that we’re still in sync with theirs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've already diverged a bit from rust-analyzer by passing a range in the request and by returning positions along with the expansions in the response.
After researching this a bit more, I have found that upstream (LSP) seems to favor a slightly more general approach where the server acts as a content provider for a custom URI scheme, see
- Feature request: Show generated code microsoft/language-server-protocol#689
- Proposal: Allow a language server to provide a reference content for a specific uri scheme microsoft/language-server-protocol#336
Under that model it would probably make more sense to implement macro expansion as a command where the server then invokes workspace/openDocument
/workspace/peekDocument
(or whichever request ends up getting implemented) with a custom URI (swift-macro-expansion://...
).
Until upstream has figured out those details, my initial gut feeling was to stick with a custom request here, since that affords us some flexibility with the precise request design. It shouldn't be too hard to move over to a command-based implementation eventually either.
Reading #892 (comment) however got me thinking, that would actually work really well with the model that upstream LSP favors. I think the request-based design is a bit simpler for now, but maybe we should still move our own implementation in that direction too?
Perhaps
sourcekit-lsp/getReferenceDocument
(client -> server request)sourcekit-lsp/peekDocument
(server -> client request)
and then add a new command that peeks the macro expansion at the given position?
One complication would be that the client would have to support a custom request too, though I don't think that should be that hard to implement.
Another complication would be that SourceKit-LSP already seems to support semantic refactorings via commands. This is super nice in theory, since we wouldn't have to do anything (in principle) to support macro expansions, however (if I understand them correctly), they would actually insert the expansion currently due to being modeled as text edits.
A crazy thought: Let the client pass an additional argument to ExecuteCommandRequest
that contains e.g. a config for how the refactoring is to be applied (e.g. whether the edits should actually be performed or just previewed, e.g. with the proposed sourcekit-lsp/peekDocument
request). This could then be used to show macro expansions without actually applying them, but would even work for other semantic refactorings.
So many options! Perhaps you have some more thoughts on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of having a URL scheme to describe the expanded macro buffers and implementing a sourcekit-lsp/getReferenceDocument
request to get the generated contents to the client. The main advantage I see with it are:
- We have a URI that we can use to talk about the expanded macro buffer. Adding support for semantic functionality within the generated buffer is just a matter of implementing LSP requests for that URL scheme.
- We can use it to show the content of generated interfaces where we currently write the generated interface to disk and then open it.
I don’t quite understand where the sourcekit-lsp/peekDocument
request comes in. My design of this feature would be using ExecuteCommandRequest
.
- The client sends a
textDocument/codeAction
request to the server - If we are at a macro, the server responds with a
Expand Macro
code action that has a command with the custom URI scheme in thearguments
. - If the user decides to pick that action, the client sends a
workspace/executeCommand
for that command. - And now is where it get’s a little tricky: The server replies with some kind of special response (reply of
workspace/executeCommand
isLSPAny?
) that the client understands as: OK, I need to show an inline text buffer with the text of this URI scheme. E.g. the reply is
{
"action": "showInlineBuffer",
"uri": "swift-macro-expansion:///path/to/file.swift?buffer=@swiftmacro_…&line=6&column=8"
}
- The client then sends a
sourcekit-lsp/getReferenceDocument
request to the server with that URI - The server replies with the contents of the buffer to display
Steps 3 and 4 are a little bit roundabout and might not actually be necessary if we just short-circuit the command in the editor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, your showInlineBuffer
reply is effectively what I meant by sourcekit-lsp/peekDocument
(only that it wouldn't be a reply, but a separate request (which would effectively let the server invoke it multiple times, if it so desires).
But until upstream has decided on a specific design, I would be fine either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my mind the client should always be driving the user interaction and the LSP server should just reply to the user’s request, that’s why I think peekDocument
would be a little weird.
Is peekDocument
a request you came up with or is it based on some proposal? I couldn’t find anything related to it in my short search.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and simply return a
swift-macro-expansion://
URI
Which request would this be a reply to (and in which field)? AFAICT CodeAction
doesn’t have a URI field (unless we just shove it in the arguments: [LSPAny]?
)
Edit: I am not sure if there's a way to handle custom responses for commands triggered by a code action though, since code actions are handled by the
vscode-languageclient
library.
Oh, that would indeed be a major limitation, unfortunately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keep our non-standard macro expansion request for now and simply return a
swift-macro-expansion://
with that I meant using the current design from this PR and returning something along the lines of
struct MacroExpansion {
let uri: DocumentURI // = DocumentURI("swift-macro-expansion://...")
let position: Position
}
from sourcekit-lsp/macroExpansion
, which the client then resolves. But, yeah that wouldn't work as a code action, unfortunately, the client would have to implement the command manually (as my vscode-swift
PR currently does).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But, yeah that wouldn't work as a code action, unfortunately, the client would have to implement the command manually (as my
vscode-swift
PR currently does).
That’s the major downside I see with the current design. IIUC it currently offers “Expand Macro” everywhere, even when the cursor isn’t currently on a macro. I think implementing it on top of Code Actions would be the cleanest design that also yields the best UI results.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So implementing it as a command where the server calls the client back seems like the only option that fulfills all requirements (works as a code action and doesn't need a custom response)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that is the case. So your design was the right one all along 😄
Sources/LanguageServerProtocol/Requests/MacroExpansionRequest.swift
Outdated
Show resolved
Hide resolved
//===----------------------------------------------------------------------===// | ||
|
||
/// The expansion of a macro in a source file. | ||
public struct MacroExpansion: ResponseType, Hashable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just an idle thought: This is different from rust-analyzer’s macro expansion, right. Do you know what they use their name
property for?
All I’m thinking here is that the closer we keep the two requests, the easier it might make life for us when/if expand macro becomes part of the LSP spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comments regarding staying in sync with rust-analyzer/LSP above.
public let position: Position | ||
|
||
/// The Swift code that the macro expands to. | ||
public let sourceText: String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to include the buffer name here as well. My thinking is that we could to encode it like
swift-macro-expansion:///path/to/file.swift?buffer=@swiftmacro_...
That way the editor could open the buffer with that URI and we could start supporting semantic functionality (like nested expansion) inside the expansion buffer as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I like the idea (I'll also refer to my longer comment here)
Co-authored-by: Alex Hoppen <alex@alexhoppen.de>
Co-authored-by: Alex Hoppen <alex@ale xhoppen.de>
Hi, before I open a new issue, does this address |
No, that’s completely unrelated. It probably means that some compiler arguments are missing. |
In this case, there is no Package.swift, it's just me opening a folder with Swift files in it. We use Bazel. Should the default arguments be adjusted? |
Let’s discuss this in an issue to not spam this PR because it’s unrelated to this PR. |
This implements #755 by introducing a new, non-standard request for fetching macro expansions, wrapping the corresponding SourceKitD request,
SyntacticMacroExpansion
.Motivation
rust-analyzer
for such a request, see this documentSimilar to what we did with inlay hints last year (#465), my hope is that a macro expansion request could eventually be upstreamed to LSP, given that such a request might be more generally useful to other languages too, as evidenced by the
rust-analyzer
implementation. This would then give clients full flexibility to implement a tailored UI too.Feel free to leave some thoughts on whether this is the right approach to go with or alternative suggestions.
To do
sourcekit-lsp/macroExpansion
) and support structuresSyntacticMacroExpansion
ToolchainLanguageServer
and its implementations to support the requestSwiftLanguageServer.macroExpansion
)nested: Bool
orrecursive: Bool
?)See also
SyntacticMacroExpansion
testSyntacticMacroExpansion
implementation