Merge branch 'master' into assists_extract_enum
This commit is contained in:
commit
eefa10bc6b
2
.github/actions/github-release/main.js
vendored
2
.github/actions/github-release/main.js
vendored
@ -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}`);
|
||||
|
17
.github/workflows/ci.yaml
vendored
17
.github/workflows/ci.yaml
vendored
@ -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
|
||||
|
5
.github/workflows/release.yaml
vendored
5
.github/workflows/release.yaml
vendored
@ -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
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -7,4 +7,6 @@ crates/*/target
|
||||
*.log
|
||||
*.iml
|
||||
.vscode/settings.json
|
||||
cargo-timing*.html
|
||||
*.html
|
||||
generated_assists.adoc
|
||||
generated_features.adoc
|
||||
|
34
.vscode/launch.json
vendored
34
.vscode/launch.json
vendored
@ -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": [
|
||||
"<node_internals>/**/*.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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
12
.vscode/tasks.json
vendored
12
.vscode/tasks.json
vendored
@ -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",
|
||||
|
118
Cargo.lock
generated
118
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) }
|
||||
|
303
crates/ra_assists/src/handlers/introduce_named_lifetime.rs
Normal file
303
crates/ra_assists/src/handlers/introduce_named_lifetime.rs
Normal file
@ -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<ast::TypeParamList>,
|
||||
) -> Option<char> {
|
||||
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<TypeParamsOwner: ast::TypeParamsOwner>(
|
||||
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<T> Cursor<T, '_<|>>"#,
|
||||
r#"impl<T, 'a> Cursor<T, 'a>"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[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>"#,
|
||||
);
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ use crate::{AssistContext, AssistId, Assists};
|
||||
// ```
|
||||
//
|
||||
pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
reorder::<ast::RecordLit>(acc, ctx.clone()).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
|
||||
reorder::<ast::RecordLit>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
|
||||
}
|
||||
|
||||
fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
|
@ -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,
|
||||
|
@ -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<u32> 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(
|
||||
|
@ -88,13 +88,17 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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<FilePosit
|
||||
let mut file_position = None;
|
||||
|
||||
for entry in fixture.iter() {
|
||||
let meta = match parse_meta(&entry.meta) {
|
||||
let meta = match ParsedMeta::from(&entry.meta) {
|
||||
ParsedMeta::Root { path } => {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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()..]))
|
||||
}
|
||||
|
@ -311,6 +311,21 @@ impl fmt::Display for Edition {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<T> for Env
|
||||
where
|
||||
T: Iterator<Item = (&'a String, &'a String)>,
|
||||
{
|
||||
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);
|
||||
|
@ -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<TypeRef> {
|
||||
pub fn target_trait(self, db: &dyn HirDatabase) -> Option<TypeRef> {
|
||||
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<AssocItem> {
|
||||
pub fn items(self, db: &dyn HirDatabase) -> Vec<AssocItem> {
|
||||
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<InFile<ast::Attr>> {
|
||||
pub fn is_builtin_derive(self, db: &dyn HirDatabase) -> Option<InFile<ast::Attr>> {
|
||||
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),
|
||||
|
@ -62,6 +62,7 @@ pub use crate::{
|
||||
|
||||
pub use hir_def::{
|
||||
adt::StructKind,
|
||||
attr::Attrs,
|
||||
body::scope::ExprScopes,
|
||||
builtin_type::BuiltinType,
|
||||
docs::Documentation,
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,13 @@ impl Documentation {
|
||||
Documentation(s.into())
|
||||
}
|
||||
|
||||
pub fn from_ast<N>(node: &N) -> Option<Documentation>
|
||||
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<Documentation> {
|
||||
node.doc_comment_text().map(|it| Documentation::new(&it))
|
||||
pub(crate) fn docs_from_ast<N>(node: &N) -> Option<Documentation>
|
||||
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<String>,
|
||||
doc_attr_text: Option<String>,
|
||||
) -> Option<String> {
|
||||
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<String> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -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<Expr>;
|
||||
@ -52,18 +52,22 @@ pub enum Expr {
|
||||
Block {
|
||||
statements: Vec<Statement>,
|
||||
tail: Option<ExprId>,
|
||||
label: Option<Name>,
|
||||
},
|
||||
Loop {
|
||||
body: ExprId,
|
||||
label: Option<Name>,
|
||||
},
|
||||
While {
|
||||
condition: ExprId,
|
||||
body: ExprId,
|
||||
label: Option<Name>,
|
||||
},
|
||||
For {
|
||||
iterable: ExprId,
|
||||
pat: PatId,
|
||||
body: ExprId,
|
||||
label: Option<Name>,
|
||||
},
|
||||
Call {
|
||||
callee: ExprId,
|
||||
@ -79,9 +83,12 @@ pub enum Expr {
|
||||
expr: ExprId,
|
||||
arms: Vec<MatchArm>,
|
||||
},
|
||||
Continue,
|
||||
Continue {
|
||||
label: Option<Name>,
|
||||
},
|
||||
Break {
|
||||
expr: Option<ExprId>,
|
||||
label: Option<Name>,
|
||||
},
|
||||
Return {
|
||||
expr: Option<ExprId>,
|
||||
@ -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);
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ impl LangItems {
|
||||
T: Into<AttrDefId> + 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ pub(super) enum DefKind {
|
||||
}
|
||||
|
||||
impl DefKind {
|
||||
pub fn ast_id(&self) -> FileAstId<ast::ModuleItem> {
|
||||
pub fn ast_id(self) -> FileAstId<ast::ModuleItem> {
|
||||
match self {
|
||||
DefKind::Function(it) => it.upcast(),
|
||||
DefKind::Struct(it, _) => it.upcast(),
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -25,7 +25,7 @@ impl ProcMacroExpander {
|
||||
}
|
||||
|
||||
pub fn expand(
|
||||
&self,
|
||||
self,
|
||||
db: &dyn AstDatabase,
|
||||
_id: LazyMacroId,
|
||||
tt: &tt::Subtree,
|
||||
|
@ -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"
|
||||
|
@ -76,6 +76,8 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
||||
#[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<dyn DefDatabase> {
|
||||
#[salsa::invoke(crate::traits::chalk::impl_datum_query)]
|
||||
fn impl_datum(&self, krate: CrateId, impl_id: chalk::ImplId) -> Arc<chalk::ImplDatum>;
|
||||
|
||||
#[salsa::invoke(crate::traits::chalk::fn_def_datum_query)]
|
||||
fn fn_def_datum(&self, krate: CrateId, fn_def_id: chalk::FnDefId) -> Arc<chalk::FnDefDatum>;
|
||||
|
||||
#[salsa::invoke(crate::traits::chalk::associated_ty_value_query)]
|
||||
fn associated_ty_value(
|
||||
&self,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -219,6 +219,17 @@ struct InferenceContext<'a> {
|
||||
struct BreakableContext {
|
||||
pub may_break: bool,
|
||||
pub break_ty: Ty,
|
||||
pub label: Option<name::Name>,
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -520,3 +520,53 @@ fn main() {
|
||||
105..107 '()': ()
|
||||
")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_ergonomics_in_closure_params() {
|
||||
assert_snapshot!(
|
||||
infer(r#"
|
||||
#[lang = "fn_once"]
|
||||
trait FnOnce<Args> {
|
||||
type Output;
|
||||
}
|
||||
|
||||
fn foo<T, U, F: FnOnce(T) -> 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
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
@ -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: Copy>(T) -> T {}
|
||||
struct Struct(usize);
|
||||
enum Enum { Variant(usize) }
|
||||
|
||||
trait Test { fn test(&self) -> bool; }
|
||||
impl<T: Copy> 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<T: Copy> 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<Idx> {
|
||||
pub start: Idx,
|
||||
pub end: Idx,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod iter {
|
||||
pub trait Iterator {
|
||||
type Item;
|
||||
}
|
||||
|
||||
pub trait IntoIterator {
|
||||
type Item;
|
||||
type IntoIter: Iterator<Item = Self::Item>;
|
||||
}
|
||||
|
||||
impl<T> IntoIterator for T where T: Iterator {
|
||||
type Item = <T as Iterator>::Item;
|
||||
type IntoIter = Self;
|
||||
}
|
||||
}
|
||||
|
||||
trait Step {}
|
||||
impl Step for i32 {}
|
||||
impl Step for i64 {}
|
||||
|
||||
impl<A: Step> iter::Iterator for ops::Range<A> {
|
||||
type Item = A;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
assert_eq!(t, "i32");
|
||||
}
|
||||
|
@ -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 };
|
||||
|
||||
|
@ -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<Interner> for ChalkContext<'a> {
|
||||
|
||||
fn fn_def_datum(
|
||||
&self,
|
||||
_fn_def_id: chalk_ir::FnDefId<Interner>,
|
||||
) -> Arc<chalk_rust_ir::FnDefDatum<Interner>> {
|
||||
// We don't yet provide any FnDefs to Chalk
|
||||
unimplemented!()
|
||||
fn_def_id: chalk_ir::FnDefId<Interner>,
|
||||
) -> Arc<rust_ir::FnDefDatum<Interner>> {
|
||||
self.db.fn_def_datum(self.krate, fn_def_id)
|
||||
}
|
||||
|
||||
fn impls_for_trait(
|
||||
@ -113,7 +112,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
|
||||
}
|
||||
fn well_known_trait_id(
|
||||
&self,
|
||||
well_known_trait: chalk_rust_ir::WellKnownTrait,
|
||||
well_known_trait: rust_ir::WellKnownTrait,
|
||||
) -> Option<chalk_ir::TraitId<Interner>> {
|
||||
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<Interner> for ChalkContext<'a> {
|
||||
fn opaque_ty_data(
|
||||
&self,
|
||||
_id: chalk_ir::OpaqueTyId<Interner>,
|
||||
) -> Arc<chalk_rust_ir::OpaqueTyDatum<Interner>> {
|
||||
) -> Arc<rust_ir::OpaqueTyDatum<Interner>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn force_impl_for(
|
||||
&self,
|
||||
_well_known: chalk_rust_ir::WellKnownTrait,
|
||||
_well_known: rust_ir::WellKnownTrait,
|
||||
_ty: &chalk_ir::TyData<Interner>,
|
||||
) -> Option<bool> {
|
||||
// this method is mostly for rustc
|
||||
@ -151,6 +150,10 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
|
||||
// FIXME: implement actual object safety
|
||||
true
|
||||
}
|
||||
|
||||
fn hidden_opaque_type(&self, _id: chalk_ir::OpaqueTyId<Interner>) -> chalk_ir::Ty<Interner> {
|
||||
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<FnDefDatum> {
|
||||
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<AdtId> for crate::TypeCtorId {
|
||||
fn from(struct_id: AdtId) -> Self {
|
||||
struct_id.0
|
||||
@ -417,6 +436,18 @@ impl From<crate::TypeCtorId> for AdtId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FnDefId> for crate::CallableDefId {
|
||||
fn from(fn_def_id: FnDefId) -> Self {
|
||||
InternKey::from_intern_id(fn_def_id.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::CallableDefId> for FnDefId {
|
||||
fn from(callable_def_id: crate::CallableDefId) -> Self {
|
||||
chalk_ir::FnDefId(callable_def_id.as_intern_id())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImplId> for crate::traits::GlobalImplId {
|
||||
fn from(impl_id: ImplId) -> Self {
|
||||
InternKey::from_intern_id(impl_id.0)
|
||||
@ -429,14 +460,14 @@ impl From<crate::traits::GlobalImplId> for ImplId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<chalk_rust_ir::AssociatedTyValueId<Interner>> for crate::traits::AssocTyValueId {
|
||||
fn from(id: chalk_rust_ir::AssociatedTyValueId<Interner>) -> Self {
|
||||
impl From<rust_ir::AssociatedTyValueId<Interner>> for crate::traits::AssocTyValueId {
|
||||
fn from(id: rust_ir::AssociatedTyValueId<Interner>) -> Self {
|
||||
Self::from_intern_id(id.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::traits::AssocTyValueId> for chalk_rust_ir::AssociatedTyValueId<Interner> {
|
||||
impl From<crate::traits::AssocTyValueId> for rust_ir::AssociatedTyValueId<Interner> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -11,15 +11,17 @@ use std::{fmt, sync::Arc};
|
||||
pub struct Interner;
|
||||
|
||||
pub type AssocTypeId = chalk_ir::AssocTypeId<Interner>;
|
||||
pub type AssociatedTyDatum = chalk_rust_ir::AssociatedTyDatum<Interner>;
|
||||
pub type AssociatedTyDatum = chalk_solve::rust_ir::AssociatedTyDatum<Interner>;
|
||||
pub type TraitId = chalk_ir::TraitId<Interner>;
|
||||
pub type TraitDatum = chalk_rust_ir::TraitDatum<Interner>;
|
||||
pub type TraitDatum = chalk_solve::rust_ir::TraitDatum<Interner>;
|
||||
pub type AdtId = chalk_ir::AdtId<Interner>;
|
||||
pub type StructDatum = chalk_rust_ir::AdtDatum<Interner>;
|
||||
pub type StructDatum = chalk_solve::rust_ir::AdtDatum<Interner>;
|
||||
pub type ImplId = chalk_ir::ImplId<Interner>;
|
||||
pub type ImplDatum = chalk_rust_ir::ImplDatum<Interner>;
|
||||
pub type AssociatedTyValueId = chalk_rust_ir::AssociatedTyValueId<Interner>;
|
||||
pub type AssociatedTyValue = chalk_rust_ir::AssociatedTyValue<Interner>;
|
||||
pub type ImplDatum = chalk_solve::rust_ir::ImplDatum<Interner>;
|
||||
pub type AssociatedTyValueId = chalk_solve::rust_ir::AssociatedTyValueId<Interner>;
|
||||
pub type AssociatedTyValue = chalk_solve::rust_ir::AssociatedTyValue<Interner>;
|
||||
pub type FnDefId = chalk_ir::FnDefId<Interner>;
|
||||
pub type FnDefDatum = chalk_solve::rust_ir::FnDefDatum<Interner>;
|
||||
|
||||
impl chalk_ir::interner::Interner for Interner {
|
||||
type InternedType = Box<chalk_ir::TyData<Self>>; // FIXME use Arc?
|
||||
|
@ -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<Interner>;
|
||||
fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Ty<Interner> {
|
||||
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<T::Chalk> {
|
||||
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<chalk_rust_ir::InlineBound<Interner>> {
|
||||
) -> Option<rust_ir::InlineBound<Interner>> {
|
||||
// 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,
|
||||
}
|
||||
|
@ -247,10 +247,24 @@ impl DebugContext<'_> {
|
||||
|
||||
pub fn debug_fn_def_id(
|
||||
&self,
|
||||
_fn_def_id: chalk_ir::FnDefId<Interner>,
|
||||
fn_def_id: chalk_ir::FnDefId<Interner>,
|
||||
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(
|
||||
|
@ -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(
|
||||
|
@ -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" },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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!(
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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..)) {
|
||||
|
@ -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<String>,
|
||||
description: Option<String>,
|
||||
) -> 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<TextRange>,
|
||||
full_range: TextRange,
|
||||
kind: SyntaxKind,
|
||||
docs: Option<String>,
|
||||
description: Option<String>,
|
||||
) -> 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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<StructureNode> {
|
||||
let mut res = Vec::new();
|
||||
let mut stack = Vec::new();
|
||||
|
@ -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<ExpandedMacro> {
|
||||
let sema = Semantics::new(db);
|
||||
let file = sema.parse(position.file_id);
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
@ -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,
|
||||
|
@ -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<RangeInfo<HoverResult>> {
|
||||
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<String>,
|
||||
desc: Option<String>,
|
||||
@ -114,13 +164,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
|
||||
return match def {
|
||||
Definition::Macro(it) => {
|
||||
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<Strin
|
||||
Definition::ModuleDef(it) => 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<Strin
|
||||
fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String>
|
||||
where
|
||||
D: HasSource<Ast = A>,
|
||||
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<RangeInfo<HoverResult>> {
|
||||
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<SyntaxToken>) -> Option<SyntaxToken> {
|
||||
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"],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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<Option<SourceChange>> {
|
||||
/// The edit will be a snippet (with `$0`).
|
||||
pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<TextEdit>> {
|
||||
self.with_db(|db| typing::on_enter(&db, position))
|
||||
}
|
||||
|
||||
@ -372,7 +373,7 @@ impl Analysis {
|
||||
&self,
|
||||
position: FilePosition,
|
||||
) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
|
||||
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`.
|
||||
|
@ -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<TextSize> {
|
||||
const BRACES: &[SyntaxKind] =
|
||||
&[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]];
|
||||
|
@ -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<FixtureEntry> 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<MockFileData>,
|
||||
}
|
||||
|
||||
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 <|>.
|
||||
|
@ -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<NavigationTarget> {
|
||||
|
@ -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()
|
||||
|
@ -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<CfgExpr>,
|
||||
}
|
||||
|
||||
#[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<Runnable> {
|
||||
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<RootDatabase>, item: SyntaxNode) -> Option<Runnable> {
|
||||
fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
|
||||
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<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> {
|
||||
fn runnable_fn(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
fn_def: ast::FnDef,
|
||||
file_id: FileId,
|
||||
) -> Option<Runnable> {
|
||||
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<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
|
||||
}
|
||||
});
|
||||
|
||||
let path_iter = module
|
||||
.path_to_root(sema.db)
|
||||
.into_iter()
|
||||
.rev()
|
||||
.filter_map(|it| it.name(sema.db))
|
||||
.map(|name| name.to_string());
|
||||
let path_iter = module
|
||||
.path_to_root(sema.db)
|
||||
.into_iter()
|
||||
.rev()
|
||||
.filter_map(|it| it.name(sema.db))
|
||||
.map(|name| name.to_string());
|
||||
|
||||
let path = if let Some(impl_trait_name) = impl_trait_name {
|
||||
path_iter
|
||||
.chain(std::iter::once(impl_trait_name))
|
||||
.chain(std::iter::once(name_string))
|
||||
.join("::")
|
||||
} else {
|
||||
path_iter.chain(std::iter::once(name_string)).join("::")
|
||||
};
|
||||
let path = if let Some(impl_trait_name) = impl_trait_name {
|
||||
path_iter
|
||||
.chain(std::iter::once(impl_trait_name))
|
||||
.chain(std::iter::once(name_string))
|
||||
.join("::")
|
||||
} else {
|
||||
path_iter.chain(std::iter::once(name_string)).join("::")
|
||||
};
|
||||
|
||||
TestId::Path(path)
|
||||
} else {
|
||||
TestId::Name(name_string)
|
||||
TestId::Path(path)
|
||||
}
|
||||
None => TestId::Name(name_string),
|
||||
};
|
||||
|
||||
if has_test_related_attribute(&fn_def) {
|
||||
@ -111,7 +126,13 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(Runnable { range: fn_def.syntax().text_range(), kind })
|
||||
|
||||
let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
|
||||
let cfg_exprs =
|
||||
attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
|
||||
|
||||
let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def));
|
||||
Some(Runnable { nav, kind, cfg_exprs })
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -147,7 +168,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool {
|
||||
fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
|
||||
}
|
||||
|
||||
fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> {
|
||||
fn runnable_mod(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
module: ast::Module,
|
||||
file_id: FileId,
|
||||
) -> Option<Runnable> {
|
||||
let has_test_function = module
|
||||
.item_list()?
|
||||
.items()
|
||||
@ -159,12 +184,21 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R
|
||||
if !has_test_function {
|
||||
return None;
|
||||
}
|
||||
let range = module.syntax().text_range();
|
||||
let module = sema.to_def(&module)?;
|
||||
let module_def = sema.to_def(&module)?;
|
||||
|
||||
let path =
|
||||
module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
|
||||
Some(Runnable { range, kind: RunnableKind::TestMod { path } })
|
||||
let path = module_def
|
||||
.path_to_root(sema.db)
|
||||
.into_iter()
|
||||
.rev()
|
||||
.filter_map(|it| it.name(sema.db))
|
||||
.join("::");
|
||||
|
||||
let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
|
||||
let cfg_exprs =
|
||||
attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
|
||||
|
||||
let nav = module_def.to_nav(sema.db);
|
||||
Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -194,11 +228,38 @@ mod tests {
|
||||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..21,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..21,
|
||||
name: "main",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
12..16,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Bin,
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 22..46,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 22..46,
|
||||
name: "test_foo",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
33..41,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"test_foo",
|
||||
@ -207,9 +268,23 @@ mod tests {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 47..81,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 47..81,
|
||||
name: "test_foo",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
68..76,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"test_foo",
|
||||
@ -218,6 +293,7 @@ mod tests {
|
||||
ignore: true,
|
||||
},
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
]
|
||||
"###
|
||||
@ -243,16 +319,44 @@ mod tests {
|
||||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..21,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..21,
|
||||
name: "main",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
12..16,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Bin,
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 22..64,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 22..64,
|
||||
name: "foo",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
56..59,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: DocTest {
|
||||
test_id: Path(
|
||||
"foo",
|
||||
),
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
]
|
||||
"###
|
||||
@ -281,16 +385,44 @@ mod tests {
|
||||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..21,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..21,
|
||||
name: "main",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
12..16,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Bin,
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 51..105,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 51..105,
|
||||
name: "foo",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
97..100,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: DocTest {
|
||||
test_id: Path(
|
||||
"Data::foo",
|
||||
),
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
]
|
||||
"###
|
||||
@ -314,13 +446,40 @@ mod tests {
|
||||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..59,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..59,
|
||||
name: "test_mod",
|
||||
kind: MODULE,
|
||||
focus_range: Some(
|
||||
13..21,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: TestMod {
|
||||
path: "test_mod",
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 28..57,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 28..57,
|
||||
name: "test_foo1",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
43..52,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"test_mod::test_foo1",
|
||||
@ -329,6 +488,7 @@ mod tests {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
]
|
||||
"###
|
||||
@ -354,13 +514,40 @@ mod tests {
|
||||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 23..85,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 23..85,
|
||||
name: "test_mod",
|
||||
kind: MODULE,
|
||||
focus_range: Some(
|
||||
27..35,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: TestMod {
|
||||
path: "foo::test_mod",
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 46..79,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 46..79,
|
||||
name: "test_foo1",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
65..74,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"foo::test_mod::test_foo1",
|
||||
@ -369,6 +556,7 @@ mod tests {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
]
|
||||
"###
|
||||
@ -396,13 +584,40 @@ mod tests {
|
||||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 41..115,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 41..115,
|
||||
name: "test_mod",
|
||||
kind: MODULE,
|
||||
focus_range: Some(
|
||||
45..53,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: TestMod {
|
||||
path: "foo::bar::test_mod",
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 68..105,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 68..105,
|
||||
name: "test_foo1",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
91..100,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"foo::bar::test_mod::test_foo1",
|
||||
@ -411,6 +626,115 @@ mod tests {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
]
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runnables_with_feature() {
|
||||
let (analysis, pos) = analysis_and_position(
|
||||
r#"
|
||||
//- /lib.rs crate:foo cfg:feature=foo
|
||||
<|> //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",
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
"###
|
||||
|
@ -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; }
|
||||
|
@ -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
|
||||
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// => "test"</span>
|
||||
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// => "2 1"</span>
|
||||
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// => "a 3 b"</span>
|
||||
<span class="macro">println!</span>(<span class="string_literal">"{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">}}"</span>, <span class="numeric_literal">2</span>); <span class="comment">// => "{2}"</span>
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>);
|
||||
|
48
crates/ra_ide/src/snapshots/highlight_unsafe.html
Normal file
48
crates/ra_ide/src/snapshots/highlight_unsafe.html
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
|
||||
|
||||
.lifetime { color: #DFAF8F; font-style: italic; }
|
||||
.comment { color: #7F9F7F; }
|
||||
.struct, .enum { color: #7CB8BB; }
|
||||
.enum_variant { color: #BDE0F3; }
|
||||
.string_literal { color: #CC9393; }
|
||||
.field { color: #94BFF3; }
|
||||
.function { color: #93E0E3; }
|
||||
.operator.unsafe { color: #E28C14; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.text { color: #DCDCCC; }
|
||||
.type { color: #7CB8BB; }
|
||||
.builtin_type { color: #8CD0D3; }
|
||||
.type_param { color: #DFAF8F; }
|
||||
.attribute { color: #94BFF3; }
|
||||
.numeric_literal { color: #BFEBBF; }
|
||||
.bool_literal { color: #BFE6EB; }
|
||||
.macro { color: #94BFF3; }
|
||||
.module { color: #AFD8AF; }
|
||||
.variable { color: #DCDCCC; }
|
||||
.format_specifier { color: #CC696B; }
|
||||
.mutable { text-decoration: underline; }
|
||||
|
||||
.keyword { color: #F0DFAF; font-weight: bold; }
|
||||
.keyword.unsafe { color: #BC8383; font-weight: bold; }
|
||||
.control { font-style: italic; }
|
||||
</style>
|
||||
<pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span>() {}
|
||||
|
||||
<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span>;
|
||||
|
||||
<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> {
|
||||
<span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span>(&<span class="self_keyword">self</span>) {}
|
||||
}
|
||||
|
||||
<span class="keyword">fn</span> <span class="function declaration">main</span>() {
|
||||
<span class="keyword">let</span> <span class="variable declaration">x</span> = &<span class="numeric_literal">5</span> <span class="keyword">as</span> *<span class="keyword">const</span> <span class="builtin_type">usize</span>;
|
||||
<span class="keyword unsafe">unsafe</span> {
|
||||
<span class="function unsafe">unsafe_fn</span>();
|
||||
<span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>();
|
||||
<span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span><span class="variable">x</span>;
|
||||
<span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>;
|
||||
}
|
||||
}</code></pre>
|
@ -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; }
|
||||
</style>
|
||||
<pre><code><span class="attribute">#[derive(Clone, Debug)]</span>
|
||||
<pre><code><span class="attribute">#[</span><span class="function attribute">derive</span><span class="attribute">(Clone, Debug)]</span>
|
||||
<span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
|
||||
<span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>,
|
||||
<span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>,
|
||||
}
|
||||
|
||||
<span class="keyword">trait</span> <span class="trait declaration">Bar</span> {
|
||||
<span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -> <span class="builtin_type">i32</span>;
|
||||
<span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">i32</span>;
|
||||
}
|
||||
|
||||
<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> {
|
||||
<span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -> <span class="builtin_type">i32</span> {
|
||||
<span class="keyword">self</span>.<span class="field">x</span>
|
||||
<span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">i32</span> {
|
||||
<span class="self_keyword">self</span>.<span class="field">x</span>
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +66,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>);
|
||||
|
||||
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> = <span class="unresolved_reference">Vec</span>::<span class="unresolved_reference">new</span>();
|
||||
<span class="keyword control">if</span> <span class="keyword">true</span> {
|
||||
<span class="keyword control">if</span> <span class="bool_literal">true</span> {
|
||||
<span class="keyword">let</span> <span class="variable declaration">x</span> = <span class="numeric_literal">92</span>;
|
||||
<span class="variable mutable">vec</span>.<span class="unresolved_reference">push</span>(<span class="struct">Foo</span> { <span class="field">x</span>, <span class="field">y</span>: <span class="numeric_literal">1</span> });
|
||||
}
|
||||
@ -91,7 +93,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
<span class="keyword">use</span> <span class="enum">Option</span>::*;
|
||||
|
||||
<span class="keyword">impl</span><<span class="type_param declaration">T</span>> <span class="enum">Option</span><<span class="type_param">T</span>> {
|
||||
<span class="keyword">fn</span> <span class="function declaration">and</span><<span class="type_param declaration">U</span>>(<span class="keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span><<span class="type_param">U</span>>) -> <span class="enum">Option</span><(<span class="type_param">T</span>, <span class="type_param">U</span>)> {
|
||||
<span class="keyword">fn</span> <span class="function declaration">and</span><<span class="type_param declaration">U</span>>(<span class="self_keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span><<span class="type_param">U</span>>) -> <span class="enum">Option</span><(<span class="type_param">T</span>, <span class="type_param">U</span>)> {
|
||||
<span class="keyword control">match</span> <span class="variable">other</span> {
|
||||
<span class="enum_variant">None</span> => <span class="macro">unimplemented!</span>(),
|
||||
<span class="variable declaration">Nope</span> => <span class="variable">Nope</span>,
|
||||
|
@ -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; }
|
||||
|
@ -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 `<search_pattern> ==>> <replace_pattern>`.
|
||||
// A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` 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<Match> {
|
||||
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<RecordField> =
|
||||
pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
|
||||
let mut code_fields: Vec<RecordField> =
|
||||
code.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
|
||||
|
||||
if pattern_fields.len() != code_fields.len() {
|
||||
return None;
|
||||
|
@ -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::<SyntaxTreeStats>()
|
||||
}
|
||||
|
||||
// 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::<FilesStats>();
|
||||
let syntax_tree_stats = syntax_tree_stats(db);
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Implements syntax highlighting.
|
||||
|
||||
mod tags;
|
||||
mod html;
|
||||
#[cfg(test)]
|
||||
@ -32,81 +30,15 @@ pub struct HighlightedRange {
|
||||
pub binding_hash: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HighlightedRangeStack {
|
||||
stack: Vec<Vec<HighlightedRange>>,
|
||||
}
|
||||
|
||||
/// 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<HighlightedRange> {
|
||||
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<Vec<HighlightedRange>>,
|
||||
}
|
||||
|
||||
/// 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<HighlightedRange> {
|
||||
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<HighlightTag> {
|
||||
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(
|
||||
|
@ -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; }
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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<SourceChange> {
|
||||
pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> {
|
||||
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<Sour
|
||||
let inserted = format!("\n{}{} $0", indent, prefix);
|
||||
let edit = TextEdit::insert(position.offset, inserted);
|
||||
|
||||
let mut res = SourceChange::from(SourceFileEdit { edit, file_id: position.file_id });
|
||||
res.is_snippet = true;
|
||||
Some(res)
|
||||
Some(edit)
|
||||
}
|
||||
|
||||
fn followed_by_comment(comment: &ast::Comment) -> 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)
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -110,6 +110,27 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex>
|
||||
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<FileSymbol> {
|
||||
/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
|
||||
struct Snap(salsa::Snapshot<RootDatabase>);
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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" }
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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<Root>,
|
||||
pub(crate) crates: Vec<Crate>,
|
||||
}
|
||||
|
||||
/// 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<Dep>,
|
||||
|
||||
// This is the preferred method of providing cfg options.
|
||||
#[serde(default)]
|
||||
pub(crate) cfg: FxHashSet<String>,
|
||||
|
||||
// These two are here for transition only.
|
||||
#[serde(default)]
|
||||
pub(crate) atom_cfgs: FxHashSet<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) key_value_cfgs: FxHashMap<String, String>,
|
||||
|
||||
pub(crate) out_dir: Option<PathBuf>,
|
||||
pub(crate) proc_macro_dylib_path: Option<PathBuf>,
|
||||
}
|
||||
@ -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<Root>,
|
||||
pub(crate) crates: Vec<Crate>,
|
||||
#[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()));
|
||||
}
|
||||
}
|
||||
|
@ -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<JsonProject> 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<ProjectRoot> {
|
||||
impl ProjectManifest {
|
||||
pub fn from_manifest_file(path: PathBuf) -> Result<ProjectManifest> {
|
||||
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<ProjectRoot> {
|
||||
let mut candidates = ProjectRoot::discover(path)?;
|
||||
pub fn discover_single(path: &Path) -> Result<ProjectManifest> {
|
||||
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<Vec<ProjectRoot>> {
|
||||
pub fn discover(path: &Path) -> io::Result<Vec<ProjectManifest>> {
|
||||
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<Vec<PathBuf>> {
|
||||
match find_in_parent_dirs(path, "Cargo.toml") {
|
||||
@ -128,16 +134,28 @@ impl ProjectRoot {
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn discover_all(paths: &[impl AsRef<Path>]) -> Vec<ProjectManifest> {
|
||||
let mut res = paths
|
||||
.iter()
|
||||
.filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
|
||||
.flatten()
|
||||
.collect::<FxHashSet<_>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
res.sort();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl ProjectWorkspace {
|
||||
pub fn load(
|
||||
root: ProjectRoot,
|
||||
manifest: ProjectManifest,
|
||||
cargo_features: &CargoConfig,
|
||||
with_sysroot: bool,
|
||||
) -> Result<ProjectWorkspace> {
|
||||
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());
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -75,7 +75,7 @@ impl<N> AstChildren<N> {
|
||||
impl<N: AstNode> Iterator for AstChildren<N> {
|
||||
type Item = N;
|
||||
fn next(&mut self) -> Option<N> {
|
||||
self.inner.by_ref().find_map(N::cast)
|
||||
self.inner.find_map(N::cast)
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user