|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +published: true |
| 4 | +date: 2030-12-28 11:59:59 |
| 5 | +title: Expansion of Swift Macros in Visual Studio Code |
| 6 | +author: [lokeshtr, ahoppen, adam-fowler] |
| 7 | +--- |
| 8 | + |
| 9 | +`/* DUMMY DATE TO BE CHANGED BEFORE PR MERGE */` We're excited to announce that, with Swift 6.1, you can finally view the generated contents of a Swift Macro in Visual Studio Code and other LSP-based editors such as Neovim using the all-new "Expand Macro" Code Action. |
| 10 | + |
| 11 | +I'm Lokesh T. R. from India. I'm a sophomore currently pursuing my bachelor's degree in Computer Science and Engineering at Vel Tech University in Chennai. I'm thrilled to share with you, my Google Summer of Code 2024 Project which I worked on with my mentors, Alex Hoppen and Adam Fowler. |
| 12 | + |
| 13 | +## Overview |
| 14 | + |
| 15 | +Over the summer, we worked on adding support for expansion of Swift Macros in Visual Studio Code. Our project's main goal is to implement a code action in VS Code that allows users to view the generated contents of a Swift Macro. |
| 16 | + |
| 17 | +Here's what it looks like to show the generated contents of a Swift Macro in a peeked editor when the "Expand Macro" Code Action is invoked by the user: |
| 18 | + |
| 19 | + |
| 20 | + |
| 21 | + |
| 22 | +There were also some stretch goals which include: |
| 23 | + |
| 24 | +1. Bringing Semantic Functionality (such as jump-to-defintion, quick help on hover, Syntax Highlighting, etc.) to the macro expansion being previewed. |
| 25 | +2. Allowing to perform the "Expand Macro" Code Action on a macro that is present in the generated macro expansion to support the expansion of nested macros. |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +And as a bonus, we also worked on supporting macro expansions in other LSP-based editors which by default cannot make use of the LSP extensions that we introduced. |
| 32 | + |
| 33 | + |
| 34 | + |
| 35 | + |
| 36 | +### When can you start using this feature? |
| 37 | + |
| 38 | +This will be available with SourceKit-LSP bundled with Swift 6.1 and the corresponding VS Code Swift Extension Release. |
| 39 | + |
| 40 | +For the curious minds, This feature is available in the `main` branch of `sourcekit-lsp` and `vscode-swift` repositories, right now. |
| 41 | + |
| 42 | +## Implementation Details |
| 43 | + |
| 44 | +### How Swift Language Features work in LSP-based editors |
| 45 | + |
| 46 | +Let's have a look at three key components that you use in your everyday life in an LSP-based editor (e.g. VS Code): |
| 47 | + |
| 48 | +1. VS Code-Swift Extension (Client): |
| 49 | + - primarily acts as a bridge between VS Code and SourceKit-LSP |
| 50 | +2. SourceKit-LSP (Server): |
| 51 | + - provides the necessary editor features to VS Code |
| 52 | + - communicates using Language Server Protocol (LSP) |
| 53 | +3. SourceKitD (Background Service): |
| 54 | + - provides the raw data and operations to SourceKit-LSP |
| 55 | + - baked into the swift compiler |
| 56 | + |
| 57 | +### Main Goal |
| 58 | + |
| 59 | +In order to achieve the main goal, we introduced two new LSP extensions and new custom URL scheme as follows: |
| 60 | + |
| 61 | +```typescript |
| 62 | +// NEW LSP EXTENSIONS (SPECIFICATIONS) |
| 63 | +// ----------------------------------- |
| 64 | + |
| 65 | +// workspace/peekDocuments (sourcekit-lsp -> vscode-swift) |
| 66 | +export interface PeekDocumentsParams { |
| 67 | + uri: DocumentUri; |
| 68 | + position: Position; |
| 69 | + locations: DocumentUri[]; |
| 70 | +} |
| 71 | + |
| 72 | +export interface PeekDocumentsResult { |
| 73 | + success: boolean; |
| 74 | +} |
| 75 | + |
| 76 | +// workspace/getReferenceDocument (vscode-swift -> sourcekit-lsp) |
| 77 | +export interface GetReferenceDocumentParams { |
| 78 | + uri: DocumentUri; |
| 79 | +} |
| 80 | + |
| 81 | +export interface GetReferenceDocumentResult { |
| 82 | + content: string; |
| 83 | +} |
| 84 | + |
| 85 | +// NEW CUSTOM URL SCHEME (SPECIFICATIONS) |
| 86 | +// -------------------------------------- |
| 87 | + |
| 88 | +// Reference Document URL |
| 89 | +("sourcekit-lsp://<document-type>/<display-name>?<parameters>"); |
| 90 | + |
| 91 | +// Reference Document URL with Macro Expansion Document Type |
| 92 | +("sourcekit-lsp://swift-macro-expansion/LaCb-LcCd.swift?fromLine=&fromColumn=&toLine=&toColumn=&bufferName=&parent="); |
| 93 | +``` |
| 94 | + |
| 95 | +- `"workspace/peekDocuments"` allows the SourceKit-LSP Server to show the the contents stored in the `locations` inside the source file `uri` as a peek window |
| 96 | +- Reference Document URL Scheme `sourcekit-lsp://` so that we can use it to encode the necessary data required to generate any form of content to which the URL corresponds to. |
| 97 | +- We introduce the very first `document-type` of the Reference Document URL which is `swift-macro expansion`. This will encode all the necessary data required to generate the macro expansion contents. |
| 98 | +- `"workspace/getReferenceDocument"` is introduced so that the Editor Client can make a request to the SourceKit-LSP Server with the Reference Document URL to fetch its contents. |
| 99 | + |
| 100 | +The way this works is that, we generate the Reference Document URLs from the macro expansions generated using sourcekitd and make a `"workspace/peekDocuments"` request to the Editor Client. In VS Code, this executes the `"editor.action.peekLocations"` command to present a peeked editor. |
| 101 | + |
| 102 | + |
| 103 | + |
| 104 | +Since VS Code can't resolve the contents of Reference Document URL, it makes a `"workspace/getReferenceDocument"` request to the SourceKit-LSP Server, thereby retrieveing the contents and successfully displaying it in the peeked editor. |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +### Stretch Goals |
| 109 | + |
| 110 | +1. Achieving Semantic Functionality (jump-to-definition, quick help on hover, syntax highlighting, etc.): |
| 111 | + - SourceKit-LSP and SourceKitD by default doesn't know how to handle Reference Document URLs. |
| 112 | + - The build arguments of a file is needed to provide semantic functionality. |
| 113 | + - We used the source file's build arguments as build arguments of the reference documents to trick sourcekitd to provide Semantic Functionality for the reference documents. |
| 114 | +2. Achieving Nested Macro Expansion: |
| 115 | + - Due to the flexible nature of the Reference Document URLs, nested macro expansions becomes trivial. |
| 116 | + - The beauty of the Reference Document URLs is that we can nest the Reference Document URLs. |
| 117 | + - We set the `parent` parameter of the macro expansion reference document URL to the source file if it's a first level macro expansion or to the reference document from which the macro expansion originates if it was second or third or n-th level macro expansion. This allows us to expand nested macros efficiently. |
| 118 | + |
| 119 | +### Bonus |
| 120 | + |
| 121 | +While the new LSP Extensions which we introduced, doesn't work out-of-the-box in other LSP-based editors and requires the extension / plugin developers of respective editors to make use of it, We worked on providing a basic form of first level macro expansion support using the standard LSP Requests to support other LSP-based editors. |
| 122 | + |
| 123 | +This works as follows: |
| 124 | + |
| 125 | +1. SourceKitLSP asks SourceKitD for macro expansions. |
| 126 | +2. It then stitches all the macro expansions together in a single file. |
| 127 | +3. It stores the file in some temporary location in the disk. |
| 128 | +4. It then makes a `ShowDocumentRequest` to open and show the file in the editor. |
| 129 | + |
| 130 | +That wraps up the GSoC project successfully! |
| 131 | + |
| 132 | +### What's left to do? |
| 133 | + |
| 134 | +- I will be working on implementing a test case that encompasses all the semantic features in all nested macro levels in sourcekit-lsp. |
| 135 | +- I will also implement some end-to-end test cases in the vscode-swift side which ensures that they really work as intended in a real world situation. |
| 136 | + |
| 137 | +Test cases for freestanding macros, attached macros and nested macros are already in place. |
| 138 | +Code Documentation is also in place for everything that we have implemented so far. |
| 139 | + |
| 140 | +### Future Directions |
| 141 | + |
| 142 | +1. Feedback, Feedback, Feedback! |
| 143 | + |
| 144 | + - We had put so much attention to detail and ensured that every decision made in the design process was thoughtful. |
| 145 | + - But, you may have a better idea, or you may find some issues, and we would love to improve this feature. |
| 146 | + - Please file an issue to suggest an idea or report bugs in the sourcekit-lsp or vscode-swift repository wherever you face the issue. |
| 147 | + |
| 148 | +2. Migrating the non-standard `"workspace/getReferenceDocument"` to the new standard `"workspace/textDocumentContent"` Request |
| 149 | + |
| 150 | + - We should be able to perform this migration when the specifications of LSP 3.18 gets finalised. |
| 151 | + - With this, the custom request need not be handled by the extension / plugin developer of an LSP-based editor. |
| 152 | + |
| 153 | +3. Migrate from generating temporary files in the Disk to Reference Document URLs for other LSP-based editors |
| 154 | + |
| 155 | + - We are currently unable to generate macro expansions on-the-fly in other LSP-based editors since we don't have our LSP Extension for getting the contents of the reference document. |
| 156 | + - Building on top of the previous idea, when LSP 3.18 gets finalised, we should be able to completely eliminate temporary file storage in favour of the standard `"workspace/textDocumentContent"` Request. |
| 157 | + |
| 158 | +4. Adding Semantic Functionality & Nested Macro Expansion support for other LSP-based editors |
| 159 | + |
| 160 | + - This is tricky to implement since the temporary file (or reference document after LSP 3.18) will have all the macro expansions of a given macro in a single file. |
| 161 | + - Although the same approach can be used such as passing the source file's build arguments and `parent` to be the macro's originating file, there will be line and character position shifts that should be taken into consideration. |
| 162 | + - For example, the third macro expansion of a given attached macro will be expected to start at `0:0` but its actual location will be shifted by the first and second macro expansion's content length and three lines of comments that describes where the macro will be present in the original file |
| 163 | + |
| 164 | +5. Other Use Cases for the Reference Document URL |
| 165 | + |
| 166 | + - Reference Document URLs where built from the ground up to allow for encoding the data required to show any form of content, one such example which we discussed today is `swift-macro-expansion` document type. |
| 167 | + - This should allow anyone to show any content of their choice, in a peeked editor or a fully open document, as long as its generated during compile time. |
| 168 | + - The following use cases are not related to macro expansions but uses the Reference Document URL that we created to show other document types: |
| 169 | + - Migrating `OpenInterfaceRequest` to Reference Document URLs to show Swift Generated Interfaces |
| 170 | + - Showing Implicitly generated constructors and Synthesized code upon `Equatable`, `Hashable` and `Codable` Conformances. |
| 171 | + - Showing a preview of generated HTML or rendering the generated HTML from the Mustache template engine by hooking up its CLI. |
| 172 | + - These are just few examples, and the fact that you can show various document formats based on various code generation behaviours brings a wide range of possibilities, and hence, you can bring your own idea. |
| 173 | + - And with LSP 3.18, this will be standardised across all editors, not just VS Code. |
| 174 | + |
| 175 | +## Thanks & Gratitude |
| 176 | + |
| 177 | +I offer my deepest gratitude to my mentors, Alex Hoppen (@ahoppen) and Adam Fowler (@adam-fowler) without whom this journey is impossible. Google Summer of Code is not only the work of myself as a contributor but also the work of my mentors in guiding me and helping me out whenever possible. |
| 178 | + |
| 179 | +Thanks to you both for accepting my project proposal, spending hours setting up my environment, getting me started with PRs, your wonderful ideas and feedback on my ideas, attending the weekly meetings, allowing me to be flexible, detailed PR reviews, giving me directions and all the immediate and quick responses. I hope I gave you both a wonderful GSoC experience. |
| 180 | + |
| 181 | +If not for you two people, this project wouldn't be a success. |
| 182 | + |
| 183 | +### Special Thanks |
| 184 | + |
| 185 | +- Thanks to Fredrik Wieczerkowski (@fwcd) for his initial proof-of-concept on Expansion of Swift Macros in Visual Studio Code and also for his Pull Request that migrates `workspace/getReferenceDocument` to the upcoming `workspace/textDocumentContent`. |
| 186 | +- Thanks to Paul LeMarquand (@plemarquand) for testing out my project in its very early stages. |
| 187 | +- Thanks to Douglas Gregor (@douglas_gregor) and Rintaro Ishizaki (@rintaro) for coming up with their own use cases with the generic LSP Requests that also laid the foundation for macro expansions. |
| 188 | +- Thanks to Mateusz Bąk (@Matejkob) and Paris Pittman (@parispittman) for fueling me with positivity to present my project wherever possible. |
| 189 | +- Thanks to the entire Swift Community always being so welcoming and supportive. |
0 commit comments