diff --git a/.github/actions/github-release/main.js b/.github/actions/github-release/main.js index 2c7eedbe290a..b499cd0fd837 100644 --- a/.github/actions/github-release/main.js +++ b/.github/actions/github-release/main.js @@ -16,7 +16,7 @@ async function runOnce() { const slug = process.env.GITHUB_REPOSITORY; const owner = slug.split('/')[0]; const repo = slug.split('/')[1]; - const sha = process.env.GITHUB_SHA; + const sha = process.env.HEAD_SHA; core.info(`files: ${files}`); core.info(`name: ${name}`); diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 00f299ff1828..ed9191c49c37 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -97,7 +97,13 @@ jobs: typescript: name: TypeScript - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + runs-on: ${{ matrix.os }} + steps: - name: Checkout repository uses: actions/checkout@v2 @@ -111,10 +117,19 @@ jobs: working-directory: ./editors/code - run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; } + if: runner.os == 'Linux' working-directory: ./editors/code - run: npm run lint working-directory: ./editors/code + - name: Run vscode tests + uses: GabrielBB/xvfb-action@v1.2 + env: + VSCODE_CLI: 1 + with: + run: npm --prefix ./editors/code test + # working-directory: ./editors/code # does not work: https://github.com/GabrielBB/xvfb-action/issues/8 + - run: npm run package --scripts-prepend-node-path working-directory: ./editors/code diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 29ac895493ac..1ae8ed1b63ef 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -6,7 +6,7 @@ on: push: branches: - release - - nightly + - trigger-nightly env: CARGO_INCREMENTAL: 0 @@ -88,6 +88,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - run: echo "::set-env name=HEAD_SHA::$(git rev-parse HEAD)" + - run: 'echo "HEAD_SHA: $HEAD_SHA"' + - uses: actions/download-artifact@v1 with: name: dist-macos-latest diff --git a/.gitignore b/.gitignore index f835edef0062..aef0fac3397f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ crates/*/target *.log *.iml .vscode/settings.json -cargo-timing*.html +*.html +generated_assists.adoc +generated_features.adoc diff --git a/.vscode/launch.json b/.vscode/launch.json index 6a2fff906510..8ca27d878363 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -70,6 +70,28 @@ "__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/release/rust-analyzer" } }, + { + // Used for testing the extension with a local build of the LSP server (in `target/release`) + // with all other extendions loaded. + "name": "Run With Extensions", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--disable-extension", "matklad.rust-analyzer", + "--extensionDevelopmentPath=${workspaceFolder}/editors/code" + ], + "outFiles": [ + "${workspaceFolder}/editors/code/out/**/*.js" + ], + "preLaunchTask": "Build Server (Release) and Extension", + "skipFiles": [ + "/**/*.js" + ], + "env": { + "__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/release/rust-analyzer" + } + }, { // Used to attach LLDB to a running LSP server. // NOTE: Might require root permissions. For this run: @@ -87,5 +109,17 @@ "rust" ] }, + { + "name": "Run Unit Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/editors/code", + "--extensionTestsPath=${workspaceFolder}/editors/code/out/tests/unit" ], + "sourceMaps": true, + "outFiles": [ "${workspaceFolder}/editors/code/out/tests/unit/**/*.js" ], + "preLaunchTask": "Pretest" + } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0969ce89a107..a25dff19e415 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -40,6 +40,18 @@ "command": "cargo build --release --package rust-analyzer", "problemMatcher": "$rustc" }, + { + "label": "Pretest", + "group": "build", + "isBackground": false, + "type": "npm", + "script": "pretest", + "path": "editors/code/", + "problemMatcher": { + "base": "$tsc", + "fileLocation": ["relative", "${workspaceFolder}/editors/code/"] + } + }, { "label": "Build Server and Extension", diff --git a/Cargo.lock b/Cargo.lock index 007f05b4d03e..5f88ad0c4db5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.53" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" [[package]] name = "cfg-if" @@ -114,7 +114,7 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "chalk-derive" version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" +source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" dependencies = [ "proc-macro2", "quote", @@ -125,51 +125,30 @@ dependencies = [ [[package]] name = "chalk-engine" version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" +source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" dependencies = [ - "chalk-macros", + "chalk-derive", + "chalk-ir", "rustc-hash", ] [[package]] name = "chalk-ir" version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" +source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" dependencies = [ "chalk-derive", - "chalk-engine", - "chalk-macros", -] - -[[package]] -name = "chalk-macros" -version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" -dependencies = [ "lazy_static", ] -[[package]] -name = "chalk-rust-ir" -version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" -dependencies = [ - "chalk-derive", - "chalk-engine", - "chalk-ir", - "chalk-macros", -] - [[package]] name = "chalk-solve" version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" +source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" dependencies = [ "chalk-derive", "chalk-engine", "chalk-ir", - "chalk-macros", - "chalk-rust-ir", "ena", "itertools", "petgraph", @@ -264,9 +243,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -382,9 +361,9 @@ dependencies = [ [[package]] name = "fst" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f9cac32c1741cdf6b66be7dcf0d9c7f25ccf12f8aa84c16cfa31f9f14513b3" +checksum = "a7293de202dbfe786c0b3fe6110a027836c5438ed06db7b715c9955ff4bfea51" [[package]] name = "fuchsia-zircon" @@ -483,9 +462,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" dependencies = [ "autocfg", ] @@ -610,9 +589,9 @@ checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" [[package]] name = "libc" -version = "0.2.70" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "libloading" @@ -661,9 +640,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.74.1" +version = "0.74.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0e6a2b8837d27b29deb3f3e6dc1c6d2f57947677f9be1024e482ec5b59525" +checksum = "b360754e89e0e13c114245131382ba921d4ff1efabb918e549422938aaa8d392" dependencies = [ "base64", "bitflags", @@ -830,9 +809,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a229b1c58c692edcaa5b9b0948084f130f55d2dcc15b02fcc5340b2b4521476" +checksum = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec" dependencies = [ "paste-impl", "proc-macro-hack", @@ -840,9 +819,9 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e0bf239e447e67ff6d16a8bb5e4d4bd2343acf5066061c0e8e06ac5ba8ca68c" +checksum = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -858,9 +837,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "petgraph" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c127eea4a29ec6c85d153c59dc1213f33ec74cead30fe4730aecc88cc1fd92" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ "fixedbitset", "indexmap", @@ -886,15 +865,15 @@ checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "proc-macro-hack" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro2" -version = "1.0.13" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ "unicode-xid", ] @@ -1036,7 +1015,6 @@ version = "0.1.0" dependencies = [ "arrayvec", "chalk-ir", - "chalk-rust-ir", "chalk-solve", "ena", "insta", @@ -1141,6 +1119,7 @@ dependencies = [ "memmap", "ra_mbe", "ra_proc_macro", + "ra_toolchain", "ra_tt", "serde_derive", "test_utils", @@ -1313,9 +1292,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "regex" -version = "1.3.7" +version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" dependencies = [ "aho-corasick", "memchr", @@ -1325,9 +1304,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" [[package]] name = "relative-path" @@ -1372,17 +1351,20 @@ dependencies = [ "lsp-types", "parking_lot", "pico-args", + "ra_cfg", "ra_db", "ra_flycheck", "ra_hir", "ra_hir_def", "ra_hir_ty", "ra_ide", + "ra_mbe", "ra_proc_macro_srv", "ra_prof", "ra_project_model", "ra_syntax", "ra_text_edit", + "ra_tt", "ra_vfs", "rand", "relative-path", @@ -1398,9 +1380,9 @@ dependencies = [ [[package]] name = "rustc-ap-rustc_lexer" -version = "656.0.0" +version = "661.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cbba98ec46e96a4663197dfa8c0378752de2006e314e5400c0ca74929d6692f" +checksum = "a6d88abd7c634b52557e46fc7ba47644f0cbe45c358c33f51c532d60d1da239e" dependencies = [ "unicode-xid", ] @@ -1419,9 +1401,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "salsa" @@ -1510,18 +1492,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" +checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" +checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" dependencies = [ "proc-macro2", "quote", @@ -1595,9 +1577,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" [[package]] name = "syn" -version = "1.0.22" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" +checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" dependencies = [ "proc-macro2", "quote", @@ -1654,7 +1636,11 @@ name = "test_utils" version = "0.1.0" dependencies = [ "difference", + "ra_cfg", + "relative-path", + "rustc-hash", "serde_json", + "stdx", "text-size", ] @@ -1813,9 +1799,9 @@ dependencies = [ [[package]] name = "yaml-rust" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" dependencies = [ "linked-hash-map", ] diff --git a/Cargo.toml b/Cargo.toml index c034e24244e0..5278b5a1676c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,6 @@ opt-level = 0 opt-level = 0 [profile.release.package.chalk-derive] opt-level = 0 -[profile.release.package.chalk-macros] -opt-level = 0 [profile.release.package.salsa-macros] opt-level = 0 [profile.release.package.xtask] diff --git a/README.md b/README.md index 1c334d55e6d2..1e7c3e9b4344 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,13 @@ If you want to **use** rust-analyzer's language server with your editor of choice, check [the manual](https://rust-analyzer.github.io/manual.html) folder. It also contains some tips & tricks to help you be more productive when using rust-analyzer. -## Getting in touch +## Communication -We are on the rust-lang Zulip! +For usage and troubleshooting requests, please use "IDEs and Editors" category of the Rust forum: + +https://users.rust-lang.org/c/ide/14 + +For questions about development and implementation, join rls-2.0 working group on Zulip: https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frls-2.2E0 diff --git a/bors.toml b/bors.toml index 0bc71860f2a9..13ce236df5b4 100644 --- a/bors.toml +++ b/bors.toml @@ -2,6 +2,8 @@ status = [ "Rust (ubuntu-latest)", "Rust (windows-latest)", "Rust (macos-latest)", - "TypeScript" + "TypeScript (ubuntu-latest)", + "TypeScript (windows-latest)", + "TypeScript (macos-latest)", ] delete_merged_branches = true diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 6a675e8126dd..776bddf918a9 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs @@ -4,9 +4,9 @@ use test_utils::mark; use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; -// Assist add_from_impl_for_enum +// Assist: add_from_impl_for_enum // -// Adds a From impl for an enum variant with one tuple field +// Adds a From impl for an enum variant with one tuple field. // // ``` // enum A { <|>One(u32) } diff --git a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs new file mode 100644 index 000000000000..beb5b7366d90 --- /dev/null +++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs @@ -0,0 +1,303 @@ +use ra_syntax::{ + ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, + AstNode, SyntaxKind, TextRange, TextSize, +}; +use rustc_hash::FxHashSet; + +use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; + +static ASSIST_NAME: &str = "introduce_named_lifetime"; +static ASSIST_LABEL: &str = "Introduce named lifetime"; + +// Assist: introduce_named_lifetime +// +// Change an anonymous lifetime to a named lifetime. +// +// ``` +// impl Cursor<'_<|>> { +// fn node(self) -> &SyntaxNode { +// match self { +// Cursor::Replace(node) | Cursor::Before(node) => node, +// } +// } +// } +// ``` +// -> +// ``` +// impl<'a> Cursor<'a> { +// fn node(self) -> &SyntaxNode { +// match self { +// Cursor::Replace(node) | Cursor::Before(node) => node, +// } +// } +// } +// ``` +// FIXME: How can we handle renaming any one of multiple anonymous lifetimes? +// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo +pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let lifetime_token = ctx + .find_token_at_offset(SyntaxKind::LIFETIME) + .filter(|lifetime| lifetime.text() == "'_")?; + if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { + generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) + } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { + // only allow naming the last anonymous lifetime + lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?; + generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) + } else { + None + } +} + +/// Generate the assist for the fn def case +fn generate_fn_def_assist( + acc: &mut Assists, + fn_def: &ast::FnDef, + lifetime_loc: TextRange, +) -> Option<()> { + let param_list: ast::ParamList = fn_def.param_list()?; + let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; + let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); + let self_param = + // use the self if it's a reference and has no explicit lifetime + param_list.self_param().filter(|p| p.lifetime_token().is_none() && p.amp_token().is_some()); + // compute the location which implicitly has the same lifetime as the anonymous lifetime + let loc_needing_lifetime = if let Some(self_param) = self_param { + // if we have a self reference, use that + Some(self_param.self_token()?.text_range().start()) + } else { + // otherwise, if there's a single reference parameter without a named liftime, use that + let fn_params_without_lifetime: Vec<_> = param_list + .params() + .filter_map(|param| match param.ascribed_type() { + Some(ast::TypeRef::ReferenceType(ascribed_type)) + if ascribed_type.lifetime_token() == None => + { + Some(ascribed_type.amp_token()?.text_range().end()) + } + _ => None, + }) + .collect(); + match fn_params_without_lifetime.len() { + 1 => Some(fn_params_without_lifetime.into_iter().nth(0)?), + 0 => None, + // multiple unnnamed is invalid. assist is not applicable + _ => return None, + } + }; + acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { + add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); + builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); + loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); + }) +} + +/// Generate the assist for the impl def case +fn generate_impl_def_assist( + acc: &mut Assists, + impl_def: &ast::ImplDef, + lifetime_loc: TextRange, +) -> Option<()> { + let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?; + let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); + acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { + add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); + builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); + }) +} + +/// Given a type parameter list, generate a unique lifetime parameter name +/// which is not in the list +fn generate_unique_lifetime_param_name( + existing_type_param_list: &Option, +) -> Option { + match existing_type_param_list { + Some(type_params) => { + let used_lifetime_params: FxHashSet<_> = type_params + .lifetime_params() + .map(|p| p.syntax().text().to_string()[1..].to_owned()) + .collect(); + (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string())) + } + None => Some('a'), + } +} + +/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise +/// add new type params brackets with the lifetime parameter at `new_type_params_loc`. +fn add_lifetime_param( + type_params_owner: &TypeParamsOwner, + builder: &mut AssistBuilder, + new_type_params_loc: TextSize, + new_lifetime_param: char, +) { + match type_params_owner.type_param_list() { + // add the new lifetime parameter to an existing type param list + Some(type_params) => { + builder.insert( + (u32::from(type_params.syntax().text_range().end()) - 1).into(), + format!(", '{}", new_lifetime_param), + ); + } + // create a new type param list containing only the new lifetime parameter + None => { + builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param)); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn test_example_case() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor<'_<|>> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } + }"#, + r#"impl<'a> Cursor<'a> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } + }"#, + ); + } + + #[test] + fn test_example_case_simplified() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor<'_<|>> {"#, + r#"impl<'a> Cursor<'a> {"#, + ); + } + + #[test] + fn test_example_case_cursor_after_tick() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor<'<|>_> {"#, + r#"impl<'a> Cursor<'a> {"#, + ); + } + + #[test] + fn test_example_case_cursor_before_tick() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor<<|>'_> {"#, + r#"impl<'a> Cursor<'a> {"#, + ); + } + + #[test] + fn test_not_applicable_cursor_position() { + check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_><|> {"#); + check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<|><'_> {"#); + } + + #[test] + fn test_not_applicable_lifetime_already_name() { + check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a<|>> {"#); + check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a<|>>"#); + } + + #[test] + fn test_with_type_parameter() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor>"#, + r#"impl Cursor"#, + ); + } + + #[test] + fn test_with_existing_lifetime_name_conflict() { + check_assist( + introduce_named_lifetime, + r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#, + r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, + ); + } + + #[test] + fn test_function_return_value_anon_lifetime_param() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun() -> X<'_<|>>"#, + r#"fn my_fun<'a>() -> X<'a>"#, + ); + } + + #[test] + fn test_function_return_value_anon_reference_lifetime() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun() -> &'_<|> X"#, + r#"fn my_fun<'a>() -> &'a X"#, + ); + } + + #[test] + fn test_function_param_anon_lifetime() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun(x: X<'_<|>>)"#, + r#"fn my_fun<'a>(x: X<'a>)"#, + ); + } + + #[test] + fn test_function_add_lifetime_to_params() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun(f: &Foo) -> X<'_<|>>"#, + r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#, + ); + } + + #[test] + fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#, + r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#, + ); + } + + #[test] + fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() { + // this is not permitted under lifetime elision rules + check_assist_not_applicable( + introduce_named_lifetime, + r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#, + ); + } + + #[test] + fn test_function_add_lifetime_to_self_ref_param() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, + r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#, + ); + } + + #[test] + fn test_function_add_lifetime_to_param_with_non_ref_self() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, + r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, + ); + } +} diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs index 30229edc2f24..897da283238f 100644 --- a/crates/ra_assists/src/handlers/reorder_fields.rs +++ b/crates/ra_assists/src/handlers/reorder_fields.rs @@ -23,7 +23,7 @@ use crate::{AssistContext, AssistId, Assists}; // ``` // pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - reorder::(acc, ctx.clone()).or_else(|| reorder::(acc, ctx)) + reorder::(acc, ctx).or_else(|| reorder::(acc, ctx)) } fn reorder(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 9933f7a50546..88ce9b62ebf0 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -122,6 +122,7 @@ mod handlers { mod flip_comma; mod flip_trait_bound; mod inline_local_variable; + mod introduce_named_lifetime; mod introduce_variable; mod invert_if; mod merge_imports; @@ -162,6 +163,7 @@ mod handlers { flip_comma::flip_comma, flip_trait_bound::flip_trait_bound, inline_local_variable::inline_local_variable, + introduce_named_lifetime::introduce_named_lifetime, introduce_variable::introduce_variable, invert_if::invert_if, merge_imports::merge_imports, diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index 250e56a69624..d17504529fd4 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs @@ -58,6 +58,25 @@ fn main() { ) } +#[test] +fn doctest_add_from_impl_for_enum() { + check_doc_test( + "add_from_impl_for_enum", + r#####" +enum A { <|>One(u32) } +"#####, + r#####" +enum A { One(u32) } + +impl From for A { + fn from(v: u32) -> Self { + A::One(v) + } +} +"#####, + ) +} + #[test] fn doctest_add_function() { check_doc_test( @@ -432,6 +451,31 @@ fn main() { ) } +#[test] +fn doctest_introduce_named_lifetime() { + check_doc_test( + "introduce_named_lifetime", + r#####" +impl Cursor<'_<|>> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } +} +"#####, + r#####" +impl<'a> Cursor<'a> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } +} +"#####, + ) +} + #[test] fn doctest_introduce_variable() { check_doc_test( diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs index 39d71851ca74..85b100c6adfe 100644 --- a/crates/ra_cfg/src/cfg_expr.rs +++ b/crates/ra_cfg/src/cfg_expr.rs @@ -88,13 +88,17 @@ fn next_cfg_expr(it: &mut SliceIter) -> Option { mod tests { use super::*; - use mbe::ast_to_token_tree; + use mbe::{ast_to_token_tree, TokenMap}; use ra_syntax::ast::{self, AstNode}; - fn assert_parse_result(input: &str, expected: CfgExpr) { + fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) { let source_file = ast::SourceFile::parse(input).ok().unwrap(); let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let (tt, _) = ast_to_token_tree(&tt).unwrap(); + ast_to_token_tree(&tt).unwrap() + } + + fn assert_parse_result(input: &str, expected: CfgExpr) { + let (tt, _) = get_token_tree_generated(input); assert_eq!(parse_cfg(&tt), expected); } diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs index f8f7670919c2..482a2f3e651e 100644 --- a/crates/ra_db/src/fixture.rs +++ b/crates/ra_db/src/fixture.rs @@ -63,7 +63,7 @@ use std::sync::Arc; use ra_cfg::CfgOptions; use rustc_hash::FxHashMap; -use test_utils::{extract_offset, parse_fixture, parse_single_fixture, CURSOR_MARKER}; +use test_utils::{extract_offset, parse_fixture, parse_single_fixture, FixtureMeta, CURSOR_MARKER}; use crate::{ input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, RelativePathBuf, @@ -113,7 +113,7 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId let fixture = parse_single_fixture(ra_fixture); let crate_graph = if let Some(entry) = fixture { - let meta = match parse_meta(&entry.meta) { + let meta = match ParsedMeta::from(&entry.meta) { ParsedMeta::File(it) => it, _ => panic!("with_single_file only support file meta"), }; @@ -170,7 +170,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option { let source_root = std::mem::replace(&mut source_root, SourceRoot::new_local()); db.set_source_root(source_root_id, Arc::new(source_root)); @@ -258,53 +258,25 @@ struct FileMeta { env: Env, } -//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo) -fn parse_meta(meta: &str) -> ParsedMeta { - let components = meta.split_ascii_whitespace().collect::>(); - - if components[0] == "root" { - let path: RelativePathBuf = components[1].into(); - assert!(path.starts_with("/") && path.ends_with("/")); - return ParsedMeta::Root { path }; - } - - let path: RelativePathBuf = components[0].into(); - assert!(path.starts_with("/")); - - let mut krate = None; - let mut deps = Vec::new(); - let mut edition = Edition::Edition2018; - let mut cfg = CfgOptions::default(); - let mut env = Env::default(); - for component in components[1..].iter() { - let (key, value) = split1(component, ':').unwrap(); - match key { - "crate" => krate = Some(value.to_string()), - "deps" => deps = value.split(',').map(|it| it.to_string()).collect(), - "edition" => edition = Edition::from_str(&value).unwrap(), - "cfg" => { - for key in value.split(',') { - match split1(key, '=') { - None => cfg.insert_atom(key.into()), - Some((k, v)) => cfg.insert_key_value(k.into(), v.into()), - } - } +impl From<&FixtureMeta> for ParsedMeta { + fn from(meta: &FixtureMeta) -> Self { + match meta { + FixtureMeta::Root { path } => { + // `Self::Root` causes a false warning: 'variant is never constructed: `Root` ' + // see https://github.com/rust-lang/rust/issues/69018 + ParsedMeta::Root { path: path.to_owned() } } - "env" => { - for key in value.split(',') { - if let Some((k, v)) = split1(key, '=') { - env.set(k, v.into()); - } - } - } - _ => panic!("bad component: {:?}", component), + FixtureMeta::File(f) => Self::File(FileMeta { + path: f.path.to_owned(), + krate: f.crate_name.to_owned(), + deps: f.deps.to_owned(), + cfg: f.cfg.to_owned(), + edition: f + .edition + .as_ref() + .map_or(Edition::Edition2018, |v| Edition::from_str(&v).unwrap()), + env: Env::from(f.env.iter()), + }), } } - - ParsedMeta::File(FileMeta { path, krate, deps, edition, cfg, env }) -} - -fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> { - let idx = haystack.find(delim)?; - Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..])) } diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs index ab14e2d5e64d..4d2d3b48a0c0 100644 --- a/crates/ra_db/src/input.rs +++ b/crates/ra_db/src/input.rs @@ -311,6 +311,21 @@ impl fmt::Display for Edition { } } +impl<'a, T> From for Env +where + T: Iterator, +{ + fn from(iter: T) -> Self { + let mut result = Self::default(); + + for (k, v) in iter { + result.entries.insert(k.to_owned(), v.to_owned()); + } + + result + } +} + impl Env { pub fn set(&mut self, env: &str, value: String) { self.entries.insert(env.to_owned(), value); diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 840cfdfc8238..4a06f3bcddb6 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -532,7 +532,7 @@ impl Adt { Some(self.module(db).krate()) } - pub fn name(&self, db: &dyn HirDatabase) -> Name { + pub fn name(self, db: &dyn HirDatabase) -> Name { match self { Adt::Struct(s) => s.name(db), Adt::Union(u) => u.name(db), @@ -637,6 +637,10 @@ impl Function { db.function_data(self.id).params.clone() } + pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool { + db.function_data(self.id).is_unsafe + } + pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { let _p = profile("Function::diagnostics"); let infer = db.infer(self.id.into()); @@ -1018,15 +1022,15 @@ impl ImplDef { impls.lookup_impl_defs_for_trait(trait_.id).map(Self::from).collect() } - pub fn target_trait(&self, db: &dyn HirDatabase) -> Option { + pub fn target_trait(self, db: &dyn HirDatabase) -> Option { db.impl_data(self.id).target_trait.clone() } - pub fn target_type(&self, db: &dyn HirDatabase) -> TypeRef { + pub fn target_type(self, db: &dyn HirDatabase) -> TypeRef { db.impl_data(self.id).target_type.clone() } - pub fn target_ty(&self, db: &dyn HirDatabase) -> Type { + pub fn target_ty(self, db: &dyn HirDatabase) -> Type { let impl_data = db.impl_data(self.id); let resolver = self.id.resolver(db.upcast()); let ctx = hir_ty::TyLoweringContext::new(db, &resolver); @@ -1038,23 +1042,23 @@ impl ImplDef { } } - pub fn items(&self, db: &dyn HirDatabase) -> Vec { + pub fn items(self, db: &dyn HirDatabase) -> Vec { db.impl_data(self.id).items.iter().map(|it| (*it).into()).collect() } - pub fn is_negative(&self, db: &dyn HirDatabase) -> bool { + pub fn is_negative(self, db: &dyn HirDatabase) -> bool { db.impl_data(self.id).is_negative } - pub fn module(&self, db: &dyn HirDatabase) -> Module { + pub fn module(self, db: &dyn HirDatabase) -> Module { self.id.lookup(db.upcast()).container.module(db.upcast()).into() } - pub fn krate(&self, db: &dyn HirDatabase) -> Crate { + pub fn krate(self, db: &dyn HirDatabase) -> Crate { Crate { id: self.module(db).id.krate } } - pub fn is_builtin_derive(&self, db: &dyn HirDatabase) -> Option> { + pub fn is_builtin_derive(self, db: &dyn HirDatabase) -> Option> { let src = self.source(db); let item = src.file_id.is_builtin_derive(db.upcast())?; let hygenic = hir_expand::hygiene::Hygiene::new(db.upcast(), item.file_id); @@ -1190,6 +1194,10 @@ impl Type { ) } + pub fn is_raw_ptr(&self) -> bool { + matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. })) + } + pub fn contains_unknown(&self) -> bool { return go(&self.ty.value); @@ -1363,6 +1371,7 @@ impl HirDisplay for Type { } /// For IDE only +#[derive(Debug)] pub enum ScopeDef { ModuleDef(ModuleDef), MacroDef(MacroDef), diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 4af0f046a720..f4a6b0503875 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -62,6 +62,7 @@ pub use crate::{ pub use hir_def::{ adt::StructKind, + attr::Attrs, body::scope::ExprScopes, builtin_type::BuiltinType, docs::Documentation, diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 576cd0c65ba9..2eeba0572991 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -81,18 +81,24 @@ impl Attrs { } } - fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs { + pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs { let hygiene = Hygiene::new(db.upcast(), owner.file_id); Attrs::new(owner.value, &hygiene) } pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs { + let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map( + |docs_text| Attr { + input: Some(AttrInput::Literal(SmolStr::new(docs_text))), + path: ModPath::from(hir_expand::name!(doc)), + }, + ); let mut attrs = owner.attrs().peekable(); let entries = if attrs.peek().is_none() { // Avoid heap allocation None } else { - Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect()) + Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect()) }; Attrs { entries } } diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs index f5a7305dc090..273036cee1cf 100644 --- a/crates/ra_hir_def/src/body.rs +++ b/crates/ra_hir_def/src/body.rs @@ -29,7 +29,7 @@ use crate::{ AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId, }; -/// A subset of Exander that only deals with cfg attributes. We only need it to +/// A subset of Expander that only deals with cfg attributes. We only need it to /// avoid cyclic queries in crate def map during enum processing. pub(crate) struct CfgExpander { cfg_options: CfgOptions, diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs index e08d62dd6869..f159f80af05c 100644 --- a/crates/ra_hir_def/src/body/lower.rs +++ b/crates/ra_hir_def/src/body/lower.rs @@ -28,7 +28,7 @@ use crate::{ }, item_scope::BuiltinShadowMode, path::{GenericArgs, Path}, - type_ref::{Mutability, TypeRef}, + type_ref::{Mutability, Rawness, TypeRef}, AdtId, ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, Intern, ModuleDefId, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, }; @@ -134,7 +134,7 @@ impl ExprCollector<'_> { self.make_expr(expr, Err(SyntheticSyntax)) } fn empty_block(&mut self) -> ExprId { - self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None }) + self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None, label: None }) } fn missing_expr(&mut self) -> ExprId { self.alloc_expr_desugared(Expr::Missing) @@ -215,7 +215,16 @@ impl ExprCollector<'_> { ast::Expr::BlockExpr(e) => self.collect_block(e), ast::Expr::LoopExpr(e) => { let body = self.collect_block_opt(e.loop_body()); - self.alloc_expr(Expr::Loop { body }, syntax_ptr) + self.alloc_expr( + Expr::Loop { + body, + label: e + .label() + .and_then(|l| l.lifetime_token()) + .map(|l| Name::new_lifetime(&l)), + }, + syntax_ptr, + ) } ast::Expr::WhileExpr(e) => { let body = self.collect_block_opt(e.loop_body()); @@ -230,25 +239,56 @@ impl ExprCollector<'_> { let pat = self.collect_pat(pat); let match_expr = self.collect_expr_opt(condition.expr()); let placeholder_pat = self.missing_pat(); - let break_ = self.alloc_expr_desugared(Expr::Break { expr: None }); + let break_ = + self.alloc_expr_desugared(Expr::Break { expr: None, label: None }); let arms = vec![ MatchArm { pat, expr: body, guard: None }, MatchArm { pat: placeholder_pat, expr: break_, guard: None }, ]; let match_expr = self.alloc_expr_desugared(Expr::Match { expr: match_expr, arms }); - return self.alloc_expr(Expr::Loop { body: match_expr }, syntax_ptr); + return self.alloc_expr( + Expr::Loop { + body: match_expr, + label: e + .label() + .and_then(|l| l.lifetime_token()) + .map(|l| Name::new_lifetime(&l)), + }, + syntax_ptr, + ); } }, }; - self.alloc_expr(Expr::While { condition, body }, syntax_ptr) + self.alloc_expr( + Expr::While { + condition, + body, + label: e + .label() + .and_then(|l| l.lifetime_token()) + .map(|l| Name::new_lifetime(&l)), + }, + syntax_ptr, + ) } ast::Expr::ForExpr(e) => { let iterable = self.collect_expr_opt(e.iterable()); let pat = self.collect_pat_opt(e.pat()); let body = self.collect_block_opt(e.loop_body()); - self.alloc_expr(Expr::For { iterable, pat, body }, syntax_ptr) + self.alloc_expr( + Expr::For { + iterable, + pat, + body, + label: e + .label() + .and_then(|l| l.lifetime_token()) + .map(|l| Name::new_lifetime(&l)), + }, + syntax_ptr, + ) } ast::Expr::CallExpr(e) => { let callee = self.collect_expr_opt(e.expr()); @@ -301,13 +341,16 @@ impl ExprCollector<'_> { .unwrap_or(Expr::Missing); self.alloc_expr(path, syntax_ptr) } - ast::Expr::ContinueExpr(_e) => { - // FIXME: labels - self.alloc_expr(Expr::Continue, syntax_ptr) - } + ast::Expr::ContinueExpr(e) => self.alloc_expr( + Expr::Continue { label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) }, + syntax_ptr, + ), ast::Expr::BreakExpr(e) => { let expr = e.expr().map(|e| self.collect_expr(e)); - self.alloc_expr(Expr::Break { expr }, syntax_ptr) + self.alloc_expr( + Expr::Break { expr, label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) }, + syntax_ptr, + ) } ast::Expr::ParenExpr(e) => { let inner = self.collect_expr_opt(e.expr()); @@ -378,8 +421,21 @@ impl ExprCollector<'_> { } ast::Expr::RefExpr(e) => { let expr = self.collect_expr_opt(e.expr()); - let mutability = Mutability::from_mutable(e.mut_token().is_some()); - self.alloc_expr(Expr::Ref { expr, mutability }, syntax_ptr) + let raw_tok = e.raw_token().is_some(); + let mutability = if raw_tok { + if e.mut_token().is_some() { + Mutability::Mut + } else if e.const_token().is_some() { + Mutability::Shared + } else { + unreachable!("parser only remaps to raw_token() if matching mutability token follows") + } + } else { + Mutability::from_mutable(e.mut_token().is_some()) + }; + let rawness = Rawness::from_raw(raw_tok); + + self.alloc_expr(Expr::Ref { expr, rawness, mutability }, syntax_ptr) } ast::Expr::PrefixExpr(e) => { let expr = self.collect_expr_opt(e.expr()); @@ -516,7 +572,8 @@ impl ExprCollector<'_> { }) .collect(); let tail = block.expr().map(|e| self.collect_expr(e)); - self.alloc_expr(Expr::Block { statements, tail }, syntax_node_ptr) + let label = block.label().and_then(|l| l.lifetime_token()).map(|t| Name::new_lifetime(&t)); + self.alloc_expr(Expr::Block { statements, tail, label }, syntax_node_ptr) } fn collect_block_items(&mut self, block: &ast::BlockExpr) { diff --git a/crates/ra_hir_def/src/body/scope.rs b/crates/ra_hir_def/src/body/scope.rs index 09e92b74e1ab..e48ff38f9604 100644 --- a/crates/ra_hir_def/src/body/scope.rs +++ b/crates/ra_hir_def/src/body/scope.rs @@ -138,10 +138,10 @@ fn compute_block_scopes( fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { scopes.set_scope(expr, scope); match &body[expr] { - Expr::Block { statements, tail } => { + Expr::Block { statements, tail, .. } => { compute_block_scopes(&statements, *tail, body, scopes, scope); } - Expr::For { iterable, pat, body: body_expr } => { + Expr::For { iterable, pat, body: body_expr, .. } => { compute_expr_scopes(*iterable, body, scopes, scope); let scope = scopes.new_scope(scope); scopes.add_bindings(body, scope, *pat); diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs index e2130d931fdb..807195d25ad9 100644 --- a/crates/ra_hir_def/src/data.rs +++ b/crates/ra_hir_def/src/data.rs @@ -34,6 +34,7 @@ pub struct FunctionData { /// True if the first param is `self`. This is relevant to decide whether this /// can be called as a method. pub has_self_param: bool, + pub is_unsafe: bool, pub visibility: RawVisibility, } @@ -85,11 +86,14 @@ impl FunctionData { ret_type }; + let is_unsafe = src.value.unsafe_token().is_some(); + let vis_default = RawVisibility::default_for_container(loc.container); let visibility = RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility())); - let sig = FunctionData { name, params, ret_type, has_self_param, visibility, attrs }; + let sig = + FunctionData { name, params, ret_type, has_self_param, is_unsafe, visibility, attrs }; Arc::new(sig) } } diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index b221ae1cece3..2630b3d895e6 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -29,6 +29,13 @@ impl Documentation { Documentation(s.into()) } + pub fn from_ast(node: &N) -> Option + where + N: ast::DocCommentsOwner + ast::AttrsOwner, + { + docs_from_ast(node) + } + pub fn as_str(&self) -> &str { &*self.0 } @@ -70,6 +77,45 @@ impl Documentation { } } -pub(crate) fn docs_from_ast(node: &impl ast::DocCommentsOwner) -> Option { - node.doc_comment_text().map(|it| Documentation::new(&it)) +pub(crate) fn docs_from_ast(node: &N) -> Option +where + N: ast::DocCommentsOwner + ast::AttrsOwner, +{ + let doc_comment_text = node.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(node); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + docs.map(|it| Documentation::new(&it)) +} + +fn merge_doc_comments_and_attrs( + doc_comment_text: Option, + doc_attr_text: Option, +) -> Option { + match (doc_comment_text, doc_attr_text) { + (Some(mut comment_text), Some(attr_text)) => { + comment_text.push_str("\n\n"); + comment_text.push_str(&attr_text); + Some(comment_text) + } + (Some(comment_text), None) => Some(comment_text), + (None, Some(attr_text)) => Some(attr_text), + (None, None) => None, + } +} + +fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { + let mut docs = String::new(); + for attr in owner.attrs() { + if let Some(("doc", value)) = + attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) + { + docs.push_str(value); + docs.push_str("\n\n"); + } + } + if docs.is_empty() { + None + } else { + Some(docs.trim_end_matches("\n\n").to_owned()) + } } diff --git a/crates/ra_hir_def/src/expr.rs b/crates/ra_hir_def/src/expr.rs index a0cdad529b3f..ca49b26d15f8 100644 --- a/crates/ra_hir_def/src/expr.rs +++ b/crates/ra_hir_def/src/expr.rs @@ -19,7 +19,7 @@ use ra_syntax::ast::RangeOp; use crate::{ builtin_type::{BuiltinFloat, BuiltinInt}, path::{GenericArgs, Path}, - type_ref::{Mutability, TypeRef}, + type_ref::{Mutability, Rawness, TypeRef}, }; pub type ExprId = Idx; @@ -52,18 +52,22 @@ pub enum Expr { Block { statements: Vec, tail: Option, + label: Option, }, Loop { body: ExprId, + label: Option, }, While { condition: ExprId, body: ExprId, + label: Option, }, For { iterable: ExprId, pat: PatId, body: ExprId, + label: Option, }, Call { callee: ExprId, @@ -79,9 +83,12 @@ pub enum Expr { expr: ExprId, arms: Vec, }, - Continue, + Continue { + label: Option, + }, Break { expr: Option, + label: Option, }, Return { expr: Option, @@ -110,6 +117,7 @@ pub enum Expr { }, Ref { expr: ExprId, + rawness: Rawness, mutability: Mutability, }, Box { @@ -224,7 +232,7 @@ impl Expr { f(*else_branch); } } - Expr::Block { statements, tail } => { + Expr::Block { statements, tail, .. } => { for stmt in statements { match stmt { Statement::Let { initializer, .. } => { @@ -240,8 +248,8 @@ impl Expr { } } Expr::TryBlock { body } => f(*body), - Expr::Loop { body } => f(*body), - Expr::While { condition, body } => { + Expr::Loop { body, .. } => f(*body), + Expr::While { condition, body, .. } => { f(*condition); f(*body); } @@ -267,8 +275,8 @@ impl Expr { f(arm.expr); } } - Expr::Continue => {} - Expr::Break { expr } | Expr::Return { expr } => { + Expr::Continue { .. } => {} + Expr::Break { expr, .. } | Expr::Return { expr } => { if let Some(expr) = expr { f(*expr); } diff --git a/crates/ra_hir_def/src/lang_item.rs b/crates/ra_hir_def/src/lang_item.rs index d962db3cc792..3516784b8d8d 100644 --- a/crates/ra_hir_def/src/lang_item.rs +++ b/crates/ra_hir_def/src/lang_item.rs @@ -164,7 +164,7 @@ impl LangItems { T: Into + Copy, { if let Some(lang_item_name) = lang_attr(db, item) { - self.items.entry(lang_item_name.clone()).or_insert_with(|| constructor(item)); + self.items.entry(lang_item_name).or_insert_with(|| constructor(item)); } } } diff --git a/crates/ra_hir_def/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs index 4e628b14d921..f44baa57942f 100644 --- a/crates/ra_hir_def/src/nameres/raw.rs +++ b/crates/ra_hir_def/src/nameres/raw.rs @@ -175,7 +175,7 @@ pub(super) enum DefKind { } impl DefKind { - pub fn ast_id(&self) -> FileAstId { + pub fn ast_id(self) -> FileAstId { match self { DefKind::Function(it) => it.upcast(), DefKind::Struct(it, _) => it.upcast(), diff --git a/crates/ra_hir_def/src/type_ref.rs b/crates/ra_hir_def/src/type_ref.rs index 5bdad9efdef3..86a77b7046d4 100644 --- a/crates/ra_hir_def/src/type_ref.rs +++ b/crates/ra_hir_def/src/type_ref.rs @@ -35,6 +35,22 @@ impl Mutability { } } +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum Rawness { + RawPtr, + Ref, +} + +impl Rawness { + pub fn from_raw(is_raw: bool) -> Rawness { + if is_raw { + Rawness::RawPtr + } else { + Rawness::Ref + } + } +} + /// Compare ty::Ty #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum TypeRef { diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index fecce224ee60..660bdfe3365b 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs @@ -37,6 +37,11 @@ impl Name { Name(Repr::TupleField(idx)) } + pub fn new_lifetime(lt: &ra_syntax::SyntaxToken) -> Name { + assert!(lt.kind() == ra_syntax::SyntaxKind::LIFETIME); + Name(Repr::Text(lt.text().clone())) + } + /// Shortcut to create inline plain text name const fn new_inline_ascii(text: &[u8]) -> Name { Name::new_text(SmolStr::new_inline_from_ascii(text.len(), text)) @@ -148,6 +153,7 @@ pub mod known { str, // Special names macro_rules, + doc, // Components of known path (value or mod name) std, core, diff --git a/crates/ra_hir_expand/src/proc_macro.rs b/crates/ra_hir_expand/src/proc_macro.rs index 4e0e069c8c27..04c0260046bc 100644 --- a/crates/ra_hir_expand/src/proc_macro.rs +++ b/crates/ra_hir_expand/src/proc_macro.rs @@ -25,7 +25,7 @@ impl ProcMacroExpander { } pub fn expand( - &self, + self, db: &dyn AstDatabase, _id: LazyMacroId, tt: &tt::Subtree, diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index b2de7fa34cc1..4b8dcdc07ff5 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml @@ -27,9 +27,8 @@ test_utils = { path = "../test_utils" } scoped-tls = "1" -chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "eaab84b394007d1bed15f5470409a6ea02900a96" } -chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "eaab84b394007d1bed15f5470409a6ea02900a96" } -chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "eaab84b394007d1bed15f5470409a6ea02900a96" } +chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" } +chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" } [dev-dependencies] insta = "0.16.0" diff --git a/crates/ra_hir_ty/src/db.rs b/crates/ra_hir_ty/src/db.rs index dfc6c7dd682a..0a8bb24ac239 100644 --- a/crates/ra_hir_ty/src/db.rs +++ b/crates/ra_hir_ty/src/db.rs @@ -76,6 +76,8 @@ pub trait HirDatabase: DefDatabase + Upcast { #[salsa::interned] fn intern_type_ctor(&self, type_ctor: TypeCtor) -> crate::TypeCtorId; #[salsa::interned] + fn intern_callable_def(&self, callable_def: CallableDef) -> crate::CallableDefId; + #[salsa::interned] fn intern_type_param_id(&self, param_id: TypeParamId) -> GlobalTypeParamId; #[salsa::interned] fn intern_chalk_impl(&self, impl_: Impl) -> crate::traits::GlobalImplId; @@ -94,6 +96,9 @@ pub trait HirDatabase: DefDatabase + Upcast { #[salsa::invoke(crate::traits::chalk::impl_datum_query)] fn impl_datum(&self, krate: CrateId, impl_id: chalk::ImplId) -> Arc; + #[salsa::invoke(crate::traits::chalk::fn_def_datum_query)] + fn fn_def_datum(&self, krate: CrateId, fn_def_id: chalk::FnDefId) -> Arc; + #[salsa::invoke(crate::traits::chalk::associated_ty_value_query)] fn associated_ty_value( &self, diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs index 41ac702724e8..2c7298714d8d 100644 --- a/crates/ra_hir_ty/src/diagnostics.rs +++ b/crates/ra_hir_ty/src/diagnostics.rs @@ -40,7 +40,7 @@ impl Diagnostic for MissingFields { fn message(&self) -> String { let mut buf = String::from("Missing structure fields:\n"); for field in &self.missed_fields { - format_to!(buf, "- {}", field); + format_to!(buf, "- {}\n", field); } buf } @@ -73,7 +73,7 @@ impl Diagnostic for MissingPatFields { fn message(&self) -> String { let mut buf = String::from("Missing structure fields:\n"); for field in &self.missed_fields { - format_to!(buf, "- {}", field); + format_to!(buf, "- {}\n", field); } buf } diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs index 957d6e0b5792..dc77e88e50b2 100644 --- a/crates/ra_hir_ty/src/infer.rs +++ b/crates/ra_hir_ty/src/infer.rs @@ -219,6 +219,17 @@ struct InferenceContext<'a> { struct BreakableContext { pub may_break: bool, pub break_ty: Ty, + pub label: Option, +} + +fn find_breakable<'c>( + ctxs: &'c mut [BreakableContext], + label: Option<&name::Name>, +) -> Option<&'c mut BreakableContext> { + match label { + Some(_) => ctxs.iter_mut().rev().find(|ctx| ctx.label.as_ref() == label), + None => ctxs.last_mut(), + } } impl<'a> InferenceContext<'a> { diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs index 2ee9adb16425..32c7c57cd61a 100644 --- a/crates/ra_hir_ty/src/infer/coerce.rs +++ b/crates/ra_hir_ty/src/infer/coerce.rs @@ -45,9 +45,7 @@ impl<'a> InferenceContext<'a> { self.coerce_merge_branch(&ptr_ty1, &ptr_ty2) } else { mark::hit!(coerce_merge_fail_fallback); - // For incompatible types, we use the latter one as result - // to be better recovery for `if` without `else`. - ty2.clone() + ty1.clone() } } } diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index b28724f0e946..4a98e2debff5 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs @@ -17,13 +17,13 @@ use crate::{ autoderef, method_resolution, op, traits::InEnvironment, utils::{generics, variant_data, Generics}, - ApplicationTy, Binders, CallableDef, InferTy, IntTy, Mutability, Obligation, Substs, TraitRef, - Ty, TypeCtor, Uncertain, + ApplicationTy, Binders, CallableDef, InferTy, IntTy, Mutability, Obligation, Rawness, Substs, + TraitRef, Ty, TypeCtor, Uncertain, }; use super::{ - BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic, - TypeMismatch, + find_breakable, BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, + InferenceDiagnostic, TypeMismatch, }; impl<'a> InferenceContext<'a> { @@ -86,16 +86,20 @@ impl<'a> InferenceContext<'a> { self.coerce_merge_branch(&then_ty, &else_ty) } - Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected), + Expr::Block { statements, tail, .. } => { + // FIXME: Breakable block inference + self.infer_block(statements, *tail, expected) + } Expr::TryBlock { body } => { let _inner = self.infer_expr(*body, expected); // FIXME should be std::result::Result<{inner}, _> Ty::Unknown } - Expr::Loop { body } => { + Expr::Loop { body, label } => { self.breakables.push(BreakableContext { may_break: false, break_ty: self.table.new_type_var(), + label: label.clone(), }); self.infer_expr(*body, &Expectation::has_type(Ty::unit())); @@ -110,8 +114,12 @@ impl<'a> InferenceContext<'a> { Ty::simple(TypeCtor::Never) } } - Expr::While { condition, body } => { - self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown }); + Expr::While { condition, body, label } => { + self.breakables.push(BreakableContext { + may_break: false, + break_ty: Ty::Unknown, + label: label.clone(), + }); // while let is desugared to a match loop, so this is always simple while self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); self.infer_expr(*body, &Expectation::has_type(Ty::unit())); @@ -120,10 +128,14 @@ impl<'a> InferenceContext<'a> { self.diverges = Diverges::Maybe; Ty::unit() } - Expr::For { iterable, body, pat } => { + Expr::For { iterable, body, pat, label } => { let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); - self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown }); + self.breakables.push(BreakableContext { + may_break: false, + break_ty: Ty::Unknown, + label: label.clone(), + }); let pat_ty = self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); @@ -140,13 +152,13 @@ impl<'a> InferenceContext<'a> { let mut sig_tys = Vec::new(); - for (arg_pat, arg_type) in args.iter().zip(arg_types.iter()) { - let expected = if let Some(type_ref) = arg_type { + // collect explicitly written argument types + for arg_type in arg_types.iter() { + let arg_ty = if let Some(type_ref) = arg_type { self.make_ty(type_ref) } else { - Ty::Unknown + self.table.new_type_var() }; - let arg_ty = self.infer_pat(*arg_pat, &expected, BindingMode::default()); sig_tys.push(arg_ty); } @@ -158,7 +170,7 @@ impl<'a> InferenceContext<'a> { sig_tys.push(ret_ty.clone()); let sig_ty = Ty::apply( TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1 }, - Substs(sig_tys.into()), + Substs(sig_tys.clone().into()), ); let closure_ty = Ty::apply_one(TypeCtor::Closure { def: self.owner, expr: tgt_expr }, sig_ty); @@ -168,6 +180,12 @@ impl<'a> InferenceContext<'a> { // infer the body. self.coerce(&closure_ty, &expected.ty); + // Now go through the argument patterns + for (arg_pat, arg_ty) in args.iter().zip(sig_tys) { + let resolved = self.resolve_ty_as_possible(arg_ty); + self.infer_pat(*arg_pat, &resolved, BindingMode::default()); + } + let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); @@ -230,23 +248,24 @@ impl<'a> InferenceContext<'a> { let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or(Ty::Unknown) } - Expr::Continue => Ty::simple(TypeCtor::Never), - Expr::Break { expr } => { + Expr::Continue { .. } => Ty::simple(TypeCtor::Never), + Expr::Break { expr, label } => { let val_ty = if let Some(expr) = expr { self.infer_expr(*expr, &Expectation::none()) } else { Ty::unit() }; - let last_ty = if let Some(ctxt) = self.breakables.last() { - ctxt.break_ty.clone() - } else { - Ty::Unknown - }; + let last_ty = + if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { + ctxt.break_ty.clone() + } else { + Ty::Unknown + }; let merged_type = self.coerce_merge_branch(&last_ty, &val_ty); - if let Some(ctxt) = self.breakables.last_mut() { + if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { ctxt.break_ty = merged_type; ctxt.may_break = true; } else { @@ -350,19 +369,28 @@ impl<'a> InferenceContext<'a> { // FIXME check the cast... cast_ty } - Expr::Ref { expr, mutability } => { - let expectation = - if let Some((exp_inner, exp_mutability)) = &expected.ty.as_reference() { - if *exp_mutability == Mutability::Mut && *mutability == Mutability::Shared { - // FIXME: throw type error - expected mut reference but found shared ref, - // which cannot be coerced - } - Expectation::rvalue_hint(Ty::clone(exp_inner)) - } else { - Expectation::none() - }; + Expr::Ref { expr, rawness, mutability } => { + let expectation = if let Some((exp_inner, exp_rawness, exp_mutability)) = + &expected.ty.as_reference_or_ptr() + { + if *exp_mutability == Mutability::Mut && *mutability == Mutability::Shared { + // FIXME: throw type error - expected mut reference but found shared ref, + // which cannot be coerced + } + if *exp_rawness == Rawness::Ref && *rawness == Rawness::RawPtr { + // FIXME: throw type error - expected reference but found ptr, + // which cannot be coerced + } + Expectation::rvalue_hint(Ty::clone(exp_inner)) + } else { + Expectation::none() + }; let inner_ty = self.infer_expr_inner(*expr, &expectation); - Ty::apply_one(TypeCtor::Ref(*mutability), inner_ty) + let ty = match rawness { + Rawness::RawPtr => TypeCtor::RawPtr(*mutability), + Rawness::Ref => TypeCtor::Ref(*mutability), + }; + Ty::apply_one(ty, inner_ty) } Expr::Box { expr } => { let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index e91c9be040a6..9fa8d3bdc3f4 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -49,8 +49,10 @@ use std::sync::Arc; use std::{iter, mem}; use hir_def::{ - expr::ExprId, type_ref::Mutability, AdtId, AssocContainerId, DefWithBodyId, GenericDefId, - HasModule, Lookup, TraitId, TypeAliasId, TypeParamId, + expr::ExprId, + type_ref::{Mutability, Rawness}, + AdtId, AssocContainerId, DefWithBodyId, GenericDefId, HasModule, Lookup, TraitId, TypeAliasId, + TypeParamId, }; use ra_db::{impl_intern_key, salsa, CrateId}; @@ -159,6 +161,12 @@ pub enum TypeCtor { pub struct TypeCtorId(salsa::InternId); impl_intern_key!(TypeCtorId); +/// This exists just for Chalk, because Chalk just has a single `FnDefId` where +/// we have different IDs for struct and enum variant constructors. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct CallableDefId(salsa::InternId); +impl_intern_key!(CallableDefId); + impl TypeCtor { pub fn num_ty_params(self, db: &dyn HirDatabase) -> usize { match self { @@ -703,6 +711,18 @@ impl Ty { } } + pub fn as_reference_or_ptr(&self) -> Option<(&Ty, Rawness, Mutability)> { + match self { + Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(mutability), parameters }) => { + Some((parameters.as_single(), Rawness::Ref, *mutability)) + } + Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(mutability), parameters }) => { + Some((parameters.as_single(), Rawness::RawPtr, *mutability)) + } + _ => None, + } + } + pub fn strip_references(&self) -> &Ty { let mut t: &Ty = self; diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs index 2cc4f4bf964c..6f777ed8c937 100644 --- a/crates/ra_hir_ty/src/tests/coercion.rs +++ b/crates/ra_hir_ty/src/tests/coercion.rs @@ -116,15 +116,20 @@ fn infer_let_stmt_coerce() { assert_snapshot!( infer(r#" fn test() { - let x: &[i32] = &[1]; + let x: &[isize] = &[1]; + let x: *const [isize] = &[1]; } "#), @r###" - 11..40 '{ ...[1]; }': () - 21..22 'x': &[i32] - 33..37 '&[1]': &[i32; _] - 34..37 '[1]': [i32; _] - 35..36 '1': i32 + 11..76 '{ ...[1]; }': () + 21..22 'x': &[isize] + 35..39 '&[1]': &[isize; _] + 36..39 '[1]': [isize; _] + 37..38 '1': isize + 49..50 'x': *const [isize] + 69..73 '&[1]': &[isize; _] + 70..73 '[1]': [isize; _] + 71..72 '1': isize "###); } diff --git a/crates/ra_hir_ty/src/tests/patterns.rs b/crates/ra_hir_ty/src/tests/patterns.rs index 0c5f972a2c6e..fe62587c0d70 100644 --- a/crates/ra_hir_ty/src/tests/patterns.rs +++ b/crates/ra_hir_ty/src/tests/patterns.rs @@ -520,3 +520,53 @@ fn main() { 105..107 '()': () ") } + +#[test] +fn match_ergonomics_in_closure_params() { + assert_snapshot!( + infer(r#" +#[lang = "fn_once"] +trait FnOnce { + type Output; +} + +fn foo U>(t: T, f: F) -> U { loop {} } + +fn test() { + foo(&(1, "a"), |&(x, y)| x); // normal, no match ergonomics + foo(&(1, "a"), |(x, y)| x); +} +"#), + @r###" + 94..95 't': T + 100..101 'f': F + 111..122 '{ loop {} }': U + 113..120 'loop {}': ! + 118..120 '{}': () + 134..233 '{ ... x); }': () + 140..143 'foo': fn foo<&(i32, &str), i32, |&(i32, &str)| -> i32>(&(i32, &str), |&(i32, &str)| -> i32) -> i32 + 140..167 'foo(&(...y)| x)': i32 + 144..153 '&(1, "a")': &(i32, &str) + 145..153 '(1, "a")': (i32, &str) + 146..147 '1': i32 + 149..152 '"a"': &str + 155..166 '|&(x, y)| x': |&(i32, &str)| -> i32 + 156..163 '&(x, y)': &(i32, &str) + 157..163 '(x, y)': (i32, &str) + 158..159 'x': i32 + 161..162 'y': &str + 165..166 'x': i32 + 204..207 'foo': fn foo<&(i32, &str), &i32, |&(i32, &str)| -> &i32>(&(i32, &str), |&(i32, &str)| -> &i32) -> &i32 + 204..230 'foo(&(...y)| x)': &i32 + 208..217 '&(1, "a")': &(i32, &str) + 209..217 '(1, "a")': (i32, &str) + 210..211 '1': i32 + 213..216 '"a"': &str + 219..229 '|(x, y)| x': |&(i32, &str)| -> &i32 + 220..226 '(x, y)': (i32, &str) + 221..222 'x': &i32 + 224..225 'y': &&str + 228..229 'x': &i32 + "### + ); +} diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index fd2208af280e..88309157b78c 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs @@ -384,6 +384,26 @@ fn test(a: &u32, b: &mut u32, c: *const u32, d: *mut u32) { ); } +#[test] +fn infer_raw_ref() { + assert_snapshot!( + infer(r#" +fn test(a: i32) { + &raw mut a; + &raw const a; +} +"#), + @r###" + 9..10 'a': i32 + 17..54 '{ ...t a; }': () + 23..33 '&raw mut a': *mut i32 + 32..33 'a': i32 + 39..51 '&raw const a': *const i32 + 50..51 'a': i32 + "### + ); +} + #[test] fn infer_literals() { assert_snapshot!( @@ -937,7 +957,7 @@ fn main(foo: Foo) { 51..107 'if tru... }': () 54..58 'true': bool 59..67 '{ }': () - 73..107 'if fal... }': () + 73..107 'if fal... }': i32 76..81 'false': bool 82..107 '{ ... }': i32 92..95 'foo': Foo @@ -1923,3 +1943,57 @@ fn test() { "### ); } + +#[test] +fn infer_labelled_break_with_val() { + assert_snapshot!( + infer(r#" +fn foo() { + let _x = || 'outer: loop { + let inner = 'inner: loop { + let i = Default::default(); + if (break 'outer i) { + loop { break 'inner 5i8; }; + } else if true { + break 'inner 6; + } + break 7; + }; + break inner < 8; + }; +} +"#), + @r###" + 10..336 '{ ... }; }': () + 20..22 '_x': || -> bool + 25..333 '|| 'ou... }': || -> bool + 28..333 ''outer... }': bool + 41..333 '{ ... }': () + 55..60 'inner': i8 + 63..301 ''inner... }': i8 + 76..301 '{ ... }': () + 94..95 'i': bool + 98..114 'Defaul...efault': {unknown} + 98..116 'Defaul...ault()': bool + 130..270 'if (br... }': () + 134..148 'break 'outer i': ! + 147..148 'i': bool + 150..209 '{ ... }': () + 168..194 'loop {...5i8; }': ! + 173..194 '{ brea...5i8; }': () + 175..191 'break ...er 5i8': ! + 188..191 '5i8': i8 + 215..270 'if tru... }': () + 218..222 'true': bool + 223..270 '{ ... }': () + 241..255 'break 'inner 6': ! + 254..255 '6': i8 + 283..290 'break 7': ! + 289..290 '7': i8 + 311..326 'break inner < 8': ! + 317..322 'inner': i8 + 317..326 'inner < 8': bool + 325..326 '8': i8 + "### + ); +} diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs index 6826610cb3b5..e8778d419be7 100644 --- a/crates/ra_hir_ty/src/tests/traits.rs +++ b/crates/ra_hir_ty/src/tests/traits.rs @@ -2643,6 +2643,79 @@ fn test() { ); } +#[test] +fn builtin_fn_def_copy() { + assert_snapshot!( + infer_with_mismatches(r#" +#[lang = "copy"] +trait Copy {} + +fn foo() {} +fn bar(T) -> T {} +struct Struct(usize); +enum Enum { Variant(usize) } + +trait Test { fn test(&self) -> bool; } +impl Test for T {} + +fn test() { + foo.test(); + bar.test(); + Struct.test(); + Enum::Variant.test(); +} +"#, true), + @r###" + 42..44 '{}': () + 61..62 'T': {unknown} + 69..71 '{}': () + 69..71: expected T, got () + 146..150 'self': &Self + 202..282 '{ ...t(); }': () + 208..211 'foo': fn foo() + 208..218 'foo.test()': bool + 224..227 'bar': fn bar<{unknown}>({unknown}) -> {unknown} + 224..234 'bar.test()': bool + 240..246 'Struct': Struct(usize) -> Struct + 240..253 'Struct.test()': bool + 259..272 'Enum::Variant': Variant(usize) -> Enum + 259..279 'Enum::...test()': bool + "### + ); +} + +#[test] +fn builtin_fn_ptr_copy() { + assert_snapshot!( + infer_with_mismatches(r#" +#[lang = "copy"] +trait Copy {} + +trait Test { fn test(&self) -> bool; } +impl Test for T {} + +fn test(f1: fn(), f2: fn(usize) -> u8, f3: fn(u8, u8) -> &u8) { + f1.test(); + f2.test(); + f3.test(); +} +"#, true), + @r###" + 55..59 'self': &Self + 109..111 'f1': fn() + 119..121 'f2': fn(usize) -> u8 + 140..142 'f3': fn(u8, u8) -> &u8 + 163..211 '{ ...t(); }': () + 169..171 'f1': fn() + 169..178 'f1.test()': bool + 184..186 'f2': fn(usize) -> u8 + 184..193 'f2.test()': bool + 199..201 'f3': fn(u8, u8) -> &u8 + 199..208 'f3.test()': bool + "### + ); +} + #[test] fn builtin_sized() { assert_snapshot!( @@ -2680,3 +2753,48 @@ fn test() { "### ); } + +#[test] +fn integer_range_iterate() { + let t = type_at( + r#" +//- /main.rs crate:main deps:std +fn test() { + for x in 0..100 { x<|>; } +} + +//- /std.rs crate:std +pub mod ops { + pub struct Range { + pub start: Idx, + pub end: Idx, + } +} + +pub mod iter { + pub trait Iterator { + type Item; + } + + pub trait IntoIterator { + type Item; + type IntoIter: Iterator; + } + + impl IntoIterator for T where T: Iterator { + type Item = ::Item; + type IntoIter = Self; + } +} + +trait Step {} +impl Step for i32 {} +impl Step for i64 {} + +impl iter::Iterator for ops::Range { + type Item = A; +} +"#, + ); + assert_eq!(t, "i32"); +} diff --git a/crates/ra_hir_ty/src/traits/builtin.rs b/crates/ra_hir_ty/src/traits/builtin.rs index ccab246bfa71..88a422d2cb48 100644 --- a/crates/ra_hir_ty/src/traits/builtin.rs +++ b/crates/ra_hir_ty/src/traits/builtin.rs @@ -290,8 +290,7 @@ fn trait_object_unsize_impl_datum( let self_trait_ref = TraitRef { trait_, substs: self_substs }; let where_clauses = vec![GenericPredicate::Implemented(self_trait_ref)]; - let impl_substs = - Substs::builder(2).push(self_ty).push(Ty::Dyn(target_bounds.clone().into())).build(); + let impl_substs = Substs::builder(2).push(self_ty).push(Ty::Dyn(target_bounds.into())).build(); let trait_ref = TraitRef { trait_: unsize_trait, substs: impl_substs }; diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs index e2f2a9ccbc64..61de3cc30c66 100644 --- a/crates/ra_hir_ty/src/traits/chalk.rs +++ b/crates/ra_hir_ty/src/traits/chalk.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use log::debug; use chalk_ir::{fold::shift::Shift, GenericArg, TypeName}; +use chalk_solve::rust_ir::{self, WellKnownTrait}; use hir_def::{ lang_item::{lang_attr, LangItemTarget}, @@ -14,9 +15,8 @@ use ra_db::{salsa::InternKey, CrateId}; use super::{builtin, AssocTyValue, ChalkContext, Impl}; use crate::{ db::HirDatabase, display::HirDisplay, method_resolution::TyFingerprint, utils::generics, - DebruijnIndex, GenericPredicate, Substs, Ty, TypeCtor, + CallableDef, DebruijnIndex, GenericPredicate, Substs, Ty, TypeCtor, }; -use chalk_rust_ir::WellKnownTrait; use mapping::{convert_where_clauses, generic_predicate_to_inline_bound, make_binders}; pub use self::interner::*; @@ -54,10 +54,9 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { fn fn_def_datum( &self, - _fn_def_id: chalk_ir::FnDefId, - ) -> Arc> { - // We don't yet provide any FnDefs to Chalk - unimplemented!() + fn_def_id: chalk_ir::FnDefId, + ) -> Arc> { + self.db.fn_def_datum(self.krate, fn_def_id) } fn impls_for_trait( @@ -113,7 +112,7 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { } fn well_known_trait_id( &self, - well_known_trait: chalk_rust_ir::WellKnownTrait, + well_known_trait: rust_ir::WellKnownTrait, ) -> Option> { let lang_attr = lang_attr_from_well_known_trait(well_known_trait); let lang_items = self.db.crate_lang_items(self.krate); @@ -134,13 +133,13 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { fn opaque_ty_data( &self, _id: chalk_ir::OpaqueTyId, - ) -> Arc> { + ) -> Arc> { unimplemented!() } fn force_impl_for( &self, - _well_known: chalk_rust_ir::WellKnownTrait, + _well_known: rust_ir::WellKnownTrait, _ty: &chalk_ir::TyData, ) -> Option { // this method is mostly for rustc @@ -151,6 +150,10 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { // FIXME: implement actual object safety true } + + fn hidden_opaque_type(&self, _id: chalk_ir::OpaqueTyId) -> chalk_ir::Ty { + Ty::Unknown.to_chalk(self.db) + } } pub(crate) fn program_clauses_for_chalk_env_query( @@ -189,7 +192,7 @@ pub(crate) fn associated_ty_data_query( .collect(); let where_clauses = convert_where_clauses(db, type_alias.into(), &bound_vars); - let bound_data = chalk_rust_ir::AssociatedTyDatumBound { bounds, where_clauses }; + let bound_data = rust_ir::AssociatedTyDatumBound { bounds, where_clauses }; let datum = AssociatedTyDatum { trait_id: trait_.to_chalk(db), id, @@ -210,7 +213,7 @@ pub(crate) fn trait_datum_query( debug!("trait {:?} = {:?}", trait_id, trait_data.name); let generic_params = generics(db.upcast(), trait_.into()); let bound_vars = Substs::bound_vars(&generic_params, DebruijnIndex::INNERMOST); - let flags = chalk_rust_ir::TraitFlags { + let flags = rust_ir::TraitFlags { auto: trait_data.auto, upstream: trait_.lookup(db.upcast()).container.module(db.upcast()).krate != krate, non_enumerable: true, @@ -222,7 +225,7 @@ pub(crate) fn trait_datum_query( let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars); let associated_ty_ids = trait_data.associated_types().map(|type_alias| type_alias.to_chalk(db)).collect(); - let trait_datum_bound = chalk_rust_ir::TraitDatumBound { where_clauses }; + let trait_datum_bound = rust_ir::TraitDatumBound { where_clauses }; let well_known = lang_attr(db.upcast(), trait_).and_then(|name| well_known_trait_from_lang_attr(&name)); let trait_datum = TraitDatum { @@ -272,12 +275,12 @@ pub(crate) fn struct_datum_query( convert_where_clauses(db, generic_def, &bound_vars) }) .unwrap_or_else(Vec::new); - let flags = chalk_rust_ir::AdtFlags { + let flags = rust_ir::AdtFlags { upstream, // FIXME set fundamental flag correctly fundamental: false, }; - let struct_datum_bound = chalk_rust_ir::AdtDatumBound { + let struct_datum_bound = rust_ir::AdtDatumBound { fields: Vec::new(), // FIXME add fields (only relevant for auto traits) where_clauses, }; @@ -317,9 +320,9 @@ fn impl_def_datum( let bound_vars = Substs::bound_vars(&generic_params, DebruijnIndex::INNERMOST); let trait_ = trait_ref.trait_; let impl_type = if impl_id.lookup(db.upcast()).container.module(db.upcast()).krate == krate { - chalk_rust_ir::ImplType::Local + rust_ir::ImplType::Local } else { - chalk_rust_ir::ImplType::External + rust_ir::ImplType::External }; let where_clauses = convert_where_clauses(db, impl_id.into(), &bound_vars); let negative = impl_data.is_negative; @@ -332,13 +335,9 @@ fn impl_def_datum( ); let trait_ref = trait_ref.to_chalk(db); - let polarity = if negative { - chalk_rust_ir::Polarity::Negative - } else { - chalk_rust_ir::Polarity::Positive - }; + let polarity = if negative { rust_ir::Polarity::Negative } else { rust_ir::Polarity::Positive }; - let impl_datum_bound = chalk_rust_ir::ImplDatumBound { trait_ref, where_clauses }; + let impl_datum_bound = rust_ir::ImplDatumBound { trait_ref, where_clauses }; let trait_data = db.trait_data(trait_); let associated_ty_value_ids = impl_data .items @@ -396,8 +395,8 @@ fn type_alias_associated_ty_value( .associated_type_by_name(&type_alias_data.name) .expect("assoc ty value should not exist"); // validated when building the impl data as well let ty = db.ty(type_alias.into()); - let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty: ty.value.to_chalk(db) }; - let value = chalk_rust_ir::AssociatedTyValue { + let value_bound = rust_ir::AssociatedTyValueBound { ty: ty.value.to_chalk(db) }; + let value = rust_ir::AssociatedTyValue { impl_id: Impl::ImplDef(impl_id).to_chalk(db), associated_ty_id: assoc_ty.to_chalk(db), value: make_binders(value_bound, ty.num_binders), @@ -405,6 +404,26 @@ fn type_alias_associated_ty_value( Arc::new(value) } +pub(crate) fn fn_def_datum_query( + db: &dyn HirDatabase, + _krate: CrateId, + fn_def_id: FnDefId, +) -> Arc { + let callable_def: CallableDef = from_chalk(db, fn_def_id); + let generic_params = generics(db.upcast(), callable_def.into()); + let sig = db.callable_item_signature(callable_def); + let bound_vars = Substs::bound_vars(&generic_params, DebruijnIndex::INNERMOST); + let where_clauses = convert_where_clauses(db, callable_def.into(), &bound_vars); + let bound = rust_ir::FnDefDatumBound { + // Note: Chalk doesn't actually use this information yet as far as I am aware, but we provide it anyway + argument_types: sig.value.params().iter().map(|ty| ty.clone().to_chalk(db)).collect(), + return_type: sig.value.ret().clone().to_chalk(db), + where_clauses, + }; + let datum = FnDefDatum { id: fn_def_id, binders: make_binders(bound, sig.num_binders) }; + Arc::new(datum) +} + impl From for crate::TypeCtorId { fn from(struct_id: AdtId) -> Self { struct_id.0 @@ -417,6 +436,18 @@ impl From for AdtId { } } +impl From for crate::CallableDefId { + fn from(fn_def_id: FnDefId) -> Self { + InternKey::from_intern_id(fn_def_id.0) + } +} + +impl From for FnDefId { + fn from(callable_def_id: crate::CallableDefId) -> Self { + chalk_ir::FnDefId(callable_def_id.as_intern_id()) + } +} + impl From for crate::traits::GlobalImplId { fn from(impl_id: ImplId) -> Self { InternKey::from_intern_id(impl_id.0) @@ -429,14 +460,14 @@ impl From for ImplId { } } -impl From> for crate::traits::AssocTyValueId { - fn from(id: chalk_rust_ir::AssociatedTyValueId) -> Self { +impl From> for crate::traits::AssocTyValueId { + fn from(id: rust_ir::AssociatedTyValueId) -> Self { Self::from_intern_id(id.0) } } -impl From for chalk_rust_ir::AssociatedTyValueId { +impl From for rust_ir::AssociatedTyValueId { fn from(assoc_ty_value_id: crate::traits::AssocTyValueId) -> Self { - chalk_rust_ir::AssociatedTyValueId(assoc_ty_value_id.as_intern_id()) + rust_ir::AssociatedTyValueId(assoc_ty_value_id.as_intern_id()) } } diff --git a/crates/ra_hir_ty/src/traits/chalk/interner.rs b/crates/ra_hir_ty/src/traits/chalk/interner.rs index 0603728197e4..e27074ba6fad 100644 --- a/crates/ra_hir_ty/src/traits/chalk/interner.rs +++ b/crates/ra_hir_ty/src/traits/chalk/interner.rs @@ -11,15 +11,17 @@ use std::{fmt, sync::Arc}; pub struct Interner; pub type AssocTypeId = chalk_ir::AssocTypeId; -pub type AssociatedTyDatum = chalk_rust_ir::AssociatedTyDatum; +pub type AssociatedTyDatum = chalk_solve::rust_ir::AssociatedTyDatum; pub type TraitId = chalk_ir::TraitId; -pub type TraitDatum = chalk_rust_ir::TraitDatum; +pub type TraitDatum = chalk_solve::rust_ir::TraitDatum; pub type AdtId = chalk_ir::AdtId; -pub type StructDatum = chalk_rust_ir::AdtDatum; +pub type StructDatum = chalk_solve::rust_ir::AdtDatum; pub type ImplId = chalk_ir::ImplId; -pub type ImplDatum = chalk_rust_ir::ImplDatum; -pub type AssociatedTyValueId = chalk_rust_ir::AssociatedTyValueId; -pub type AssociatedTyValue = chalk_rust_ir::AssociatedTyValue; +pub type ImplDatum = chalk_solve::rust_ir::ImplDatum; +pub type AssociatedTyValueId = chalk_solve::rust_ir::AssociatedTyValueId; +pub type AssociatedTyValue = chalk_solve::rust_ir::AssociatedTyValue; +pub type FnDefId = chalk_ir::FnDefId; +pub type FnDefDatum = chalk_solve::rust_ir::FnDefDatum; impl chalk_ir::interner::Interner for Interner { type InternedType = Box>; // FIXME use Arc? diff --git a/crates/ra_hir_ty/src/traits/chalk/mapping.rs b/crates/ra_hir_ty/src/traits/chalk/mapping.rs index a83d82fd8bf4..5f6daf842be6 100644 --- a/crates/ra_hir_ty/src/traits/chalk/mapping.rs +++ b/crates/ra_hir_ty/src/traits/chalk/mapping.rs @@ -7,6 +7,7 @@ use chalk_ir::{ cast::Cast, fold::shift::Shift, interner::HasInterner, PlaceholderIndex, Scalar, TypeName, UniverseIndex, }; +use chalk_solve::rust_ir; use hir_def::{type_ref::Mutability, AssocContainerId, GenericDefId, Lookup, TypeAliasId}; use ra_db::salsa::InternKey; @@ -15,8 +16,8 @@ use crate::{ db::HirDatabase, primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness, Uncertain}, traits::{builtin, AssocTyValue, Canonical, Impl, Obligation}, - ApplicationTy, GenericPredicate, InEnvironment, ProjectionPredicate, ProjectionTy, Substs, - TraitEnvironment, TraitRef, Ty, TypeCtor, + ApplicationTy, CallableDef, GenericPredicate, InEnvironment, ProjectionPredicate, ProjectionTy, + Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, }; use super::interner::*; @@ -26,14 +27,19 @@ impl ToChalk for Ty { type Chalk = chalk_ir::Ty; fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Ty { match self { - Ty::Apply(apply_ty) => { - if let TypeCtor::Ref(m) = apply_ty.ctor { - return ref_to_chalk(db, m, apply_ty.parameters); + Ty::Apply(apply_ty) => match apply_ty.ctor { + TypeCtor::Ref(m) => ref_to_chalk(db, m, apply_ty.parameters), + TypeCtor::FnPtr { num_args: _ } => { + let substitution = apply_ty.parameters.to_chalk(db).shifted_in(&Interner); + chalk_ir::TyData::Function(chalk_ir::Fn { num_binders: 0, substitution }) + .intern(&Interner) } - let name = apply_ty.ctor.to_chalk(db); - let substitution = apply_ty.parameters.to_chalk(db); - chalk_ir::ApplicationTy { name, substitution }.cast(&Interner).intern(&Interner) - } + _ => { + let name = apply_ty.ctor.to_chalk(db); + let substitution = apply_ty.parameters.to_chalk(db); + chalk_ir::ApplicationTy { name, substitution }.cast(&Interner).intern(&Interner) + } + }, Ty::Projection(proj_ty) => { let associated_ty_id = proj_ty.associated_ty.to_chalk(db); let substitution = proj_ty.parameters.to_chalk(db); @@ -93,9 +99,15 @@ impl ToChalk for Ty { Ty::Projection(ProjectionTy { associated_ty, parameters }) } chalk_ir::TyData::Alias(chalk_ir::AliasTy::Opaque(_)) => unimplemented!(), - chalk_ir::TyData::Function(_) => unimplemented!(), + chalk_ir::TyData::Function(chalk_ir::Fn { num_binders: _, substitution }) => { + let parameters: Substs = from_chalk(db, substitution); + Ty::Apply(ApplicationTy { + ctor: TypeCtor::FnPtr { num_args: (parameters.len() - 1) as u16 }, + parameters, + }) + } chalk_ir::TyData::BoundVar(idx) => Ty::Bound(idx), - chalk_ir::TyData::InferenceVar(_iv) => Ty::Unknown, + chalk_ir::TyData::InferenceVar(_iv, _kind) => Ty::Unknown, chalk_ir::TyData::Dyn(where_clauses) => { assert_eq!(where_clauses.bounds.binders.len(&Interner), 1); let predicates = where_clauses @@ -217,13 +229,17 @@ impl ToChalk for TypeCtor { TypeCtor::Slice => TypeName::Slice, TypeCtor::Ref(mutability) => TypeName::Ref(mutability.to_chalk(db)), TypeCtor::Str => TypeName::Str, + TypeCtor::FnDef(callable_def) => { + let id = callable_def.to_chalk(db); + TypeName::FnDef(id) + } + TypeCtor::Never => TypeName::Never, + TypeCtor::Int(Uncertain::Unknown) | TypeCtor::Float(Uncertain::Unknown) | TypeCtor::Adt(_) | TypeCtor::Array - | TypeCtor::FnDef(_) | TypeCtor::FnPtr { .. } - | TypeCtor::Never | TypeCtor::Closure { .. } => { // other TypeCtors get interned and turned into a chalk StructId let struct_id = db.intern_type_ctor(self).into(); @@ -259,10 +275,14 @@ impl ToChalk for TypeCtor { TypeName::Slice => TypeCtor::Slice, TypeName::Ref(mutability) => TypeCtor::Ref(from_chalk(db, mutability)), TypeName::Str => TypeCtor::Str, + TypeName::Never => TypeCtor::Never, - TypeName::FnDef(_) => unreachable!(), + TypeName::FnDef(fn_def_id) => { + let callable_def = from_chalk(db, fn_def_id); + TypeCtor::FnDef(callable_def) + } - TypeName::Error => { + TypeName::Array | TypeName::Error => { // this should not be reached, since we don't represent TypeName::Error with TypeCtor unreachable!() } @@ -347,6 +367,18 @@ impl ToChalk for Impl { } } +impl ToChalk for CallableDef { + type Chalk = FnDefId; + + fn to_chalk(self, db: &dyn HirDatabase) -> FnDefId { + db.intern_callable_def(self).into() + } + + fn from_chalk(db: &dyn HirDatabase, fn_def_id: FnDefId) -> CallableDef { + db.lookup_intern_callable_def(fn_def_id.into()) + } +} + impl ToChalk for TypeAliasId { type Chalk = AssocTypeId; @@ -479,7 +511,7 @@ where fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Canonical { let parameter = chalk_ir::CanonicalVarKind::new( - chalk_ir::VariableKind::Ty, + chalk_ir::VariableKind::Ty(chalk_ir::TyKind::General), chalk_ir::UniverseIndex::ROOT, ); let value = self.value.to_chalk(db); @@ -550,17 +582,17 @@ impl ToChalk for builtin::BuiltinImplData { type Chalk = ImplDatum; fn to_chalk(self, db: &dyn HirDatabase) -> ImplDatum { - let impl_type = chalk_rust_ir::ImplType::External; + let impl_type = rust_ir::ImplType::External; let where_clauses = self.where_clauses.into_iter().map(|w| w.to_chalk(db)).collect(); let impl_datum_bound = - chalk_rust_ir::ImplDatumBound { trait_ref: self.trait_ref.to_chalk(db), where_clauses }; + rust_ir::ImplDatumBound { trait_ref: self.trait_ref.to_chalk(db), where_clauses }; let associated_ty_value_ids = self.assoc_ty_values.into_iter().map(|v| v.to_chalk(db)).collect(); - chalk_rust_ir::ImplDatum { + rust_ir::ImplDatum { binders: make_binders(impl_datum_bound, self.num_vars), impl_type, - polarity: chalk_rust_ir::Polarity::Positive, + polarity: rust_ir::Polarity::Positive, associated_ty_value_ids, } } @@ -575,9 +607,9 @@ impl ToChalk for builtin::BuiltinImplAssocTyValueData { fn to_chalk(self, db: &dyn HirDatabase) -> AssociatedTyValue { let ty = self.value.to_chalk(db); - let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty }; + let value_bound = rust_ir::AssociatedTyValueBound { ty }; - chalk_rust_ir::AssociatedTyValue { + rust_ir::AssociatedTyValue { associated_ty_id: self.assoc_ty_id.to_chalk(db), impl_id: self.impl_.to_chalk(db), value: make_binders(value_bound, self.num_vars), @@ -599,7 +631,7 @@ where chalk_ir::Binders::new( chalk_ir::VariableKinds::from( &Interner, - std::iter::repeat(chalk_ir::VariableKind::Ty).take(num_vars), + std::iter::repeat(chalk_ir::VariableKind::Ty(chalk_ir::TyKind::General)).take(num_vars), ), value, ) @@ -626,7 +658,7 @@ pub(super) fn generic_predicate_to_inline_bound( db: &dyn HirDatabase, pred: &GenericPredicate, self_ty: &Ty, -) -> Option> { +) -> Option> { // An InlineBound is like a GenericPredicate, except the self type is left out. // We don't have a special type for this, but Chalk does. match pred { @@ -641,8 +673,8 @@ pub(super) fn generic_predicate_to_inline_bound( .map(|ty| ty.clone().to_chalk(db).cast(&Interner)) .collect(); let trait_bound = - chalk_rust_ir::TraitBound { trait_id: trait_ref.trait_.to_chalk(db), args_no_self }; - Some(chalk_rust_ir::InlineBound::TraitBound(trait_bound)) + rust_ir::TraitBound { trait_id: trait_ref.trait_.to_chalk(db), args_no_self }; + Some(rust_ir::InlineBound::TraitBound(trait_bound)) } GenericPredicate::Projection(proj) => { if &proj.projection_ty.parameters[0] != self_ty { @@ -656,16 +688,13 @@ pub(super) fn generic_predicate_to_inline_bound( .iter() .map(|ty| ty.clone().to_chalk(db).cast(&Interner)) .collect(); - let alias_eq_bound = chalk_rust_ir::AliasEqBound { + let alias_eq_bound = rust_ir::AliasEqBound { value: proj.ty.clone().to_chalk(db), - trait_bound: chalk_rust_ir::TraitBound { - trait_id: trait_.to_chalk(db), - args_no_self, - }, + trait_bound: rust_ir::TraitBound { trait_id: trait_.to_chalk(db), args_no_self }, associated_ty_id: proj.projection_ty.associated_ty.to_chalk(db), parameters: Vec::new(), // FIXME we don't support generic associated types yet }; - Some(chalk_rust_ir::InlineBound::AliasEqBound(alias_eq_bound)) + Some(rust_ir::InlineBound::AliasEqBound(alias_eq_bound)) } GenericPredicate::Error => None, } diff --git a/crates/ra_hir_ty/src/traits/chalk/tls.rs b/crates/ra_hir_ty/src/traits/chalk/tls.rs index ebf402a0793a..d88828c7c0a0 100644 --- a/crates/ra_hir_ty/src/traits/chalk/tls.rs +++ b/crates/ra_hir_ty/src/traits/chalk/tls.rs @@ -247,10 +247,24 @@ impl DebugContext<'_> { pub fn debug_fn_def_id( &self, - _fn_def_id: chalk_ir::FnDefId, + fn_def_id: chalk_ir::FnDefId, fmt: &mut fmt::Formatter<'_>, ) -> Result<(), fmt::Error> { - write!(fmt, "fn") + let def: CallableDef = from_chalk(self.0, fn_def_id); + let name = match def { + CallableDef::FunctionId(ff) => self.0.function_data(ff).name.clone(), + CallableDef::StructId(s) => self.0.struct_data(s).name.clone(), + CallableDef::EnumVariantId(e) => { + let enum_data = self.0.enum_data(e.parent); + enum_data.variants[e.local_id].name.clone() + } + }; + match def { + CallableDef::FunctionId(_) => write!(fmt, "{{fn {}}}", name), + CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => { + write!(fmt, "{{ctor {}}}", name) + } + } } pub fn debug_const( diff --git a/crates/ra_ide/src/call_hierarchy.rs b/crates/ra_ide/src/call_hierarchy.rs index 85d1f0cb15e7..defd8176ff87 100644 --- a/crates/ra_ide/src/call_hierarchy.rs +++ b/crates/ra_ide/src/call_hierarchy.rs @@ -245,6 +245,35 @@ mod tests { ); } + #[test] + fn test_call_hierarchy_in_tests_mod() { + check_hierarchy( + r#" + //- /lib.rs cfg:test + fn callee() {} + fn caller1() { + call<|>ee(); + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_caller() { + callee(); + } + } + "#, + "callee FN_DEF FileId(1) 0..14 3..9", + &[ + "caller1 FN_DEF FileId(1) 15..45 18..25 : [34..40]", + "test_caller FN_DEF FileId(1) 93..147 108..119 : [132..138]", + ], + &[], + ); + } + #[test] fn test_call_hierarchy_in_different_files() { check_hierarchy( diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 191300704b55..a721e23c6934 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - mod completion_config; mod completion_item; mod completion_context; @@ -35,6 +33,51 @@ pub use crate::completion::{ completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, }; +//FIXME: split the following feature into fine-grained features. + +// Feature: Magic Completions +// +// In addition to usual reference completion, rust-analyzer provides some ✨magic✨ +// completions as well: +// +// Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor +// is placed at the appropriate position. Even though `if` is easy to type, you +// still want to complete it, to get ` { }` for free! `return` is inserted with a +// space or `;` depending on the return type of the function. +// +// When completing a function call, `()` are automatically inserted. If a function +// takes arguments, the cursor is positioned inside the parenthesis. +// +// There are postfix completions, which can be triggered by typing something like +// `foo().if`. The word after `.` determines postfix completion. Possible variants are: +// +// - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` +// - `expr.match` -> `match expr {}` +// - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` +// - `expr.ref` -> `&expr` +// - `expr.refm` -> `&mut expr` +// - `expr.not` -> `!expr` +// - `expr.dbg` -> `dbg!(expr)` +// +// There also snippet completions: +// +// .Expressions +// - `pd` -> `println!("{:?}")` +// - `ppd` -> `println!("{:#?}")` +// +// .Items +// - `tfn` -> `#[test] fn f(){}` +// - `tmod` -> +// ```rust +// #[cfg(test)] +// mod tests { +// use super::*; +// +// #[test] +// fn test_fn() {} +// } +// ``` + /// Main entry point for completion. We run completion as a two-phase process. /// /// First, we look at the position and collect a so-called `CompletionContext. @@ -82,3 +125,81 @@ pub(crate) fn completions( Some(acc) } + +#[cfg(test)] +mod tests { + use crate::completion::completion_config::CompletionConfig; + use crate::mock_analysis::analysis_and_position; + + struct DetailAndDocumentation<'a> { + detail: &'a str, + documentation: &'a str, + } + + fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) { + let (analysis, position) = analysis_and_position(fixture); + let config = CompletionConfig::default(); + let completions = analysis.completions(&config, position).unwrap().unwrap(); + for item in completions { + if item.detail() == Some(expected.detail) { + let opt = item.documentation(); + let doc = opt.as_ref().map(|it| it.as_str()); + assert_eq!(doc, Some(expected.documentation)); + return; + } + } + panic!("completion detail not found: {}", expected.detail) + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" }, + ); + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, + ); + } +} diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index f17266221fd6..fb3f0b743ef2 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs @@ -112,7 +112,7 @@ const ATTRIBUTES: &[AttrCompletion] = &[ AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false }, AttrCompletion { label: "should_panic", - snippet: Some(r#"expected = "${0:reason}""#), + snippet: Some(r#"should_panic(expected = "${0:reason}")"#), should_be_inner: false, }, AttrCompletion { @@ -571,7 +571,7 @@ mod tests { label: "should_panic", source_range: 19..19, delete: 19..19, - insert: "expected = \"${0:reason}\"", + insert: "should_panic(expected = \"${0:reason}\")", kind: Attribute, }, CompletionItem { @@ -810,7 +810,7 @@ mod tests { label: "should_panic", source_range: 20..20, delete: 20..20, - insert: "expected = \"${0:reason}\"", + insert: "should_panic(expected = \"${0:reason}\")", kind: Attribute, }, CompletionItem { diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index f2a52a407a97..59b58bf98b6b 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs @@ -1,12 +1,11 @@ //! FIXME: write short doc here - +use ra_assists::utils::TryEnum; use ra_syntax::{ ast::{self, AstNode}, TextRange, TextSize, }; use ra_text_edit::TextEdit; -use super::completion_config::SnippetCap; use crate::{ completion::{ completion_context::CompletionContext, @@ -14,7 +13,8 @@ use crate::{ }, CompletionItem, }; -use ra_assists::utils::TryEnum; + +use super::completion_config::SnippetCap; pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { if !ctx.config.enable_postfix_completions { @@ -184,6 +184,16 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { &format!("dbg!({})", receiver_text), ) .add_to(acc); + + postfix_snippet( + ctx, + cap, + &dot_receiver, + "call", + "function(expr)", + &format!("${{1}}({})", receiver_text), + ) + .add_to(acc); } fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { @@ -255,6 +265,13 @@ mod tests { insert: "Box::new(bar)", detail: "Box::new(expr)", }, + CompletionItem { + label: "call", + source_range: 89..89, + delete: 85..89, + insert: "${1}(bar)", + detail: "function(expr)", + }, CompletionItem { label: "dbg", source_range: 89..89, @@ -334,6 +351,13 @@ mod tests { insert: "Box::new(bar)", detail: "Box::new(expr)", }, + CompletionItem { + label: "call", + source_range: 210..210, + delete: 206..210, + insert: "${1}(bar)", + detail: "function(expr)", + }, CompletionItem { label: "dbg", source_range: 210..210, @@ -413,6 +437,13 @@ mod tests { insert: "Box::new(bar)", detail: "Box::new(expr)", }, + CompletionItem { + label: "call", + source_range: 211..211, + delete: 207..211, + insert: "${1}(bar)", + detail: "function(expr)", + }, CompletionItem { label: "dbg", source_range: 211..211, @@ -487,6 +518,13 @@ mod tests { insert: "Box::new(bar)", detail: "Box::new(expr)", }, + CompletionItem { + label: "call", + source_range: 91..91, + delete: 87..91, + insert: "${1}(bar)", + detail: "function(expr)", + }, CompletionItem { label: "dbg", source_range: 91..91, @@ -546,6 +584,13 @@ mod tests { insert: "Box::new(42)", detail: "Box::new(expr)", }, + CompletionItem { + label: "call", + source_range: 52..52, + delete: 49..52, + insert: "${1}(42)", + detail: "function(expr)", + }, CompletionItem { label: "dbg", source_range: 52..52, @@ -607,6 +652,13 @@ mod tests { insert: "Box::new(bar)", detail: "Box::new(expr)", }, + CompletionItem { + label: "call", + source_range: 149..150, + delete: 145..150, + insert: "${1}(bar)", + detail: "function(expr)", + }, CompletionItem { label: "dbg", source_range: 149..150, @@ -666,6 +718,13 @@ mod tests { insert: "Box::new(&&&&42)", detail: "Box::new(expr)", }, + CompletionItem { + label: "call", + source_range: 56..56, + delete: 49..56, + insert: "${1}(&&&&42)", + detail: "function(expr)", + }, CompletionItem { label: "dbg", source_range: 56..56, diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs index 039df03e0547..21c9316e6662 100644 --- a/crates/ra_ide/src/completion/complete_trait_impl.rs +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs @@ -49,56 +49,53 @@ use crate::{ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { if let Some((trigger, impl_def)) = completion_match(ctx) { match trigger.kind() { - SyntaxKind::NAME_REF => { - get_missing_assoc_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { + SyntaxKind::NAME_REF => get_missing_assoc_items(&ctx.sema, &impl_def) + .into_iter() + .for_each(|item| match item { hir::AssocItem::Function(fn_item) => { - add_function_impl(&trigger, acc, ctx, &fn_item) + add_function_impl(&trigger, acc, ctx, fn_item) } hir::AssocItem::TypeAlias(type_item) => { - add_type_alias_impl(&trigger, acc, ctx, &type_item) + add_type_alias_impl(&trigger, acc, ctx, type_item) } hir::AssocItem::Const(const_item) => { - add_const_impl(&trigger, acc, ctx, &const_item) + add_const_impl(&trigger, acc, ctx, const_item) } - }) - } + }), SyntaxKind::FN_DEF => { - for missing_fn in - get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { - match item { - hir::AssocItem::Function(fn_item) => Some(fn_item), - _ => None, - } + for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) + .into_iter() + .filter_map(|item| match item { + hir::AssocItem::Function(fn_item) => Some(fn_item), + _ => None, }) { - add_function_impl(&trigger, acc, ctx, &missing_fn); + add_function_impl(&trigger, acc, ctx, missing_fn); } } SyntaxKind::TYPE_ALIAS_DEF => { - for missing_fn in - get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { - match item { - hir::AssocItem::TypeAlias(type_item) => Some(type_item), - _ => None, - } + for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) + .into_iter() + .filter_map(|item| match item { + hir::AssocItem::TypeAlias(type_item) => Some(type_item), + _ => None, }) { - add_type_alias_impl(&trigger, acc, ctx, &missing_fn); + add_type_alias_impl(&trigger, acc, ctx, missing_fn); } } SyntaxKind::CONST_DEF => { - for missing_fn in - get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { - match item { - hir::AssocItem::Const(const_item) => Some(const_item), - _ => None, - } + for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) + .into_iter() + .filter_map(|item| match item { + hir::AssocItem::Const(const_item) => Some(const_item), + _ => None, }) { - add_const_impl(&trigger, acc, ctx, &missing_fn); + add_const_impl(&trigger, acc, ctx, missing_fn); } } @@ -126,9 +123,9 @@ fn add_function_impl( fn_def_node: &SyntaxNode, acc: &mut Completions, ctx: &CompletionContext, - func: &hir::Function, + func: hir::Function, ) { - let signature = FunctionSignature::from_hir(ctx.db, *func); + let signature = FunctionSignature::from_hir(ctx.db, func); let fn_name = func.name(ctx.db).to_string(); @@ -167,7 +164,7 @@ fn add_type_alias_impl( type_def_node: &SyntaxNode, acc: &mut Completions, ctx: &CompletionContext, - type_alias: &hir::TypeAlias, + type_alias: hir::TypeAlias, ) { let alias_name = type_alias.name(ctx.db).to_string(); @@ -187,7 +184,7 @@ fn add_const_impl( const_def_node: &SyntaxNode, acc: &mut Completions, ctx: &CompletionContext, - const_: &hir::Const, + const_: hir::Const, ) { let const_name = const_.name(ctx.db).map(|n| n.to_string()); diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs index db791660a18a..68032c37eda7 100644 --- a/crates/ra_ide/src/completion/complete_unqualified_path.rs +++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs @@ -297,6 +297,42 @@ mod tests { ); } + #[test] + fn completes_bindings_from_for_with_in_prefix() { + mark::check!(completes_bindings_from_for_with_in_prefix); + assert_debug_snapshot!( + do_reference_completion( + r" + fn test() { + for index in &[1, 2, 3] { + let t = in<|> + } + } + " + ), + @r###" + [ + CompletionItem { + label: "index", + source_range: 107..107, + delete: 107..107, + insert: "index", + kind: Binding, + }, + CompletionItem { + label: "test()", + source_range: 107..107, + delete: 107..107, + insert: "test()$0", + kind: Function, + lookup: "test", + detail: "fn test()", + }, + ] + "### + ); + } + #[test] fn completes_generic_params() { assert_debug_snapshot!( diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index da336973c180..c4646b727c63 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs @@ -12,6 +12,7 @@ use ra_syntax::{ use ra_text_edit::Indel; use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; +use test_utils::mark; /// `CompletionContext` is created early during completion to figure out, where /// exactly is the cursor, syntax-wise. @@ -169,7 +170,17 @@ impl<'a> CompletionContext<'a> { match self.token.kind() { // workaroud when completion is triggered by trigger characters. IDENT => self.original_token.text_range(), - _ => TextRange::empty(self.offset), + _ => { + // If we haven't characters between keyword and our cursor we take the keyword start range to edit + if self.token.kind().is_keyword() + && self.offset == self.original_token.text_range().end() + { + mark::hit!(completes_bindings_from_for_with_in_prefix); + TextRange::empty(self.original_token.text_range().start()) + } else { + TextRange::empty(self.offset) + } + } } } diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 440ffa31d4e0..61565c84fe2b 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs @@ -211,7 +211,7 @@ impl Completions { .parameter_names .iter() .skip(if function_signature.has_self_param { 1 } else { 0 }) - .cloned() + .map(|name| name.trim_start_matches('_').into()) .collect(); builder = builder.add_call_parens(ctx, name, Params::Named(params)); @@ -669,6 +669,37 @@ mod tests { ] "### ); + assert_debug_snapshot!( + do_reference_completion( + r" + fn with_ignored_args(_foo: i32, ___bar: bool, ho_ge_: String) {} + fn main() { with_<|> } + " + ), + @r###" + [ + CompletionItem { + label: "main()", + source_range: 110..115, + delete: 110..115, + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + CompletionItem { + label: "with_ignored_args(…)", + source_range: 110..115, + delete: 110..115, + insert: "with_ignored_args(${1:foo}, ${2:bar}, ${3:ho_ge_})$0", + kind: Function, + lookup: "with_ignored_args", + detail: "fn with_ignored_args(_foo: i32, ___bar: bool, ho_ge_: String)", + trigger_call_info: true, + }, + ] + "### + ); assert_debug_snapshot!( do_reference_completion( r" @@ -695,6 +726,33 @@ mod tests { ] "### ); + assert_debug_snapshot!( + do_reference_completion( + r" + struct S {} + impl S { + fn foo_ignored_args(&self, _a: bool, b: i32) {} + } + fn bar(s: &S) { + s.f<|> + } + " + ), + @r###" + [ + CompletionItem { + label: "foo_ignored_args(…)", + source_range: 194..195, + delete: 194..195, + insert: "foo_ignored_args(${1:a}, ${2:b})$0", + kind: Method, + lookup: "foo_ignored_args", + detail: "fn foo_ignored_args(&self, _a: bool, b: i32)", + trigger_call_info: true, + }, + ] + "### + ); } #[test] diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 3d83c0f71c5e..15dc50cf143d 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs @@ -664,7 +664,7 @@ mod tests { assert_debug_snapshot!(diagnostics, @r###" [ Diagnostic { - message: "Missing structure fields:\n- b", + message: "Missing structure fields:\n- b\n", range: 224..233, severity: Error, fix: Some( diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs index 722092de97d8..827c094e7163 100644 --- a/crates/ra_ide/src/display.rs +++ b/crates/ra_ide/src/display.rs @@ -79,16 +79,17 @@ pub(crate) fn rust_code_markup_with_doc( doc: Option<&str>, mod_path: Option<&str>, ) -> String { - let mut buf = "```rust\n".to_owned(); + let mut buf = String::new(); if let Some(mod_path) = mod_path { if !mod_path.is_empty() { - format_to!(buf, "{}\n", mod_path); + format_to!(buf, "```rust\n{}\n```\n\n", mod_path); } } - format_to!(buf, "{}\n```", code); + format_to!(buf, "```rust\n{}\n```", code); if let Some(doc) = doc { + format_to!(buf, "\n___"); format_to!(buf, "\n\n{}", doc); } diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index 9572debd822c..ca8a6a650994 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs @@ -10,7 +10,7 @@ use std::{ use hir::{Docs, Documentation, HasSource, HirDisplay}; use ra_ide_db::RootDatabase; use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; -use stdx::SepBy; +use stdx::{split1, SepBy}; use crate::display::{generic_parameters, where_predicates}; @@ -207,7 +207,16 @@ impl From<&'_ ast::FnDef> for FunctionSignature { res.push(raw_param); } - res.extend(param_list.params().map(|param| param.syntax().text().to_string())); + // macro-generated functions are missing whitespace + fn fmt_param(param: ast::Param) -> String { + let text = param.syntax().text().to_string(); + match split1(&text, ':') { + Some((left, right)) => format!("{}: {}", left.trim(), right.trim()), + _ => text, + } + } + + res.extend(param_list.params().map(fmt_param)); res_types.extend(param_list.params().map(|param| { let param_text = param.syntax().text().to_string(); match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index 5da28edd2414..c7bb1e69f8a5 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs @@ -92,15 +92,16 @@ impl NavigationTarget { let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); if let Some(src) = module.declaration_source(db) { let frange = original_range(db, src.as_ref().map(|it| it.syntax())); - return NavigationTarget::from_syntax( + let mut res = NavigationTarget::from_syntax( frange.file_id, name, None, frange.range, src.value.syntax().kind(), - src.value.doc_comment_text(), - src.value.short_label(), ); + res.docs = src.value.doc_comment_text(); + res.description = src.value.short_label(); + return res; } module.to_nav(db) } @@ -130,11 +131,9 @@ impl NavigationTarget { } /// Allows `NavigationTarget` to be created from a `NameOwner` - fn from_named( + pub(crate) fn from_named( db: &RootDatabase, node: InFile<&dyn ast::NameOwner>, - docs: Option, - description: Option, ) -> NavigationTarget { //FIXME: use `_` instead of empty string let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); @@ -148,8 +147,6 @@ impl NavigationTarget { focus_range, frange.range, node.value.syntax().kind(), - docs, - description, ) } @@ -159,8 +156,6 @@ impl NavigationTarget { focus_range: Option, full_range: TextRange, kind: SyntaxKind, - docs: Option, - description: Option, ) -> NavigationTarget { NavigationTarget { file_id, @@ -169,8 +164,8 @@ impl NavigationTarget { full_range, focus_range, container_name: None, - description, - docs, + description: None, + docs: None, } } } @@ -238,12 +233,11 @@ where { fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { let src = self.source(db); - NavigationTarget::from_named( - db, - src.as_ref().map(|it| it as &dyn ast::NameOwner), - src.value.doc_comment_text(), - src.value.short_label(), - ) + let mut res = + NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); + res.docs = src.value.doc_comment_text(); + res.description = src.value.short_label(); + res } } @@ -258,15 +252,7 @@ impl ToNav for hir::Module { } }; let frange = original_range(db, src.with_value(syntax)); - NavigationTarget::from_syntax( - frange.file_id, - name, - focus, - frange.range, - syntax.kind(), - None, - None, - ) + NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind()) } } @@ -285,8 +271,6 @@ impl ToNav for hir::ImplDef { None, frange.range, src.value.syntax().kind(), - None, - None, ) } } @@ -296,12 +280,12 @@ impl ToNav for hir::Field { let src = self.source(db); match &src.value { - FieldSource::Named(it) => NavigationTarget::from_named( - db, - src.with_value(it), - it.doc_comment_text(), - it.short_label(), - ), + FieldSource::Named(it) => { + let mut res = NavigationTarget::from_named(db, src.with_value(it)); + res.docs = it.doc_comment_text(); + res.description = it.short_label(); + res + } FieldSource::Pos(it) => { let frange = original_range(db, src.with_value(it.syntax())); NavigationTarget::from_syntax( @@ -310,8 +294,6 @@ impl ToNav for hir::Field { None, frange.range, it.syntax().kind(), - None, - None, ) } } @@ -322,12 +304,10 @@ impl ToNav for hir::MacroDef { fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { let src = self.source(db); log::debug!("nav target {:#?}", src.value.syntax()); - NavigationTarget::from_named( - db, - src.as_ref().map(|it| it as &dyn ast::NameOwner), - src.value.doc_comment_text(), - None, - ) + let mut res = + NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); + res.docs = src.value.doc_comment_text(); + res } } diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs index 967eee5d2328..aad5a8e4db69 100644 --- a/crates/ra_ide/src/display/structure.rs +++ b/crates/ra_ide/src/display/structure.rs @@ -1,10 +1,6 @@ -//! FIXME: write short doc here - -use crate::TextRange; - use ra_syntax::{ ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, - match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, + match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent, }; #[derive(Debug, Clone)] @@ -18,6 +14,19 @@ pub struct StructureNode { pub deprecated: bool, } +// Feature: File Structure +// +// Provides a tree of the symbols defined in the file. Can be used to +// +// * fuzzy search symbol in a file (super useful) +// * draw breadcrumbs to describe the context around the cursor +// * draw outline of the file +// +// |=== +// | Editor | Shortcut +// +// | VS Code | kbd:[Ctrl+Shift+O] +// |=== pub fn file_structure(file: &SourceFile) -> Vec { let mut res = Vec::new(); let mut stack = Vec::new(); diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs index f536ba3e7865..54a47aac0684 100644 --- a/crates/ra_ide/src/expand_macro.rs +++ b/crates/ra_ide/src/expand_macro.rs @@ -1,5 +1,3 @@ -//! This modules implements "expand macro" functionality in the IDE - use hir::Semantics; use ra_ide_db::RootDatabase; use ra_syntax::{ @@ -14,6 +12,15 @@ pub struct ExpandedMacro { pub expansion: String, } +// Feature: Expand Macro Recursively +// +// Shows the full macro expansion of the macro at current cursor. +// +// |=== +// | Editor | Action Name +// +// | VS Code | **Rust Analyzer: Expand macro recursively** +// |=== pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option { let sema = Semantics::new(db); let file = sema.parse(position.file_id); diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index 554594a436ce..a4bc93cdbaa7 100644 --- a/crates/ra_ide/src/extend_selection.rs +++ b/crates/ra_ide/src/extend_selection.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use std::iter::successors; use hir::Semantics; @@ -14,6 +12,16 @@ use ra_syntax::{ use crate::FileRange; +// Feature: Extend Selection +// +// Extends the current selection to the encompassing syntactic construct +// (expression, statement, item, module, etc). It works with multiple cursors. +// +// |=== +// | Editor | Shortcut +// +// | VS Code | kbd:[Ctrl+Shift+→] +// |=== pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { let sema = Semantics::new(db); let src = sema.parse(frange.file_id); diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 90e85d419712..a6c86e99c95e 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use hir::Semantics; use ra_ide_db::{ defs::{classify_name, classify_name_ref}, @@ -17,6 +15,15 @@ use crate::{ FilePosition, NavigationTarget, RangeInfo, }; +// Feature: Go to Definition +// +// Navigates to the definition of an identifier. +// +// |=== +// | Editor | Shortcut +// +// | VS Code | kbd:[F12] +// |=== pub(crate) fn goto_definition( db: &RootDatabase, position: FilePosition, diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/goto_implementation.rs similarity index 96% rename from crates/ra_ide/src/impls.rs rename to crates/ra_ide/src/goto_implementation.rs index ea2225f70724..0cec0657e416 100644 --- a/crates/ra_ide/src/impls.rs +++ b/crates/ra_ide/src/goto_implementation.rs @@ -1,11 +1,18 @@ -//! FIXME: write short doc here - use hir::{Crate, ImplDef, Semantics}; use ra_ide_db::RootDatabase; use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; +// Feature: Go to Implementation +// +// Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. +// +// |=== +// | Editor | Shortcut +// +// | VS Code | kbd:[Ctrl+F12] +// |=== pub(crate) fn goto_implementation( db: &RootDatabase, position: FilePosition, diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs index a84637489e1f..91a3097fbb36 100644 --- a/crates/ra_ide/src/goto_type_definition.rs +++ b/crates/ra_ide/src/goto_type_definition.rs @@ -1,10 +1,17 @@ -//! FIXME: write short doc here - use ra_ide_db::RootDatabase; use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; +// Feature: Go to Type Definition +// +// Navigates to the type of an identifier. +// +// |=== +// | Editor | Action Name +// +// | VS Code | **Go to Type Definition* +// |=== pub(crate) fn goto_type_definition( db: &RootDatabase, position: FilePosition, diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index befa977c7fe7..9636cd0d6af7 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,28 +1,21 @@ -//! Logic for computing info that is displayed when the user hovers over any -//! source code items (e.g. function call, struct field, variable symbol...) +use std::iter::once; use hir::{ - Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, + Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, + ModuleDef, ModuleSource, Semantics, }; +use itertools::Itertools; use ra_db::SourceDatabase; use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ - ast::{self, DocCommentsOwner}, - match_ast, AstNode, - SyntaxKind::*, - SyntaxToken, TokenAtOffset, -}; +use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use crate::{ display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, FilePosition, RangeInfo, }; -use itertools::Itertools; -use std::iter::once; /// Contains the results when hovering over an item #[derive(Debug, Default)] @@ -62,6 +55,63 @@ impl HoverResult { } } +// Feature: Hover +// +// Shows additional information, like type of an expression or documentation for definition when "focusing" code. +// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. +pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option> { + let sema = Semantics::new(db); + let file = sema.parse(position.file_id).syntax().clone(); + let token = pick_best(file.token_at_offset(position.offset))?; + let token = sema.descend_into_macros(token); + + let mut res = HoverResult::new(); + + if let Some((node, name_kind)) = match_ast! { + match (token.parent()) { + ast::NameRef(name_ref) => { + classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) + }, + ast::Name(name) => { + classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) + }, + _ => None, + } + } { + let range = sema.original_range(&node).range; + res.extend(hover_text_from_name_kind(db, name_kind)); + + if !res.is_empty() { + return Some(RangeInfo::new(range, res)); + } + } + + let node = token + .ancestors() + .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; + + let ty = match_ast! { + match node { + ast::MacroCall(_it) => { + // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. + // (e.g expanding a builtin macro). So we give up here. + return None; + }, + ast::Expr(it) => { + sema.type_of_expr(&it) + }, + ast::Pat(it) => { + sema.type_of_pat(&it) + }, + _ => None, + } + }?; + + res.extend(Some(rust_code_markup(&ty.display(db)))); + let range = sema.original_range(&node).range; + Some(RangeInfo::new(range, res)) +} + fn hover_text( docs: Option, desc: Option, @@ -114,13 +164,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option { let src = it.source(db); - hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) + let docs = Documentation::from_ast(&src.value).map(Into::into); + hover_text(docs, Some(macro_label(&src.value)), mod_path) } Definition::Field(it) => { let src = it.source(db); match src.value { FieldSource::Named(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let docs = Documentation::from_ast(&it).map(Into::into); + hover_text(docs, it.short_label(), mod_path) } _ => None, } @@ -128,7 +180,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option match it { ModuleDef::Module(it) => match it.definition_source(db).value { ModuleSource::Module(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let docs = Documentation::from_ast(&it).map(Into::into); + hover_text(docs, it.short_label(), mod_path) } _ => None, }, @@ -153,66 +206,14 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option(db: &RootDatabase, def: D, mod_path: Option) -> Option where D: HasSource, - A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, + A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, { let src = def.source(db); - hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) + let docs = Documentation::from_ast(&src.value).map(Into::into); + hover_text(docs, src.value.short_label(), mod_path) } } -pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option> { - let sema = Semantics::new(db); - let file = sema.parse(position.file_id).syntax().clone(); - let token = pick_best(file.token_at_offset(position.offset))?; - let token = sema.descend_into_macros(token); - - let mut res = HoverResult::new(); - - if let Some((node, name_kind)) = match_ast! { - match (token.parent()) { - ast::NameRef(name_ref) => { - classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) - }, - ast::Name(name) => { - classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) - }, - _ => None, - } - } { - let range = sema.original_range(&node).range; - res.extend(hover_text_from_name_kind(db, name_kind)); - - if !res.is_empty() { - return Some(RangeInfo::new(range, res)); - } - } - - let node = token - .ancestors() - .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; - - let ty = match_ast! { - match node { - ast::MacroCall(_it) => { - // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. - // (e.g expanding a builtin macro). So we give up here. - return None; - }, - ast::Expr(it) => { - sema.type_of_expr(&it) - }, - ast::Pat(it) => { - sema.type_of_pat(&it) - }, - _ => None, - } - }?; - - res.extend(Some(rust_code_markup(&ty.display(db)))); - let range = sema.original_range(&node).range; - Some(RangeInfo::new(range, res)) -} - fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); fn priority(n: &SyntaxToken) -> usize { @@ -405,7 +406,7 @@ mod tests { }; } "#, - &["Foo\nfield_a: u32"], + &["Foo\n```\n\n```rust\nfield_a: u32"], ); // Hovering over the field in the definition @@ -422,7 +423,7 @@ mod tests { }; } "#, - &["Foo\nfield_a: u32"], + &["Foo\n```\n\n```rust\nfield_a: u32"], ); } @@ -475,7 +476,7 @@ fn main() { ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\nSome")); + assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n```\n\n```rust\nSome")); let (analysis, position) = single_file_with_position( " @@ -503,8 +504,12 @@ fn main() { "#, &[" Option +``` + +```rust None ``` +___ The None variant " @@ -524,8 +529,12 @@ The None variant "#, &[" Option +``` + +```rust Some ``` +___ The Some variant " @@ -606,7 +615,10 @@ fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); + assert_eq!( + trim_markup_opt(hover.info.first()), + Some("wrapper::Thing\n```\n\n```rust\nfn new() -> Thing") + ); } #[test] @@ -882,7 +894,7 @@ fn func(foo: i32) { if true { <|>foo; }; } fo<|>o(); } ", - &["fn foo()\n```\n\n<- `\u{3000}` here"], + &["fn foo()\n```\n___\n\n<- `\u{3000}` here"], ); } @@ -938,4 +950,106 @@ fn func(foo: i32) { if true { <|>foo; }; } &["mod my"], ); } + + #[test] + fn test_hover_struct_doc_comment() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr() { + check_hover_result( + r#" + //- /lib.rs + #[doc = "bar docs"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr_multiple_and_mixed() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs 0 + #[doc = "bar docs 1"] + #[doc = "bar docs 2"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_comment() { + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_attr() { + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], + ); + } } diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index b391f903a80a..75bd3c96bb1f 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs @@ -1,5 +1,3 @@ -//! This module defines multiple types of inlay hints and their visibility - use hir::{Adt, HirDisplay, Semantics, Type}; use ra_ide_db::RootDatabase; use ra_prof::profile; @@ -39,6 +37,26 @@ pub struct InlayHint { pub label: SmolStr, } +// Feature: Inlay Hints +// +// rust-analyzer shows additional information inline with the source code. +// Editors usually render this using read-only virtual text snippets interspersed with code. +// +// rust-analyzer shows hits for +// +// * types of local variables +// * names of function arguments +// * types of chained expressions +// +// **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations. +// This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: +// https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. +// +// |=== +// | Editor | Action Name +// +// | VS Code | **Rust Analyzer: Toggle inlay hints* +// |=== pub(crate) fn inlay_hints( db: &RootDatabase, file_id: FileId, diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index af1ade8a1e12..5036c1fb0c62 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use itertools::Itertools; use ra_fmt::{compute_ws, extract_trivial_expression}; use ra_syntax::{ @@ -11,6 +9,15 @@ use ra_syntax::{ }; use ra_text_edit::{TextEdit, TextEditBuilder}; +// Feature: Join Lines +// +// Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces. +// +// |=== +// | Editor | Action Name +// +// | VS Code | **Rust Analyzer: Join lines** +// |=== pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { let range = if range.is_empty() { let syntax = file.syntax(); diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 5ac002d82f0c..12d5716e8359 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -23,6 +23,7 @@ mod completion; mod runnables; mod goto_definition; mod goto_type_definition; +mod goto_implementation; mod extend_selection; mod hover; mod call_hierarchy; @@ -30,7 +31,6 @@ mod call_info; mod syntax_highlighting; mod parent_module; mod references; -mod impls; mod diagnostics; mod syntax_tree; mod folding_ranges; @@ -309,7 +309,8 @@ impl Analysis { /// Returns an edit which should be applied when opening a new line, fixing /// up minor stuff like continuing the comment. - pub fn on_enter(&self, position: FilePosition) -> Cancelable> { + /// The edit will be a snippet (with `$0`). + pub fn on_enter(&self, position: FilePosition) -> Cancelable> { self.with_db(|db| typing::on_enter(&db, position)) } @@ -372,7 +373,7 @@ impl Analysis { &self, position: FilePosition, ) -> Cancelable>>> { - self.with_db(|db| impls::goto_implementation(db, position)) + self.with_db(|db| goto_implementation::goto_implementation(db, position)) } /// Returns the type definitions for the symbol at `position`. diff --git a/crates/ra_ide/src/matching_brace.rs b/crates/ra_ide/src/matching_brace.rs index b8534870604f..407a9636d1d1 100644 --- a/crates/ra_ide/src/matching_brace.rs +++ b/crates/ra_ide/src/matching_brace.rs @@ -1,7 +1,16 @@ -//! FIXME: write short doc here - use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; +// Feature: Matching Brace +// +// If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, +// moves cursor to the matching brace. It uses the actual parser to determine +// braces, so it won't confuse generics with comparisons. +// +// |=== +// | Editor | Action Name +// +// | VS Code | **Rust Analyzer: Find matching brace** +// |=== pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option { const BRACES: &[SyntaxKind] = &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index 2c13f206a17c..ad78d2d93fd4 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs @@ -1,21 +1,81 @@ //! FIXME: write short doc here +use std::str::FromStr; use std::sync::Arc; use ra_cfg::CfgOptions; use ra_db::{CrateName, Env, RelativePathBuf}; -use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; +use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER}; use crate::{ - Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition::Edition2018, FileId, FilePosition, - FileRange, SourceRootId, + Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange, + SourceRootId, }; +#[derive(Debug)] +enum MockFileData { + Plain { path: String, content: String }, + Fixture(FixtureEntry), +} + +impl MockFileData { + fn new(path: String, content: String) -> Self { + // `Self::Plain` causes a false warning: 'variant is never constructed: `Plain` ' + // see https://github.com/rust-lang/rust/issues/69018 + MockFileData::Plain { path, content } + } + + fn path(&self) -> &str { + match self { + MockFileData::Plain { path, .. } => path.as_str(), + MockFileData::Fixture(f) => f.meta.path().as_str(), + } + } + + fn content(&self) -> &str { + match self { + MockFileData::Plain { content, .. } => content, + MockFileData::Fixture(f) => f.text.as_str(), + } + } + + fn cfg_options(&self) -> CfgOptions { + match self { + MockFileData::Fixture(f) => { + f.meta.cfg_options().map_or_else(Default::default, |o| o.clone()) + } + _ => CfgOptions::default(), + } + } + + fn edition(&self) -> Edition { + match self { + MockFileData::Fixture(f) => { + f.meta.edition().map_or(Edition::Edition2018, |v| Edition::from_str(v).unwrap()) + } + _ => Edition::Edition2018, + } + } + + fn env(&self) -> Env { + match self { + MockFileData::Fixture(f) => Env::from(f.meta.env()), + _ => Env::default(), + } + } +} + +impl From for MockFileData { + fn from(fixture: FixtureEntry) -> Self { + Self::Fixture(fixture) + } +} + /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis /// from a set of in-memory files. #[derive(Debug, Default)] pub struct MockAnalysis { - files: Vec<(String, String)>, + files: Vec, } impl MockAnalysis { @@ -35,7 +95,7 @@ impl MockAnalysis { pub fn with_files(fixture: &str) -> MockAnalysis { let mut res = MockAnalysis::new(); for entry in parse_fixture(fixture) { - res.add_file(&entry.meta, &entry.text); + res.add_file_fixture(entry); } res } @@ -48,30 +108,44 @@ impl MockAnalysis { for entry in parse_fixture(fixture) { if entry.text.contains(CURSOR_MARKER) { assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); - position = Some(res.add_file_with_position(&entry.meta, &entry.text)); + position = Some(res.add_file_fixture_with_position(entry)); } else { - res.add_file(&entry.meta, &entry.text); + res.add_file_fixture(entry); } } let position = position.expect("expected a marker (<|>)"); (res, position) } + pub fn add_file_fixture(&mut self, fixture: FixtureEntry) -> FileId { + let file_id = self.next_id(); + self.files.push(MockFileData::from(fixture)); + file_id + } + + pub fn add_file_fixture_with_position(&mut self, mut fixture: FixtureEntry) -> FilePosition { + let (offset, text) = extract_offset(&fixture.text); + fixture.text = text; + let file_id = self.next_id(); + self.files.push(MockFileData::from(fixture)); + FilePosition { file_id, offset } + } + pub fn add_file(&mut self, path: &str, text: &str) -> FileId { - let file_id = FileId((self.files.len() + 1) as u32); - self.files.push((path.to_string(), text.to_string())); + let file_id = self.next_id(); + self.files.push(MockFileData::new(path.to_string(), text.to_string())); file_id } pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { let (offset, text) = extract_offset(text); - let file_id = FileId((self.files.len() + 1) as u32); - self.files.push((path.to_string(), text)); + let file_id = self.next_id(); + self.files.push(MockFileData::new(path.to_string(), text)); FilePosition { file_id, offset } } pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { let (range, text) = extract_range(text); - let file_id = FileId((self.files.len() + 1) as u32); - self.files.push((path.to_string(), text)); + let file_id = self.next_id(); + self.files.push(MockFileData::new(path.to_string(), text)); FileRange { file_id, range } } pub fn id_of(&self, path: &str) -> FileId { @@ -79,7 +153,7 @@ impl MockAnalysis { .files .iter() .enumerate() - .find(|(_, (p, _text))| path == p) + .find(|(_, data)| path == data.path()) .expect("no file in this mock"); FileId(idx as u32 + 1) } @@ -90,18 +164,21 @@ impl MockAnalysis { change.add_root(source_root, true); let mut crate_graph = CrateGraph::default(); let mut root_crate = None; - for (i, (path, contents)) in self.files.into_iter().enumerate() { + for (i, data) in self.files.into_iter().enumerate() { + let path = data.path(); assert!(path.starts_with('/')); let path = RelativePathBuf::from_path(&path[1..]).unwrap(); + let cfg_options = data.cfg_options(); let file_id = FileId(i as u32 + 1); - let cfg_options = CfgOptions::default(); + let edition = data.edition(); + let env = data.env(); if path == "/lib.rs" || path == "/main.rs" { root_crate = Some(crate_graph.add_crate_root( file_id, - Edition2018, + edition, None, cfg_options, - Env::default(), + env, Default::default(), Default::default(), )); @@ -109,10 +186,10 @@ impl MockAnalysis { let crate_name = path.parent().unwrap().file_name().unwrap(); let other_crate = crate_graph.add_crate_root( file_id, - Edition2018, + edition, Some(CrateName::new(crate_name).unwrap()), cfg_options, - Env::default(), + env, Default::default(), Default::default(), ); @@ -122,7 +199,7 @@ impl MockAnalysis { .unwrap(); } } - change.add_file(source_root, file_id, path, Arc::new(contents)); + change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned())); } change.set_crate_graph(crate_graph); host.apply_change(change); @@ -131,6 +208,10 @@ impl MockAnalysis { pub fn analysis(self) -> Analysis { self.analysis_host().analysis() } + + fn next_id(&self) -> FileId { + FileId((self.files.len() + 1) as u32) + } } /// Creates analysis from a multi-file fixture, returns positions marked with <|>. diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index a083fb1eb358..fa1535da5b0a 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use hir::Semantics; use ra_db::{CrateId, FileId, FilePosition}; use ra_ide_db::RootDatabase; @@ -11,6 +9,16 @@ use test_utils::mark; use crate::NavigationTarget; +// Feature: Parent Module +// +// Navigates to the parent module of the current module. +// +// |=== +// | Editor | Action Name +// +// | VS Code | **Rust Analyzer: Locate parent module** +// |=== + /// This returns `Vec` because a module may be included from several places. We /// don't handle this case yet though, so the Vec has length at most one. pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec { diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 96444bf6a52d..bb40d2043786 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -615,6 +615,33 @@ mod tests { ); } + #[test] + fn test_find_all_refs_nested_module() { + let code = r#" + //- /lib.rs + mod foo { + mod bar; + } + + fn f<|>() {} + + //- /foo/bar.rs + use crate::f; + + fn g() { + f(); + } + "#; + + let (analysis, pos) = analysis_and_position(code); + let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); + check_result( + refs, + "f FN_DEF FileId(1) 25..34 28..29 Other", + &["FileId(2) 11..12 Other", "FileId(2) 27..28 StructLiteral"], + ); + } + fn get_all_refs(text: &str) -> ReferenceSearchResult { let (analysis, position) = single_file_with_position(text); analysis.find_all_refs(position, None).unwrap().unwrap() diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 131b8f307c13..f32ce0d229b8 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs @@ -1,21 +1,21 @@ -//! FIXME: write short doc here +use std::fmt; -use hir::{AsAssocItem, Semantics}; +use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; use itertools::Itertools; +use ra_cfg::CfgExpr; use ra_ide_db::RootDatabase; use ra_syntax::{ - ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, - match_ast, SyntaxNode, TextRange, + ast::{self, AstNode, AttrsOwner, DocCommentsOwner, ModuleItemOwner, NameOwner}, + match_ast, SyntaxNode, }; -use crate::FileId; -use ast::DocCommentsOwner; -use std::fmt::Display; +use crate::{display::ToNav, FileId, NavigationTarget}; #[derive(Debug)] pub struct Runnable { - pub range: TextRange, + pub nav: NavigationTarget, pub kind: RunnableKind, + pub cfg_exprs: Vec, } #[derive(Debug)] @@ -24,8 +24,8 @@ pub enum TestId { Path(String), } -impl Display for TestId { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl fmt::Display for TestId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { TestId::Name(name) => write!(f, "{}", name), TestId::Path(path) => write!(f, "{}", path), @@ -42,32 +42,47 @@ pub enum RunnableKind { Bin, } +// Feature: Run +// +// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor +// location**. Super useful for repeatedly running just a single test. Do bind this +// to a shortcut! +// +// |=== +// | Editor | Action Name +// +// | VS Code | **Rust Analyzer: Run** +// |=== pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec { let sema = Semantics::new(db); let source_file = sema.parse(file_id); - source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() + source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() } -fn runnable(sema: &Semantics, item: SyntaxNode) -> Option { +fn runnable(sema: &Semantics, item: SyntaxNode, file_id: FileId) -> Option { match_ast! { match item { - ast::FnDef(it) => runnable_fn(sema, it), - ast::Module(it) => runnable_mod(sema, it), + ast::FnDef(it) => runnable_fn(sema, it, file_id), + ast::Module(it) => runnable_mod(sema, it, file_id), _ => None, } } } -fn runnable_fn(sema: &Semantics, fn_def: ast::FnDef) -> Option { +fn runnable_fn( + sema: &Semantics, + fn_def: ast::FnDef, + file_id: FileId, +) -> Option { let name_string = fn_def.name()?.text().to_string(); let kind = if name_string == "main" { RunnableKind::Bin } else { - let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { - let def = sema.to_def(&fn_def)?; - let impl_trait_name = - def.as_assoc_item(sema.db).and_then(|assoc_item| { + let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) { + Some(module) => { + let def = sema.to_def(&fn_def)?; + let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| { match assoc_item.container(sema.db) { hir::AssocItemContainer::Trait(trait_item) => { Some(trait_item.name(sema.db).to_string()) @@ -79,25 +94,25 @@ fn runnable_fn(sema: &Semantics, fn_def: ast::FnDef) -> Option TestId::Name(name_string), }; if has_test_related_attribute(&fn_def) { @@ -111,7 +126,13 @@ fn runnable_fn(sema: &Semantics, fn_def: ast::FnDef) -> Option bool { fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) } -fn runnable_mod(sema: &Semantics, module: ast::Module) -> Option { +fn runnable_mod( + sema: &Semantics, + module: ast::Module, + file_id: FileId, +) -> Option { let has_test_function = module .item_list()? .items() @@ -159,12 +184,21 @@ fn runnable_mod(sema: &Semantics, module: ast::Module) -> Option //empty + #[test] + #[cfg(feature = "foo")] + fn test_foo1() {} + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 1..58, + name: "test_foo1", + kind: FN_DEF, + focus_range: Some( + 44..53, + ), + container_name: None, + description: None, + docs: None, + }, + kind: Test { + test_id: Path( + "test_foo1", + ), + attr: TestAttr { + ignore: false, + }, + }, + cfg_exprs: [ + KeyValue { + key: "feature", + value: "foo", + }, + ], + }, + ] + "### + ); + } + + #[test] + fn test_runnables_with_features() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs crate:foo cfg:feature=foo,feature=bar + <|> //empty + #[test] + #[cfg(all(feature = "foo", feature = "bar"))] + fn test_foo1() {} + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 1..80, + name: "test_foo1", + kind: FN_DEF, + focus_range: Some( + 66..75, + ), + container_name: None, + description: None, + docs: None, + }, + kind: Test { + test_id: Path( + "test_foo1", + ), + attr: TestAttr { + ignore: false, + }, + }, + cfg_exprs: [ + All( + [ + KeyValue { + key: "feature", + value: "foo", + }, + KeyValue { + key: "feature", + value: "bar", + }, + ], + ), + ], }, ] "### diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index ea026d7a0464..fcdc98201f2e 100644 --- a/crates/ra_ide/src/snapshots/highlight_injection.html +++ b/crates/ra_ide/src/snapshots/highlight_injection.html @@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.operator.unsafe { color: #E28C14; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } @@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .type_param { color: #DFAF8F; } .attribute { color: #94BFF3; } .numeric_literal { color: #BFEBBF; } +.bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } .module { color: #AFD8AF; } .variable { color: #DCDCCC; } diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index 752b487e82fa..e97192b614a0 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html @@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.operator.unsafe { color: #E28C14; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } @@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .type_param { color: #DFAF8F; } .attribute { color: #94BFF3; } .numeric_literal { color: #BFEBBF; } +.bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } .module { color: #AFD8AF; } .variable { color: #DCDCCC; } @@ -51,6 +53,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd println!("{argument}", argument = "test"); // => "test" println!("{name} {}", 1, name = 2); // => "2 1" println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" + println!("{{{}}}", 2); // => "{2}" println!("Hello {:5}!", "x"); println!("Hello {:1$}!", "x", 5); println!("Hello {1:0$}!", 5, "x"); diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html new file mode 100644 index 000000000000..17ffc727cd06 --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html @@ -0,0 +1,48 @@ + + +
unsafe fn unsafe_fn() {}
+
+struct HasUnsafeFn;
+
+impl HasUnsafeFn {
+    unsafe fn unsafe_method(&self) {}
+}
+
+fn main() {
+    let x = &5 as *const usize;
+    unsafe {
+        unsafe_fn();
+        HasUnsafeFn.unsafe_method();
+        let y = *x;
+        let z = -x;
+    }
+}
\ No newline at end of file diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html index 635fe5cf9ddf..42c5f3e5515e 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html @@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.operator.unsafe { color: #E28C14; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } @@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .type_param { color: #DFAF8F; } .attribute { color: #94BFF3; } .numeric_literal { color: #BFEBBF; } +.bool_literal { color: #BFE6EB; } .macro { color: #94BFF3; } .module { color: #AFD8AF; } .variable { color: #DCDCCC; } @@ -27,19 +29,19 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .keyword.unsafe { color: #BC8383; font-weight: bold; } .control { font-style: italic; } -
#[derive(Clone, Debug)]
+
#[derive(Clone, Debug)]
 struct Foo {
     pub x: i32,
     pub y: i32,
 }
 
 trait Bar {
-    fn bar(&self) -> i32;
+    fn bar(&self) -> i32;
 }
 
 impl Bar for Foo {
-    fn bar(&self) -> i32 {
-        self.x
+    fn bar(&self) -> i32 {
+        self.x
     }
 }
 
@@ -64,7 +66,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     println!("Hello, {}!", 92);
 
     let mut vec = Vec::new();
-    if true {
+    if true {
         let x = 92;
         vec.push(Foo { x, y: 1 });
     }
@@ -91,7 +93,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 use Option::*;
 
 impl<T> Option<T> {
-    fn and<U>(self, other: Option<U>) -> Option<(T, U)> {
+    fn and<U>(self, other: Option<U>) -> Option<(T, U)> {
         match other {
             None => unimplemented!(),
             Nope => Nope,
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
index 11e1f3e44e59..2dd61d20d691 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -10,6 +10,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .string_literal     { color: #CC9393; }
 .field              { color: #94BFF3; }
 .function           { color: #93E0E3; }
+.operator.unsafe    { color: #E28C14; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
@@ -17,6 +18,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .type_param         { color: #DFAF8F; }
 .attribute          { color: #94BFF3; }
 .numeric_literal    { color: #BFEBBF; }
+.bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
 .module             { color: #AFD8AF; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 1873d1d0d0ab..93e9aee1d01e 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,5 +1,3 @@
-//!  structural search replace
-
 use std::{collections::HashMap, iter::once, str::FromStr};
 
 use ra_db::{SourceDatabase, SourceDatabaseExt};
@@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError {
 
 impl std::error::Error for SsrError {}
 
+// Feature: Structural Seach and Replace
+//
+// Search and replace with named wildcards that will match any expression.
+// The syntax for a structural search replace command is ` ==>> `.
+// A `$:expr` placeholder in the search pattern will match any expression and `$` will reference it in the replacement.
+// Available via the command `rust-analyzer.ssr`.
+//
+// ```rust
+// // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)]
+//
+// // BEFORE
+// String::from(foo(y + 5, z))
+//
+// // AFTER
+// String::from((y + 5).foo(z))
+// ```
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Structural Search Replace**
+// |===
 pub fn parse_search_replace(
     query: &str,
     parse_only: bool,
@@ -196,10 +216,10 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
     ) -> Option {
         let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?;
 
-        let mut pattern_fields =
-            pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]);
-        let mut code_fields =
-            code.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]);
+        let mut pattern_fields: Vec =
+            pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
+        let mut code_fields: Vec =
+            code.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
 
         if pattern_fields.len() != code_fields.len() {
             return None;
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs
index 30eb5c995e63..5b7992920071 100644
--- a/crates/ra_ide/src/status.rs
+++ b/crates/ra_ide/src/status.rs
@@ -1,5 +1,3 @@
-//! FIXME: write short doc here
-
 use std::{fmt, iter::FromIterator, sync::Arc};
 
 use hir::MacroFile;
@@ -26,6 +24,15 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
     db.query(hir::db::ParseMacroQuery).entries::()
 }
 
+// Feature: Status
+//
+// Shows internal statistic about memory usage of rust-analyzer.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Status**
+// |===
 pub(crate) fn status(db: &RootDatabase) -> String {
     let files_stats = db.query(FileTextQuery).entries::();
     let syntax_tree_stats = syntax_tree_stats(db);
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index be57eeb0abea..19ecd54d6cf2 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,5 +1,3 @@
-//! Implements syntax highlighting.
-
 mod tags;
 mod html;
 #[cfg(test)]
@@ -32,81 +30,15 @@ pub struct HighlightedRange {
     pub binding_hash: Option,
 }
 
-#[derive(Debug)]
-struct HighlightedRangeStack {
-    stack: Vec>,
-}
-
-/// We use a stack to implement the flattening logic for the highlighted
-/// syntax ranges.
-impl HighlightedRangeStack {
-    fn new() -> Self {
-        Self { stack: vec![Vec::new()] }
-    }
-
-    fn push(&mut self) {
-        self.stack.push(Vec::new());
-    }
-
-    /// Flattens the highlighted ranges.
-    ///
-    /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
-    /// 1) parent-range: Attribute [0, 23)
-    /// 2) child-range: String [16, 21)
-    ///
-    /// The following code implements the flattening, for our example this results to:
-    /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
-    fn pop(&mut self) {
-        let children = self.stack.pop().unwrap();
-        let prev = self.stack.last_mut().unwrap();
-        let needs_flattening = !children.is_empty()
-            && !prev.is_empty()
-            && prev.last().unwrap().range.contains_range(children.first().unwrap().range);
-        if !needs_flattening {
-            prev.extend(children);
-        } else {
-            let mut parent = prev.pop().unwrap();
-            for ele in children {
-                assert!(parent.range.contains_range(ele.range));
-                let mut cloned = parent.clone();
-                parent.range = TextRange::new(parent.range.start(), ele.range.start());
-                cloned.range = TextRange::new(ele.range.end(), cloned.range.end());
-                if !parent.range.is_empty() {
-                    prev.push(parent);
-                }
-                prev.push(ele);
-                parent = cloned;
-            }
-            if !parent.range.is_empty() {
-                prev.push(parent);
-            }
-        }
-    }
-
-    fn add(&mut self, range: HighlightedRange) {
-        self.stack
-            .last_mut()
-            .expect("during DFS traversal, the stack must not be empty")
-            .push(range)
-    }
-
-    fn flattened(mut self) -> Vec {
-        assert_eq!(
-            self.stack.len(),
-            1,
-            "after DFS traversal, the stack should only contain a single element"
-        );
-        let mut res = self.stack.pop().unwrap();
-        res.sort_by_key(|range| range.range.start());
-        // Check that ranges are sorted and disjoint
-        assert!(res
-            .iter()
-            .zip(res.iter().skip(1))
-            .all(|(left, right)| left.range.end() <= right.range.start()));
-        res
-    }
-}
-
+// Feature: Semantic Syntax Highlighting
+//
+// rust-analyzer highlights the code semantically.
+// For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait.
+// rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token.
+// It's up to the client to map those to specific colors.
+//
+// The general rule is that a reference to an entity gets colored the same way as the entity itself.
+// We also give special modifier for `mut` and `&mut` local variables.
 pub(crate) fn highlight(
     db: &RootDatabase,
     file_id: FileId,
@@ -291,6 +223,81 @@ pub(crate) fn highlight(
     stack.flattened()
 }
 
+#[derive(Debug)]
+struct HighlightedRangeStack {
+    stack: Vec>,
+}
+
+/// We use a stack to implement the flattening logic for the highlighted
+/// syntax ranges.
+impl HighlightedRangeStack {
+    fn new() -> Self {
+        Self { stack: vec![Vec::new()] }
+    }
+
+    fn push(&mut self) {
+        self.stack.push(Vec::new());
+    }
+
+    /// Flattens the highlighted ranges.
+    ///
+    /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
+    /// 1) parent-range: Attribute [0, 23)
+    /// 2) child-range: String [16, 21)
+    ///
+    /// The following code implements the flattening, for our example this results to:
+    /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
+    fn pop(&mut self) {
+        let children = self.stack.pop().unwrap();
+        let prev = self.stack.last_mut().unwrap();
+        let needs_flattening = !children.is_empty()
+            && !prev.is_empty()
+            && prev.last().unwrap().range.contains_range(children.first().unwrap().range);
+        if !needs_flattening {
+            prev.extend(children);
+        } else {
+            let mut parent = prev.pop().unwrap();
+            for ele in children {
+                assert!(parent.range.contains_range(ele.range));
+                let mut cloned = parent.clone();
+                parent.range = TextRange::new(parent.range.start(), ele.range.start());
+                cloned.range = TextRange::new(ele.range.end(), cloned.range.end());
+                if !parent.range.is_empty() {
+                    prev.push(parent);
+                }
+                prev.push(ele);
+                parent = cloned;
+            }
+            if !parent.range.is_empty() {
+                prev.push(parent);
+            }
+        }
+    }
+
+    fn add(&mut self, range: HighlightedRange) {
+        self.stack
+            .last_mut()
+            .expect("during DFS traversal, the stack must not be empty")
+            .push(range)
+    }
+
+    fn flattened(mut self) -> Vec {
+        assert_eq!(
+            self.stack.len(),
+            1,
+            "after DFS traversal, the stack should only contain a single element"
+        );
+        let mut res = self.stack.pop().unwrap();
+        res.sort_by_key(|range| range.range.start());
+        // Check that ranges are sorted and disjoint
+        assert!(res
+            .iter()
+            .zip(res.iter().skip(1))
+            .all(|(left, right)| left.range.end() <= right.range.start()));
+        res
+    }
+}
+
 fn highlight_format_specifier(kind: FormatSpecifier) -> Option {
     Some(match kind {
         FormatSpecifier::Open
@@ -361,7 +368,9 @@ fn highlight_element(
         }
 
         // Highlight references like the definitions they resolve to
-        NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None,
+        NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
+            Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute
+        }
         NAME_REF => {
             let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
             match classify_name_ref(sema, &name_ref) {
@@ -389,6 +398,7 @@ fn highlight_element(
         INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
         BYTE => HighlightTag::ByteLiteral.into(),
         CHAR => HighlightTag::CharLiteral.into(),
+        QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow,
         LIFETIME => {
             let h = Highlight::new(HighlightTag::Lifetime);
             match element.parent().map(|it| it.kind()) {
@@ -396,6 +406,23 @@ fn highlight_element(
                 _ => h,
             }
         }
+        PREFIX_EXPR => {
+            let prefix_expr = element.into_node().and_then(ast::PrefixExpr::cast)?;
+            match prefix_expr.op_kind() {
+                Some(ast::PrefixOp::Deref) => {}
+                _ => return None,
+            }
+
+            let expr = prefix_expr.expr()?;
+            let ty = sema.type_of_expr(&expr)?;
+            if !ty.is_raw_ptr() {
+                return None;
+            }
+
+            let mut h = Highlight::new(HighlightTag::Operator);
+            h |= HighlightModifier::Unsafe;
+            h
+        }
 
         k if k.is_keyword() => {
             let h = Highlight::new(HighlightTag::Keyword);
@@ -411,6 +438,8 @@ fn highlight_element(
                 | T![in] => h | HighlightModifier::ControlFlow,
                 T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow,
                 T![unsafe] => h | HighlightModifier::Unsafe,
+                T![true] | T![false] => HighlightTag::BoolLiteral.into(),
+                T![self] => HighlightTag::SelfKeyword.into(),
                 _ => h,
             }
         }
@@ -446,7 +475,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
         Definition::Field(_) => HighlightTag::Field,
         Definition::ModuleDef(def) => match def {
             hir::ModuleDef::Module(_) => HighlightTag::Module,
-            hir::ModuleDef::Function(_) => HighlightTag::Function,
+            hir::ModuleDef::Function(func) => {
+                let mut h = HighlightTag::Function.into();
+                if func.is_unsafe(db) {
+                    h |= HighlightModifier::Unsafe;
+                }
+                return h;
+            }
             hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
             hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum,
             hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
@@ -478,23 +513,31 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
 }
 
 fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
-    let default = HighlightTag::Function.into();
+    let default = HighlightTag::UnresolvedReference;
 
     let parent = match name.syntax().parent() {
         Some(it) => it,
-        _ => return default,
+        _ => return default.into(),
     };
 
-    match parent.kind() {
-        STRUCT_DEF => HighlightTag::Struct.into(),
-        ENUM_DEF => HighlightTag::Enum.into(),
-        UNION_DEF => HighlightTag::Union.into(),
-        TRAIT_DEF => HighlightTag::Trait.into(),
-        TYPE_ALIAS_DEF => HighlightTag::TypeAlias.into(),
-        TYPE_PARAM => HighlightTag::TypeParam.into(),
-        RECORD_FIELD_DEF => HighlightTag::Field.into(),
+    let tag = match parent.kind() {
+        STRUCT_DEF => HighlightTag::Struct,
+        ENUM_DEF => HighlightTag::Enum,
+        UNION_DEF => HighlightTag::Union,
+        TRAIT_DEF => HighlightTag::Trait,
+        TYPE_ALIAS_DEF => HighlightTag::TypeAlias,
+        TYPE_PARAM => HighlightTag::TypeParam,
+        RECORD_FIELD_DEF => HighlightTag::Field,
+        MODULE => HighlightTag::Module,
+        FN_DEF => HighlightTag::Function,
+        CONST_DEF => HighlightTag::Constant,
+        STATIC_DEF => HighlightTag::Static,
+        ENUM_VARIANT => HighlightTag::EnumVariant,
+        BIND_PAT => HighlightTag::Local,
         _ => default,
-    }
+    };
+
+    tag.into()
 }
 
 fn highlight_injection(
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs
index ff0eeeb52c06..7d946c98dae7 100644
--- a/crates/ra_ide/src/syntax_highlighting/html.rs
+++ b/crates/ra_ide/src/syntax_highlighting/html.rs
@@ -69,6 +69,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .string_literal     { color: #CC9393; }
 .field              { color: #94BFF3; }
 .function           { color: #93E0E3; }
+.operator.unsafe    { color: #E28C14; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
@@ -76,6 +77,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .type_param         { color: #DFAF8F; }
 .attribute          { color: #94BFF3; }
 .numeric_literal    { color: #BFEBBF; }
+.bool_literal       { color: #BFE6EB; }
 .macro              { color: #94BFF3; }
 .module             { color: #AFD8AF; }
 .variable           { color: #DCDCCC; }
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index be1a0f12b25b..94f466966a3a 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -15,6 +15,7 @@ pub struct HighlightModifiers(u32);
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub enum HighlightTag {
     Attribute,
+    BoolLiteral,
     BuiltinType,
     ByteLiteral,
     CharLiteral,
@@ -23,12 +24,15 @@ pub enum HighlightTag {
     Enum,
     EnumVariant,
     Field,
+    FormatSpecifier,
     Function,
     Keyword,
     Lifetime,
     Macro,
     Module,
     NumericLiteral,
+    Operator,
+    SelfKeyword,
     SelfType,
     Static,
     StringLiteral,
@@ -39,14 +43,15 @@ pub enum HighlightTag {
     Union,
     Local,
     UnresolvedReference,
-    FormatSpecifier,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
 #[repr(u8)]
 pub enum HighlightModifier {
+    /// Used to differentiate individual elements within attributes.
+    Attribute = 0,
     /// Used with keywords like `if` and `break`.
-    ControlFlow = 0,
+    ControlFlow,
     /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
     /// not.
     Definition,
@@ -58,6 +63,7 @@ impl HighlightTag {
     fn as_str(self) -> &'static str {
         match self {
             HighlightTag::Attribute => "attribute",
+            HighlightTag::BoolLiteral => "bool_literal",
             HighlightTag::BuiltinType => "builtin_type",
             HighlightTag::ByteLiteral => "byte_literal",
             HighlightTag::CharLiteral => "char_literal",
@@ -66,12 +72,15 @@ impl HighlightTag {
             HighlightTag::Enum => "enum",
             HighlightTag::EnumVariant => "enum_variant",
             HighlightTag::Field => "field",
+            HighlightTag::FormatSpecifier => "format_specifier",
             HighlightTag::Function => "function",
             HighlightTag::Keyword => "keyword",
             HighlightTag::Lifetime => "lifetime",
             HighlightTag::Macro => "macro",
             HighlightTag::Module => "module",
             HighlightTag::NumericLiteral => "numeric_literal",
+            HighlightTag::Operator => "operator",
+            HighlightTag::SelfKeyword => "self_keyword",
             HighlightTag::SelfType => "self_type",
             HighlightTag::Static => "static",
             HighlightTag::StringLiteral => "string_literal",
@@ -82,7 +91,6 @@ impl HighlightTag {
             HighlightTag::Union => "union",
             HighlightTag::Local => "variable",
             HighlightTag::UnresolvedReference => "unresolved_reference",
-            HighlightTag::FormatSpecifier => "format_specifier",
         }
     }
 }
@@ -95,6 +103,7 @@ impl fmt::Display for HighlightTag {
 
 impl HighlightModifier {
     const ALL: &'static [HighlightModifier] = &[
+        HighlightModifier::Attribute,
         HighlightModifier::ControlFlow,
         HighlightModifier::Definition,
         HighlightModifier::Mutable,
@@ -103,6 +112,7 @@ impl HighlightModifier {
 
     fn as_str(self) -> &'static str {
         match self {
+            HighlightModifier::Attribute => "attribute",
             HighlightModifier::ControlFlow => "control",
             HighlightModifier::Definition => "declaration",
             HighlightModifier::Mutable => "mutable",
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index eb43a23da682..36a1aa419bc3 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -218,6 +218,7 @@ fn main() {
     println!("{argument}", argument = "test");   // => "test"
     println!("{name} {}", 1, name = 2);          // => "2 1"
     println!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
+    println!("{{{}}}", 2);                       // => "{2}"
     println!("Hello {:5}!", "x");
     println!("Hello {:1$}!", "x", 5);
     println!("Hello {1:0$}!", 5, "x");
@@ -257,3 +258,34 @@ fn main() {
     fs::write(dst_file, &actual_html).unwrap();
     assert_eq_text!(expected_html, actual_html);
 }
+
+#[test]
+fn test_unsafe_highlighting() {
+    let (analysis, file_id) = single_file(
+        r#"
+unsafe fn unsafe_fn() {}
+
+struct HasUnsafeFn;
+
+impl HasUnsafeFn {
+    unsafe fn unsafe_method(&self) {}
+}
+
+fn main() {
+    let x = &5 as *const usize;
+    unsafe {
+        unsafe_fn();
+        HasUnsafeFn.unsafe_method();
+        let y = *x;
+        let z = -x;
+    }
+}
+"#
+        .trim(),
+    );
+    let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_unsafe.html");
+    let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
+    let expected_html = &read_text(&dst_file);
+    fs::write(dst_file, &actual_html).unwrap();
+    assert_eq_text!(expected_html, actual_html);
+}
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs
index 86c70ff830b8..a341684fda20 100644
--- a/crates/ra_ide/src/syntax_tree.rs
+++ b/crates/ra_ide/src/syntax_tree.rs
@@ -1,6 +1,4 @@
-//! FIXME: write short doc here
-
-use ra_db::SourceDatabase;
+use ra_db::{FileId, SourceDatabase};
 use ra_ide_db::RootDatabase;
 use ra_syntax::{
     algo, AstNode, NodeOrToken, SourceFile,
@@ -8,8 +6,16 @@ use ra_syntax::{
     SyntaxToken, TextRange, TextSize,
 };
 
-pub use ra_db::FileId;
-
+// Feature: Show Syntax Tree
+//
+// Shows the parse tree of the current file. It exists mostly for debugging
+// rust-analyzer itself.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Show Syntax Tree**
+// |===
 pub(crate) fn syntax_tree(
     db: &RootDatabase,
     file_id: FileId,
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 39bb3b3579c0..67e2c33a000e 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -32,6 +32,13 @@ pub(crate) use on_enter::on_enter;
 
 pub(crate) const TRIGGER_CHARS: &str = ".=>";
 
+// Feature: On Typing Assists
+//
+// Some features trigger on typing certain characters:
+//
+// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
+// - Enter inside comments automatically inserts `///`
+// - typing `.` in a chain method call auto-indents
 pub(crate) fn on_char_typed(
     db: &RootDatabase,
     position: FilePosition,
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs
index e7d64b4f68c2..a40d8af9c43d 100644
--- a/crates/ra_ide/src/typing/on_enter.rs
+++ b/crates/ra_ide/src/typing/on_enter.rs
@@ -11,9 +11,7 @@ use ra_syntax::{
 };
 use ra_text_edit::TextEdit;
 
-use crate::{SourceChange, SourceFileEdit};
-
-pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option {
+pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option {
     let parse = db.parse(position.file_id);
     let file = parse.tree();
     let comment = file
@@ -41,9 +39,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option bool {
@@ -90,9 +86,8 @@ mod tests {
         let (analysis, file_id) = single_file(&before);
         let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
 
-        assert_eq!(result.source_file_edits.len(), 1);
         let mut actual = before.to_string();
-        result.source_file_edits[0].edit.apply(&mut actual);
+        result.apply(&mut actual);
         Some(actual)
     }
 
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs
index 589f44771926..335a1ad03c7b 100644
--- a/crates/ra_ide_db/src/search.rs
+++ b/crates/ra_ide_db/src/search.rs
@@ -124,29 +124,33 @@ impl Definition {
 
         let vis = self.visibility(db);
 
-        // FIXME:
-        // The following logic are wrong that it does not search
-        // for submodules within other files recursively.
-
         if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) {
             let module: Module = module.into();
             let mut res = FxHashMap::default();
-            let src = module.definition_source(db);
-            let file_id = src.file_id.original_file(db);
 
-            match src.value {
-                ModuleSource::Module(m) => {
-                    let range = Some(m.syntax().text_range());
-                    res.insert(file_id, range);
-                }
-                ModuleSource::SourceFile(_) => {
-                    res.insert(file_id, None);
-                    res.extend(module.children(db).map(|m| {
-                        let src = m.definition_source(db);
-                        (src.file_id.original_file(db), None)
-                    }));
-                }
+            let mut to_visit = vec![module];
+            let mut is_first = true;
+            while let Some(module) = to_visit.pop() {
+                let src = module.definition_source(db);
+                let file_id = src.file_id.original_file(db);
+                match src.value {
+                    ModuleSource::Module(m) => {
+                        if is_first {
+                            let range = Some(m.syntax().text_range());
+                            res.insert(file_id, range);
+                        } else {
+                            // We have already added the enclosing file to the search scope,
+                            // so do nothing.
+                        }
+                    }
+                    ModuleSource::SourceFile(_) => {
+                        res.insert(file_id, None);
+                    }
+                };
+                is_first = false;
+                to_visit.extend(module.children(db));
             }
+
             return SearchScope::new(res);
         }
 
diff --git a/crates/ra_ide_db/src/symbol_index.rs b/crates/ra_ide_db/src/symbol_index.rs
index 95be11134554..acc31fe3b51b 100644
--- a/crates/ra_ide_db/src/symbol_index.rs
+++ b/crates/ra_ide_db/src/symbol_index.rs
@@ -110,6 +110,27 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc
     Arc::new(SymbolIndex::new(symbols))
 }
 
+// Feature: Workspace Symbol
+//
+// Uses fuzzy-search to find types, modules and functions by name across your
+// project and dependencies. This is **the** most useful feature, which improves code
+// navigation tremendously. It mostly works on top of the built-in LSP
+// functionality, however `#` and `*` symbols can be used to narrow down the
+// search. Specifically,
+//
+// - `Foo` searches for `Foo` type in the current workspace
+// - `foo#` searches for `foo` function in the current workspace
+// - `Foo*` searches for `Foo` type among dependencies, including `stdlib`
+// - `foo#*` searches for `foo` function among dependencies
+//
+// That is, `#` switches from "types" to all symbols, `*` switches from the current
+// workspace to dependencies.
+//
+// |===
+// | Editor  | Shortcut
+//
+// | VS Code | kbd:[Ctrl+T]
+// |===
 pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec {
     /// Need to wrap Snapshot to provide `Clone` impl for `map_with`
     struct Snap(salsa::Snapshot);
diff --git a/crates/ra_parser/src/grammar.rs b/crates/ra_parser/src/grammar.rs
index be0cd5661bd6..293baecf6a2c 100644
--- a/crates/ra_parser/src/grammar.rs
+++ b/crates/ra_parser/src/grammar.rs
@@ -18,9 +18,10 @@
 //! // fn foo() {}
 //! ```
 //!
-//! After adding a new inline-test, run `cargo collect-tests` to extract
-//! it as a standalone text-fixture into `tests/data/parser/inline`, and
-//! run `cargo test` once to create the "gold" value.
+//! After adding a new inline-test, run `cargo xtask codegen` to
+//! extract it as a standalone text-fixture into
+//! `crates/ra_syntax/test_data/parser/`, and run `cargo test` once to
+//! create the "gold" value.
 //!
 //! Coding convention: rules like `where_clause` always produce either a
 //! node or an error, rules like `opt_where_clause` may produce nothing.
diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs
index 34f0397686fb..d6e8df32a14e 100644
--- a/crates/ra_parser/src/grammar/expressions.rs
+++ b/crates/ra_parser/src/grammar/expressions.rs
@@ -325,13 +325,27 @@ fn lhs(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)>
     let kind = match p.current() {
         // test ref_expr
         // fn foo() {
+        //     // reference operator
         //     let _ = &1;
         //     let _ = &mut &f();
+        //     let _ = &raw;
+        //     let _ = &raw.0;
+        //     // raw reference operator
+        //     let _ = &raw mut foo;
+        //     let _ = &raw const foo;
         // }
         T![&] => {
             m = p.start();
             p.bump(T![&]);
-            p.eat(T![mut]);
+            if p.at(IDENT)
+                && p.at_contextual_kw("raw")
+                && (p.nth_at(1, T![mut]) || p.nth_at(1, T![const]))
+            {
+                p.bump_remap(T![raw]);
+                p.bump_any();
+            } else {
+                p.eat(T![mut]);
+            }
             REF_EXPR
         }
         // test unary_expr
diff --git a/crates/ra_proc_macro_srv/Cargo.toml b/crates/ra_proc_macro_srv/Cargo.toml
index bb3003278516..5821029450f5 100644
--- a/crates/ra_proc_macro_srv/Cargo.toml
+++ b/crates/ra_proc_macro_srv/Cargo.toml
@@ -22,3 +22,4 @@ cargo_metadata = "0.10.0"
 difference = "2.0.0"
 # used as proc macro test target
 serde_derive = "1.0.106"
+ra_toolchain = { path = "../ra_toolchain" }
diff --git a/crates/ra_proc_macro_srv/src/tests/utils.rs b/crates/ra_proc_macro_srv/src/tests/utils.rs
index 84348b5defce..8d85f2d8a775 100644
--- a/crates/ra_proc_macro_srv/src/tests/utils.rs
+++ b/crates/ra_proc_macro_srv/src/tests/utils.rs
@@ -2,7 +2,6 @@
 
 use crate::dylib;
 use crate::ProcMacroSrv;
-pub use difference::Changeset as __Changeset;
 use ra_proc_macro::ListMacrosTask;
 use std::str::FromStr;
 use test_utils::assert_eq_text;
@@ -13,7 +12,7 @@ mod fixtures {
 
     // Use current project metadata to get the proc-macro dylib path
     pub fn dylib_path(crate_name: &str, version: &str) -> std::path::PathBuf {
-        let command = Command::new("cargo")
+        let command = Command::new(ra_toolchain::cargo())
             .args(&["check", "--message-format", "json"])
             .output()
             .unwrap()
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index a306ce95f36e..4b7444039e7b 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -64,7 +64,7 @@ impl Default for CargoConfig {
     fn default() -> Self {
         CargoConfig {
             no_default_features: false,
-            all_features: true,
+            all_features: false,
             features: Vec::new(),
             load_out_dirs_from_check: false,
             target: None,
diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs
index b030c8a6a18f..09c06fef9359 100644
--- a/crates/ra_project_model/src/json_project.rs
+++ b/crates/ra_project_model/src/json_project.rs
@@ -5,6 +5,13 @@ use std::path::PathBuf;
 use rustc_hash::{FxHashMap, FxHashSet};
 use serde::Deserialize;
 
+/// Roots and crates that compose this Rust project.
+#[derive(Clone, Debug, Deserialize)]
+pub struct JsonProject {
+    pub(crate) roots: Vec,
+    pub(crate) crates: Vec,
+}
+
 /// A root points to the directory which contains Rust crates. rust-analyzer watches all files in
 /// all roots. Roots might be nested.
 #[derive(Clone, Debug, Deserialize)]
@@ -20,8 +27,17 @@ pub struct Crate {
     pub(crate) root_module: PathBuf,
     pub(crate) edition: Edition,
     pub(crate) deps: Vec,
+
+    // This is the preferred method of providing cfg options.
+    #[serde(default)]
+    pub(crate) cfg: FxHashSet,
+
+    // These two are here for transition only.
+    #[serde(default)]
     pub(crate) atom_cfgs: FxHashSet,
+    #[serde(default)]
     pub(crate) key_value_cfgs: FxHashMap,
+
     pub(crate) out_dir: Option,
     pub(crate) proc_macro_dylib_path: Option,
 }
@@ -48,9 +64,72 @@ pub struct Dep {
     pub(crate) name: String,
 }
 
-/// Roots and crates that compose this Rust project.
-#[derive(Clone, Debug, Deserialize)]
-pub struct JsonProject {
-    pub(crate) roots: Vec,
-    pub(crate) crates: Vec,
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use serde_json::json;
+
+    #[test]
+    fn test_crate_deserialization() {
+        let raw_json = json!(    {
+            "crate_id": 2,
+            "root_module": "this/is/a/file/path.rs",
+            "deps": [
+              {
+                "crate": 1,
+                "name": "some_dep_crate"
+              },
+            ],
+            "edition": "2015",
+            "cfg": [
+              "atom_1",
+              "atom_2",
+              "feature=feature_1",
+              "feature=feature_2",
+              "other=value",
+            ],
+
+        });
+
+        let krate: Crate = serde_json::from_value(raw_json).unwrap();
+
+        assert!(krate.cfg.contains(&"atom_1".to_string()));
+        assert!(krate.cfg.contains(&"atom_2".to_string()));
+        assert!(krate.cfg.contains(&"feature=feature_1".to_string()));
+        assert!(krate.cfg.contains(&"feature=feature_2".to_string()));
+        assert!(krate.cfg.contains(&"other=value".to_string()));
+    }
+
+    #[test]
+    fn test_crate_deserialization_old_json() {
+        let raw_json = json!(    {
+           "crate_id": 2,
+           "root_module": "this/is/a/file/path.rs",
+           "deps": [
+             {
+               "crate": 1,
+               "name": "some_dep_crate"
+             },
+           ],
+           "edition": "2015",
+           "atom_cfgs": [
+             "atom_1",
+             "atom_2",
+           ],
+           "key_value_cfgs": {
+             "feature": "feature_1",
+             "feature": "feature_2",
+             "other": "value",
+           },
+        });
+
+        let krate: Crate = serde_json::from_value(raw_json).unwrap();
+
+        assert!(krate.atom_cfgs.contains(&"atom_1".to_string()));
+        assert!(krate.atom_cfgs.contains(&"atom_2".to_string()));
+        assert!(krate.key_value_cfgs.contains_key(&"feature".to_string()));
+        assert_eq!(krate.key_value_cfgs.get("feature"), Some(&"feature_2".to_string()));
+        assert!(krate.key_value_cfgs.contains_key(&"other".to_string()));
+        assert_eq!(krate.key_value_cfgs.get("other"), Some(&"value".to_string()));
+    }
 }
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index a2e9f65effc0..7ad941279506 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -14,7 +14,7 @@ use std::{
 use anyhow::{bail, Context, Result};
 use ra_cfg::CfgOptions;
 use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId};
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
 use serde_json::from_reader;
 
 pub use crate::{
@@ -32,6 +32,12 @@ pub enum ProjectWorkspace {
     Json { project: JsonProject },
 }
 
+impl From for ProjectWorkspace {
+    fn from(project: JsonProject) -> ProjectWorkspace {
+        ProjectWorkspace::Json { project }
+    }
+}
+
 /// `PackageRoot` describes a package root folder.
 /// Which may be an external dependency, or a member of
 /// the current workspace.
@@ -57,25 +63,25 @@ impl PackageRoot {
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub enum ProjectRoot {
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub enum ProjectManifest {
     ProjectJson(PathBuf),
     CargoToml(PathBuf),
 }
 
-impl ProjectRoot {
-    pub fn from_manifest_file(path: PathBuf) -> Result {
+impl ProjectManifest {
+    pub fn from_manifest_file(path: PathBuf) -> Result {
         if path.ends_with("rust-project.json") {
-            return Ok(ProjectRoot::ProjectJson(path));
+            return Ok(ProjectManifest::ProjectJson(path));
         }
         if path.ends_with("Cargo.toml") {
-            return Ok(ProjectRoot::CargoToml(path));
+            return Ok(ProjectManifest::CargoToml(path));
         }
         bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
     }
 
-    pub fn discover_single(path: &Path) -> Result {
-        let mut candidates = ProjectRoot::discover(path)?;
+    pub fn discover_single(path: &Path) -> Result {
+        let mut candidates = ProjectManifest::discover(path)?;
         let res = match candidates.pop() {
             None => bail!("no projects"),
             Some(it) => it,
@@ -87,12 +93,12 @@ impl ProjectRoot {
         Ok(res)
     }
 
-    pub fn discover(path: &Path) -> io::Result> {
+    pub fn discover(path: &Path) -> io::Result> {
         if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
-            return Ok(vec![ProjectRoot::ProjectJson(project_json)]);
+            return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
         }
         return find_cargo_toml(path)
-            .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect());
+            .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect());
 
         fn find_cargo_toml(path: &Path) -> io::Result> {
             match find_in_parent_dirs(path, "Cargo.toml") {
@@ -128,16 +134,28 @@ impl ProjectRoot {
                 .collect()
         }
     }
+
+    pub fn discover_all(paths: &[impl AsRef]) -> Vec {
+        let mut res = paths
+            .iter()
+            .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
+            .flatten()
+            .collect::>()
+            .into_iter()
+            .collect::>();
+        res.sort();
+        res
+    }
 }
 
 impl ProjectWorkspace {
     pub fn load(
-        root: ProjectRoot,
+        manifest: ProjectManifest,
         cargo_features: &CargoConfig,
         with_sysroot: bool,
     ) -> Result {
-        let res = match root {
-            ProjectRoot::ProjectJson(project_json) => {
+        let res = match manifest {
+            ProjectManifest::ProjectJson(project_json) => {
                 let file = File::open(&project_json).with_context(|| {
                     format!("Failed to open json file {}", project_json.display())
                 })?;
@@ -148,7 +166,7 @@ impl ProjectWorkspace {
                     })?,
                 }
             }
-            ProjectRoot::CargoToml(cargo_toml) => {
+            ProjectManifest::CargoToml(cargo_toml) => {
                 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
                     .with_context(|| {
                         format!(
@@ -252,6 +270,16 @@ impl ProjectWorkspace {
                         };
                         let cfg_options = {
                             let mut opts = default_cfg_options.clone();
+                            for cfg in &krate.cfg {
+                                match cfg.find('=') {
+                                    None => opts.insert_atom(cfg.into()),
+                                    Some(pos) => {
+                                        let key = &cfg[..pos];
+                                        let value = cfg[pos + 1..].trim_matches('"');
+                                        opts.insert_key_value(key.into(), value.into());
+                                    }
+                                }
+                            }
                             for name in &krate.atom_cfgs {
                                 opts.insert_atom(name.into());
                             }
diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml
index c07ff488e4f0..a8ff2e74f5c6 100644
--- a/crates/ra_syntax/Cargo.toml
+++ b/crates/ra_syntax/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
 [dependencies]
 itertools = "0.9.0"
 rowan = "0.10.0"
-rustc_lexer = { version = "656.0.0", package = "rustc-ap-rustc_lexer" }
+rustc_lexer = { version = "661.0.0", package = "rustc-ap-rustc_lexer" }
 rustc-hash = "1.1.0"
 arrayvec = "0.5.1"
 once_cell = "1.3.1"
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index 1876afe958e0..eddc807d5a50 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -75,7 +75,7 @@ impl AstChildren {
 impl Iterator for AstChildren {
     type Item = N;
     fn next(&mut self) -> Option {
-        self.inner.by_ref().find_map(N::cast)
+        self.inner.find_map(N::cast)
     }
 }
 
diff --git a/crates/ra_syntax/src/ast/generated/nodes.rs b/crates/ra_syntax/src/ast/generated/nodes.rs
index cf6067e57c42..cb430ca01352 100644
--- a/crates/ra_syntax/src/ast/generated/nodes.rs
+++ b/crates/ra_syntax/src/ast/generated/nodes.rs
@@ -1081,6 +1081,7 @@ pub struct BlockExpr {
 impl ast::AttrsOwner for BlockExpr {}
 impl ast::ModuleItemOwner for BlockExpr {}
 impl BlockExpr {
+    pub fn label(&self) -> Option