diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:19:53 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:20:13 +0100 |
commit | 7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch) | |
tree | bdb47765991cb973b2cd5481a088fac636bd326c /docs | |
parent | ca464650eeaca6195891199a93f4f76cf3e7e697 (diff) | |
parent | e65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff) |
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'docs')
-rw-r--r-- | docs/dev/README.md | 356 | ||||
-rw-r--r-- | docs/dev/architecture.md | 24 | ||||
-rw-r--r-- | docs/dev/guide.md | 66 | ||||
-rw-r--r-- | docs/dev/lsp-extensions.md | 8 | ||||
-rw-r--r-- | docs/dev/style.md | 247 | ||||
-rw-r--r-- | docs/dev/syntax.md | 4 |
6 files changed, 392 insertions, 313 deletions
diff --git a/docs/dev/README.md b/docs/dev/README.md index 417352c9d..36edddc70 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md | |||
@@ -14,7 +14,7 @@ To learn more about how rust-analyzer works, see | |||
14 | 14 | ||
15 | We also publish rustdoc docs to pages: | 15 | We also publish rustdoc docs to pages: |
16 | 16 | ||
17 | https://rust-analyzer.github.io/rust-analyzer/ra_ide/ | 17 | https://rust-analyzer.github.io/rust-analyzer/ide/ |
18 | 18 | ||
19 | Various organizational and process issues are discussed in this document. | 19 | Various organizational and process issues are discussed in this document. |
20 | 20 | ||
@@ -50,271 +50,85 @@ We use bors-ng to enforce the [not rocket science](https://graydon2.dreamwidth.o | |||
50 | 50 | ||
51 | You can run `cargo xtask install-pre-commit-hook` to install git-hook to run rustfmt on commit. | 51 | You can run `cargo xtask install-pre-commit-hook` to install git-hook to run rustfmt on commit. |
52 | 52 | ||
53 | # Code organization | ||
54 | |||
55 | All Rust code lives in the `crates` top-level directory, and is organized as a | ||
56 | single Cargo workspace. The `editors` top-level directory contains code for | ||
57 | integrating with editors. Currently, it contains the plugin for VS Code (in | ||
58 | TypeScript). The `docs` top-level directory contains both developer and user | ||
59 | documentation. | ||
60 | |||
61 | We have some automation infra in Rust in the `xtask` package. It contains | ||
62 | stuff like formatting checking, code generation and powers `cargo xtask install`. | ||
63 | The latter syntax is achieved with the help of cargo aliases (see `.cargo` | ||
64 | directory). | ||
65 | |||
66 | # Launching rust-analyzer | 53 | # Launching rust-analyzer |
67 | 54 | ||
68 | Debugging the language server can be tricky: LSP is rather chatty, so driving it | 55 | Debugging the language server can be tricky. |
69 | from the command line is not really feasible, driving it via VS Code requires | 56 | LSP is rather chatty, so driving it from the command line is not really feasible, driving it via VS Code requires interacting with two processes. |
70 | interacting with two processes. | ||
71 | 57 | ||
72 | For this reason, the best way to see how rust-analyzer works is to find a | 58 | For this reason, the best way to see how rust-analyzer works is to find a relevant test and execute it. |
73 | relevant test and execute it (VS Code includes an action for running a single | 59 | VS Code & Emacs include an action for running a single test. |
74 | test). | ||
75 | 60 | ||
76 | However, launching a VS Code instance with a locally built language server is | 61 | Launching a VS Code instance with a locally built language server is also possible. |
77 | possible. There's **"Run Extension (Debug Build)"** launch configuration for this. | 62 | There's **"Run Extension (Debug Build)"** launch configuration for this in VS Code. |
78 | 63 | ||
79 | In general, I use one of the following workflows for fixing bugs and | 64 | In general, I use one of the following workflows for fixing bugs and implementing features: |
80 | implementing features. | ||
81 | 65 | ||
82 | If the problem concerns only internal parts of rust-analyzer (i.e. I don't need | 66 | If the problem concerns only internal parts of rust-analyzer (i.e. I don't need to touch the `rust-analyzer` crate or TypeScript code), there is a unit-test for it. |
83 | to touch the `rust-analyzer` crate or TypeScript code), there is a unit-test for it. | 67 | So, I use **Rust Analyzer: Run** action in VS Code to run this single test, and then just do printf-driven development/debugging. |
84 | So, I use **Rust Analyzer: Run** action in VS Code to run this single test, and | 68 | As a sanity check after I'm done, I use `cargo xtask install --server` and **Reload Window** action in VS Code to verify that the thing works as I expect. |
85 | then just do printf-driven development/debugging. As a sanity check after I'm | ||
86 | done, I use `cargo xtask install --server` and **Reload Window** action in VS | ||
87 | Code to sanity check that the thing works as I expect. | ||
88 | 69 | ||
89 | If the problem concerns only the VS Code extension, I use **Run Installed Extension** | 70 | If the problem concerns only the VS Code extension, I use **Run Installed Extension** launch configuration from `launch.json`. |
90 | launch configuration from `launch.json`. Notably, this uses the usual | 71 | Notably, this uses the usual `rust-analyzer` binary from `PATH`. |
91 | `rust-analyzer` binary from `PATH`. For this, it is important to have the following | 72 | For this, it is important to have the following in your `settings.json` file: |
92 | in your `settings.json` file: | ||
93 | ```json | 73 | ```json |
94 | { | 74 | { |
95 | "rust-analyzer.serverPath": "rust-analyzer" | 75 | "rust-analyzer.serverPath": "rust-analyzer" |
96 | } | 76 | } |
97 | ``` | 77 | ``` |
98 | After I am done with the fix, I use `cargo | 78 | After I am done with the fix, I use `cargo xtask install --client-code` to try the new extension for real. |
99 | xtask install --client-code` to try the new extension for real. | ||
100 | |||
101 | If I need to fix something in the `rust-analyzer` crate, I feel sad because it's | ||
102 | on the boundary between the two processes, and working there is slow. I usually | ||
103 | just `cargo xtask install --server` and poke changes from my live environment. | ||
104 | Note that this uses `--release`, which is usually faster overall, because | ||
105 | loading stdlib into debug version of rust-analyzer takes a lot of time. To speed | ||
106 | things up, sometimes I open a temporary hello-world project which has | ||
107 | `"rust-analyzer.withSysroot": false` in `.code/settings.json`. This flag causes | ||
108 | rust-analyzer to skip loading the sysroot, which greatly reduces the amount of | ||
109 | things rust-analyzer needs to do, and makes printf's more useful. Note that you | ||
110 | should only use the `eprint!` family of macros for debugging: stdout is used for LSP | ||
111 | communication, and `print!` would break it. | ||
112 | |||
113 | If I need to fix something simultaneously in the server and in the client, I | ||
114 | feel even more sad. I don't have a specific workflow for this case. | ||
115 | |||
116 | Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats | ||
117 | path/to/some/rust/crate` to run a batch analysis. This is primarily useful for | ||
118 | performance optimizations, or for bug minimization. | ||
119 | |||
120 | # Code Style & Review Process | ||
121 | |||
122 | Our approach to "clean code" is two-fold: | ||
123 | |||
124 | * We generally don't block PRs on style changes. | ||
125 | * At the same time, all code in rust-analyzer is constantly refactored. | ||
126 | |||
127 | It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author. | ||
128 | Sending small cleanup PRs (like renaming a single local variable) is encouraged. | ||
129 | |||
130 | ## Scale of Changes | ||
131 | |||
132 | Everyone knows that it's better to send small & focused pull requests. | ||
133 | The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs. | ||
134 | |||
135 | The main things to keep an eye on are the boundaries between various components. | ||
136 | There are three kinds of changes: | ||
137 | |||
138 | 1. Internals of a single component are changed. | ||
139 | Specifically, you don't change any `pub` items. | ||
140 | A good example here would be an addition of a new assist. | ||
141 | |||
142 | 2. API of a component is expanded. | ||
143 | Specifically, you add a new `pub` function which wasn't there before. | ||
144 | A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups. | ||
145 | |||
146 | 3. A new dependency between components is introduced. | ||
147 | Specifically, you add a `pub use` reexport from another crate or you add a new line to the `[dependencies]` section of `Cargo.toml`. | ||
148 | A good example here would be adding reference search capability to the assists crates. | ||
149 | |||
150 | For the first group, the change is generally merged as long as: | ||
151 | |||
152 | * it works for the happy case, | ||
153 | * it has tests, | ||
154 | * it doesn't panic for the unhappy case. | ||
155 | |||
156 | For the second group, the change would be subjected to quite a bit of scrutiny and iteration. | ||
157 | The new API needs to be right (or at least easy to change later). | ||
158 | The actual implementation doesn't matter that much. | ||
159 | It's very important to minimize the amount of changed lines of code for changes of the second kind. | ||
160 | Often, you start doing a change of the first kind, only to realise that you need to elevate to a change of the second kind. | ||
161 | In this case, we'll probably ask you to split API changes into a separate PR. | ||
162 | |||
163 | Changes of the third group should be pretty rare, so we don't specify any specific process for them. | ||
164 | That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it! | ||
165 | |||
166 | Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate | ||
167 | https://www.tedinski.com/2018/02/06/system-boundaries.html | ||
168 | |||
169 | ## Minimal Tests | ||
170 | |||
171 | Most tests in rust-analyzer start with a snippet of Rust code. | ||
172 | This snippets should be minimal -- if you copy-paste a snippet of real code into the tests, make sure to remove everything which could be removed. | ||
173 | There are many benefits to this: | ||
174 | |||
175 | * less to read or to scroll past | ||
176 | * easier to understand what exactly is tested | ||
177 | * less stuff printed during printf-debugging | ||
178 | * less time to run test | ||
179 | |||
180 | It also makes sense to format snippets more compactly (for example, by placing enum defitions like `enum E { Foo, Bar }` on a single line), | ||
181 | as long as they are still readable. | ||
182 | |||
183 | ## Order of Imports | ||
184 | |||
185 | We separate import groups with blank lines | ||
186 | |||
187 | ```rust | ||
188 | mod x; | ||
189 | mod y; | ||
190 | |||
191 | use std::{ ... } | ||
192 | |||
193 | use crate_foo::{ ... } | ||
194 | use crate_bar::{ ... } | ||
195 | |||
196 | use crate::{} | ||
197 | |||
198 | use super::{} // but prefer `use crate::` | ||
199 | ``` | ||
200 | |||
201 | ## Import Style | ||
202 | |||
203 | Items from `hir` and `ast` should be used qualified: | ||
204 | |||
205 | ```rust | ||
206 | // Good | ||
207 | use ra_syntax::ast; | ||
208 | |||
209 | fn frobnicate(func: hir::Function, strukt: ast::StructDef) {} | ||
210 | |||
211 | // Not as good | ||
212 | use hir::Function; | ||
213 | use ra_syntax::ast::StructDef; | ||
214 | |||
215 | fn frobnicate(func: Function, strukt: StructDef) {} | ||
216 | ``` | ||
217 | |||
218 | Avoid local `use MyEnum::*` imports. | ||
219 | |||
220 | Prefer `use crate::foo::bar` to `use super::bar`. | ||
221 | |||
222 | ## Order of Items | ||
223 | |||
224 | Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on. | ||
225 | People read things from top to bottom, so place most important things first. | ||
226 | |||
227 | Specifically, if all items except one are private, always put the non-private item on top. | ||
228 | |||
229 | Put `struct`s and `enum`s first, functions and impls last. | ||
230 | |||
231 | Do | ||
232 | |||
233 | ```rust | ||
234 | // Good | ||
235 | struct Foo { | ||
236 | bars: Vec<Bar> | ||
237 | } | ||
238 | |||
239 | struct Bar; | ||
240 | ``` | ||
241 | |||
242 | rather than | ||
243 | 79 | ||
244 | ```rust | 80 | If I need to fix something in the `rust-analyzer` crate, I feel sad because it's on the boundary between the two processes, and working there is slow. |
245 | // Not as good | 81 | I usually just `cargo xtask install --server` and poke changes from my live environment. |
246 | struct Bar; | 82 | Note that this uses `--release`, which is usually faster overall, because loading stdlib into debug version of rust-analyzer takes a lot of time. |
247 | 83 | To speed things up, sometimes I open a temporary hello-world project which has `"rust-analyzer.withSysroot": false` in `.code/settings.json`. | |
248 | struct Foo { | 84 | This flag causes rust-analyzer to skip loading the sysroot, which greatly reduces the amount of things rust-analyzer needs to do, and makes printf's more useful. |
249 | bars: Vec<Bar> | 85 | Note that you should only use the `eprint!` family of macros for debugging: stdout is used for LSP communication, and `print!` would break it. |
250 | } | ||
251 | ``` | ||
252 | 86 | ||
253 | ## Variable Naming | 87 | If I need to fix something simultaneously in the server and in the client, I feel even more sad. |
88 | I don't have a specific workflow for this case. | ||
254 | 89 | ||
255 | We generally use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)). | 90 | Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats path/to/some/rust/crate` to run a batch analysis. |
256 | The default name is a lowercased name of the type: `global_state: GlobalState`. | 91 | This is primarily useful for performance optimizations, or for bug minimization. |
257 | Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`). | ||
258 | The default name for "result of the function" local variable is `res`. | ||
259 | 92 | ||
260 | ## Collection types | 93 | ## Parser Tests |
261 | 94 | ||
262 | We prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. | 95 | Tests for the parser (`parser`) live in the `syntax` crate (see `test_data` directory). |
263 | They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount. | 96 | There are two kinds of tests: |
264 | 97 | ||
265 | ## Preconditions | 98 | * Manually written test cases in `parser/ok` and `parser/err` |
99 | * "Inline" tests in `parser/inline` (these are generated) from comments in `parser` crate. | ||
266 | 100 | ||
267 | Function preconditions should generally be expressed in types and provided by the caller (rather than checked by callee): | 101 | The purpose of inline tests is not to achieve full coverage by test cases, but to explain to the reader of the code what each particular `if` and `match` is responsible for. |
102 | If you are tempted to add a large inline test, it might be a good idea to leave only the simplest example in place, and move the test to a manual `parser/ok` test. | ||
268 | 103 | ||
269 | ```rust | 104 | To update test data, run with `UPDATE_EXPECT` variable: |
270 | // Good | ||
271 | fn frbonicate(walrus: Walrus) { | ||
272 | ... | ||
273 | } | ||
274 | 105 | ||
275 | // Not as good | 106 | ```bash |
276 | fn frobnicate(walrus: Option<Walrus>) { | 107 | env UPDATE_EXPECT=1 cargo qt |
277 | let walrus = match walrus { | ||
278 | Some(it) => it, | ||
279 | None => return, | ||
280 | }; | ||
281 | ... | ||
282 | } | ||
283 | ``` | 108 | ``` |
284 | 109 | ||
285 | ## Premature Pessimization | 110 | After adding a new inline test you need to run `cargo xtest codegen` and also update the test data as described above. |
286 | |||
287 | While we don't specifically optimize code yet, avoid writing code which is slower than it needs to be. | ||
288 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. | ||
289 | 111 | ||
290 | ```rust | 112 | ## TypeScript Tests |
291 | // Good | ||
292 | use itertools::Itertools; | ||
293 | 113 | ||
294 | let (first_word, second_word) = match text.split_ascii_whitespace().collect_tuple() { | 114 | If you change files under `editors/code` and would like to run the tests and linter, install npm and run: |
295 | Some(it) => it, | ||
296 | None => return, | ||
297 | } | ||
298 | 115 | ||
299 | // Not as good | 116 | ```bash |
300 | let words = text.split_ascii_whitespace().collect::<Vec<_>>(); | 117 | cd editors/code |
301 | if words.len() != 2 { | 118 | npm ci |
302 | return | 119 | npm run lint |
303 | } | ||
304 | ``` | 120 | ``` |
305 | 121 | ||
306 | ## Documentation | 122 | # Code organization |
307 | |||
308 | For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. | ||
309 | If the line is too long, you want to split the sentence in two :-) | ||
310 | |||
311 | ## Commit Style | ||
312 | 123 | ||
313 | We don't have specific rules around git history hygiene. | 124 | All Rust code lives in the `crates` top-level directory, and is organized as a single Cargo workspace. |
314 | Maintaining clean git history is encouraged, but not enforced. | 125 | The `editors` top-level directory contains code for integrating with editors. |
315 | We use rebase workflow, it's OK to rewrite history during PR review process. | 126 | Currently, it contains the plugin for VS Code (in TypeScript). |
127 | The `docs` top-level directory contains both developer and user documentation. | ||
316 | 128 | ||
317 | Avoid @mentioning people in commit messages and pull request descriptions (they are added to commit message by bors), as such messages create a lot of duplicate notification traffic during rebases. | 129 | We have some automation infra in Rust in the `xtask` package. |
130 | It contains stuff like formatting checking, code generation and powers `cargo xtask install`. | ||
131 | The latter syntax is achieved with the help of cargo aliases (see `.cargo` directory). | ||
318 | 132 | ||
319 | # Architecture Invariants | 133 | # Architecture Invariants |
320 | 134 | ||
@@ -334,50 +148,31 @@ Internal representations are lowered to LSP in the `rust-analyzer` crate (the on | |||
334 | 148 | ||
335 | ## IDE/Compiler split | 149 | ## IDE/Compiler split |
336 | 150 | ||
337 | There's a semi-hard split between "compiler" and "IDE", at the `ra_hir` crate. | 151 | There's a semi-hard split between "compiler" and "IDE", at the `hir` crate. |
338 | Compiler derives new facts about source code. | 152 | Compiler derives new facts about source code. |
339 | It explicitly acknowledges that not all info is available (i.e. you can't look at types during name resolution). | 153 | It explicitly acknowledges that not all info is available (i.e. you can't look at types during name resolution). |
340 | 154 | ||
341 | IDE assumes that all information is available at all times. | 155 | IDE assumes that all information is available at all times. |
342 | 156 | ||
343 | IDE should use only types from `ra_hir`, and should not depend on the underling compiler types. | 157 | IDE should use only types from `hir`, and should not depend on the underling compiler types. |
344 | `ra_hir` is a facade. | 158 | `hir` is a facade. |
345 | 159 | ||
346 | ## IDE API | 160 | ## IDE API |
347 | 161 | ||
348 | The main IDE crate (`ra_ide`) uses "Plain Old Data" for the API. | 162 | The main IDE crate (`ide`) uses "Plain Old Data" for the API. |
349 | Rather than talking in definitions and references, it talks in Strings and textual offsets. | 163 | Rather than talking in definitions and references, it talks in Strings and textual offsets. |
350 | In general, API is centered around UI concerns -- the result of the call is what the user sees in the editor, and not what the compiler sees underneath. | 164 | In general, API is centered around UI concerns -- the result of the call is what the user sees in the editor, and not what the compiler sees underneath. |
351 | The results are 100% Rust specific though. | 165 | The results are 100% Rust specific though. |
166 | Shout outs to LSP developers for popularizing the idea that "UI" is a good place to draw a boundary at. | ||
352 | 167 | ||
353 | ## Parser Tests | 168 | ## CI |
354 | |||
355 | Tests for the parser (`ra_parser`) live in the `ra_syntax` crate (see `test_data` directory). | ||
356 | There are two kinds of tests: | ||
357 | |||
358 | * Manually written test cases in `parser/ok` and `parser/err` | ||
359 | * "Inline" tests in `parser/inline` (these are generated) from comments in `ra_parser` crate. | ||
360 | |||
361 | The purpose of inline tests is not to achieve full coverage by test cases, but to explain to the reader of the code what each particular `if` and `match` is responsible for. | ||
362 | If you are tempted to add a large inline test, it might be a good idea to leave only the simplest example in place, and move the test to a manual `parser/ok` test. | ||
363 | |||
364 | To update test data, run with `UPDATE_EXPECT` variable: | ||
365 | |||
366 | ```bash | ||
367 | env UPDATE_EXPECT=1 cargo qt | ||
368 | ``` | ||
369 | 169 | ||
370 | After adding a new inline test you need to run `cargo xtest codegen` and also update the test data as described above. | 170 | CI does not test rust-analyzer, CI is a core part of rust-analyzer, and is maintained with above average standard of quality. |
171 | CI is reproducible -- it can only be broken by changes to files in this repository, any dependence on externalities is a bug. | ||
371 | 172 | ||
372 | ## TypeScript Tests | 173 | # Code Style & Review Process |
373 | |||
374 | If you change files under `editors/code` and would like to run the tests and linter, install npm and run: | ||
375 | 174 | ||
376 | ```bash | 175 | Do see [./style.md](./style.md). |
377 | cd editors/code | ||
378 | npm ci | ||
379 | npm run lint | ||
380 | ``` | ||
381 | 176 | ||
382 | # Logging | 177 | # Logging |
383 | 178 | ||
@@ -445,3 +240,34 @@ For measuring time of incremental analysis, use either of these: | |||
445 | $ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --highlight ../chalk/chalk-engine/src/logic.rs | 240 | $ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --highlight ../chalk/chalk-engine/src/logic.rs |
446 | $ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --complete ../chalk/chalk-engine/src/logic.rs:94:0 | 241 | $ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --complete ../chalk/chalk-engine/src/logic.rs:94:0 |
447 | ``` | 242 | ``` |
243 | |||
244 | # Release Process | ||
245 | |||
246 | Release process is handled by `release`, `dist` and `promote` xtasks, `release` being the main one. | ||
247 | |||
248 | `release` assumes that you have checkouts of `rust-analyzer`, `rust-analyzer.github.io`, and `rust-lang/rust` in the same directory: | ||
249 | |||
250 | ``` | ||
251 | ./rust-analyzer | ||
252 | ./rust-analyzer.github.io | ||
253 | ./rust-rust-analyzer # Note the name! | ||
254 | ``` | ||
255 | |||
256 | Additionally, it assumes that remote for `rust-analyzer` is called `upstream` (I use `origin` to point to my fork). | ||
257 | |||
258 | Release steps: | ||
259 | |||
260 | 1. Inside rust-analyzer, run `cargo xtask release`. This will: | ||
261 | * checkout the `release` branch | ||
262 | * reset it to `upstream/nightly` | ||
263 | * push it to `upstream`. This triggers GitHub Actions which: | ||
264 | * runs `cargo xtask dist` to package binaries and VS Code extension | ||
265 | * makes a GitHub release | ||
266 | * pushes VS Code extension to the marketplace | ||
267 | * create new changelog in `rust-analyzer.github.io` | ||
268 | * create `rust-analyzer.github.io/git.log` file with the log of merge commits since last release | ||
269 | 2. While the release is in progress, fill-in the changelog using `git.log` | ||
270 | 3. Commit & push the changelog | ||
271 | 4. Tweet | ||
272 | 5. Inside `rust-analyzer`, run `cargo xtask promote` -- this will create a PR to rust-lang/rust updating rust-analyzer's submodule. | ||
273 | Self-approve the PR. | ||
diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index d0c6eea61..6f1377f2f 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md | |||
@@ -56,7 +56,7 @@ In particular, `cargo xtask codegen` generates: | |||
56 | 2. [`ast/generated`](https://github.com/rust-analyzer/rust-analyzer/blob/a0be39296d2925972cacd9fbf8b5fb258fad6947/crates/ra_syntax/src/ast/generated.rs) | 56 | 2. [`ast/generated`](https://github.com/rust-analyzer/rust-analyzer/blob/a0be39296d2925972cacd9fbf8b5fb258fad6947/crates/ra_syntax/src/ast/generated.rs) |
57 | -- AST data structure. | 57 | -- AST data structure. |
58 | 58 | ||
59 | 3. [`doc_tests/generated`](https://github.com/rust-analyzer/rust-analyzer/blob/a0be39296d2925972cacd9fbf8b5fb258fad6947/crates/ra_assists/src/doc_tests/generated.rs), | 59 | 3. [`doc_tests/generated`](https://github.com/rust-analyzer/rust-analyzer/blob/a0be39296d2925972cacd9fbf8b5fb258fad6947/crates/assists/src/doc_tests/generated.rs), |
60 | [`test_data/parser/inline`](https://github.com/rust-analyzer/rust-analyzer/tree/a0be39296d2925972cacd9fbf8b5fb258fad6947/crates/ra_syntax/test_data/parser/inline) | 60 | [`test_data/parser/inline`](https://github.com/rust-analyzer/rust-analyzer/tree/a0be39296d2925972cacd9fbf8b5fb258fad6947/crates/ra_syntax/test_data/parser/inline) |
61 | -- tests for assists and the parser. | 61 | -- tests for assists and the parser. |
62 | 62 | ||
@@ -64,7 +64,7 @@ The source for 1 and 2 is in [`ast_src.rs`](https://github.com/rust-analyzer/rus | |||
64 | 64 | ||
65 | ## Code Walk-Through | 65 | ## Code Walk-Through |
66 | 66 | ||
67 | ### `crates/ra_syntax`, `crates/ra_parser` | 67 | ### `crates/ra_syntax`, `crates/parser` |
68 | 68 | ||
69 | Rust syntax tree structure and parser. See | 69 | Rust syntax tree structure and parser. See |
70 | [RFC](https://github.com/rust-lang/rfcs/pull/2256) and [./syntax.md](./syntax.md) for some design notes. | 70 | [RFC](https://github.com/rust-lang/rfcs/pull/2256) and [./syntax.md](./syntax.md) for some design notes. |
@@ -92,17 +92,17 @@ in particular: it shows off various methods of working with syntax tree. | |||
92 | See [#93](https://github.com/rust-analyzer/rust-analyzer/pull/93) for an example PR which | 92 | See [#93](https://github.com/rust-analyzer/rust-analyzer/pull/93) for an example PR which |
93 | fixes a bug in the grammar. | 93 | fixes a bug in the grammar. |
94 | 94 | ||
95 | ### `crates/ra_db` | 95 | ### `crates/base_db` |
96 | 96 | ||
97 | We use the [salsa](https://github.com/salsa-rs/salsa) crate for incremental and | 97 | We use the [salsa](https://github.com/salsa-rs/salsa) crate for incremental and |
98 | on-demand computation. Roughly, you can think of salsa as a key-value store, but | 98 | on-demand computation. Roughly, you can think of salsa as a key-value store, but |
99 | it also can compute derived values using specified functions. The `ra_db` crate | 99 | it also can compute derived values using specified functions. The `base_db` crate |
100 | provides basic infrastructure for interacting with salsa. Crucially, it | 100 | provides basic infrastructure for interacting with salsa. Crucially, it |
101 | defines most of the "input" queries: facts supplied by the client of the | 101 | defines most of the "input" queries: facts supplied by the client of the |
102 | analyzer. Reading the docs of the `ra_db::input` module should be useful: | 102 | analyzer. Reading the docs of the `base_db::input` module should be useful: |
103 | everything else is strictly derived from those inputs. | 103 | everything else is strictly derived from those inputs. |
104 | 104 | ||
105 | ### `crates/ra_hir*` crates | 105 | ### `crates/hir*` crates |
106 | 106 | ||
107 | HIR provides high-level "object oriented" access to Rust code. | 107 | HIR provides high-level "object oriented" access to Rust code. |
108 | 108 | ||
@@ -113,12 +113,12 @@ is responsible for guessing a HIR for a particular source position. | |||
113 | 113 | ||
114 | Underneath, HIR works on top of salsa, using a `HirDatabase` trait. | 114 | Underneath, HIR works on top of salsa, using a `HirDatabase` trait. |
115 | 115 | ||
116 | `ra_hir_xxx` crates have a strong ECS flavor, in that they work with raw ids and | 116 | `hir_xxx` crates have a strong ECS flavor, in that they work with raw ids and |
117 | directly query the database. | 117 | directly query the database. |
118 | 118 | ||
119 | The top-level `ra_hir` façade crate wraps ids into a more OO-flavored API. | 119 | The top-level `hir` façade crate wraps ids into a more OO-flavored API. |
120 | 120 | ||
121 | ### `crates/ra_ide` | 121 | ### `crates/ide` |
122 | 122 | ||
123 | A stateful library for analyzing many Rust files as they change. `AnalysisHost` | 123 | A stateful library for analyzing many Rust files as they change. `AnalysisHost` |
124 | is a mutable entity (clojure's atom) which holds the current state, incorporates | 124 | is a mutable entity (clojure's atom) which holds the current state, incorporates |
@@ -136,11 +136,11 @@ offsets and strings as output. This works on top of rich code model powered by | |||
136 | 136 | ||
137 | ### `crates/rust-analyzer` | 137 | ### `crates/rust-analyzer` |
138 | 138 | ||
139 | An LSP implementation which wraps `ra_ide` into a language server protocol. | 139 | An LSP implementation which wraps `ide` into a language server protocol. |
140 | 140 | ||
141 | ### `ra_vfs` | 141 | ### `ra_vfs` |
142 | 142 | ||
143 | Although `hir` and `ra_ide` don't do any IO, we need to be able to read | 143 | Although `hir` and `ide` don't do any IO, we need to be able to read |
144 | files from disk at the end of the day. This is what `ra_vfs` does. It also | 144 | files from disk at the end of the day. This is what `ra_vfs` does. It also |
145 | manages overlays: "dirty" files in the editor, whose "true" contents is | 145 | manages overlays: "dirty" files in the editor, whose "true" contents is |
146 | different from data on disk. This is more or less the single really | 146 | different from data on disk. This is more or less the single really |
@@ -161,7 +161,7 @@ disk. For this reason, we try to avoid writing too many tests on this boundary: | |||
161 | in a statically typed language, it's hard to make an error in the protocol | 161 | in a statically typed language, it's hard to make an error in the protocol |
162 | itself if messages are themselves typed. | 162 | itself if messages are themselves typed. |
163 | 163 | ||
164 | The middle, and most important, boundary is `ra_ide`. Unlike | 164 | The middle, and most important, boundary is `ide`. Unlike |
165 | `rust-analyzer`, which exposes API, `ide` uses Rust API and is intended to | 165 | `rust-analyzer`, which exposes API, `ide` uses Rust API and is intended to |
166 | use by various tools. Typical test creates an `AnalysisHost`, calls some | 166 | use by various tools. Typical test creates an `AnalysisHost`, calls some |
167 | `Analysis` functions and compares the results against expectation. | 167 | `Analysis` functions and compares the results against expectation. |
diff --git a/docs/dev/guide.md b/docs/dev/guide.md index c3252f1f6..b5a5d7c93 100644 --- a/docs/dev/guide.md +++ b/docs/dev/guide.md | |||
@@ -40,8 +40,8 @@ terms of files and offsets, and **not** in terms of Rust concepts like structs, | |||
40 | traits, etc. The "typed" API with Rust specific types is slightly lower in the | 40 | traits, etc. The "typed" API with Rust specific types is slightly lower in the |
41 | stack, we'll talk about it later. | 41 | stack, we'll talk about it later. |
42 | 42 | ||
43 | [`AnalysisHost`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/lib.rs#L265-L284 | 43 | [`AnalysisHost`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/lib.rs#L265-L284 |
44 | [`Analysis`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/lib.rs#L291-L478 | 44 | [`Analysis`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/lib.rs#L291-L478 |
45 | 45 | ||
46 | The reason for this separation of `Analysis` and `AnalysisHost` is that we want to apply | 46 | The reason for this separation of `Analysis` and `AnalysisHost` is that we want to apply |
47 | changes "uniquely", but we might also want to fork an `Analysis` and send it to | 47 | changes "uniquely", but we might also want to fork an `Analysis` and send it to |
@@ -69,7 +69,7 @@ the `AnalysisHost::apply_change` method, which accepts a single argument, a | |||
69 | "transaction", so it suffices to study its methods to understand all of the | 69 | "transaction", so it suffices to study its methods to understand all of the |
70 | input data. | 70 | input data. |
71 | 71 | ||
72 | [`AnalysisChange`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/lib.rs#L119-L167 | 72 | [`AnalysisChange`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/lib.rs#L119-L167 |
73 | 73 | ||
74 | The `(add|change|remove)_file` methods control the set of the input files, where | 74 | The `(add|change|remove)_file` methods control the set of the input files, where |
75 | each file has an integer id (`FileId`, picked by the client), text (`String`) | 75 | each file has an integer id (`FileId`, picked by the client), text (`String`) |
@@ -253,13 +253,13 @@ All analyzer information is stored in a salsa database. `Analysis` and | |||
253 | `AnalysisHost` types are newtype wrappers for [`RootDatabase`] -- a salsa | 253 | `AnalysisHost` types are newtype wrappers for [`RootDatabase`] -- a salsa |
254 | database. | 254 | database. |
255 | 255 | ||
256 | [`RootDatabase`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/db.rs#L88-L134 | 256 | [`RootDatabase`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/db.rs#L88-L134 |
257 | 257 | ||
258 | Salsa input queries are defined in [`FilesDatabase`] (which is a part of | 258 | Salsa input queries are defined in [`FilesDatabase`] (which is a part of |
259 | `RootDatabase`). They closely mirror the familiar `AnalysisChange` structure: | 259 | `RootDatabase`). They closely mirror the familiar `AnalysisChange` structure: |
260 | indeed, what `apply_change` does is it sets the values of input queries. | 260 | indeed, what `apply_change` does is it sets the values of input queries. |
261 | 261 | ||
262 | [`FilesDatabase`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_db/src/input.rs#L150-L174 | 262 | [`FilesDatabase`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/base_db/src/input.rs#L150-L174 |
263 | 263 | ||
264 | ## From text to semantic model | 264 | ## From text to semantic model |
265 | 265 | ||
@@ -275,7 +275,7 @@ several times, with different sets of `cfg`s enabled. The IDE-specific task of | |||
275 | mapping source code position into a semantic model is inherently imprecise for | 275 | mapping source code position into a semantic model is inherently imprecise for |
276 | this reason, and is handled by the [`source_binder`]. | 276 | this reason, and is handled by the [`source_binder`]. |
277 | 277 | ||
278 | [`source_binder`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/source_binder.rs | 278 | [`source_binder`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/source_binder.rs |
279 | 279 | ||
280 | The semantic interface is declared in the [`code_model_api`] module. Each entity is | 280 | The semantic interface is declared in the [`code_model_api`] module. Each entity is |
281 | identified by an integer ID and has a bunch of methods which take a salsa database | 281 | identified by an integer ID and has a bunch of methods which take a salsa database |
@@ -283,8 +283,8 @@ as an argument and returns other entities (which are also IDs). Internally, thes | |||
283 | methods invoke various queries on the database to build the model on demand. | 283 | methods invoke various queries on the database to build the model on demand. |
284 | Here's [the list of queries]. | 284 | Here's [the list of queries]. |
285 | 285 | ||
286 | [`code_model_api`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/code_model_api.rs | 286 | [`code_model_api`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/code_model_api.rs |
287 | [the list of queries]: https://github.com/rust-analyzer/rust-analyzer/blob/7e84440e25e19529e4ff8a66e521d1b06349c6ec/crates/ra_hir/src/db.rs#L20-L106 | 287 | [the list of queries]: https://github.com/rust-analyzer/rust-analyzer/blob/7e84440e25e19529e4ff8a66e521d1b06349c6ec/crates/hir/src/db.rs#L20-L106 |
288 | 288 | ||
289 | The first step of building the model is parsing the source code. | 289 | The first step of building the model is parsing the source code. |
290 | 290 | ||
@@ -341,7 +341,7 @@ The algorithm for building a tree of modules is to start with a crate root | |||
341 | declarations and recursively process child modules. This is handled by the | 341 | declarations and recursively process child modules. This is handled by the |
342 | [`module_tree_query`], with two slight variations. | 342 | [`module_tree_query`], with two slight variations. |
343 | 343 | ||
344 | [`module_tree_query`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/module_tree.rs#L116-L123 | 344 | [`module_tree_query`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/module_tree.rs#L116-L123 |
345 | 345 | ||
346 | First, rust-analyzer builds a module tree for all crates in a source root | 346 | First, rust-analyzer builds a module tree for all crates in a source root |
347 | simultaneously. The main reason for this is historical (`module_tree` predates | 347 | simultaneously. The main reason for this is historical (`module_tree` predates |
@@ -364,7 +364,7 @@ the same, we don't have to re-execute [`module_tree_query`]. In fact, we only | |||
364 | need to re-execute it when we add/remove new files or when we change mod | 364 | need to re-execute it when we add/remove new files or when we change mod |
365 | declarations. | 365 | declarations. |
366 | 366 | ||
367 | [`submodules_query`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/module_tree.rs#L41 | 367 | [`submodules_query`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/module_tree.rs#L41 |
368 | 368 | ||
369 | We store the resulting modules in a `Vec`-based indexed arena. The indices in | 369 | We store the resulting modules in a `Vec`-based indexed arena. The indices in |
370 | the arena becomes module IDs. And this brings us to the next topic: | 370 | the arena becomes module IDs. And this brings us to the next topic: |
@@ -392,8 +392,8 @@ integers which can "intern" a location and return an integer ID back. The salsa | |||
392 | database we use includes a couple of [interners]. How to "garbage collect" | 392 | database we use includes a couple of [interners]. How to "garbage collect" |
393 | unused locations is an open question. | 393 | unused locations is an open question. |
394 | 394 | ||
395 | [`LocationInterner`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_db/src/loc2id.rs#L65-L71 | 395 | [`LocationInterner`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/base_db/src/loc2id.rs#L65-L71 |
396 | [interners]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/db.rs#L22-L23 | 396 | [interners]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/db.rs#L22-L23 |
397 | 397 | ||
398 | For example, we use `LocationInterner` to assign IDs to definitions of functions, | 398 | For example, we use `LocationInterner` to assign IDs to definitions of functions, |
399 | structs, enums, etc. The location, [`DefLoc`] contains two bits of information: | 399 | structs, enums, etc. The location, [`DefLoc`] contains two bits of information: |
@@ -407,7 +407,7 @@ using offsets, text ranges or syntax trees as keys and values for queries. What | |||
407 | we do instead is we store "index" of the item among all of the items of a file | 407 | we do instead is we store "index" of the item among all of the items of a file |
408 | (so, a positional based ID, but localized to a single file). | 408 | (so, a positional based ID, but localized to a single file). |
409 | 409 | ||
410 | [`DefLoc`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/ids.rs#L127-L139 | 410 | [`DefLoc`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/ids.rs#L127-L139 |
411 | 411 | ||
412 | One thing we've glossed over for the time being is support for macros. We have | 412 | One thing we've glossed over for the time being is support for macros. We have |
413 | only proof of concept handling of macros at the moment, but they are extremely | 413 | only proof of concept handling of macros at the moment, but they are extremely |
@@ -440,7 +440,7 @@ terms of `HirFileId`! This does not recur infinitely though: any chain of | |||
440 | `HirFileId`s bottoms out in `HirFileId::FileId`, that is, some source file | 440 | `HirFileId`s bottoms out in `HirFileId::FileId`, that is, some source file |
441 | actually written by the user. | 441 | actually written by the user. |
442 | 442 | ||
443 | [`HirFileId`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/ids.rs#L18-L125 | 443 | [`HirFileId`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/ids.rs#L18-L125 |
444 | 444 | ||
445 | Now that we understand how to identify a definition, in a source or in a | 445 | Now that we understand how to identify a definition, in a source or in a |
446 | macro-generated file, we can discuss name resolution a bit. | 446 | macro-generated file, we can discuss name resolution a bit. |
@@ -454,14 +454,14 @@ each module into a position-independent representation which does not change if | |||
454 | we modify bodies of the items. After that we [loop] resolving all imports until | 454 | we modify bodies of the items. After that we [loop] resolving all imports until |
455 | we've reached a fixed point. | 455 | we've reached a fixed point. |
456 | 456 | ||
457 | [lower]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/nameres/lower.rs#L113-L117 | 457 | [lower]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/lower.rs#L113-L117 |
458 | [loop]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/nameres.rs#L186-L196 | 458 | [loop]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres.rs#L186-L196 |
459 | 459 | ||
460 | And, given all our preparation with IDs and a position-independent representation, | 460 | And, given all our preparation with IDs and a position-independent representation, |
461 | it is satisfying to [test] that typing inside function body does not invalidate | 461 | it is satisfying to [test] that typing inside function body does not invalidate |
462 | name resolution results. | 462 | name resolution results. |
463 | 463 | ||
464 | [test]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/nameres/tests.rs#L376 | 464 | [test]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/tests.rs#L376 |
465 | 465 | ||
466 | An interesting fact about name resolution is that it "erases" all of the | 466 | An interesting fact about name resolution is that it "erases" all of the |
467 | intermediate paths from the imports: in the end, we know which items are defined | 467 | intermediate paths from the imports: in the end, we know which items are defined |
@@ -496,10 +496,10 @@ there's an intermediate [projection query] which returns only the first | |||
496 | position-independent part of the lowering. The result of this query is stable. | 496 | position-independent part of the lowering. The result of this query is stable. |
497 | Naturally, name resolution [uses] this stable projection query. | 497 | Naturally, name resolution [uses] this stable projection query. |
498 | 498 | ||
499 | [imports]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/nameres/lower.rs#L52-L59 | 499 | [imports]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/lower.rs#L52-L59 |
500 | [`SourceMap`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/nameres/lower.rs#L52-L59 | 500 | [`SourceMap`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/lower.rs#L52-L59 |
501 | [projection query]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/nameres/lower.rs#L97-L103 | 501 | [projection query]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/lower.rs#L97-L103 |
502 | [uses]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/query_definitions.rs#L49 | 502 | [uses]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/query_definitions.rs#L49 |
503 | 503 | ||
504 | ## Type inference | 504 | ## Type inference |
505 | 505 | ||
@@ -521,10 +521,10 @@ construct a mapping from `ExprId`s to types. | |||
521 | 521 | ||
522 | [@flodiebold]: https://github.com/flodiebold | 522 | [@flodiebold]: https://github.com/flodiebold |
523 | [#327]: https://github.com/rust-analyzer/rust-analyzer/pull/327 | 523 | [#327]: https://github.com/rust-analyzer/rust-analyzer/pull/327 |
524 | [lower the AST]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/expr.rs | 524 | [lower the AST]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/expr.rs |
525 | [positional ID]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/expr.rs#L13-L15 | 525 | [positional ID]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/expr.rs#L13-L15 |
526 | [a source map]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/expr.rs#L41-L44 | 526 | [a source map]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/expr.rs#L41-L44 |
527 | [type inference]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_hir/src/ty.rs#L1208-L1223 | 527 | [type inference]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/hir/src/ty.rs#L1208-L1223 |
528 | 528 | ||
529 | ## Tying it all together: completion | 529 | ## Tying it all together: completion |
530 | 530 | ||
@@ -565,11 +565,11 @@ the type to completion. | |||
565 | [schedule it on the threadpool]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L428 | 565 | [schedule it on the threadpool]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L428 |
566 | [catch]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L436-L442 | 566 | [catch]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L436-L442 |
567 | [the handler]: https://salsa.zulipchat.com/#narrow/stream/181542-rfcs.2Fsalsa-query-group/topic/design.20next.20steps | 567 | [the handler]: https://salsa.zulipchat.com/#narrow/stream/181542-rfcs.2Fsalsa-query-group/topic/design.20next.20steps |
568 | [ask analysis for completion]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/lib.rs#L439-L444 | 568 | [ask analysis for completion]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/lib.rs#L439-L444 |
569 | [completion implementation]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/completion.rs#L46-L62 | 569 | [completion implementation]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion.rs#L46-L62 |
570 | [`CompletionContext`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/completion/completion_context.rs#L14-L37 | 570 | [`CompletionContext`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/completion_context.rs#L14-L37 |
571 | ["IntelliJ Trick"]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/completion/completion_context.rs#L72-L75 | 571 | ["IntelliJ Trick"]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/completion_context.rs#L72-L75 |
572 | [find an ancestor `fn` node]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/completion/completion_context.rs#L116-L120 | 572 | [find an ancestor `fn` node]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/completion_context.rs#L116-L120 |
573 | [semantic model]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/completion/completion_context.rs#L123 | 573 | [semantic model]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/completion_context.rs#L123 |
574 | [series of independent completion routines]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/completion.rs#L52-L59 | 574 | [series of independent completion routines]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion.rs#L52-L59 |
575 | [`complete_dot`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/completion/complete_dot.rs#L6-L22 | 575 | [`complete_dot`]: https://github.com/rust-analyzer/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/complete_dot.rs#L6-L22 |
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 1be01fd88..2e3133449 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md | |||
@@ -412,7 +412,13 @@ Reloads project information (that is, re-executes `cargo metadata`). | |||
412 | 412 | ||
413 | **Method:** `rust-analyzer/status` | 413 | **Method:** `rust-analyzer/status` |
414 | 414 | ||
415 | **Notification:** `"loading" | "ready" | "invalid" | "needsReload"` | 415 | **Notification:** |
416 | |||
417 | ```typescript | ||
418 | interface StatusParams { | ||
419 | status: "loading" | "ready" | "invalid" | "needsReload", | ||
420 | } | ||
421 | ``` | ||
416 | 422 | ||
417 | This notification is sent from server to client. | 423 | This notification is sent from server to client. |
418 | The client can use it to display persistent status to the user (in modline). | 424 | The client can use it to display persistent status to the user (in modline). |
diff --git a/docs/dev/style.md b/docs/dev/style.md new file mode 100644 index 000000000..44f0956c2 --- /dev/null +++ b/docs/dev/style.md | |||
@@ -0,0 +1,247 @@ | |||
1 | Our approach to "clean code" is two-fold: | ||
2 | |||
3 | * We generally don't block PRs on style changes. | ||
4 | * At the same time, all code in rust-analyzer is constantly refactored. | ||
5 | |||
6 | It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author. | ||
7 | Sending small cleanup PRs (like renaming a single local variable) is encouraged. | ||
8 | |||
9 | # Scale of Changes | ||
10 | |||
11 | Everyone knows that it's better to send small & focused pull requests. | ||
12 | The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs. | ||
13 | |||
14 | The main things to keep an eye on are the boundaries between various components. | ||
15 | There are three kinds of changes: | ||
16 | |||
17 | 1. Internals of a single component are changed. | ||
18 | Specifically, you don't change any `pub` items. | ||
19 | A good example here would be an addition of a new assist. | ||
20 | |||
21 | 2. API of a component is expanded. | ||
22 | Specifically, you add a new `pub` function which wasn't there before. | ||
23 | A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups. | ||
24 | |||
25 | 3. A new dependency between components is introduced. | ||
26 | Specifically, you add a `pub use` reexport from another crate or you add a new line to the `[dependencies]` section of `Cargo.toml`. | ||
27 | A good example here would be adding reference search capability to the assists crates. | ||
28 | |||
29 | For the first group, the change is generally merged as long as: | ||
30 | |||
31 | * it works for the happy case, | ||
32 | * it has tests, | ||
33 | * it doesn't panic for the unhappy case. | ||
34 | |||
35 | For the second group, the change would be subjected to quite a bit of scrutiny and iteration. | ||
36 | The new API needs to be right (or at least easy to change later). | ||
37 | The actual implementation doesn't matter that much. | ||
38 | It's very important to minimize the amount of changed lines of code for changes of the second kind. | ||
39 | Often, you start doing a change of the first kind, only to realise that you need to elevate to a change of the second kind. | ||
40 | In this case, we'll probably ask you to split API changes into a separate PR. | ||
41 | |||
42 | Changes of the third group should be pretty rare, so we don't specify any specific process for them. | ||
43 | That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it! | ||
44 | |||
45 | Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate | ||
46 | https://www.tedinski.com/2018/02/06/system-boundaries.html | ||
47 | |||
48 | # Crates.io Dependencies | ||
49 | |||
50 | We try to be very conservative with usage of crates.io dependencies. | ||
51 | Don't use small "helper" crates (exception: `itertools` is allowed). | ||
52 | If there's some general reusable bit of code you need, consider adding it to the `stdx` crate. | ||
53 | |||
54 | # Minimal Tests | ||
55 | |||
56 | Most tests in rust-analyzer start with a snippet of Rust code. | ||
57 | This snippets should be minimal -- if you copy-paste a snippet of real code into the tests, make sure to remove everything which could be removed. | ||
58 | There are many benefits to this: | ||
59 | |||
60 | * less to read or to scroll past | ||
61 | * easier to understand what exactly is tested | ||
62 | * less stuff printed during printf-debugging | ||
63 | * less time to run test | ||
64 | |||
65 | It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line), | ||
66 | as long as they are still readable. | ||
67 | |||
68 | # Order of Imports | ||
69 | |||
70 | Separate import groups with blank lines. | ||
71 | Use one `use` per crate. | ||
72 | |||
73 | ```rust | ||
74 | mod x; | ||
75 | mod y; | ||
76 | |||
77 | // First std. | ||
78 | use std::{ ... } | ||
79 | |||
80 | // Second, external crates (both crates.io crates and other rust-analyzer crates). | ||
81 | use crate_foo::{ ... } | ||
82 | use crate_bar::{ ... } | ||
83 | |||
84 | // Then current crate. | ||
85 | use crate::{} | ||
86 | |||
87 | // Finally, parent and child modules, but prefer `use crate::`. | ||
88 | use super::{} | ||
89 | ``` | ||
90 | |||
91 | Module declarations come before the imports. | ||
92 | Order them in "suggested reading order" for a person new to the code base. | ||
93 | |||
94 | # Import Style | ||
95 | |||
96 | Qualify items from `hir` and `ast`. | ||
97 | |||
98 | ```rust | ||
99 | // Good | ||
100 | use syntax::ast; | ||
101 | |||
102 | fn frobnicate(func: hir::Function, strukt: ast::StructDef) {} | ||
103 | |||
104 | // Not as good | ||
105 | use hir::Function; | ||
106 | use syntax::ast::StructDef; | ||
107 | |||
108 | fn frobnicate(func: Function, strukt: StructDef) {} | ||
109 | ``` | ||
110 | |||
111 | Avoid local `use MyEnum::*` imports. | ||
112 | |||
113 | Prefer `use crate::foo::bar` to `use super::bar`. | ||
114 | |||
115 | # Order of Items | ||
116 | |||
117 | Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on. | ||
118 | People read things from top to bottom, so place most important things first. | ||
119 | |||
120 | Specifically, if all items except one are private, always put the non-private item on top. | ||
121 | |||
122 | Put `struct`s and `enum`s first, functions and impls last. | ||
123 | |||
124 | Do | ||
125 | |||
126 | ```rust | ||
127 | // Good | ||
128 | struct Foo { | ||
129 | bars: Vec<Bar> | ||
130 | } | ||
131 | |||
132 | struct Bar; | ||
133 | ``` | ||
134 | |||
135 | rather than | ||
136 | |||
137 | ```rust | ||
138 | // Not as good | ||
139 | struct Bar; | ||
140 | |||
141 | struct Foo { | ||
142 | bars: Vec<Bar> | ||
143 | } | ||
144 | ``` | ||
145 | |||
146 | # Variable Naming | ||
147 | |||
148 | Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)). | ||
149 | The default name is a lowercased name of the type: `global_state: GlobalState`. | ||
150 | Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`). | ||
151 | |||
152 | Default names: | ||
153 | |||
154 | * `res` -- "result of the function" local variable | ||
155 | * `it` -- I don't really care about the name | ||
156 | * `n_foo` -- number of foos | ||
157 | * `foo_idx` -- index of `foo` | ||
158 | |||
159 | # Collection types | ||
160 | |||
161 | Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. | ||
162 | They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount. | ||
163 | |||
164 | # Preconditions | ||
165 | |||
166 | Express function preconditions in types and force the caller to provide them (rather than checking in callee): | ||
167 | |||
168 | ```rust | ||
169 | // Good | ||
170 | fn frbonicate(walrus: Walrus) { | ||
171 | ... | ||
172 | } | ||
173 | |||
174 | // Not as good | ||
175 | fn frobnicate(walrus: Option<Walrus>) { | ||
176 | let walrus = match walrus { | ||
177 | Some(it) => it, | ||
178 | None => return, | ||
179 | }; | ||
180 | ... | ||
181 | } | ||
182 | ``` | ||
183 | |||
184 | # Getters & Setters | ||
185 | |||
186 | If a field can have any value without breaking invariants, make the field public. | ||
187 | Conversely, if there is an invariant, document it, enforce it in the "constructor" function, make the field private, and provide a getter. | ||
188 | Never provide setters. | ||
189 | |||
190 | Getters should return borrowed data: | ||
191 | |||
192 | ``` | ||
193 | struct Person { | ||
194 | // Invariant: never empty | ||
195 | first_name: String, | ||
196 | middle_name: Option<String> | ||
197 | } | ||
198 | |||
199 | // Good | ||
200 | impl Person { | ||
201 | fn first_name(&self) -> &str { self.first_name.as_str() } | ||
202 | fn middle_name(&self) -> Option<&str> { self.middle_name.as_ref() } | ||
203 | } | ||
204 | |||
205 | // Not as good | ||
206 | impl Person { | ||
207 | fn first_name(&self) -> String { self.first_name.clone() } | ||
208 | fn middle_name(&self) -> &Option<String> { &self.middle_name } | ||
209 | } | ||
210 | ``` | ||
211 | |||
212 | |||
213 | # Premature Pessimization | ||
214 | |||
215 | Avoid writing code which is slower than it needs to be. | ||
216 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. | ||
217 | |||
218 | ```rust | ||
219 | // Good | ||
220 | use itertools::Itertools; | ||
221 | |||
222 | let (first_word, second_word) = match text.split_ascii_whitespace().collect_tuple() { | ||
223 | Some(it) => it, | ||
224 | None => return, | ||
225 | } | ||
226 | |||
227 | // Not as good | ||
228 | let words = text.split_ascii_whitespace().collect::<Vec<_>>(); | ||
229 | if words.len() != 2 { | ||
230 | return | ||
231 | } | ||
232 | ``` | ||
233 | |||
234 | # Documentation | ||
235 | |||
236 | For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. | ||
237 | If the line is too long, you want to split the sentence in two :-) | ||
238 | |||
239 | # Commit Style | ||
240 | |||
241 | We don't have specific rules around git history hygiene. | ||
242 | Maintaining clean git history is strongly encouraged, but not enforced. | ||
243 | Use rebase workflow, it's OK to rewrite history during PR review process. | ||
244 | After you are happy with the state of the code, please use [interactive rebase](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) to squash fixup commits. | ||
245 | |||
246 | Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors). | ||
247 | Such messages create a lot of duplicate notification traffic during rebases. | ||
diff --git a/docs/dev/syntax.md b/docs/dev/syntax.md index d4bc4b07c..c08062ef4 100644 --- a/docs/dev/syntax.md +++ b/docs/dev/syntax.md | |||
@@ -11,7 +11,7 @@ The things described are implemented in two places | |||
11 | * [rowan](https://github.com/rust-analyzer/rowan/tree/v0.9.0) -- a generic library for rowan syntax trees. | 11 | * [rowan](https://github.com/rust-analyzer/rowan/tree/v0.9.0) -- a generic library for rowan syntax trees. |
12 | * [ra_syntax](https://github.com/rust-analyzer/rust-analyzer/tree/cf5bdf464cad7ceb9a67e07985a3f4d3799ec0b6/crates/ra_syntax) crate inside rust-analyzer which wraps `rowan` into rust-analyzer specific API. | 12 | * [ra_syntax](https://github.com/rust-analyzer/rust-analyzer/tree/cf5bdf464cad7ceb9a67e07985a3f4d3799ec0b6/crates/ra_syntax) crate inside rust-analyzer which wraps `rowan` into rust-analyzer specific API. |
13 | Nothing in rust-analyzer except this crate knows about `rowan`. | 13 | Nothing in rust-analyzer except this crate knows about `rowan`. |
14 | * [ra_parser](https://github.com/rust-analyzer/rust-analyzer/tree/cf5bdf464cad7ceb9a67e07985a3f4d3799ec0b6/crates/ra_parser) crate parses input tokens into an `ra_syntax` tree | 14 | * [parser](https://github.com/rust-analyzer/rust-analyzer/tree/cf5bdf464cad7ceb9a67e07985a3f4d3799ec0b6/crates/parser) crate parses input tokens into an `ra_syntax` tree |
15 | 15 | ||
16 | ## Design Goals | 16 | ## Design Goals |
17 | 17 | ||
@@ -74,7 +74,7 @@ Points of note: | |||
74 | * The original text can be recovered by concatenating the texts of all tokens in order. | 74 | * The original text can be recovered by concatenating the texts of all tokens in order. |
75 | * Accessing a child of particular type (for example, parameter list of a function) generally involves linerary traversing the children, looking for a specific `kind`. | 75 | * Accessing a child of particular type (for example, parameter list of a function) generally involves linerary traversing the children, looking for a specific `kind`. |
76 | * Modifying the tree is roughly `O(depth)`. | 76 | * Modifying the tree is roughly `O(depth)`. |
77 | We don't make special efforts to guarantree that the depth is not liner, but, in practice, syntax trees are branchy and shallow. | 77 | We don't make special efforts to guarantee that the depth is not linear, but, in practice, syntax trees are branchy and shallow. |
78 | * If mandatory (grammar wise) node is missing from the input, it's just missing from the tree. | 78 | * If mandatory (grammar wise) node is missing from the input, it's just missing from the tree. |
79 | * If an extra erroneous input is present, it is wrapped into a node with `ERROR` kind, and treated just like any other node. | 79 | * If an extra erroneous input is present, it is wrapped into a node with `ERROR` kind, and treated just like any other node. |
80 | * Parser errors are not a part of syntax tree. | 80 | * Parser errors are not a part of syntax tree. |