Use josh for subtree syncs
This commit is contained in:
parent
55d9a533b3
commit
8f2138102f
1987 changed files with 494678 additions and 0 deletions
12
src/tools/rust-analyzer/.cargo/config.toml
Normal file
12
src/tools/rust-analyzer/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[alias]
|
||||
xtask = "run --package xtask --bin xtask --"
|
||||
tq = "test -- -q"
|
||||
qt = "tq"
|
||||
lint = "clippy --all-targets -- --cap-lints warn"
|
||||
codegen = "run --package xtask --bin xtask -- codegen"
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
|
||||
[env]
|
||||
CARGO_WORKSPACE_DIR = { value = "", relative = true }
|
||||
17
src/tools/rust-analyzer/.editorconfig
Normal file
17
src/tools/rust-analyzer/.editorconfig
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# https://EditorConfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 100
|
||||
|
||||
[*.md]
|
||||
indent_size = 2
|
||||
|
||||
[*.{yml, yaml}]
|
||||
indent_size = 2
|
||||
8
src/tools/rust-analyzer/.git-blame-ignore-revs
Normal file
8
src/tools/rust-analyzer/.git-blame-ignore-revs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# for this file to take effect make sure you use git ^2.23 and
|
||||
# add ignoreFile to your git configuration:
|
||||
# ```
|
||||
# git config --global blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
# ```
|
||||
|
||||
# prettier format
|
||||
f247090558c9ba3c551566eae5882b7ca865225f
|
||||
9
src/tools/rust-analyzer/.gitattributes
vendored
Normal file
9
src/tools/rust-analyzer/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
* text=auto eol=lf
|
||||
|
||||
# git grep shouldn't match entries in this benchmark data
|
||||
bench_data/** binary
|
||||
|
||||
# Older git versions try to fix line endings on images, this prevents it.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.ico binary
|
||||
35
src/tools/rust-analyzer/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
35
src/tools/rust-analyzer/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a bug report for rust-analyzer.
|
||||
title: ''
|
||||
labels: 'C-bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Troubleshooting guide: https://rust-analyzer.github.io/manual.html#troubleshooting
|
||||
Forum for questions: https://users.rust-lang.org/c/ide/14
|
||||
|
||||
Before submitting, please make sure that you're not running into one of these known issues:
|
||||
|
||||
1. on-the-fly diagnostics are mostly unimplemented (`cargo check` diagnostics will be shown when saving a file): #3107
|
||||
|
||||
Otherwise please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3.
|
||||
-->
|
||||
|
||||
**rust-analyzer version**: (eg. output of "rust-analyzer: Show RA Version" command, accessible in VSCode via <kbd>Ctrl/⌘</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>)
|
||||
|
||||
**rustc version**: (eg. output of `rustc -V`)
|
||||
|
||||
**editor or extension**: (eg. VSCode, Vim, Emacs, etc. For VSCode users, specify your extension version; for users of other editors, provide the distribution if applicable)
|
||||
|
||||
**relevant settings**: (eg. client settings, or environment variables like `CARGO`, `RUSTC`, `RUSTUP_HOME` or `CARGO_HOME`)
|
||||
|
||||
**repository link (if public, optional)**: (eg. [rust-analyzer](https://github.com/rust-lang/rust-analyzer))
|
||||
|
||||
**code snippet to reproduce**:
|
||||
```rust
|
||||
// add your code here
|
||||
|
||||
```
|
||||
16
src/tools/rust-analyzer/.github/ISSUE_TEMPLATE/critical_nightly_regression.md
vendored
Normal file
16
src/tools/rust-analyzer/.github/ISSUE_TEMPLATE/critical_nightly_regression.md
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
name: Critical Nightly Regression
|
||||
about: You are using nightly rust-analyzer and the latest version is unusable.
|
||||
title: ''
|
||||
labels: 'Broken Window'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Troubleshooting guide: https://rust-analyzer.github.io/manual.html#troubleshooting
|
||||
|
||||
Please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3.
|
||||
-->
|
||||
|
||||
This is a serious regression in nightly and it's important to fix it before the next release.
|
||||
8
src/tools/rust-analyzer/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
8
src/tools/rust-analyzer/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: Feature Request
|
||||
about: Create a feature request for rust-analyzer.
|
||||
title: ''
|
||||
labels: 'C-feature'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
8
src/tools/rust-analyzer/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
8
src/tools/rust-analyzer/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: Support Question
|
||||
about: A question regarding functionality of rust-analyzer.
|
||||
title: ''
|
||||
labels: 'C-support'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
8
src/tools/rust-analyzer/.github/actions/github-release/Dockerfile
vendored
Normal file
8
src/tools/rust-analyzer/.github/actions/github-release/Dockerfile
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
FROM node:slim
|
||||
|
||||
COPY . /action
|
||||
WORKDIR /action
|
||||
|
||||
RUN npm install --production
|
||||
|
||||
ENTRYPOINT ["node", "/action/main.js"]
|
||||
21
src/tools/rust-analyzer/.github/actions/github-release/README.md
vendored
Normal file
21
src/tools/rust-analyzer/.github/actions/github-release/README.md
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# github-release
|
||||
|
||||
Copy-pasted from
|
||||
https://github.com/bytecodealliance/wasmtime/tree/8acfdbdd8aa550d1b84e0ce1e6222a6605d14e38/.github/actions/github-release
|
||||
|
||||
An action used to publish GitHub releases for `wasmtime`.
|
||||
|
||||
As of the time of this writing there's a few actions floating around which
|
||||
perform github releases but they all tend to have their set of drawbacks.
|
||||
Additionally nothing handles deleting releases which we need for our rolling
|
||||
`dev` release.
|
||||
|
||||
To handle all this, this action rolls its own implementation using the
|
||||
actions/toolkit repository and packages published there. These run in a Docker
|
||||
container and take various inputs to orchestrate the release from the build.
|
||||
|
||||
More comments can be found in `main.js`.
|
||||
|
||||
Testing this is really hard. If you want to try though run `npm install` and
|
||||
then `node main.js`. You'll have to configure a bunch of env vars though to get
|
||||
anything reasonably working.
|
||||
15
src/tools/rust-analyzer/.github/actions/github-release/action.yml
vendored
Normal file
15
src/tools/rust-analyzer/.github/actions/github-release/action.yml
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
name: 'wasmtime github releases'
|
||||
description: 'wasmtime github releases'
|
||||
inputs:
|
||||
token:
|
||||
description: ''
|
||||
required: true
|
||||
name:
|
||||
description: ''
|
||||
required: true
|
||||
files:
|
||||
description: ''
|
||||
required: true
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
144
src/tools/rust-analyzer/.github/actions/github-release/main.js
vendored
Normal file
144
src/tools/rust-analyzer/.github/actions/github-release/main.js
vendored
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
const core = require('@actions/core');
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const github = require('@actions/github');
|
||||
const glob = require('glob');
|
||||
|
||||
function sleep(milliseconds) {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
}
|
||||
|
||||
async function runOnce() {
|
||||
// Load all our inputs and env vars. Note that `getInput` reads from `INPUT_*`
|
||||
const files = core.getInput('files');
|
||||
const name = core.getInput('name');
|
||||
const token = core.getInput('token');
|
||||
const slug = process.env.GITHUB_REPOSITORY;
|
||||
const owner = slug.split('/')[0];
|
||||
const repo = slug.split('/')[1];
|
||||
const sha = process.env.HEAD_SHA;
|
||||
|
||||
core.info(`files: ${files}`);
|
||||
core.info(`name: ${name}`);
|
||||
|
||||
const options = {
|
||||
request: {
|
||||
timeout: 30000,
|
||||
}
|
||||
};
|
||||
const octokit = github.getOctokit(token, options);
|
||||
|
||||
// Delete the previous release since we can't overwrite one. This may happen
|
||||
// due to retrying an upload or it may happen because we're doing the dev
|
||||
// release.
|
||||
const releases = await octokit.paginate("GET /repos/:owner/:repo/releases", { owner, repo });
|
||||
for (const release of releases) {
|
||||
if (release.tag_name !== name) {
|
||||
continue;
|
||||
}
|
||||
const release_id = release.id;
|
||||
core.info(`deleting release ${release_id}`);
|
||||
await octokit.rest.repos.deleteRelease({ owner, repo, release_id });
|
||||
}
|
||||
|
||||
// We also need to update the `dev` tag while we're at it on the `dev` branch.
|
||||
if (name == 'nightly') {
|
||||
try {
|
||||
core.info(`updating nightly tag`);
|
||||
await octokit.rest.git.updateRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: 'tags/nightly',
|
||||
sha,
|
||||
force: true,
|
||||
});
|
||||
} catch (e) {
|
||||
core.error(e);
|
||||
core.info(`creating nightly tag`);
|
||||
await octokit.rest.git.createTag({
|
||||
owner,
|
||||
repo,
|
||||
tag: 'nightly',
|
||||
message: 'nightly release',
|
||||
object: sha,
|
||||
type: 'commit',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Creates an official GitHub release for this `tag`, and if this is `dev`
|
||||
// then we know that from the previous block this should be a fresh release.
|
||||
core.info(`creating a release`);
|
||||
const release = await octokit.rest.repos.createRelease({
|
||||
owner,
|
||||
repo,
|
||||
name,
|
||||
tag_name: name,
|
||||
target_commitish: sha,
|
||||
prerelease: name === 'nightly',
|
||||
});
|
||||
const release_id = release.data.id;
|
||||
|
||||
// Upload all the relevant assets for this release as just general blobs.
|
||||
for (const file of glob.sync(files)) {
|
||||
const size = fs.statSync(file).size;
|
||||
const name = path.basename(file);
|
||||
|
||||
await runWithRetry(async function () {
|
||||
// We can't overwrite assets, so remove existing ones from a previous try.
|
||||
let assets = await octokit.rest.repos.listReleaseAssets({
|
||||
owner,
|
||||
repo,
|
||||
release_id
|
||||
});
|
||||
for (const asset of assets.data) {
|
||||
if (asset.name === name) {
|
||||
core.info(`delete asset ${name}`);
|
||||
const asset_id = asset.id;
|
||||
await octokit.rest.repos.deleteReleaseAsset({ owner, repo, asset_id });
|
||||
}
|
||||
}
|
||||
|
||||
core.info(`upload ${file}`);
|
||||
const headers = { 'content-length': size, 'content-type': 'application/octet-stream' };
|
||||
const data = fs.createReadStream(file);
|
||||
await octokit.rest.repos.uploadReleaseAsset({
|
||||
data,
|
||||
headers,
|
||||
name,
|
||||
url: release.data.upload_url,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function runWithRetry(f) {
|
||||
const retries = 10;
|
||||
const maxDelay = 4000;
|
||||
let delay = 1000;
|
||||
|
||||
for (let i = 0; i < retries; i++) {
|
||||
try {
|
||||
await f();
|
||||
break;
|
||||
} catch (e) {
|
||||
if (i === retries - 1)
|
||||
throw e;
|
||||
|
||||
core.error(e);
|
||||
const currentDelay = Math.round(Math.random() * delay);
|
||||
core.info(`sleeping ${currentDelay} ms`);
|
||||
await sleep(currentDelay);
|
||||
delay = Math.min(delay * 2, maxDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
await runWithRetry(runOnce);
|
||||
}
|
||||
|
||||
run().catch(err => {
|
||||
core.error(err);
|
||||
core.setFailed(err.message);
|
||||
});
|
||||
10
src/tools/rust-analyzer/.github/actions/github-release/package.json
vendored
Normal file
10
src/tools/rust-analyzer/.github/actions/github-release/package.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "wasmtime-github-release",
|
||||
"version": "0.0.0",
|
||||
"main": "main.js",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.6",
|
||||
"@actions/github": "^5.0",
|
||||
"glob": "^7.1.5"
|
||||
}
|
||||
}
|
||||
33
src/tools/rust-analyzer/.github/rust.json
vendored
Normal file
33
src/tools/rust-analyzer/.github/rust.json
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "rustfmt",
|
||||
"severity": "warning",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(Diff in (.+)) at line (\\d+):$",
|
||||
"message": 1,
|
||||
"file": 2,
|
||||
"line": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "clippy",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(?:\\x1b\\[[\\d;]+m)*(warning|warn|error)(?:\\x1b\\[[\\d;]+m)*(\\[(.*)\\])?(?:\\x1b\\[[\\d;]+m)*:(?:\\x1b\\[[\\d;]+m)* ([^\\x1b]*)(?:\\x1b\\[[\\d;]+m)*$",
|
||||
"severity": 1,
|
||||
"message": 4,
|
||||
"code": 3
|
||||
},
|
||||
{
|
||||
"regexp": "^(?:\\x1b\\[[\\d;]+m)*\\s*(?:\\x1b\\[[\\d;]+m)*\\s*--> (?:\\x1b\\[[\\d;]+m)*(.*):(\\d*):(\\d*)(?:\\x1b\\[[\\d;]+m)*$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
60
src/tools/rust-analyzer/.github/workflows/autopublish.yaml
vendored
Normal file
60
src/tools/rust-analyzer/.github/workflows/autopublish.yaml
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
name: autopublish
|
||||
on:
|
||||
workflow_dispatch: # We can add version input when 1.0 is released and scheduled releases are removed
|
||||
|
||||
# schedule:
|
||||
# - cron: "0 0 * * *" # midnight UTC
|
||||
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# https://github.com/jlumbroso/free-disk-space/blob/main/action.yml
|
||||
- name: Free up some disk space
|
||||
run: sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup update --no-self-update stable
|
||||
|
||||
- name: Install cargo-workspaces
|
||||
run: cargo install cargo-workspaces
|
||||
|
||||
- name: Publish Crates
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
RUN_NUMBER: ${{ github.run_number }}
|
||||
shell: bash
|
||||
run: |
|
||||
git config --global user.email "runner@gha.local"
|
||||
git config --global user.name "GitHub Action"
|
||||
rm Cargo.lock
|
||||
# Fix names for crates that were published before switch to kebab-case.
|
||||
cargo workspaces rename --from base-db base_db
|
||||
cargo workspaces rename --from hir-def hir_def
|
||||
cargo workspaces rename --from hir-expand hir_expand
|
||||
cargo workspaces rename --from hir-ty hir_ty
|
||||
cargo workspaces rename --from ide-assists ide_assists
|
||||
cargo workspaces rename --from ide-completion ide_completion
|
||||
cargo workspaces rename --from ide-db ide_db
|
||||
cargo workspaces rename --from ide-diagnostics ide_diagnostics
|
||||
cargo workspaces rename --from ide-ssr ide_ssr
|
||||
cargo workspaces rename --from proc-macro-api proc_macro_api
|
||||
cargo workspaces rename --from proc-macro-srv proc_macro_srv
|
||||
cargo workspaces rename --from project-model project_model
|
||||
cargo workspaces rename --from test-utils test_utils
|
||||
cargo workspaces rename --from text-edit text_edit
|
||||
# Remove library crates from the workspaces so we don't auto-publish them as well
|
||||
sed -i 's/ "lib\/\*",//' ./Cargo.toml
|
||||
cargo workspaces rename ra_ap_%n
|
||||
find crates/rust-analyzer -type f -name '*.rs' -exec sed -i 's/rust_analyzer/ra_ap_rust_analyzer/g' {} +
|
||||
cargo workspaces publish --yes --force '*' --exact --no-git-commit --allow-dirty --skip-published custom 0.0.$(($RUN_NUMBER + 133))
|
||||
256
src/tools/rust-analyzer/.github/workflows/ci.yaml
vendored
Normal file
256
src/tools/rust-analyzer/.github/workflows/ci.yaml
vendored
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
# Please make sure that the `needs` fields for both `end-success` and `end-failure`
|
||||
# are updated when adding new jobs!
|
||||
|
||||
name: CI
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- auto
|
||||
- try
|
||||
- automation/bors/try
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CI: 1
|
||||
RUST_BACKTRACE: short
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W bare-trait-objects"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
outputs:
|
||||
typescript: ${{ steps.filter.outputs.typescript }}
|
||||
proc_macros: ${{ steps.filter.outputs.proc_macros }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@1441771bbfdd59dcd748680ee64ebd8faab1a242
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
typescript:
|
||||
- 'editors/code/**'
|
||||
proc_macros:
|
||||
- 'crates/proc-macro-api/**'
|
||||
- 'crates/proc-macro-srv/**'
|
||||
- 'crates/proc-macro-srv-cli/**'
|
||||
|
||||
rust:
|
||||
needs: changes
|
||||
if: github.repository == 'rust-lang/rust-analyzer'
|
||||
name: Rust
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CC: deny_c
|
||||
RUST_CHANNEL: "${{ needs.changes.outputs.proc_macros == 'true' && 'nightly' || 'stable' }}"
|
||||
USE_SYSROOT_ABI: "${{ needs.changes.outputs.proc_macros == 'true' && '--features sysroot-abi' || '' }}"
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: |
|
||||
rustup update --no-self-update ${{ env.RUST_CHANNEL }}
|
||||
rustup component add --toolchain ${{ env.RUST_CHANNEL }} rustfmt rust-src
|
||||
rustup default ${{ env.RUST_CHANNEL }}
|
||||
# https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/rust.json
|
||||
- name: Install Rust Problem Matcher
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: echo "::add-matcher::.github/rust.json"
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@640a22190e7a783d4c409684cea558f081f92012
|
||||
with:
|
||||
key: ${{ env.RUST_CHANNEL }}
|
||||
|
||||
- name: Bump opt-level
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sed -i '/\[profile.dev]/a opt-level=1' Cargo.toml
|
||||
|
||||
- name: Codegen checks (rust-analyzer)
|
||||
run: cargo codegen --check
|
||||
|
||||
- name: Compile (tests)
|
||||
run: cargo test --no-run --locked ${{ env.USE_SYSROOT_ABI }}
|
||||
|
||||
# It's faster to `test` before `build` ¯\_(ツ)_/¯
|
||||
- name: Compile (rust-analyzer)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: cargo build --quiet ${{ env.USE_SYSROOT_ABI }}
|
||||
|
||||
- name: Test
|
||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'windows-latest' || github.event_name == 'push'
|
||||
run: cargo test ${{ env.USE_SYSROOT_ABI }} -- --nocapture --quiet
|
||||
|
||||
- name: Switch to stable toolchain
|
||||
run: |
|
||||
rustup update --no-self-update stable
|
||||
rustup component add --toolchain stable rust-src clippy
|
||||
rustup default stable
|
||||
|
||||
- name: Run analysis-stats on rust-analyzer
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: target/${{ matrix.target }}/debug/rust-analyzer analysis-stats .
|
||||
|
||||
- name: Run analysis-stats on rust std library
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
env:
|
||||
RUSTC_BOOTSTRAP: 1
|
||||
run: target/${{ matrix.target }}/debug/rust-analyzer analysis-stats --with-deps $(rustc --print sysroot)/lib/rustlib/src/rust/library/std
|
||||
|
||||
- name: clippy
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: cargo clippy --all-targets -- -D clippy::disallowed_macros -D clippy::dbg_macro -D clippy::todo -D clippy::print_stdout -D clippy::print_stderr
|
||||
|
||||
- name: rustfmt
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: cargo fmt -- --check
|
||||
|
||||
# Weird targets to catch non-portable code
|
||||
rust-cross:
|
||||
if: github.repository == 'rust-lang/rust-analyzer'
|
||||
name: Rust Cross
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
targets: "powerpc-unknown-linux-gnu x86_64-unknown-linux-musl"
|
||||
# The rust-analyzer binary is not expected to compile on WASM, but the IDE
|
||||
# crate should
|
||||
targets_ide: "wasm32-unknown-unknown"
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: |
|
||||
rustup update --no-self-update stable
|
||||
rustup target add ${{ env.targets }} ${{ env.targets_ide }}
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@640a22190e7a783d4c409684cea558f081f92012
|
||||
|
||||
- name: Check
|
||||
run: |
|
||||
for target in ${{ env.targets }}; do
|
||||
cargo check --target=$target --all-targets
|
||||
done
|
||||
for target in ${{ env.targets_ide }}; do
|
||||
cargo check -p ide --target=$target --all-targets
|
||||
done
|
||||
|
||||
typescript:
|
||||
needs: changes
|
||||
if: github.repository == 'rust-lang/rust-analyzer'
|
||||
name: TypeScript
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
if: needs.changes.outputs.typescript == 'true'
|
||||
|
||||
- name: Install Nodejs
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
if: needs.changes.outputs.typescript == 'true'
|
||||
|
||||
- name: Install xvfb
|
||||
if: matrix.os == 'ubuntu-latest' && needs.changes.outputs.typescript == 'true'
|
||||
run: sudo apt-get install -y xvfb
|
||||
|
||||
- run: npm ci
|
||||
working-directory: ./editors/code
|
||||
if: needs.changes.outputs.typescript == 'true'
|
||||
|
||||
# - run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; }
|
||||
# if: runner.os == 'Linux'
|
||||
# working-directory: ./editors/code
|
||||
|
||||
# If this steps fails, your code's type integrity might be wrong at some places at TypeScript level.
|
||||
- run: npm run typecheck
|
||||
working-directory: ./editors/code
|
||||
if: needs.changes.outputs.typescript == 'true'
|
||||
|
||||
# You may fix the code automatically by running `npm run lint:fix` if this steps fails.
|
||||
- run: npm run lint
|
||||
working-directory: ./editors/code
|
||||
if: needs.changes.outputs.typescript == 'true'
|
||||
|
||||
# To fix this steps, please run `npm run format`.
|
||||
- run: npm run format:check
|
||||
working-directory: ./editors/code
|
||||
if: needs.changes.outputs.typescript == 'true'
|
||||
|
||||
- name: Run VS Code tests (Linux)
|
||||
if: matrix.os == 'ubuntu-latest' && needs.changes.outputs.typescript == 'true'
|
||||
env:
|
||||
VSCODE_CLI: 1
|
||||
run: xvfb-run npm test
|
||||
working-directory: ./editors/code
|
||||
|
||||
- name: Run VS Code tests (Windows)
|
||||
if: matrix.os == 'windows-latest' && needs.changes.outputs.typescript == 'true'
|
||||
env:
|
||||
VSCODE_CLI: 1
|
||||
run: npm test
|
||||
working-directory: ./editors/code
|
||||
|
||||
- run: npm run package --scripts-prepend-node-path
|
||||
working-directory: ./editors/code
|
||||
if: needs.changes.outputs.typescript == 'true'
|
||||
|
||||
typo-check:
|
||||
name: Typo Check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
TYPOS_VERSION: v1.18.0
|
||||
steps:
|
||||
- name: download typos
|
||||
run: curl -LsSf https://github.com/crate-ci/typos/releases/download/$TYPOS_VERSION/typos-$TYPOS_VERSION-x86_64-unknown-linux-musl.tar.gz | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: check for typos
|
||||
run: typos
|
||||
|
||||
end-success:
|
||||
name: bors build finished
|
||||
if: github.event.pusher.name == 'bors' && success()
|
||||
runs-on: ubuntu-latest
|
||||
needs: [rust, rust-cross, typescript, typo-check]
|
||||
steps:
|
||||
- name: Mark the job as successful
|
||||
run: exit 0
|
||||
|
||||
end-failure:
|
||||
name: bors build finished
|
||||
if: github.event.pusher.name == 'bors' && !success()
|
||||
runs-on: ubuntu-latest
|
||||
needs: [rust, rust-cross, typescript, typo-check]
|
||||
steps:
|
||||
- name: Mark the job as a failure
|
||||
run: exit 1
|
||||
43
src/tools/rust-analyzer/.github/workflows/fuzz.yml
vendored
Normal file
43
src/tools/rust-analyzer/.github/workflows/fuzz.yml
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
name: Fuzz
|
||||
on:
|
||||
schedule:
|
||||
# Once a week
|
||||
- cron: '0 0 * * 0'
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/fuzz.yml'
|
||||
# Allow manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CI: 1
|
||||
RUST_BACKTRACE: short
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W bare-trait-objects"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
rust:
|
||||
if: ${{ github.repository == 'rust-lang/rust-analyzer' || github.event.action == 'workflow_dispatch' }}
|
||||
name: Rust
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CC: deny_c
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: |
|
||||
rustup install --profile minimal nightly
|
||||
|
||||
- name: Build fuzzers
|
||||
run: |
|
||||
cargo install cargo-fuzz
|
||||
cd crates/syntax
|
||||
cargo +nightly fuzz build
|
||||
153
src/tools/rust-analyzer/.github/workflows/metrics.yaml
vendored
Normal file
153
src/tools/rust-analyzer/.github/workflows/metrics.yaml
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
name: metrics
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
setup_cargo:
|
||||
if: github.repository == 'rust-lang/rust-analyzer'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Rust toolchain
|
||||
run: |
|
||||
rustup update --no-self-update stable
|
||||
rustup component add rustfmt rust-src
|
||||
rustup default stable
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: ${{ runner.os }}-cargo-${{ github.sha }}
|
||||
|
||||
build_metrics:
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup_cargo
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore cargo cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: ${{ runner.os }}-cargo-${{ github.sha }}
|
||||
|
||||
- name: Collect build metrics
|
||||
run: cargo xtask metrics build
|
||||
|
||||
- name: Cache target
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: target/
|
||||
key: ${{ runner.os }}-target-${{ github.sha }}
|
||||
|
||||
- name: Upload build metrics
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ github.sha }}
|
||||
path: target/build.json
|
||||
if-no-files-found: error
|
||||
|
||||
other_metrics:
|
||||
strategy:
|
||||
matrix:
|
||||
names: [self, ripgrep-13.0.0, webrender-2022, diesel-1.4.8, hyper-0.14.18]
|
||||
runs-on: ubuntu-latest
|
||||
needs: [setup_cargo, build_metrics]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore cargo cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: ${{ runner.os }}-cargo-${{ github.sha }}
|
||||
|
||||
- name: Restore target cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: target/
|
||||
key: ${{ runner.os }}-target-${{ github.sha }}
|
||||
|
||||
- name: Collect metrics
|
||||
run: cargo xtask metrics "${{ matrix.names }}"
|
||||
|
||||
- name: Upload metrics
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.names }}-${{ github.sha }}
|
||||
path: target/${{ matrix.names }}.json
|
||||
if-no-files-found: error
|
||||
|
||||
generate_final_metrics:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_metrics, other_metrics]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download build metrics
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-${{ github.sha }}
|
||||
|
||||
- name: Download self metrics
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: self-${{ github.sha }}
|
||||
|
||||
- name: Download ripgrep-13.0.0 metrics
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ripgrep-13.0.0-${{ github.sha }}
|
||||
|
||||
- name: Download webrender-2022 metrics
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: webrender-2022-${{ github.sha }}
|
||||
|
||||
- name: Download diesel-1.4.8 metrics
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: diesel-1.4.8-${{ github.sha }}
|
||||
|
||||
- name: Download hyper-0.14.18 metrics
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hyper-0.14.18-${{ github.sha }}
|
||||
|
||||
- name: Combine json
|
||||
run: |
|
||||
mkdir ~/.ssh
|
||||
echo "${{ secrets.METRICS_DEPLOY_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
chmod 700 ~/.ssh
|
||||
|
||||
git clone --depth 1 git@github.com:rust-analyzer/metrics.git
|
||||
jq -s ".[0] * .[1] * .[2] * .[3] * .[4] * .[5]" build.json self.json ripgrep-13.0.0.json webrender-2022.json diesel-1.4.8.json hyper-0.14.18.json -c >> metrics/metrics.json
|
||||
cd metrics
|
||||
git add .
|
||||
git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈
|
||||
git push origin master
|
||||
36
src/tools/rust-analyzer/.github/workflows/publish-libs.yaml
vendored
Normal file
36
src/tools/rust-analyzer/.github/workflows/publish-libs.yaml
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
name: publish-libs
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "lib/**"
|
||||
|
||||
jobs:
|
||||
publish-libs:
|
||||
name: publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup update --no-self-update stable
|
||||
|
||||
- name: Install cargo-workspaces
|
||||
run: cargo install cargo-workspaces
|
||||
|
||||
- name: Publish Crates
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
git config --global user.email "runner@gha.local"
|
||||
git config --global user.name "GitHub Action"
|
||||
# Remove r-a crates from the workspaces so we don't auto-publish them as well
|
||||
sed -i 's/ "crates\/\*"//' ./Cargo.toml
|
||||
sed -i 's/ "xtask\/"//' ./Cargo.toml
|
||||
cargo workspaces publish --yes --exact --from-git --no-git-commit --allow-dirty
|
||||
280
src/tools/rust-analyzer/.github/workflows/release.yaml
vendored
Normal file
280
src/tools/rust-analyzer/.github/workflows/release.yaml
vendored
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
name: release
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # midnight UTC
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
- trigger-nightly
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
FETCH_DEPTH: 0 # pull in the tags for the version string
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.15
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
||||
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
|
||||
|
||||
jobs:
|
||||
dist:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
code-target: win32-x64
|
||||
- os: windows-latest
|
||||
target: i686-pc-windows-msvc
|
||||
- os: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
code-target: win32-arm64
|
||||
- os: ubuntu-20.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
code-target: linux-x64
|
||||
container: rockylinux:8
|
||||
- os: ubuntu-20.04
|
||||
target: aarch64-unknown-linux-gnu
|
||||
code-target: linux-arm64
|
||||
- os: ubuntu-20.04
|
||||
target: arm-unknown-linux-gnueabihf
|
||||
code-target: linux-armhf
|
||||
- os: macos-12
|
||||
target: x86_64-apple-darwin
|
||||
code-target: darwin-x64
|
||||
- os: macos-12
|
||||
target: aarch64-apple-darwin
|
||||
code-target: darwin-arm64
|
||||
|
||||
name: dist (${{ matrix.target }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
|
||||
env:
|
||||
RA_TARGET: ${{ matrix.target }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: ${{ env.FETCH_DEPTH }}
|
||||
|
||||
- name: Install toolchain dependencies
|
||||
if: matrix.container == 'rockylinux:8'
|
||||
shell: bash
|
||||
run: |
|
||||
dnf install -y gcc
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL "https://sh.rustup.rs" | sh -s -- --profile minimal --default-toolchain none -y
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: |
|
||||
rustup update --no-self-update stable
|
||||
rustup target add ${{ matrix.target }}
|
||||
rustup component add rust-src
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Update apt repositories
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'arm-unknown-linux-gnueabihf'
|
||||
run: sudo apt-get update
|
||||
|
||||
- name: Install AArch64 target toolchain
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: sudo apt-get install gcc-aarch64-linux-gnu
|
||||
|
||||
- name: Install ARM target toolchain
|
||||
if: matrix.target == 'arm-unknown-linux-gnueabihf'
|
||||
run: sudo apt-get install gcc-arm-linux-gnueabihf
|
||||
|
||||
- name: Dist
|
||||
run: cargo xtask dist --client-patch-version ${{ github.run_number }}
|
||||
|
||||
- run: npm ci
|
||||
working-directory: editors/code
|
||||
|
||||
- name: Package Extension (release)
|
||||
if: github.ref == 'refs/heads/release' && matrix.code-target
|
||||
run: npx vsce package -o "../../dist/rust-analyzer-${{ matrix.code-target }}.vsix" --target ${{ matrix.code-target }}
|
||||
working-directory: editors/code
|
||||
|
||||
- name: Package Extension (nightly)
|
||||
if: github.ref != 'refs/heads/release' && matrix.code-target
|
||||
run: npx vsce package -o "../../dist/rust-analyzer-${{ matrix.code-target }}.vsix" --target ${{ matrix.code-target }} --pre-release
|
||||
working-directory: editors/code
|
||||
|
||||
- if: matrix.target == 'x86_64-unknown-linux-gnu'
|
||||
run: rm -rf editors/code/server
|
||||
|
||||
- if: matrix.target == 'x86_64-unknown-linux-gnu' && github.ref == 'refs/heads/release'
|
||||
run: npx vsce package -o ../../dist/rust-analyzer-no-server.vsix
|
||||
working-directory: editors/code
|
||||
|
||||
- if: matrix.target == 'x86_64-unknown-linux-gnu' && github.ref != 'refs/heads/release'
|
||||
run: npx vsce package -o ../../dist/rust-analyzer-no-server.vsix --pre-release
|
||||
working-directory: editors/code
|
||||
|
||||
- name: Run analysis-stats on rust-analyzer
|
||||
if: matrix.target == 'x86_64-unknown-linux-gnu'
|
||||
run: target/${{ matrix.target }}/release/rust-analyzer analysis-stats .
|
||||
|
||||
- name: Run analysis-stats on rust std library
|
||||
if: matrix.target == 'x86_64-unknown-linux-gnu'
|
||||
env:
|
||||
RUSTC_BOOTSTRAP: 1
|
||||
run: target/${{ matrix.target }}/release/rust-analyzer analysis-stats --with-deps $(rustc --print sysroot)/lib/rustlib/src/rust/library/std
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: dist-${{ matrix.target }}
|
||||
path: ./dist
|
||||
|
||||
dist-x86_64-unknown-linux-musl:
|
||||
name: dist (x86_64-unknown-linux-musl)
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RA_TARGET: x86_64-unknown-linux-musl
|
||||
# For some reason `-crt-static` is not working for clang without lld
|
||||
RUSTFLAGS: "-C link-arg=-fuse-ld=lld -C target-feature=-crt-static"
|
||||
container:
|
||||
image: rust:alpine
|
||||
volumes:
|
||||
- /usr/local/cargo/registry:/usr/local/cargo/registry
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: apk add --no-cache git clang lld musl-dev nodejs npm
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: ${{ env.FETCH_DEPTH }}
|
||||
|
||||
- name: Dist
|
||||
run: cargo xtask dist --client-patch-version ${{ github.run_number }}
|
||||
|
||||
- run: npm ci
|
||||
working-directory: editors/code
|
||||
|
||||
- name: Package Extension (release)
|
||||
if: github.ref == 'refs/heads/release'
|
||||
run: npx vsce package -o "../../dist/rust-analyzer-alpine-x64.vsix" --target alpine-x64
|
||||
working-directory: editors/code
|
||||
|
||||
- name: Package Extension (nightly)
|
||||
if: github.ref != 'refs/heads/release'
|
||||
run: npx vsce package -o "../../dist/rust-analyzer-alpine-x64.vsix" --target alpine-x64 --pre-release
|
||||
working-directory: editors/code
|
||||
|
||||
- run: rm -rf editors/code/server
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: dist-x86_64-unknown-linux-musl
|
||||
path: ./dist
|
||||
|
||||
publish:
|
||||
name: publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: ["dist", "dist-x86_64-unknown-linux-musl"]
|
||||
steps:
|
||||
- name: Install Nodejs
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- run: echo "TAG=$(date --iso -u)" >> $GITHUB_ENV
|
||||
if: github.ref == 'refs/heads/release'
|
||||
- run: echo "TAG=nightly" >> $GITHUB_ENV
|
||||
if: github.ref != 'refs/heads/release'
|
||||
- run: 'echo "TAG: $TAG"'
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: ${{ env.FETCH_DEPTH }}
|
||||
|
||||
- run: echo "HEAD_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV
|
||||
- run: 'echo "HEAD_SHA: $HEAD_SHA"'
|
||||
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: dist-aarch64-apple-darwin
|
||||
path: dist
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: dist-x86_64-apple-darwin
|
||||
path: dist
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: dist-x86_64-unknown-linux-gnu
|
||||
path: dist
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: dist-x86_64-unknown-linux-musl
|
||||
path: dist
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: dist-aarch64-unknown-linux-gnu
|
||||
path: dist
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: dist-arm-unknown-linux-gnueabihf
|
||||
path: dist
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: dist-x86_64-pc-windows-msvc
|
||||
path: dist
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: dist-i686-pc-windows-msvc
|
||||
path: dist
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: dist-aarch64-pc-windows-msvc
|
||||
path: dist
|
||||
- run: ls -al ./dist
|
||||
|
||||
- name: Publish Release
|
||||
uses: ./.github/actions/github-release
|
||||
with:
|
||||
files: "dist/*"
|
||||
name: ${{ env.TAG }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- run: rm dist/rust-analyzer-no-server.vsix
|
||||
|
||||
- run: npm ci
|
||||
working-directory: ./editors/code
|
||||
|
||||
- name: Publish Extension (Code Marketplace, release)
|
||||
if: github.ref == 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
|
||||
working-directory: ./editors/code
|
||||
# token from https://dev.azure.com/rust-analyzer/
|
||||
run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix
|
||||
|
||||
- name: Publish Extension (OpenVSX, release)
|
||||
if: github.ref == 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
|
||||
working-directory: ./editors/code
|
||||
run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix
|
||||
timeout-minutes: 2
|
||||
|
||||
- name: Publish Extension (Code Marketplace, nightly)
|
||||
if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
|
||||
working-directory: ./editors/code
|
||||
run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix --pre-release
|
||||
|
||||
- name: Publish Extension (OpenVSX, nightly)
|
||||
if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
|
||||
working-directory: ./editors/code
|
||||
run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix
|
||||
timeout-minutes: 2
|
||||
34
src/tools/rust-analyzer/.github/workflows/rustdoc.yaml
vendored
Normal file
34
src/tools/rust-analyzer/.github/workflows/rustdoc.yaml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
name: rustdoc
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
rustdoc:
|
||||
if: github.repository == 'rust-lang/rust-analyzer'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup update --no-self-update stable
|
||||
|
||||
- name: Build Documentation
|
||||
run: cargo doc --all --no-deps
|
||||
|
||||
- name: Deploy Docs
|
||||
uses: peaceiris/actions-gh-pages@364c31d33bb99327c77b3a5438a83a357a6729ad # v3.4.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_branch: gh-pages
|
||||
publish_dir: ./target/doc
|
||||
force_orphan: true
|
||||
16
src/tools/rust-analyzer/.gitignore
vendored
Normal file
16
src/tools/rust-analyzer/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/target/
|
||||
/dist/
|
||||
crates/*/target
|
||||
**/*.rs.bk
|
||||
**/*.rs.pending-snap
|
||||
.idea/*
|
||||
*.log
|
||||
*.iml
|
||||
.vscode/settings.json
|
||||
generated_assists.adoc
|
||||
generated_features.adoc
|
||||
generated_diagnostic.adoc
|
||||
.DS_Store
|
||||
/out/
|
||||
/dump.lsif
|
||||
.envrc
|
||||
30
src/tools/rust-analyzer/.typos.toml
Normal file
30
src/tools/rust-analyzer/.typos.toml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[files]
|
||||
extend-exclude = [
|
||||
"*.rast",
|
||||
"bench_data/",
|
||||
"crates/parser/test_data/lexer/err/",
|
||||
"crates/project-model/test_data/",
|
||||
]
|
||||
|
||||
[default]
|
||||
extend-ignore-re = [
|
||||
# ignore string which contains $0, which is used widely in tests
|
||||
".*\\$0.*",
|
||||
# ignore generated content like `boxed....nner()`, `Defaul...efault`
|
||||
"\\w*\\.{3,4}\\w*",
|
||||
'"flate2"',
|
||||
"raison d'être",
|
||||
]
|
||||
|
||||
[default.extend-words]
|
||||
anser = "anser"
|
||||
ba = "ba"
|
||||
fo = "fo"
|
||||
ket = "ket"
|
||||
makro = "makro"
|
||||
trivias = "trivias"
|
||||
|
||||
[default.extend-identifiers]
|
||||
datas = "datas"
|
||||
impl_froms = "impl_froms"
|
||||
selfs = "selfs"
|
||||
9
src/tools/rust-analyzer/.vscode/extensions.json
vendored
Normal file
9
src/tools/rust-analyzer/.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": ["vadimcn.vscode-lldb"],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
131
src/tools/rust-analyzer/.vscode/launch.json
vendored
Normal file
131
src/tools/rust-analyzer/.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
|
||||
// NOTE: --disable-extensions
|
||||
// Disable all installed extensions to increase performance of the debug instance
|
||||
// and prevent potential conflicts with other installed extensions.
|
||||
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// Used for testing the extension with the installed LSP server.
|
||||
"name": "Run Installed Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
// "--user-data-dir=${workspaceFolder}/target/code",
|
||||
"--disable-extensions",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/editors/code"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/editors/code/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "Build Extension",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
// Used for testing the extension with a local build of the LSP server (in `target/debug`).
|
||||
"name": "Run Extension (Debug Build)",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--disable-extensions",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/editors/code"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/editors/code/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "Build Server and Extension",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**/*.js"
|
||||
],
|
||||
"env": {
|
||||
"__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/debug/rust-analyzer"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Used for testing the extension with a local build of the LSP server (in `target/release`).
|
||||
"name": "Run Extension (Release Build)",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--disable-extensions",
|
||||
"--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 for testing the extension with a local build of the LSP server (in `target/release`)
|
||||
// with all other extensions loaded.
|
||||
"name": "Run With Extensions",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--disable-extension", "rust-lang.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:
|
||||
//
|
||||
// `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope`
|
||||
//
|
||||
// Don't forget to set `debug = 2` in `Cargo.toml` before building the server
|
||||
|
||||
"name": "Attach To Server",
|
||||
"type": "lldb",
|
||||
"request": "attach",
|
||||
"program": "${workspaceFolder}/target/debug/rust-analyzer",
|
||||
"pid": "${command:pickMyProcess}",
|
||||
"sourceLanguages": [
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"name": "Win Attach to Server",
|
||||
"type": "cppvsdbg",
|
||||
"processId":"${command:pickProcess}",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
||||
67
src/tools/rust-analyzer/.vscode/tasks.json
vendored
Normal file
67
src/tools/rust-analyzer/.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build Extension in Background",
|
||||
"group": "build",
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"path": "editors/code/",
|
||||
"problemMatcher": {
|
||||
"base": "$tsc-watch",
|
||||
"fileLocation": ["relative", "${workspaceFolder}/editors/code/"]
|
||||
},
|
||||
"isBackground": true,
|
||||
},
|
||||
{
|
||||
"label": "Build Extension",
|
||||
"group": "build",
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"path": "editors/code/",
|
||||
"problemMatcher": {
|
||||
"base": "$tsc",
|
||||
"fileLocation": ["relative", "${workspaceFolder}/editors/code/"]
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Build Server",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "cargo build --package rust-analyzer",
|
||||
"problemMatcher": "$rustc"
|
||||
},
|
||||
{
|
||||
"label": "Build Server (Release)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"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",
|
||||
"dependsOn": ["Build Server", "Build Extension"],
|
||||
"problemMatcher": "$rustc"
|
||||
},
|
||||
{
|
||||
"label": "Build Server (Release) and Extension",
|
||||
"dependsOn": ["Build Server (Release)", "Build Extension"],
|
||||
"problemMatcher": "$rustc"
|
||||
}
|
||||
]
|
||||
}
|
||||
30
src/tools/rust-analyzer/CONTRIBUTING.md
Normal file
30
src/tools/rust-analyzer/CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Contributing to rust-analyzer
|
||||
|
||||
Thank you for your interest in contributing to rust-analyzer! There are many ways to contribute
|
||||
and we appreciate all of them.
|
||||
|
||||
To get a quick overview of the crates and structure of the project take a look at the
|
||||
[./docs/dev](./docs/dev) folder.
|
||||
|
||||
If you have any questions please ask them in the [rust-analyzer zulip stream](
|
||||
https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer) or if unsure where
|
||||
to start out when working on a concrete issue drop a comment on the related issue for mentoring
|
||||
instructions (general discussions are recommended to happen on zulip though).
|
||||
|
||||
## Fixing a bug or improving a feature
|
||||
|
||||
Generally it's fine to just work on these kinds of things and put a pull-request out for it. If there
|
||||
is an issue accompanying it make sure to link it in the pull request description so it can be closed
|
||||
afterwards or linked for context.
|
||||
|
||||
If you want to find something to fix or work on keep a look out for the `C-bug` and `C-enhancement`
|
||||
labels.
|
||||
|
||||
## Implementing a new feature
|
||||
|
||||
It's advised to first open an issue for any kind of new feature so the team can tell upfront whether
|
||||
the feature is desirable or not before any implementation work happens. We want to minimize the
|
||||
possibility of someone putting a lot of work into a feature that is then going to waste as we deem
|
||||
it out of scope (be it due to generally not fitting in with rust-analyzer, or just not having the
|
||||
maintenance capacity). If there already is a feature issue open but it is not clear whether it is
|
||||
considered accepted feel free to just drop a comment and ask!
|
||||
2575
src/tools/rust-analyzer/Cargo.lock
generated
Normal file
2575
src/tools/rust-analyzer/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
208
src/tools/rust-analyzer/Cargo.toml
Normal file
208
src/tools/rust-analyzer/Cargo.toml
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
[workspace]
|
||||
members = ["xtask/", "lib/*", "crates/*"]
|
||||
exclude = ["crates/proc-macro-srv/proc-macro-test/imp"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
rust-version = "1.76"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["rust-analyzer team"]
|
||||
|
||||
[profile.dev]
|
||||
# Disabling debug info speeds up builds a bunch,
|
||||
# and we don't rely on it for debugging that much.
|
||||
debug = 0
|
||||
|
||||
[profile.dev.package]
|
||||
# These speed up local tests.
|
||||
rowan.opt-level = 3
|
||||
rustc-hash.opt-level = 3
|
||||
smol_str.opt-level = 3
|
||||
text-size.opt-level = 3
|
||||
# This speeds up `cargo xtask dist`.
|
||||
miniz_oxide.opt-level = 3
|
||||
salsa.opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
incremental = true
|
||||
# Set this to 1 or 2 to get more useful backtraces in debugger.
|
||||
debug = 0
|
||||
|
||||
[profile.dev-rel]
|
||||
inherits = "release"
|
||||
debug = 2
|
||||
|
||||
[patch.'crates-io']
|
||||
# rowan = { path = "../rowan" }
|
||||
|
||||
# chalk-solve = { path = "../chalk/chalk-solve" }
|
||||
# chalk-ir = { path = "../chalk/chalk-ir" }
|
||||
# chalk-recursive = { path = "../chalk/chalk-recursive" }
|
||||
# chalk-derive = { path = "../chalk/chalk-derive" }
|
||||
# line-index = { path = "lib/line-index" }
|
||||
# la-arena = { path = "lib/la-arena" }
|
||||
# lsp-server = { path = "lib/lsp-server" }
|
||||
|
||||
|
||||
# ungrammar = { path = "../ungrammar" }
|
||||
|
||||
# rust-analyzer-salsa = { path = "../salsa" }
|
||||
|
||||
[workspace.dependencies]
|
||||
# local crates
|
||||
base-db = { path = "./crates/base-db", version = "0.0.0" }
|
||||
cfg = { path = "./crates/cfg", version = "0.0.0" }
|
||||
flycheck = { path = "./crates/flycheck", version = "0.0.0" }
|
||||
hir = { path = "./crates/hir", version = "0.0.0" }
|
||||
hir-def = { path = "./crates/hir-def", version = "0.0.0" }
|
||||
hir-expand = { path = "./crates/hir-expand", version = "0.0.0" }
|
||||
hir-ty = { path = "./crates/hir-ty", version = "0.0.0" }
|
||||
ide = { path = "./crates/ide", version = "0.0.0" }
|
||||
ide-assists = { path = "./crates/ide-assists", version = "0.0.0" }
|
||||
ide-completion = { path = "./crates/ide-completion", version = "0.0.0" }
|
||||
ide-db = { path = "./crates/ide-db", version = "0.0.0" }
|
||||
ide-diagnostics = { path = "./crates/ide-diagnostics", version = "0.0.0" }
|
||||
ide-ssr = { path = "./crates/ide-ssr", version = "0.0.0" }
|
||||
intern = { path = "./crates/intern", version = "0.0.0" }
|
||||
limit = { path = "./crates/limit", version = "0.0.0" }
|
||||
load-cargo = { path = "./crates/load-cargo", version = "0.0.0" }
|
||||
mbe = { path = "./crates/mbe", version = "0.0.0" }
|
||||
parser = { path = "./crates/parser", version = "0.0.0" }
|
||||
paths = { path = "./crates/paths", version = "0.0.0" }
|
||||
proc-macro-api = { path = "./crates/proc-macro-api", version = "0.0.0" }
|
||||
proc-macro-srv = { path = "./crates/proc-macro-srv", version = "0.0.0" }
|
||||
proc-macro-srv-cli = { path = "./crates/proc-macro-srv-cli", version = "0.0.0" }
|
||||
profile = { path = "./crates/profile", version = "0.0.0" }
|
||||
project-model = { path = "./crates/project-model", version = "0.0.0" }
|
||||
salsa = { path = "./crates/salsa", version = "0.0.0" }
|
||||
span = { path = "./crates/span", version = "0.0.0" }
|
||||
stdx = { path = "./crates/stdx", version = "0.0.0" }
|
||||
syntax = { path = "./crates/syntax", version = "0.0.0" }
|
||||
text-edit = { path = "./crates/text-edit", version = "0.0.0" }
|
||||
toolchain = { path = "./crates/toolchain", version = "0.0.0" }
|
||||
tt = { path = "./crates/tt", version = "0.0.0" }
|
||||
vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
|
||||
vfs = { path = "./crates/vfs", version = "0.0.0" }
|
||||
|
||||
ra-ap-rustc_lexer = { version = "0.44.0", default-features = false }
|
||||
ra-ap-rustc_parse_format = { version = "0.44.0", default-features = false }
|
||||
ra-ap-rustc_index = { version = "0.44.0", default-features = false }
|
||||
ra-ap-rustc_abi = { version = "0.44.0", default-features = false }
|
||||
ra-ap-rustc_pattern_analysis = { version = "0.44.0", default-features = false }
|
||||
|
||||
# local crates that aren't published to crates.io. These should not have versions.
|
||||
sourcegen = { path = "./crates/sourcegen" }
|
||||
test-fixture = { path = "./crates/test-fixture" }
|
||||
test-utils = { path = "./crates/test-utils" }
|
||||
|
||||
# In-tree crates that are published separately and follow semver. See lib/README.md
|
||||
line-index = { version = "0.1.1" }
|
||||
la-arena = { version = "0.3.1" }
|
||||
lsp-server = { version = "0.7.6" }
|
||||
|
||||
# non-local crates
|
||||
anyhow = "1.0.75"
|
||||
arrayvec = "0.7.4"
|
||||
bitflags = "2.4.1"
|
||||
cargo_metadata = "0.18.1"
|
||||
camino = "1.1.6"
|
||||
chalk-solve = { version = "0.97.0", default-features = false }
|
||||
chalk-ir = "0.97.0"
|
||||
chalk-recursive = { version = "0.97.0", default-features = false }
|
||||
chalk-derive = "0.97.0"
|
||||
command-group = "2.0.1"
|
||||
crossbeam-channel = "0.5.8"
|
||||
dissimilar = "1.0.7"
|
||||
dot = "0.1.4"
|
||||
either = "1.9.0"
|
||||
expect-test = "1.4.0"
|
||||
hashbrown = { version = "0.14", features = [
|
||||
"inline-more",
|
||||
], default-features = false }
|
||||
indexmap = "2.1.0"
|
||||
itertools = "0.12.0"
|
||||
libc = "0.2.150"
|
||||
nohash-hasher = "0.2.0"
|
||||
oorandom = "11.1.3"
|
||||
object = { version = "0.33.0", default-features = false, features = [
|
||||
"std",
|
||||
"read_core",
|
||||
"elf",
|
||||
"macho",
|
||||
"pe",
|
||||
] }
|
||||
pulldown-cmark-to-cmark = "10.0.4"
|
||||
pulldown-cmark = { version = "0.9.0", default-features = false }
|
||||
rayon = "1.8.0"
|
||||
rustc-hash = "1.1.0"
|
||||
semver = "1.0.14"
|
||||
serde = { version = "1.0.192", features = ["derive"] }
|
||||
serde_json = "1.0.108"
|
||||
smallvec = { version = "1.10.0", features = [
|
||||
"const_new",
|
||||
"union",
|
||||
"const_generics",
|
||||
] }
|
||||
smol_str = "0.2.1"
|
||||
text-size = "1.1.1"
|
||||
tracing = "0.1.40"
|
||||
tracing-tree = "0.3.0"
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false, features = [
|
||||
"registry",
|
||||
"fmt",
|
||||
"tracing-log",
|
||||
] }
|
||||
triomphe = { version = "0.1.10", default-features = false, features = ["std"] }
|
||||
url = "2.3.1"
|
||||
xshell = "0.2.5"
|
||||
|
||||
|
||||
# We need to freeze the version of the crate, as the raw-api feature is considered unstable
|
||||
dashmap = { version = "=5.5.3", features = ["raw-api"] }
|
||||
|
||||
[workspace.lints.rust]
|
||||
rust_2018_idioms = "warn"
|
||||
unused_lifetimes = "warn"
|
||||
unreachable_pub = "warn"
|
||||
semicolon_in_expressions_from_macros = "warn"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# FIXME Remove the tidy test once the lint table is stable
|
||||
|
||||
## lint groups
|
||||
complexity = { level = "warn", priority = -1 }
|
||||
correctness = { level = "deny", priority = -1 }
|
||||
perf = { level = "deny", priority = -1 }
|
||||
restriction = { level = "allow", priority = -1 }
|
||||
style = { level = "warn", priority = -1 }
|
||||
suspicious = { level = "warn", priority = -1 }
|
||||
|
||||
## allow following lints
|
||||
# () makes a fine error in most cases
|
||||
result_unit_err = "allow"
|
||||
# We don't expose public APIs that matter like this
|
||||
len_without_is_empty = "allow"
|
||||
# We have macros that rely on this currently
|
||||
enum_variant_names = "allow"
|
||||
# Builder pattern disagrees
|
||||
new_ret_no_self = "allow"
|
||||
# Has a bunch of false positives
|
||||
useless_asref = "allow"
|
||||
# Has false positives
|
||||
assigning_clones = "allow"
|
||||
|
||||
## Following lints should be tackled at some point
|
||||
too_many_arguments = "allow"
|
||||
type_complexity = "allow"
|
||||
wrong_self_convention = "allow"
|
||||
|
||||
## warn at following lints
|
||||
# CI raises these to deny
|
||||
dbg_macro = "warn"
|
||||
todo = "warn"
|
||||
print_stdout = "warn"
|
||||
print_stderr = "warn"
|
||||
|
||||
rc_buffer = "warn"
|
||||
str_to_string = "warn"
|
||||
201
src/tools/rust-analyzer/LICENSE-APACHE
Normal file
201
src/tools/rust-analyzer/LICENSE-APACHE
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
23
src/tools/rust-analyzer/LICENSE-MIT
Normal file
23
src/tools/rust-analyzer/LICENSE-MIT
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
1
src/tools/rust-analyzer/PRIVACY.md
Normal file
1
src/tools/rust-analyzer/PRIVACY.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
See the [Privacy](https://rust-analyzer.github.io/manual.html#privacy) section of the user manual.
|
||||
50
src/tools/rust-analyzer/README.md
Normal file
50
src/tools/rust-analyzer/README.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<p align="center">
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/rust-analyzer/rust-analyzer/master/assets/logo-wide.svg"
|
||||
alt="rust-analyzer logo">
|
||||
</p>
|
||||
|
||||
rust-analyzer is a modular compiler frontend for the Rust language.
|
||||
It is a part of a larger rls-2.0 effort to create excellent IDE support for Rust.
|
||||
|
||||
## Quick Start
|
||||
|
||||
https://rust-analyzer.github.io/manual.html#installation
|
||||
|
||||
## Documentation
|
||||
|
||||
If you want to **contribute** to rust-analyzer check out the [CONTRIBUTING.md](./CONTRIBUTING.md) or
|
||||
if you are just curious about how things work under the hood, check the [./docs/dev](./docs/dev)
|
||||
folder.
|
||||
|
||||
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.
|
||||
|
||||
## Security and Privacy
|
||||
|
||||
See the corresponding sections of [the manual](https://rust-analyzer.github.io/manual.html#security).
|
||||
|
||||
## Communication
|
||||
|
||||
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 rust-analyzer working group on Zulip:
|
||||
|
||||
https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer
|
||||
|
||||
## Quick Links
|
||||
|
||||
* Website: https://rust-analyzer.github.io/
|
||||
* Metrics: https://rust-analyzer.github.io/metrics/
|
||||
* API docs: https://rust-lang.github.io/rust-analyzer/ide/
|
||||
* Changelog: https://rust-analyzer.github.io/thisweek
|
||||
|
||||
## License
|
||||
|
||||
rust-analyzer is primarily distributed under the terms of both the MIT
|
||||
license and the Apache License (Version 2.0).
|
||||
|
||||
See LICENSE-APACHE and LICENSE-MIT for details.
|
||||
88
src/tools/rust-analyzer/assets/logo-square.svg
Normal file
88
src/tools/rust-analyzer/assets/logo-square.svg
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24mm"
|
||||
height="24mm"
|
||||
viewBox="0 0 23.999999 24.000001"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="ra.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="-31.307418"
|
||||
inkscape:cy="43.570897"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1006"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-48.088531,-60.285631)">
|
||||
<g
|
||||
aria-label="r."
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.5px;paint-order:stroke"
|
||||
id="text3715">
|
||||
<path
|
||||
d="m 55.00077,63.442544 c -0.588718,-0.01704 -1.180779,0.251078 -1.524352,0.735633 -0.163942,0.198364 -0.296316,0.49938 -0.394953,0.683311 -0.101099,-0.416482 -0.202199,-0.832964 -0.303298,-1.249445 -0.671966,0 -1.343932,0 -2.015897,0 0,0.370348 0,0.740695 0,1.111043 0.246841,0 0.493682,0 0.740523,0 0,1.128958 0,2.257916 0,3.386874 -0.246841,0 -0.493682,0 -0.740523,0 0,0.373792 0,0.747585 0,1.121378 1.174777,0 2.349555,0 3.524332,0 0,-0.373793 0,-0.747586 0,-1.121378 -0.37052,0 -0.74104,0 -1.11156,0 0,-0.53623 0,-1.072458 0,-1.608688 0.190282,-0.586609 0.512347,-1.195617 1.085749,-1.482555 0.177384,-0.100666 0.369603,-0.139942 0.305897,0.108125 0,0.278138 0,0.556274 0,0.834412 0.349333,0 0.698666,0 1.047998,0 0.104042,-0.783071 0.208084,-1.566141 0.312126,-2.349211 -0.293304,-0.117433 -0.610556,-0.17161 -0.926042,-0.169499 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold'"
|
||||
id="path817" />
|
||||
<circle
|
||||
cx="59.49345"
|
||||
cy="68.231422"
|
||||
r="1.1800417"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold'"
|
||||
id="path819" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#30363b;fill-opacity:1;stroke:#20262b;stroke-width:0.39559129;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect3721"
|
||||
width="10.604409"
|
||||
height="12.604408"
|
||||
x="61.286327"
|
||||
y="71.483421" />
|
||||
<g
|
||||
aria-label="a"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
id="text3719">
|
||||
<path
|
||||
d="m 69.065615,79.143583 q 0,0.3175 0.08467,0.460375 0.08996,0.142875 0.28575,0.216958 l -0.343958,1.100667 q -0.497417,-0.04762 -0.841375,-0.216959 -0.338667,-0.174625 -0.534459,-0.523875 -0.322791,0.386292 -0.825499,0.576792 -0.502709,0.185208 -1.026584,0.185208 -0.867833,0 -1.386416,-0.492125 -0.513292,-0.497416 -0.513292,-1.275291 0,-0.915459 0.714375,-1.412875 0.719667,-0.497417 2.021417,-0.497417 h 0.756708 v -0.211667 q 0,-0.439208 -0.28575,-0.650875 -0.280458,-0.211666 -0.8255,-0.211666 -0.269875,0 -0.693208,0.07937 -0.423334,0.07408 -0.846667,0.216958 l -0.386292,-1.11125 q 0.545042,-0.206375 1.132417,-0.312208 0.592667,-0.105834 1.058333,-0.105834 1.254125,0 1.852083,0.513292 0.60325,0.508 0.60325,1.471083 z m -2.624666,0.60325 q 0.269875,0 0.566208,-0.15875 0.296334,-0.164042 0.449792,-0.460375 v -0.910167 h -0.41275 q -0.6985,0 -1.026583,0.216958 -0.328084,0.211667 -0.328084,0.624417 0,0.322792 0.195792,0.508 0.201083,0.179917 0.555625,0.179917 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;stroke-width:0.26458332"
|
||||
id="path822" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
142
src/tools/rust-analyzer/assets/logo-wide.svg
Normal file
142
src/tools/rust-analyzer/assets/logo-wide.svg
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="85.797134mm"
|
||||
height="24.747536mm"
|
||||
viewBox="0 0 85.797134 24.747536"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="rust analyzer.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<style>
|
||||
#text3715 {
|
||||
fill: #000000;
|
||||
stroke: #ffffff;
|
||||
stroke-width: 0.5;
|
||||
paint-order: stroke;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#text3715 {
|
||||
fill: #ffffff;
|
||||
stroke: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="64.938033"
|
||||
inkscape:cy="-10.231391"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1006"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-29.534624,-59.398722)">
|
||||
<g
|
||||
aria-label="rust."
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px"
|
||||
id="text3715">
|
||||
<path
|
||||
d="m 35.95077,62.913236 c -0.588719,-0.01704 -1.180779,0.251078 -1.524352,0.735632 -0.163943,0.198364 -0.296317,0.499384 -0.394954,0.683311 -0.101099,-0.416482 -0.202198,-0.832963 -0.303298,-1.249444 -0.671965,0 -1.343931,0 -2.015897,0 0,0.370348 0,0.740695 0,1.111043 0.246841,0 0.493682,0 0.740523,0 0,1.128958 0,2.257916 0,3.386873 -0.246841,0 -0.493682,0 -0.740523,0 0,0.373965 0,0.747931 0,1.121896 1.174777,0 2.349555,0 3.524332,0 0,-0.373965 0,-0.747931 0,-1.121896 -0.37052,0 -0.74104,0 -1.11156,0 0,-0.536229 0,-1.072458 0,-1.608687 0.190283,-0.586609 0.512347,-1.195617 1.085749,-1.482555 0.177393,-0.100673 0.369604,-0.139934 0.305898,0.108135 0,0.278134 0,0.556268 0,0.834401 0.349332,0 0.698665,0 1.047998,0 0.104041,-0.78307 0.208084,-1.56614 0.312125,-2.34921 -0.293304,-0.117432 -0.610556,-0.17161 -0.926041,-0.169499 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';"
|
||||
id="path817" />
|
||||
<path
|
||||
d="m 39.681433,63.082627 v 3.847042 q 0,0.407458 0.148167,0.560917 0.153458,0.153458 0.423333,0.153458 0.259292,0 0.518583,-0.164042 0.259292,-0.164041 0.433917,-0.4445 V 63.082627 H 42.8776 v 5.61975 h -1.4605 l -0.07408,-0.656166 q -0.28575,0.41275 -0.751416,0.624416 -0.465667,0.211667 -0.973667,0.211667 -0.814917,0 -1.211792,-0.470958 -0.396875,-0.47625 -0.396875,-1.275292 v -4.053417 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';"
|
||||
id="path819" />
|
||||
<path
|
||||
d="m 46.6135,67.686377 q 0.418042,0 0.672042,-0.132291 0.259291,-0.132292 0.259291,-0.396875 0,-0.179917 -0.100541,-0.301625 -0.100542,-0.121709 -0.386292,-0.232834 -0.28575,-0.111125 -0.846667,-0.264583 -0.513291,-0.137583 -0.910166,-0.34925 -0.391584,-0.211667 -0.613834,-0.545042 -0.216958,-0.333375 -0.216958,-0.830791 0,-0.502709 0.280458,-0.894292 0.280459,-0.391583 0.8255,-0.613833 0.545042,-0.227542 1.3335,-0.227542 0.751417,0 1.307042,0.195792 0.560917,0.1905 0.968375,0.486833 l -0.66675,0.98425 q -0.34925,-0.216958 -0.751417,-0.343958 -0.402166,-0.132292 -0.809625,-0.132292 -0.407458,0 -0.608541,0.111125 -0.195792,0.105833 -0.195792,0.322792 0,0.142875 0.100542,0.248708 0.105833,0.100542 0.391583,0.211667 0.28575,0.105833 0.836083,0.264583 0.545042,0.153458 0.947209,0.354542 0.407458,0.201083 0.629708,0.53975 0.22225,0.333375 0.22225,0.883708 0,0.613833 -0.365125,1.031875 -0.365125,0.41275 -0.968375,0.619125 -0.60325,0.206375 -1.322917,0.206375 -0.814916,0 -1.439333,-0.232833 -0.624417,-0.232834 -1.063625,-0.613834 L 44.9625,67.093711 q 0.328083,0.254 0.740833,0.423333 0.418042,0.169333 0.910167,0.169333 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';"
|
||||
id="path821" />
|
||||
<path
|
||||
d="m 55.768067,68.374294 q -0.328083,0.211667 -0.79375,0.359833 -0.465666,0.148167 -1.047749,0.148167 -1.100667,0 -1.635125,-0.560917 -0.534459,-0.566208 -0.534459,-1.534583 v -2.550583 h -1.180041 v -1.153584 h 1.180041 v -1.217083 l 1.672167,-0.201083 v 1.418166 h 1.80975 l -0.164042,1.153584 h -1.645708 v 2.550583 q 0,0.418042 0.1905,0.597958 0.1905,0.179917 0.608541,0.179917 0.296334,0 0.53975,-0.06879 0.248709,-0.07408 0.4445,-0.185208 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';"
|
||||
id="path823" />
|
||||
<circle
|
||||
cx="59.493385"
|
||||
cy="67.702255"
|
||||
r="1.1799999"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';"
|
||||
id="path825" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#30363b;fill-opacity:1;stroke:#20262b;stroke-width:0.39205828;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect3721"
|
||||
width="54.407944"
|
||||
height="12.607942"
|
||||
x="60.730652"
|
||||
y="71.342285" />
|
||||
<g
|
||||
aria-label="analyzer"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
id="text3719">
|
||||
<path
|
||||
d="m 68.007281,79.143583 q 0,0.3175 0.08467,0.460375 0.08996,0.142875 0.28575,0.216958 l -0.343959,1.100667 q -0.497416,-0.04762 -0.841375,-0.216959 -0.338666,-0.174625 -0.534458,-0.523875 -0.322792,0.386292 -0.8255,0.576792 -0.502708,0.185208 -1.026583,0.185208 -0.867833,0 -1.386417,-0.492125 -0.513291,-0.497416 -0.513291,-1.275291 0,-0.915459 0.714375,-1.412875 0.719666,-0.497417 2.021416,-0.497417 h 0.756708 v -0.211667 q 0,-0.439208 -0.28575,-0.650875 -0.280458,-0.211666 -0.825499,-0.211666 -0.269875,0 -0.693209,0.07937 -0.423333,0.07408 -0.846666,0.216958 l -0.386292,-1.11125 q 0.545042,-0.206375 1.132417,-0.312208 0.592666,-0.105834 1.058333,-0.105834 1.254125,0 1.852083,0.513292 0.60325,0.508 0.60325,1.471083 z m -2.624666,0.60325 q 0.269875,0 0.566208,-0.15875 0.296333,-0.164042 0.449791,-0.460375 v -0.910167 h -0.412749 q -0.6985,0 -1.026584,0.216958 -0.328083,0.211667 -0.328083,0.624417 0,0.322792 0.195792,0.508 0.201083,0.179917 0.555625,0.179917 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;stroke-width:0.26458332"
|
||||
id="path828" />
|
||||
<path
|
||||
d="m 69.626515,80.762833 v -5.61975 h 1.4605 l 0.116417,0.650875 q 0.375708,-0.423334 0.79375,-0.629709 0.418041,-0.206375 0.9525,-0.206375 0.719666,0 1.132416,0.439209 0.41275,0.439208 0.41275,1.23825 v 4.1275 h -1.672166 v -3.645959 q 0,-0.343958 -0.04762,-0.545041 -0.04763,-0.206375 -0.169334,-0.291042 -0.116416,-0.08996 -0.322791,-0.08996 -0.174625,0 -0.343959,0.07937 -0.164041,0.07408 -0.322791,0.216958 -0.15875,0.142875 -0.3175,0.343958 v 3.931709 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;stroke-width:0.26458332"
|
||||
id="path830" />
|
||||
<path
|
||||
d="m 80.707248,79.143583 q 0,0.3175 0.08467,0.460375 0.08996,0.142875 0.28575,0.216958 l -0.343958,1.100667 q -0.497417,-0.04762 -0.841375,-0.216959 -0.338667,-0.174625 -0.534458,-0.523875 -0.322792,0.386292 -0.8255,0.576792 -0.502709,0.185208 -1.026584,0.185208 -0.867833,0 -1.386416,-0.492125 -0.513292,-0.497416 -0.513292,-1.275291 0,-0.915459 0.714375,-1.412875 0.719667,-0.497417 2.021417,-0.497417 h 0.756708 v -0.211667 q 0,-0.439208 -0.28575,-0.650875 -0.280458,-0.211666 -0.8255,-0.211666 -0.269875,0 -0.693208,0.07937 -0.423334,0.07408 -0.846667,0.216958 l -0.386292,-1.11125 q 0.545042,-0.206375 1.132417,-0.312208 0.592667,-0.105834 1.058333,-0.105834 1.254125,0 1.852084,0.513292 0.603249,0.508 0.603249,1.471083 z m -2.624666,0.60325 q 0.269875,0 0.566208,-0.15875 0.296334,-0.164042 0.449792,-0.460375 v -0.910167 h -0.41275 q -0.6985,0 -1.026583,0.216958 -0.328084,0.211667 -0.328084,0.624417 0,0.322792 0.195792,0.508 0.201083,0.179917 0.555625,0.179917 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;stroke-width:0.26458332"
|
||||
id="path832" />
|
||||
<path
|
||||
d="m 85.258066,72.91 v 6.085416 q 0,0.338667 0.1905,0.486833 0.195792,0.142875 0.534458,0.142875 0.216959,0 0.418042,-0.04762 0.201083,-0.05292 0.375708,-0.121708 l 0.402167,1.116542 q -0.28575,0.148166 -0.687917,0.259291 -0.402166,0.111125 -0.936625,0.111125 -1.016,0 -1.49225,-0.582083 -0.47625,-0.587375 -0.47625,-1.571625 V 74.053 H 81.929608 V 72.91 Z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;stroke-width:0.26458332"
|
||||
id="path834" />
|
||||
<path
|
||||
d="m 93.989299,75.143083 -1.87325,5.61975 q -0.248708,0.746125 -0.661458,1.248833 -0.407458,0.508 -1.021292,0.783167 -0.608541,0.275166 -1.465791,0.322791 l -0.1905,-1.180041 q 0.555625,-0.06879 0.894291,-0.206375 0.343959,-0.137584 0.550334,-0.375709 0.211666,-0.232833 0.365125,-0.592666 h -0.5715 l -1.783292,-5.61975 h 1.767417 l 1.090083,4.550833 1.185333,-4.550833 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;stroke-width:0.26458332"
|
||||
id="path836" />
|
||||
<path
|
||||
d="m 97.021408,79.498124 h 2.815166 l -0.15875,1.264709 h -4.630208 v -1.180042 l 2.788708,-3.180292 h -2.555875 v -1.259416 h 4.503208 v 1.17475 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;stroke-width:0.26458332"
|
||||
id="path838" />
|
||||
<path
|
||||
d="m 102.82635,78.439791 q 0.0582,0.460375 0.23813,0.746125 0.1852,0.280458 0.47095,0.41275 0.28575,0.127 0.6403,0.127 0.38629,0 0.74612,-0.127 0.35983,-0.127 0.69321,-0.338667 l 0.67204,0.910167 q -0.39687,0.338667 -0.96308,0.555625 -0.56092,0.216958 -1.28059,0.216958 -0.96308,0 -1.61395,-0.381 -0.65088,-0.386291 -0.97896,-1.058333 -0.32809,-0.672042 -0.32809,-1.545167 0,-0.830791 0.3175,-1.508125 0.3175,-0.682625 0.92605,-1.084791 0.61383,-0.407459 1.49754,-0.407459 0.80433,0 1.39171,0.343959 0.59266,0.343958 0.91545,0.989541 0.32809,0.645584 0.32809,1.550459 0,0.142875 -0.0106,0.306916 -0.005,0.164042 -0.0212,0.291042 z m 1.03717,-2.360083 q -0.44979,0 -0.72496,0.322791 -0.27517,0.322792 -0.33338,1.031875 h 2.06375 q -0.005,-0.613833 -0.23812,-0.98425 -0.23283,-0.370416 -0.76729,-0.370416 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;stroke-width:0.26458332"
|
||||
id="path840" />
|
||||
<path
|
||||
d="m 107.77933,80.762833 v -1.121834 h 0.74084 v -3.386666 h -0.74084 v -1.11125 h 2.01613 l 0.30692,1.264708 q 0.30162,-0.724958 0.76729,-1.0795 0.47096,-0.354542 1.14829,-0.354542 0.28575,0 0.508,0.04762 0.22225,0.04233 0.41804,0.121709 l -0.508,1.381125 q -0.15346,-0.04233 -0.30692,-0.0635 -0.15345,-0.02117 -0.33866,-0.02117 -0.55034,0 -0.96838,0.449792 -0.41275,0.449791 -0.62971,1.143 v 1.608666 h 1.11125 v 1.121834 z m 3.80471,-3.27025 v -1.640417 l 0.24871,-0.709083 h 1.11125 l -0.31221,2.3495 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;stroke-width:0.26458332"
|
||||
id="path842" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
8562
src/tools/rust-analyzer/bench_data/glorious_old_parser
Normal file
8562
src/tools/rust-analyzer/bench_data/glorious_old_parser
Normal file
File diff suppressed because it is too large
Load diff
560
src/tools/rust-analyzer/bench_data/numerous_macro_rules
Normal file
560
src/tools/rust-analyzer/bench_data/numerous_macro_rules
Normal file
File diff suppressed because one or more lines are too long
5
src/tools/rust-analyzer/clippy.toml
Normal file
5
src/tools/rust-analyzer/clippy.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
disallowed-types = [
|
||||
{ path = "std::collections::HashMap", reason = "use FxHashMap" },
|
||||
{ path = "std::collections::HashSet", reason = "use FxHashSet" },
|
||||
{ path = "std::collections::hash_map::RandomState", reason = "use BuildHasherDefault<FxHasher>"}
|
||||
]
|
||||
32
src/tools/rust-analyzer/crates/base-db/Cargo.toml
Normal file
32
src/tools/rust-analyzer/crates/base-db/Cargo.toml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
name = "base-db"
|
||||
version = "0.0.0"
|
||||
description = "TBD"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
lz4_flex = { version = "0.11", default-features = false }
|
||||
|
||||
la-arena.workspace = true
|
||||
salsa.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
triomphe.workspace = true
|
||||
semver.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
# local deps
|
||||
cfg.workspace = true
|
||||
stdx.workspace = true
|
||||
syntax.workspace = true
|
||||
vfs.workspace = true
|
||||
span.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
86
src/tools/rust-analyzer/crates/base-db/src/change.rs
Normal file
86
src/tools/rust-analyzer/crates/base-db/src/change.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
//! Defines a unit of change that can applied to the database to get the next
|
||||
//! state. Changes are transactional.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use salsa::Durability;
|
||||
use triomphe::Arc;
|
||||
use vfs::FileId;
|
||||
|
||||
use crate::{CrateGraph, SourceDatabaseExt, SourceDatabaseExt2, SourceRoot, SourceRootId};
|
||||
|
||||
/// Encapsulate a bunch of raw `.set` calls on the database.
|
||||
#[derive(Default)]
|
||||
pub struct FileChange {
|
||||
pub roots: Option<Vec<SourceRoot>>,
|
||||
pub files_changed: Vec<(FileId, Option<String>)>,
|
||||
pub crate_graph: Option<CrateGraph>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for FileChange {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut d = fmt.debug_struct("Change");
|
||||
if let Some(roots) = &self.roots {
|
||||
d.field("roots", roots);
|
||||
}
|
||||
if !self.files_changed.is_empty() {
|
||||
d.field("files_changed", &self.files_changed.len());
|
||||
}
|
||||
if self.crate_graph.is_some() {
|
||||
d.field("crate_graph", &self.crate_graph);
|
||||
}
|
||||
d.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileChange {
|
||||
pub fn new() -> Self {
|
||||
FileChange::default()
|
||||
}
|
||||
|
||||
pub fn set_roots(&mut self, roots: Vec<SourceRoot>) {
|
||||
self.roots = Some(roots);
|
||||
}
|
||||
|
||||
pub fn change_file(&mut self, file_id: FileId, new_text: Option<String>) {
|
||||
self.files_changed.push((file_id, new_text))
|
||||
}
|
||||
|
||||
pub fn set_crate_graph(&mut self, graph: CrateGraph) {
|
||||
self.crate_graph = Some(graph);
|
||||
}
|
||||
|
||||
pub fn apply(self, db: &mut dyn SourceDatabaseExt) {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "RootDatabase::apply_change").entered();
|
||||
if let Some(roots) = self.roots {
|
||||
for (idx, root) in roots.into_iter().enumerate() {
|
||||
let root_id = SourceRootId(idx as u32);
|
||||
let durability = durability(&root);
|
||||
for file_id in root.iter() {
|
||||
db.set_file_source_root_with_durability(file_id, root_id, durability);
|
||||
}
|
||||
db.set_source_root_with_durability(root_id, Arc::new(root), durability);
|
||||
}
|
||||
}
|
||||
|
||||
for (file_id, text) in self.files_changed {
|
||||
let source_root_id = db.file_source_root(file_id);
|
||||
let source_root = db.source_root(source_root_id);
|
||||
let durability = durability(&source_root);
|
||||
// XXX: can't actually remove the file, just reset the text
|
||||
let text = text.unwrap_or_default();
|
||||
db.set_file_text_with_durability(file_id, &text, durability)
|
||||
}
|
||||
if let Some(crate_graph) = self.crate_graph {
|
||||
db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn durability(source_root: &SourceRoot) -> Durability {
|
||||
if source_root.is_library {
|
||||
Durability::HIGH
|
||||
} else {
|
||||
Durability::LOW
|
||||
}
|
||||
}
|
||||
892
src/tools/rust-analyzer/crates/base-db/src/input.rs
Normal file
892
src/tools/rust-analyzer/crates/base-db/src/input.rs
Normal file
|
|
@ -0,0 +1,892 @@
|
|||
//! This module specifies the input to rust-analyzer. In some sense, this is
|
||||
//! **the** most important module, because all other fancy stuff is strictly
|
||||
//! derived from this input.
|
||||
//!
|
||||
//! Note that neither this module, nor any other part of the analyzer's core do
|
||||
//! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how
|
||||
//! actual IO is done and lowered to input.
|
||||
|
||||
use std::{fmt, mem, ops};
|
||||
|
||||
use cfg::CfgOptions;
|
||||
use la_arena::{Arena, Idx, RawIdx};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use span::Edition;
|
||||
use syntax::SmolStr;
|
||||
use triomphe::Arc;
|
||||
use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath};
|
||||
|
||||
// Map from crate id to the name of the crate and path of the proc-macro. If the value is `None`,
|
||||
// then the crate for the proc-macro hasn't been build yet as the build data is missing.
|
||||
pub type ProcMacroPaths = FxHashMap<CrateId, Result<(Option<String>, AbsPathBuf), String>>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct SourceRootId(pub u32);
|
||||
|
||||
/// Files are grouped into source roots. A source root is a directory on the
|
||||
/// file systems which is watched for changes. Typically it corresponds to a
|
||||
/// Rust crate. Source roots *might* be nested: in this case, a file belongs to
|
||||
/// the nearest enclosing source root. Paths to files are always relative to a
|
||||
/// source root, and the analyzer does not know the root path of the source root at
|
||||
/// all. So, a file from one source root can't refer to a file in another source
|
||||
/// root by path.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SourceRoot {
|
||||
/// Sysroot or crates.io library.
|
||||
///
|
||||
/// Libraries are considered mostly immutable, this assumption is used to
|
||||
/// optimize salsa's query structure
|
||||
pub is_library: bool,
|
||||
file_set: FileSet,
|
||||
}
|
||||
|
||||
impl SourceRoot {
|
||||
pub fn new_local(file_set: FileSet) -> SourceRoot {
|
||||
SourceRoot { is_library: false, file_set }
|
||||
}
|
||||
|
||||
pub fn new_library(file_set: FileSet) -> SourceRoot {
|
||||
SourceRoot { is_library: true, file_set }
|
||||
}
|
||||
|
||||
pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> {
|
||||
self.file_set.path_for_file(file)
|
||||
}
|
||||
|
||||
pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> {
|
||||
self.file_set.file_for_path(path)
|
||||
}
|
||||
|
||||
pub fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
|
||||
self.file_set.resolve_path(path)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
|
||||
self.file_set.iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// `CrateGraph` is a bit of information which turns a set of text files into a
|
||||
/// number of Rust crates.
|
||||
///
|
||||
/// Each crate is defined by the `FileId` of its root module, the set of enabled
|
||||
/// `cfg` flags and the set of dependencies.
|
||||
///
|
||||
/// Note that, due to cfg's, there might be several crates for a single `FileId`!
|
||||
///
|
||||
/// For the purposes of analysis, a crate does not have a name. Instead, names
|
||||
/// are specified on dependency edges. That is, a crate might be known under
|
||||
/// different names in different dependent crates.
|
||||
///
|
||||
/// Note that `CrateGraph` is build-system agnostic: it's a concept of the Rust
|
||||
/// language proper, not a concept of the build system. In practice, we get
|
||||
/// `CrateGraph` by lowering `cargo metadata` output.
|
||||
///
|
||||
/// `CrateGraph` is `!Serialize` by design, see
|
||||
/// <https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/architecture.md#serialization>
|
||||
#[derive(Clone, Default)]
|
||||
pub struct CrateGraph {
|
||||
arena: Arena<CrateData>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for CrateGraph {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_map()
|
||||
.entries(self.arena.iter().map(|(id, data)| (u32::from(id.into_raw()), data)))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub type CrateId = Idx<CrateData>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct CrateName(SmolStr);
|
||||
|
||||
impl CrateName {
|
||||
/// Creates a crate name, checking for dashes in the string provided.
|
||||
/// Dashes are not allowed in the crate names,
|
||||
/// hence the input string is returned as `Err` for those cases.
|
||||
pub fn new(name: &str) -> Result<CrateName, &str> {
|
||||
if name.contains('-') {
|
||||
Err(name)
|
||||
} else {
|
||||
Ok(Self(SmolStr::new(name)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a crate name, unconditionally replacing the dashes with underscores.
|
||||
pub fn normalize_dashes(name: &str) -> CrateName {
|
||||
Self(SmolStr::new(name.replace('-', "_")))
|
||||
}
|
||||
|
||||
pub fn as_smol_str(&self) -> &SmolStr {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CrateName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for CrateName {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Origin of the crates.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum CrateOrigin {
|
||||
/// Crates that are from the rustc workspace.
|
||||
Rustc { name: String },
|
||||
/// Crates that are workspace members.
|
||||
Local { repo: Option<String>, name: Option<String> },
|
||||
/// Crates that are non member libraries.
|
||||
Library { repo: Option<String>, name: String },
|
||||
/// Crates that are provided by the language, like std, core, proc-macro, ...
|
||||
Lang(LangCrateOrigin),
|
||||
}
|
||||
|
||||
impl CrateOrigin {
|
||||
pub fn is_local(&self) -> bool {
|
||||
matches!(self, CrateOrigin::Local { .. })
|
||||
}
|
||||
|
||||
pub fn is_lib(&self) -> bool {
|
||||
matches!(self, CrateOrigin::Library { .. })
|
||||
}
|
||||
|
||||
pub fn is_lang(&self) -> bool {
|
||||
matches!(self, CrateOrigin::Lang { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum LangCrateOrigin {
|
||||
Alloc,
|
||||
Core,
|
||||
ProcMacro,
|
||||
Std,
|
||||
Test,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<&str> for LangCrateOrigin {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"alloc" => LangCrateOrigin::Alloc,
|
||||
"core" => LangCrateOrigin::Core,
|
||||
"proc-macro" | "proc_macro" => LangCrateOrigin::ProcMacro,
|
||||
"std" => LangCrateOrigin::Std,
|
||||
"test" => LangCrateOrigin::Test,
|
||||
_ => LangCrateOrigin::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LangCrateOrigin {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let text = match self {
|
||||
LangCrateOrigin::Alloc => "alloc",
|
||||
LangCrateOrigin::Core => "core",
|
||||
LangCrateOrigin::ProcMacro => "proc_macro",
|
||||
LangCrateOrigin::Std => "std",
|
||||
LangCrateOrigin::Test => "test",
|
||||
LangCrateOrigin::Other => "other",
|
||||
};
|
||||
f.write_str(text)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct CrateDisplayName {
|
||||
// The name we use to display various paths (with `_`).
|
||||
crate_name: CrateName,
|
||||
// The name as specified in Cargo.toml (with `-`).
|
||||
canonical_name: String,
|
||||
}
|
||||
|
||||
impl CrateDisplayName {
|
||||
pub fn canonical_name(&self) -> &str {
|
||||
&self.canonical_name
|
||||
}
|
||||
pub fn crate_name(&self) -> &CrateName {
|
||||
&self.crate_name
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CrateName> for CrateDisplayName {
|
||||
fn from(crate_name: CrateName) -> CrateDisplayName {
|
||||
let canonical_name = crate_name.to_string();
|
||||
CrateDisplayName { crate_name, canonical_name }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CrateDisplayName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.crate_name.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for CrateDisplayName {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &str {
|
||||
&self.crate_name
|
||||
}
|
||||
}
|
||||
|
||||
impl CrateDisplayName {
|
||||
pub fn from_canonical_name(canonical_name: String) -> CrateDisplayName {
|
||||
let crate_name = CrateName::normalize_dashes(&canonical_name);
|
||||
CrateDisplayName { crate_name, canonical_name }
|
||||
}
|
||||
}
|
||||
|
||||
pub type TargetLayoutLoadResult = Result<Arc<str>, Arc<str>>;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum ReleaseChannel {
|
||||
Stable,
|
||||
Beta,
|
||||
Nightly,
|
||||
}
|
||||
|
||||
impl ReleaseChannel {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
ReleaseChannel::Stable => "stable",
|
||||
ReleaseChannel::Beta => "beta",
|
||||
ReleaseChannel::Nightly => "nightly",
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(str: &str) -> Option<Self> {
|
||||
Some(match str {
|
||||
"" | "stable" => ReleaseChannel::Stable,
|
||||
"nightly" => ReleaseChannel::Nightly,
|
||||
_ if str.starts_with("beta") => ReleaseChannel::Beta,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CrateData {
|
||||
pub root_file_id: FileId,
|
||||
pub edition: Edition,
|
||||
pub version: Option<String>,
|
||||
/// A name used in the package's project declaration: for Cargo projects,
|
||||
/// its `[package].name` can be different for other project types or even
|
||||
/// absent (a dummy crate for the code snippet, for example).
|
||||
///
|
||||
/// For purposes of analysis, crates are anonymous (only names in
|
||||
/// `Dependency` matters), this name should only be used for UI.
|
||||
pub display_name: Option<CrateDisplayName>,
|
||||
pub cfg_options: Arc<CfgOptions>,
|
||||
/// The cfg options that could be used by the crate
|
||||
pub potential_cfg_options: Option<Arc<CfgOptions>>,
|
||||
pub env: Env,
|
||||
pub dependencies: Vec<Dependency>,
|
||||
pub origin: CrateOrigin,
|
||||
pub is_proc_macro: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Eq)]
|
||||
pub struct Env {
|
||||
entries: FxHashMap<String, String>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Env {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
struct EnvDebug<'s>(Vec<(&'s String, &'s String)>);
|
||||
|
||||
impl fmt::Debug for EnvDebug<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_map().entries(self.0.iter().copied()).finish()
|
||||
}
|
||||
}
|
||||
f.debug_struct("Env")
|
||||
.field("entries", &{
|
||||
let mut entries: Vec<_> = self.entries.iter().collect();
|
||||
entries.sort();
|
||||
EnvDebug(entries)
|
||||
})
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Dependency {
|
||||
pub crate_id: CrateId,
|
||||
pub name: CrateName,
|
||||
prelude: bool,
|
||||
}
|
||||
|
||||
impl Dependency {
|
||||
pub fn new(name: CrateName, crate_id: CrateId) -> Self {
|
||||
Self { name, crate_id, prelude: true }
|
||||
}
|
||||
|
||||
pub fn with_prelude(name: CrateName, crate_id: CrateId, prelude: bool) -> Self {
|
||||
Self { name, crate_id, prelude }
|
||||
}
|
||||
|
||||
/// Whether this dependency is to be added to the depending crate's extern prelude.
|
||||
pub fn is_prelude(&self) -> bool {
|
||||
self.prelude
|
||||
}
|
||||
}
|
||||
|
||||
impl CrateGraph {
|
||||
pub fn add_crate_root(
|
||||
&mut self,
|
||||
root_file_id: FileId,
|
||||
edition: Edition,
|
||||
display_name: Option<CrateDisplayName>,
|
||||
version: Option<String>,
|
||||
cfg_options: Arc<CfgOptions>,
|
||||
potential_cfg_options: Option<Arc<CfgOptions>>,
|
||||
mut env: Env,
|
||||
is_proc_macro: bool,
|
||||
origin: CrateOrigin,
|
||||
) -> CrateId {
|
||||
env.entries.shrink_to_fit();
|
||||
let data = CrateData {
|
||||
root_file_id,
|
||||
edition,
|
||||
version,
|
||||
display_name,
|
||||
cfg_options,
|
||||
potential_cfg_options,
|
||||
env,
|
||||
dependencies: Vec::new(),
|
||||
origin,
|
||||
is_proc_macro,
|
||||
};
|
||||
self.arena.alloc(data)
|
||||
}
|
||||
|
||||
/// Remove the crate from crate graph. If any crates depend on this crate, the dependency would be replaced
|
||||
/// with the second input.
|
||||
pub fn remove_and_replace(
|
||||
&mut self,
|
||||
id: CrateId,
|
||||
replace_with: CrateId,
|
||||
) -> Result<(), CyclicDependenciesError> {
|
||||
for (x, data) in self.arena.iter() {
|
||||
if x == id {
|
||||
continue;
|
||||
}
|
||||
for edge in &data.dependencies {
|
||||
if edge.crate_id == id {
|
||||
self.check_cycle_after_dependency(edge.crate_id, replace_with)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if everything was ok, start to replace
|
||||
for (x, data) in self.arena.iter_mut() {
|
||||
if x == id {
|
||||
continue;
|
||||
}
|
||||
for edge in &mut data.dependencies {
|
||||
if edge.crate_id == id {
|
||||
edge.crate_id = replace_with;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_dep(
|
||||
&mut self,
|
||||
from: CrateId,
|
||||
dep: Dependency,
|
||||
) -> Result<(), CyclicDependenciesError> {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "add_dep").entered();
|
||||
|
||||
self.check_cycle_after_dependency(from, dep.crate_id)?;
|
||||
|
||||
self.arena[from].add_dep(dep);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if adding a dep from `from` to `to` creates a cycle. To figure
|
||||
/// that out, look for a path in the *opposite* direction, from `to` to
|
||||
/// `from`.
|
||||
fn check_cycle_after_dependency(
|
||||
&self,
|
||||
from: CrateId,
|
||||
to: CrateId,
|
||||
) -> Result<(), CyclicDependenciesError> {
|
||||
if let Some(path) = self.find_path(&mut FxHashSet::default(), to, from) {
|
||||
let path = path.into_iter().map(|it| (it, self[it].display_name.clone())).collect();
|
||||
let err = CyclicDependenciesError { path };
|
||||
assert!(err.from().0 == from && err.to().0 == to);
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.arena.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.arena.len()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = CrateId> + '_ {
|
||||
self.arena.iter().map(|(idx, _)| idx)
|
||||
}
|
||||
|
||||
// FIXME: used for fixing up the toolchain sysroot, should be removed and done differently
|
||||
#[doc(hidden)]
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (CrateId, &mut CrateData)> + '_ {
|
||||
self.arena.iter_mut()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all transitive dependencies of the given crate,
|
||||
/// including the crate itself.
|
||||
pub fn transitive_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> {
|
||||
let mut worklist = vec![of];
|
||||
let mut deps = FxHashSet::default();
|
||||
|
||||
while let Some(krate) = worklist.pop() {
|
||||
if !deps.insert(krate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
worklist.extend(self[krate].dependencies.iter().map(|dep| dep.crate_id));
|
||||
}
|
||||
|
||||
deps.into_iter()
|
||||
}
|
||||
|
||||
/// Returns all transitive reverse dependencies of the given crate,
|
||||
/// including the crate itself.
|
||||
pub fn transitive_rev_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> {
|
||||
let mut worklist = vec![of];
|
||||
let mut rev_deps = FxHashSet::default();
|
||||
rev_deps.insert(of);
|
||||
|
||||
let mut inverted_graph = FxHashMap::<_, Vec<_>>::default();
|
||||
self.arena.iter().for_each(|(krate, data)| {
|
||||
data.dependencies
|
||||
.iter()
|
||||
.for_each(|dep| inverted_graph.entry(dep.crate_id).or_default().push(krate))
|
||||
});
|
||||
|
||||
while let Some(krate) = worklist.pop() {
|
||||
if let Some(krate_rev_deps) = inverted_graph.get(&krate) {
|
||||
krate_rev_deps
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&rev_dep| rev_deps.insert(rev_dep))
|
||||
.for_each(|rev_dep| worklist.push(rev_dep));
|
||||
}
|
||||
}
|
||||
|
||||
rev_deps.into_iter()
|
||||
}
|
||||
|
||||
/// Returns all crates in the graph, sorted in topological order (ie. dependencies of a crate
|
||||
/// come before the crate itself).
|
||||
pub fn crates_in_topological_order(&self) -> Vec<CrateId> {
|
||||
let mut res = Vec::new();
|
||||
let mut visited = FxHashSet::default();
|
||||
|
||||
for krate in self.iter() {
|
||||
go(self, &mut visited, &mut res, krate);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
fn go(
|
||||
graph: &CrateGraph,
|
||||
visited: &mut FxHashSet<CrateId>,
|
||||
res: &mut Vec<CrateId>,
|
||||
source: CrateId,
|
||||
) {
|
||||
if !visited.insert(source) {
|
||||
return;
|
||||
}
|
||||
for dep in graph[source].dependencies.iter() {
|
||||
go(graph, visited, res, dep.crate_id)
|
||||
}
|
||||
res.push(source)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort_deps(&mut self) {
|
||||
self.arena
|
||||
.iter_mut()
|
||||
.for_each(|(_, data)| data.dependencies.sort_by_key(|dep| dep.crate_id));
|
||||
}
|
||||
|
||||
/// Extends this crate graph by adding a complete disjoint second crate
|
||||
/// graph and adjust the ids in the [`ProcMacroPaths`] accordingly.
|
||||
///
|
||||
/// This will deduplicate the crates of the graph where possible.
|
||||
/// Note that for deduplication to fully work, `self`'s crate dependencies must be sorted by crate id.
|
||||
/// If the crate dependencies were sorted, the resulting graph from this `extend` call will also
|
||||
/// have the crate dependencies sorted.
|
||||
///
|
||||
/// Returns a mapping from `other`'s crate ids to the new crate ids in `self`.
|
||||
pub fn extend(
|
||||
&mut self,
|
||||
mut other: CrateGraph,
|
||||
proc_macros: &mut ProcMacroPaths,
|
||||
merge: impl Fn((CrateId, &mut CrateData), (CrateId, &CrateData)) -> bool,
|
||||
) -> FxHashMap<CrateId, CrateId> {
|
||||
let m = self.len();
|
||||
let topo = other.crates_in_topological_order();
|
||||
let mut id_map: FxHashMap<CrateId, CrateId> = FxHashMap::default();
|
||||
for topo in topo {
|
||||
let crate_data = &mut other.arena[topo];
|
||||
|
||||
crate_data.dependencies.iter_mut().for_each(|dep| dep.crate_id = id_map[&dep.crate_id]);
|
||||
crate_data.dependencies.sort_by_key(|dep| dep.crate_id);
|
||||
let res = self
|
||||
.arena
|
||||
.iter_mut()
|
||||
.take(m)
|
||||
.find_map(|(id, data)| merge((id, data), (topo, crate_data)).then_some(id));
|
||||
|
||||
let new_id =
|
||||
if let Some(res) = res { res } else { self.arena.alloc(crate_data.clone()) };
|
||||
id_map.insert(topo, new_id);
|
||||
}
|
||||
|
||||
*proc_macros =
|
||||
mem::take(proc_macros).into_iter().map(|(id, macros)| (id_map[&id], macros)).collect();
|
||||
|
||||
id_map
|
||||
}
|
||||
|
||||
fn find_path(
|
||||
&self,
|
||||
visited: &mut FxHashSet<CrateId>,
|
||||
from: CrateId,
|
||||
to: CrateId,
|
||||
) -> Option<Vec<CrateId>> {
|
||||
if !visited.insert(from) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if from == to {
|
||||
return Some(vec![to]);
|
||||
}
|
||||
|
||||
for dep in &self[from].dependencies {
|
||||
let crate_id = dep.crate_id;
|
||||
if let Some(mut path) = self.find_path(visited, crate_id, to) {
|
||||
path.push(from);
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// Work around for https://github.com/rust-lang/rust-analyzer/issues/6038.
|
||||
// As hacky as it gets.
|
||||
pub fn patch_cfg_if(&mut self) -> bool {
|
||||
// we stupidly max by version in an attempt to have all duplicated std's depend on the same cfg_if so that deduplication still works
|
||||
let cfg_if =
|
||||
self.hacky_find_crate("cfg_if").max_by_key(|&it| self.arena[it].version.clone());
|
||||
let std = self.hacky_find_crate("std").next();
|
||||
match (cfg_if, std) {
|
||||
(Some(cfg_if), Some(std)) => {
|
||||
self.arena[cfg_if].dependencies.clear();
|
||||
self.arena[std]
|
||||
.dependencies
|
||||
.push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if));
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn hacky_find_crate<'a>(&'a self, display_name: &'a str) -> impl Iterator<Item = CrateId> + 'a {
|
||||
self.iter().filter(move |it| self[*it].display_name.as_deref() == Some(display_name))
|
||||
}
|
||||
|
||||
/// Removes all crates from this crate graph except for the ones in `to_keep` and fixes up the dependencies.
|
||||
/// Returns a mapping from old crate ids to new crate ids.
|
||||
pub fn remove_crates_except(&mut self, to_keep: &[CrateId]) -> Vec<Option<CrateId>> {
|
||||
let mut id_map = vec![None; self.arena.len()];
|
||||
self.arena = std::mem::take(&mut self.arena)
|
||||
.into_iter()
|
||||
.filter_map(|(id, data)| if to_keep.contains(&id) { Some((id, data)) } else { None })
|
||||
.enumerate()
|
||||
.map(|(new_id, (id, data))| {
|
||||
id_map[id.into_raw().into_u32() as usize] =
|
||||
Some(CrateId::from_raw(RawIdx::from_u32(new_id as u32)));
|
||||
data
|
||||
})
|
||||
.collect();
|
||||
for (_, data) in self.arena.iter_mut() {
|
||||
data.dependencies.iter_mut().for_each(|dep| {
|
||||
dep.crate_id =
|
||||
id_map[dep.crate_id.into_raw().into_u32() as usize].expect("crate was filtered")
|
||||
});
|
||||
}
|
||||
id_map
|
||||
}
|
||||
|
||||
pub fn shrink_to_fit(&mut self) {
|
||||
self.arena.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<CrateId> for CrateGraph {
|
||||
type Output = CrateData;
|
||||
fn index(&self, crate_id: CrateId) -> &CrateData {
|
||||
&self.arena[crate_id]
|
||||
}
|
||||
}
|
||||
|
||||
impl CrateData {
|
||||
/// Add a dependency to `self` without checking if the dependency
|
||||
// is existent among `self.dependencies`.
|
||||
fn add_dep(&mut self, dep: Dependency) {
|
||||
self.dependencies.push(dep)
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<(String, String)> for Env {
|
||||
fn extend<T: IntoIterator<Item = (String, String)>>(&mut self, iter: T) {
|
||||
self.entries.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, String)> for Env {
|
||||
fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
|
||||
Env { entries: FromIterator::from_iter(iter) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Env {
|
||||
pub fn set(&mut self, env: &str, value: impl Into<String>) {
|
||||
self.entries.insert(env.to_owned(), value.into());
|
||||
}
|
||||
|
||||
pub fn get(&self, env: &str) -> Option<String> {
|
||||
self.entries.get(env).cloned()
|
||||
}
|
||||
|
||||
pub fn extend_from_other(&mut self, other: &Env) {
|
||||
self.entries.extend(other.entries.iter().map(|(x, y)| (x.to_owned(), y.to_owned())));
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Env> for Vec<(String, String)> {
|
||||
fn from(env: Env) -> Vec<(String, String)> {
|
||||
let mut entries: Vec<_> = env.entries.into_iter().collect();
|
||||
entries.sort();
|
||||
entries
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CyclicDependenciesError {
|
||||
path: Vec<(CrateId, Option<CrateDisplayName>)>,
|
||||
}
|
||||
|
||||
impl CyclicDependenciesError {
|
||||
fn from(&self) -> &(CrateId, Option<CrateDisplayName>) {
|
||||
self.path.first().unwrap()
|
||||
}
|
||||
fn to(&self) -> &(CrateId, Option<CrateDisplayName>) {
|
||||
self.path.last().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CyclicDependenciesError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let render = |(id, name): &(CrateId, Option<CrateDisplayName>)| match name {
|
||||
Some(it) => format!("{it}({id:?})"),
|
||||
None => format!("{id:?}"),
|
||||
};
|
||||
let path = self.path.iter().rev().map(render).collect::<Vec<String>>().join(" -> ");
|
||||
write!(
|
||||
f,
|
||||
"cyclic deps: {} -> {}, alternative path: {}",
|
||||
render(self.from()),
|
||||
render(self.to()),
|
||||
path
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::CrateOrigin;
|
||||
|
||||
use super::{CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId};
|
||||
|
||||
#[test]
|
||||
fn detect_cyclic_dependency_indirect() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(
|
||||
FileId::from_raw(1u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId::from_raw(2u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
let crate3 = graph.add_crate_root(
|
||||
FileId::from_raw(3u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2,))
|
||||
.is_ok());
|
||||
assert!(graph
|
||||
.add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3,))
|
||||
.is_ok());
|
||||
assert!(graph
|
||||
.add_dep(crate3, Dependency::new(CrateName::new("crate1").unwrap(), crate1,))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_cyclic_dependency_direct() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(
|
||||
FileId::from_raw(1u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId::from_raw(2u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2,))
|
||||
.is_ok());
|
||||
assert!(graph
|
||||
.add_dep(crate2, Dependency::new(CrateName::new("crate2").unwrap(), crate2,))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(
|
||||
FileId::from_raw(1u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId::from_raw(2u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
let crate3 = graph.add_crate_root(
|
||||
FileId::from_raw(3u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2,))
|
||||
.is_ok());
|
||||
assert!(graph
|
||||
.add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3,))
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dashes_are_normalized() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(
|
||||
FileId::from_raw(1u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId::from_raw(2u32),
|
||||
Edition2018,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(
|
||||
crate1,
|
||||
Dependency::new(CrateName::normalize_dashes("crate-name-with-dashes"), crate2,)
|
||||
)
|
||||
.is_ok());
|
||||
assert_eq!(
|
||||
graph[crate1].dependencies,
|
||||
vec![Dependency::new(CrateName::new("crate_name_with_dashes").unwrap(), crate2,)]
|
||||
);
|
||||
}
|
||||
}
|
||||
181
src/tools/rust-analyzer/crates/base-db/src/lib.rs
Normal file
181
src/tools/rust-analyzer/crates/base-db/src/lib.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
//! base_db defines basic database traits. The concrete DB is defined by ide.
|
||||
|
||||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
|
||||
mod change;
|
||||
mod input;
|
||||
|
||||
use std::panic;
|
||||
|
||||
use salsa::Durability;
|
||||
use syntax::{ast, Parse, SourceFile};
|
||||
use triomphe::Arc;
|
||||
|
||||
pub use crate::{
|
||||
change::FileChange,
|
||||
input::{
|
||||
CrateData, CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Env,
|
||||
LangCrateOrigin, ProcMacroPaths, ReleaseChannel, SourceRoot, SourceRootId,
|
||||
TargetLayoutLoadResult,
|
||||
},
|
||||
};
|
||||
pub use salsa::{self, Cancelled};
|
||||
pub use span::{FilePosition, FileRange};
|
||||
pub use vfs::{file_set::FileSet, AnchoredPath, AnchoredPathBuf, FileId, VfsPath};
|
||||
|
||||
pub use semver::{BuildMetadata, Prerelease, Version, VersionReq};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_intern_key {
|
||||
($name:ident) => {
|
||||
impl $crate::salsa::InternKey for $name {
|
||||
fn from_intern_id(v: $crate::salsa::InternId) -> Self {
|
||||
$name(v)
|
||||
}
|
||||
fn as_intern_id(&self) -> $crate::salsa::InternId {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait Upcast<T: ?Sized> {
|
||||
fn upcast(&self) -> &T;
|
||||
}
|
||||
|
||||
pub const DEFAULT_FILE_TEXT_LRU_CAP: usize = 16;
|
||||
pub const DEFAULT_PARSE_LRU_CAP: usize = 128;
|
||||
pub const DEFAULT_BORROWCK_LRU_CAP: usize = 2024;
|
||||
|
||||
pub trait FileLoader {
|
||||
/// Text of the file.
|
||||
fn file_text(&self, file_id: FileId) -> Arc<str>;
|
||||
fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId>;
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<[CrateId]>;
|
||||
}
|
||||
|
||||
/// Database which stores all significant input facts: source code and project
|
||||
/// model. Everything else in rust-analyzer is derived from these queries.
|
||||
#[salsa::query_group(SourceDatabaseStorage)]
|
||||
pub trait SourceDatabase: FileLoader + std::fmt::Debug {
|
||||
/// Parses the file into the syntax tree.
|
||||
fn parse(&self, file_id: FileId) -> Parse<ast::SourceFile>;
|
||||
|
||||
/// The crate graph.
|
||||
#[salsa::input]
|
||||
fn crate_graph(&self) -> Arc<CrateGraph>;
|
||||
|
||||
// FIXME: Consider removing this, making HirDatabase::target_data_layout an input query
|
||||
#[salsa::input]
|
||||
fn data_layout(&self, krate: CrateId) -> TargetLayoutLoadResult;
|
||||
|
||||
#[salsa::input]
|
||||
fn toolchain(&self, krate: CrateId) -> Option<Version>;
|
||||
|
||||
#[salsa::transparent]
|
||||
fn toolchain_channel(&self, krate: CrateId) -> Option<ReleaseChannel>;
|
||||
}
|
||||
|
||||
fn toolchain_channel(db: &dyn SourceDatabase, krate: CrateId) -> Option<ReleaseChannel> {
|
||||
db.toolchain(krate).as_ref().and_then(|v| ReleaseChannel::from_str(&v.pre))
|
||||
}
|
||||
|
||||
fn parse(db: &dyn SourceDatabase, file_id: FileId) -> Parse<ast::SourceFile> {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "parse_query", ?file_id).entered();
|
||||
let text = db.file_text(file_id);
|
||||
// FIXME: Edition based parsing
|
||||
SourceFile::parse(&text, span::Edition::CURRENT)
|
||||
}
|
||||
|
||||
/// We don't want to give HIR knowledge of source roots, hence we extract these
|
||||
/// methods into a separate DB.
|
||||
#[salsa::query_group(SourceDatabaseExtStorage)]
|
||||
pub trait SourceDatabaseExt: SourceDatabase {
|
||||
#[salsa::input]
|
||||
fn compressed_file_text(&self, file_id: FileId) -> Arc<[u8]>;
|
||||
|
||||
fn file_text(&self, file_id: FileId) -> Arc<str>;
|
||||
|
||||
/// Path to a file, relative to the root of its source root.
|
||||
/// Source root of the file.
|
||||
#[salsa::input]
|
||||
fn file_source_root(&self, file_id: FileId) -> SourceRootId;
|
||||
/// Contents of the source root.
|
||||
#[salsa::input]
|
||||
fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>;
|
||||
|
||||
fn source_root_crates(&self, id: SourceRootId) -> Arc<[CrateId]>;
|
||||
}
|
||||
|
||||
fn file_text(db: &dyn SourceDatabaseExt, file_id: FileId) -> Arc<str> {
|
||||
let bytes = db.compressed_file_text(file_id);
|
||||
let bytes =
|
||||
lz4_flex::decompress_size_prepended(&bytes).expect("lz4 decompression should not fail");
|
||||
let text = std::str::from_utf8(&bytes).expect("file contents should be valid UTF-8");
|
||||
Arc::from(text)
|
||||
}
|
||||
|
||||
pub trait SourceDatabaseExt2 {
|
||||
fn set_file_text(&mut self, file_id: FileId, text: &str) {
|
||||
self.set_file_text_with_durability(file_id, text, Durability::LOW);
|
||||
}
|
||||
|
||||
fn set_file_text_with_durability(
|
||||
&mut self,
|
||||
file_id: FileId,
|
||||
text: &str,
|
||||
durability: Durability,
|
||||
);
|
||||
}
|
||||
|
||||
impl<Db: ?Sized + SourceDatabaseExt> SourceDatabaseExt2 for Db {
|
||||
fn set_file_text_with_durability(
|
||||
&mut self,
|
||||
file_id: FileId,
|
||||
text: &str,
|
||||
durability: Durability,
|
||||
) {
|
||||
let bytes = text.as_bytes();
|
||||
let compressed = lz4_flex::compress_prepend_size(bytes);
|
||||
self.set_compressed_file_text_with_durability(
|
||||
file_id,
|
||||
Arc::from(compressed.as_slice()),
|
||||
durability,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<[CrateId]> {
|
||||
let graph = db.crate_graph();
|
||||
let mut crates = graph
|
||||
.iter()
|
||||
.filter(|&krate| {
|
||||
let root_file = graph[krate].root_file_id;
|
||||
db.file_source_root(root_file) == id
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
crates.sort();
|
||||
crates.dedup();
|
||||
crates.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Silly workaround for cyclic deps between the traits
|
||||
pub struct FileLoaderDelegate<T>(pub T);
|
||||
|
||||
impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> {
|
||||
fn file_text(&self, file_id: FileId) -> Arc<str> {
|
||||
SourceDatabaseExt::file_text(self.0, file_id)
|
||||
}
|
||||
fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
|
||||
// FIXME: this *somehow* should be platform agnostic...
|
||||
let source_root = self.0.file_source_root(path.anchor);
|
||||
let source_root = self.0.source_root(source_root);
|
||||
source_root.resolve_path(path)
|
||||
}
|
||||
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<[CrateId]> {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "relevant_crates").entered();
|
||||
let source_root = self.0.file_source_root(file_id);
|
||||
self.0.source_root_crates(source_root)
|
||||
}
|
||||
}
|
||||
34
src/tools/rust-analyzer/crates/cfg/Cargo.toml
Normal file
34
src/tools/rust-analyzer/crates/cfg/Cargo.toml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[package]
|
||||
name = "cfg"
|
||||
version = "0.0.0"
|
||||
description = "TBD"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
rustc-hash.workspace = true
|
||||
|
||||
# locals deps
|
||||
tt.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
expect-test = "1.4.1"
|
||||
oorandom = "11.1.3"
|
||||
# We depend on both individually instead of using `features = ["derive"]` to microoptimize the
|
||||
# build graph: if the feature was enabled, syn would be built early on in the graph if `smolstr`
|
||||
# supports `arbitrary`. This way, we avoid feature unification.
|
||||
arbitrary = "1.3.2"
|
||||
derive_arbitrary = "1.3.2"
|
||||
|
||||
# local deps
|
||||
mbe.workspace = true
|
||||
syntax.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
123
src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs
Normal file
123
src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
//! The condition expression used in `#[cfg(..)]` attributes.
|
||||
//!
|
||||
//! See: <https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation>
|
||||
|
||||
use std::{fmt, slice::Iter as SliceIter};
|
||||
|
||||
use tt::SmolStr;
|
||||
|
||||
/// A simple configuration value passed in from the outside.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub enum CfgAtom {
|
||||
/// eg. `#[cfg(test)]`
|
||||
Flag(SmolStr),
|
||||
/// eg. `#[cfg(target_os = "linux")]`
|
||||
///
|
||||
/// Note that a key can have multiple values that are all considered "active" at the same time.
|
||||
/// For example, `#[cfg(target_feature = "sse")]` and `#[cfg(target_feature = "sse2")]`.
|
||||
KeyValue { key: SmolStr, value: SmolStr },
|
||||
}
|
||||
|
||||
impl fmt::Display for CfgAtom {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CfgAtom::Flag(name) => name.fmt(f),
|
||||
CfgAtom::KeyValue { key, value } => write!(f, "{key} = {value:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(test, derive(derive_arbitrary::Arbitrary))]
|
||||
pub enum CfgExpr {
|
||||
Invalid,
|
||||
Atom(CfgAtom),
|
||||
All(Vec<CfgExpr>),
|
||||
Any(Vec<CfgExpr>),
|
||||
Not(Box<CfgExpr>),
|
||||
}
|
||||
|
||||
impl From<CfgAtom> for CfgExpr {
|
||||
fn from(atom: CfgAtom) -> Self {
|
||||
CfgExpr::Atom(atom)
|
||||
}
|
||||
}
|
||||
|
||||
impl CfgExpr {
|
||||
pub fn parse<S>(tt: &tt::Subtree<S>) -> CfgExpr {
|
||||
next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
|
||||
}
|
||||
|
||||
/// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
|
||||
pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option<bool> {
|
||||
match self {
|
||||
CfgExpr::Invalid => None,
|
||||
CfgExpr::Atom(atom) => Some(query(atom)),
|
||||
CfgExpr::All(preds) => {
|
||||
preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
|
||||
}
|
||||
CfgExpr::Any(preds) => {
|
||||
preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?))
|
||||
}
|
||||
CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
|
||||
}
|
||||
}
|
||||
}
|
||||
fn next_cfg_expr<S>(it: &mut SliceIter<'_, tt::TokenTree<S>>) -> Option<CfgExpr> {
|
||||
let name = match it.next() {
|
||||
None => return None,
|
||||
Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(),
|
||||
Some(_) => return Some(CfgExpr::Invalid),
|
||||
};
|
||||
|
||||
// Peek
|
||||
let ret = match it.as_slice().first() {
|
||||
Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
|
||||
match it.as_slice().get(1) {
|
||||
Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
|
||||
it.next();
|
||||
it.next();
|
||||
// FIXME: escape? raw string?
|
||||
let value =
|
||||
SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
|
||||
CfgAtom::KeyValue { key: name, value }.into()
|
||||
}
|
||||
_ => return Some(CfgExpr::Invalid),
|
||||
}
|
||||
}
|
||||
Some(tt::TokenTree::Subtree(subtree)) => {
|
||||
it.next();
|
||||
let mut sub_it = subtree.token_trees.iter();
|
||||
let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it)).collect();
|
||||
match name.as_str() {
|
||||
"all" => CfgExpr::All(subs),
|
||||
"any" => CfgExpr::Any(subs),
|
||||
"not" => CfgExpr::Not(Box::new(subs.pop().unwrap_or(CfgExpr::Invalid))),
|
||||
_ => CfgExpr::Invalid,
|
||||
}
|
||||
}
|
||||
_ => CfgAtom::Flag(name).into(),
|
||||
};
|
||||
|
||||
// Eat comma separator
|
||||
if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
|
||||
if punct.char == ',' {
|
||||
it.next();
|
||||
}
|
||||
}
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl arbitrary::Arbitrary<'_> for CfgAtom {
|
||||
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
|
||||
if u.arbitrary()? {
|
||||
Ok(CfgAtom::Flag(String::arbitrary(u)?.into()))
|
||||
} else {
|
||||
Ok(CfgAtom::KeyValue {
|
||||
key: String::arbitrary(u)?.into(),
|
||||
value: String::arbitrary(u)?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
345
src/tools/rust-analyzer/crates/cfg/src/dnf.rs
Normal file
345
src/tools/rust-analyzer/crates/cfg/src/dnf.rs
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
//! Disjunctive Normal Form construction.
|
||||
//!
|
||||
//! Algorithm from <https://www.cs.drexel.edu/~jjohnson/2015-16/fall/CS270/Lectures/3/dnf.pdf>,
|
||||
//! which would have been much easier to read if it used pattern matching. It's also missing the
|
||||
//! entire "distribute ANDs over ORs" part, which is not trivial. Oh well.
|
||||
//!
|
||||
//! This is currently both messy and inefficient. Feel free to improve, there are unit tests.
|
||||
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{CfgAtom, CfgDiff, CfgExpr, CfgOptions, InactiveReason};
|
||||
|
||||
/// A `#[cfg]` directive in Disjunctive Normal Form (DNF).
|
||||
pub struct DnfExpr {
|
||||
conjunctions: Vec<Conjunction>,
|
||||
}
|
||||
|
||||
struct Conjunction {
|
||||
literals: Vec<Literal>,
|
||||
}
|
||||
|
||||
struct Literal {
|
||||
negate: bool,
|
||||
var: Option<CfgAtom>, // None = Invalid
|
||||
}
|
||||
|
||||
impl DnfExpr {
|
||||
pub fn new(expr: CfgExpr) -> Self {
|
||||
let builder = Builder { expr: DnfExpr { conjunctions: Vec::new() } };
|
||||
|
||||
builder.lower(expr)
|
||||
}
|
||||
|
||||
/// Computes a list of present or absent atoms in `opts` that cause this expression to evaluate
|
||||
/// to `false`.
|
||||
///
|
||||
/// Note that flipping a subset of these atoms might be sufficient to make the whole expression
|
||||
/// evaluate to `true`. For that, see `compute_enable_hints`.
|
||||
///
|
||||
/// Returns `None` when `self` is already true, or contains errors.
|
||||
pub fn why_inactive(&self, opts: &CfgOptions) -> Option<InactiveReason> {
|
||||
let mut res = InactiveReason { enabled: Vec::new(), disabled: Vec::new() };
|
||||
|
||||
for conj in &self.conjunctions {
|
||||
let mut conj_is_true = true;
|
||||
for lit in &conj.literals {
|
||||
let atom = lit.var.as_ref()?;
|
||||
let enabled = opts.enabled.contains(atom);
|
||||
if lit.negate == enabled {
|
||||
// Literal is false, but needs to be true for this conjunction.
|
||||
conj_is_true = false;
|
||||
|
||||
if enabled {
|
||||
res.enabled.push(atom.clone());
|
||||
} else {
|
||||
res.disabled.push(atom.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if conj_is_true {
|
||||
// This expression is not actually inactive.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
res.enabled.sort_unstable();
|
||||
res.enabled.dedup();
|
||||
res.disabled.sort_unstable();
|
||||
res.disabled.dedup();
|
||||
Some(res)
|
||||
}
|
||||
|
||||
/// Returns `CfgDiff` objects that would enable this directive if applied to `opts`.
|
||||
pub fn compute_enable_hints<'a>(
|
||||
&'a self,
|
||||
opts: &'a CfgOptions,
|
||||
) -> impl Iterator<Item = CfgDiff> + 'a {
|
||||
// A cfg is enabled if any of `self.conjunctions` evaluate to `true`.
|
||||
|
||||
self.conjunctions.iter().filter_map(move |conj| {
|
||||
let mut enable = FxHashSet::default();
|
||||
let mut disable = FxHashSet::default();
|
||||
for lit in &conj.literals {
|
||||
let atom = lit.var.as_ref()?;
|
||||
let enabled = opts.enabled.contains(atom);
|
||||
if lit.negate && enabled {
|
||||
disable.insert(atom.clone());
|
||||
}
|
||||
if !lit.negate && !enabled {
|
||||
enable.insert(atom.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Check that this actually makes `conj` true.
|
||||
for lit in &conj.literals {
|
||||
let atom = lit.var.as_ref()?;
|
||||
let enabled = enable.contains(atom)
|
||||
|| (opts.enabled.contains(atom) && !disable.contains(atom));
|
||||
if enabled == lit.negate {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if enable.is_empty() && disable.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut diff = CfgDiff {
|
||||
enable: enable.into_iter().collect(),
|
||||
disable: disable.into_iter().collect(),
|
||||
};
|
||||
|
||||
// Undo the FxHashMap randomization for consistent output.
|
||||
diff.enable.sort_unstable();
|
||||
diff.disable.sort_unstable();
|
||||
|
||||
Some(diff)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DnfExpr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.conjunctions.len() != 1 {
|
||||
f.write_str("any(")?;
|
||||
}
|
||||
for (i, conj) in self.conjunctions.iter().enumerate() {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
|
||||
conj.fmt(f)?;
|
||||
}
|
||||
if self.conjunctions.len() != 1 {
|
||||
f.write_char(')')?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Conjunction {
|
||||
fn new(parts: Vec<CfgExpr>) -> Self {
|
||||
let mut literals = Vec::new();
|
||||
for part in parts {
|
||||
match part {
|
||||
CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
|
||||
literals.push(Literal::new(part));
|
||||
}
|
||||
CfgExpr::All(conj) => {
|
||||
// Flatten.
|
||||
literals.extend(Conjunction::new(conj).literals);
|
||||
}
|
||||
CfgExpr::Any(_) => unreachable!("disjunction in conjunction"),
|
||||
}
|
||||
}
|
||||
|
||||
Self { literals }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Conjunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.literals.len() != 1 {
|
||||
f.write_str("all(")?;
|
||||
}
|
||||
for (i, lit) in self.literals.iter().enumerate() {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
|
||||
lit.fmt(f)?;
|
||||
}
|
||||
if self.literals.len() != 1 {
|
||||
f.write_str(")")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Literal {
|
||||
fn new(expr: CfgExpr) -> Self {
|
||||
match expr {
|
||||
CfgExpr::Invalid => Self { negate: false, var: None },
|
||||
CfgExpr::Atom(atom) => Self { negate: false, var: Some(atom) },
|
||||
CfgExpr::Not(expr) => match *expr {
|
||||
CfgExpr::Invalid => Self { negate: true, var: None },
|
||||
CfgExpr::Atom(atom) => Self { negate: true, var: Some(atom) },
|
||||
_ => unreachable!("non-atom {:?}", expr),
|
||||
},
|
||||
CfgExpr::Any(_) | CfgExpr::All(_) => unreachable!("non-literal {:?}", expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Literal {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.negate {
|
||||
write!(f, "not(")?;
|
||||
}
|
||||
|
||||
match &self.var {
|
||||
Some(var) => var.fmt(f)?,
|
||||
None => f.write_str("<invalid>")?,
|
||||
}
|
||||
|
||||
if self.negate {
|
||||
f.write_char(')')?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Builder {
|
||||
expr: DnfExpr,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
fn lower(mut self, expr: CfgExpr) -> DnfExpr {
|
||||
let expr = make_nnf(expr);
|
||||
let expr = make_dnf(expr);
|
||||
|
||||
match expr {
|
||||
CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
|
||||
self.expr.conjunctions.push(Conjunction::new(vec![expr]));
|
||||
}
|
||||
CfgExpr::All(conj) => {
|
||||
self.expr.conjunctions.push(Conjunction::new(conj));
|
||||
}
|
||||
CfgExpr::Any(mut disj) => {
|
||||
disj.reverse();
|
||||
while let Some(conj) = disj.pop() {
|
||||
match conj {
|
||||
CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::All(_) | CfgExpr::Not(_) => {
|
||||
self.expr.conjunctions.push(Conjunction::new(vec![conj]));
|
||||
}
|
||||
CfgExpr::Any(inner_disj) => {
|
||||
// Flatten.
|
||||
disj.extend(inner_disj.into_iter().rev());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.expr
|
||||
}
|
||||
}
|
||||
|
||||
fn make_dnf(expr: CfgExpr) -> CfgExpr {
|
||||
match expr {
|
||||
CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => expr,
|
||||
CfgExpr::Any(e) => flatten(CfgExpr::Any(e.into_iter().map(make_dnf).collect())),
|
||||
CfgExpr::All(e) => {
|
||||
let e = e.into_iter().map(make_dnf).collect::<Vec<_>>();
|
||||
|
||||
flatten(CfgExpr::Any(distribute_conj(&e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns a conjunction of expressions into a disjunction of expressions.
|
||||
fn distribute_conj(conj: &[CfgExpr]) -> Vec<CfgExpr> {
|
||||
fn go(out: &mut Vec<CfgExpr>, with: &mut Vec<CfgExpr>, rest: &[CfgExpr]) {
|
||||
match rest {
|
||||
[head, tail @ ..] => match head {
|
||||
CfgExpr::Any(disj) => {
|
||||
for part in disj {
|
||||
with.push(part.clone());
|
||||
go(out, with, tail);
|
||||
with.pop();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
with.push(head.clone());
|
||||
go(out, with, tail);
|
||||
with.pop();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// Turn accumulated parts into a new conjunction.
|
||||
out.push(CfgExpr::All(with.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = Vec::new(); // contains only `all()`
|
||||
let mut with = Vec::new();
|
||||
|
||||
go(&mut out, &mut with, conj);
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn make_nnf(expr: CfgExpr) -> CfgExpr {
|
||||
match expr {
|
||||
CfgExpr::Invalid | CfgExpr::Atom(_) => expr,
|
||||
CfgExpr::Any(expr) => CfgExpr::Any(expr.into_iter().map(make_nnf).collect()),
|
||||
CfgExpr::All(expr) => CfgExpr::All(expr.into_iter().map(make_nnf).collect()),
|
||||
CfgExpr::Not(operand) => match *operand {
|
||||
CfgExpr::Invalid | CfgExpr::Atom(_) => CfgExpr::Not(operand.clone()), // Original negated expr
|
||||
CfgExpr::Not(expr) => {
|
||||
// Remove double negation.
|
||||
make_nnf(*expr)
|
||||
}
|
||||
// Convert negated conjunction/disjunction using DeMorgan's Law.
|
||||
CfgExpr::Any(inner) => CfgExpr::All(
|
||||
inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
|
||||
),
|
||||
CfgExpr::All(inner) => CfgExpr::Any(
|
||||
inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Collapses nested `any()` and `all()` predicates.
|
||||
fn flatten(expr: CfgExpr) -> CfgExpr {
|
||||
match expr {
|
||||
CfgExpr::All(inner) => CfgExpr::All(
|
||||
inner
|
||||
.into_iter()
|
||||
.flat_map(|e| match e {
|
||||
CfgExpr::All(inner) => inner,
|
||||
_ => vec![e],
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
CfgExpr::Any(inner) => CfgExpr::Any(
|
||||
inner
|
||||
.into_iter()
|
||||
.flat_map(|e| match e {
|
||||
CfgExpr::Any(inner) => inner,
|
||||
_ => vec![e],
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
_ => expr,
|
||||
}
|
||||
}
|
||||
226
src/tools/rust-analyzer/crates/cfg/src/lib.rs
Normal file
226
src/tools/rust-analyzer/crates/cfg/src/lib.rs
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
//! cfg defines conditional compiling options, `cfg` attribute parser and evaluator
|
||||
|
||||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
|
||||
mod cfg_expr;
|
||||
mod dnf;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use tt::SmolStr;
|
||||
|
||||
pub use cfg_expr::{CfgAtom, CfgExpr};
|
||||
pub use dnf::DnfExpr;
|
||||
|
||||
/// Configuration options used for conditional compilation on items with `cfg` attributes.
|
||||
/// We have two kind of options in different namespaces: atomic options like `unix`, and
|
||||
/// key-value options like `target_arch="x86"`.
|
||||
///
|
||||
/// Note that for key-value options, one key can have multiple values (but not none).
|
||||
/// `feature` is an example. We have both `feature="foo"` and `feature="bar"` if features
|
||||
/// `foo` and `bar` are both enabled. And here, we store key-value options as a set of tuple
|
||||
/// of key and value in `key_values`.
|
||||
///
|
||||
/// See: <https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options>
|
||||
#[derive(Clone, PartialEq, Eq, Default)]
|
||||
pub struct CfgOptions {
|
||||
enabled: FxHashSet<CfgAtom>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for CfgOptions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut items = self
|
||||
.enabled
|
||||
.iter()
|
||||
.map(|atom| match atom {
|
||||
CfgAtom::Flag(it) => it.to_string(),
|
||||
CfgAtom::KeyValue { key, value } => format!("{key}={value}"),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
items.sort();
|
||||
f.debug_tuple("CfgOptions").field(&items).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl CfgOptions {
|
||||
pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
|
||||
cfg.fold(&|atom| self.enabled.contains(atom))
|
||||
}
|
||||
|
||||
pub fn insert_atom(&mut self, key: SmolStr) {
|
||||
self.enabled.insert(CfgAtom::Flag(key));
|
||||
}
|
||||
|
||||
pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) {
|
||||
self.enabled.insert(CfgAtom::KeyValue { key, value });
|
||||
}
|
||||
|
||||
pub fn apply_diff(&mut self, diff: CfgDiff) {
|
||||
for atom in diff.enable {
|
||||
self.enabled.insert(atom);
|
||||
}
|
||||
|
||||
for atom in diff.disable {
|
||||
self.enabled.remove(&atom);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cfg_keys(&self) -> impl Iterator<Item = &SmolStr> {
|
||||
self.enabled.iter().map(|it| match it {
|
||||
CfgAtom::Flag(key) => key,
|
||||
CfgAtom::KeyValue { key, .. } => key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_cfg_values<'a>(
|
||||
&'a self,
|
||||
cfg_key: &'a str,
|
||||
) -> impl Iterator<Item = &'a SmolStr> + 'a {
|
||||
self.enabled.iter().filter_map(move |it| match it {
|
||||
CfgAtom::KeyValue { key, value } if cfg_key == key => Some(value),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<CfgAtom> for CfgOptions {
|
||||
fn extend<T: IntoIterator<Item = CfgAtom>>(&mut self, iter: T) {
|
||||
iter.into_iter().for_each(|cfg_flag| _ = self.enabled.insert(cfg_flag));
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for CfgOptions {
|
||||
type Item = <FxHashSet<CfgAtom> as IntoIterator>::Item;
|
||||
|
||||
type IntoIter = <FxHashSet<CfgAtom> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
<FxHashSet<CfgAtom> as IntoIterator>::into_iter(self.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a CfgOptions {
|
||||
type Item = <&'a FxHashSet<CfgAtom> as IntoIterator>::Item;
|
||||
|
||||
type IntoIter = <&'a FxHashSet<CfgAtom> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
<&FxHashSet<CfgAtom> as IntoIterator>::into_iter(&self.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CfgDiff {
|
||||
// Invariants: No duplicates, no atom that's both in `enable` and `disable`.
|
||||
enable: Vec<CfgAtom>,
|
||||
disable: Vec<CfgAtom>,
|
||||
}
|
||||
|
||||
impl CfgDiff {
|
||||
/// Create a new CfgDiff. Will return None if the same item appears more than once in the set
|
||||
/// of both.
|
||||
pub fn new(enable: Vec<CfgAtom>, disable: Vec<CfgAtom>) -> Option<CfgDiff> {
|
||||
let mut occupied = FxHashSet::default();
|
||||
if enable.iter().chain(disable.iter()).any(|item| !occupied.insert(item)) {
|
||||
// was present
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(CfgDiff { enable, disable })
|
||||
}
|
||||
|
||||
/// Returns the total number of atoms changed by this diff.
|
||||
pub fn len(&self) -> usize {
|
||||
self.enable.len() + self.disable.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CfgDiff {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if !self.enable.is_empty() {
|
||||
f.write_str("enable ")?;
|
||||
for (i, atom) in self.enable.iter().enumerate() {
|
||||
let sep = match i {
|
||||
0 => "",
|
||||
_ if i == self.enable.len() - 1 => " and ",
|
||||
_ => ", ",
|
||||
};
|
||||
f.write_str(sep)?;
|
||||
|
||||
atom.fmt(f)?;
|
||||
}
|
||||
|
||||
if !self.disable.is_empty() {
|
||||
f.write_str("; ")?;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.disable.is_empty() {
|
||||
f.write_str("disable ")?;
|
||||
for (i, atom) in self.disable.iter().enumerate() {
|
||||
let sep = match i {
|
||||
0 => "",
|
||||
_ if i == self.enable.len() - 1 => " and ",
|
||||
_ => ", ",
|
||||
};
|
||||
f.write_str(sep)?;
|
||||
|
||||
atom.fmt(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InactiveReason {
|
||||
enabled: Vec<CfgAtom>,
|
||||
disabled: Vec<CfgAtom>,
|
||||
}
|
||||
|
||||
impl fmt::Display for InactiveReason {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if !self.enabled.is_empty() {
|
||||
for (i, atom) in self.enabled.iter().enumerate() {
|
||||
let sep = match i {
|
||||
0 => "",
|
||||
_ if i == self.enabled.len() - 1 => " and ",
|
||||
_ => ", ",
|
||||
};
|
||||
f.write_str(sep)?;
|
||||
|
||||
atom.fmt(f)?;
|
||||
}
|
||||
let is_are = if self.enabled.len() == 1 { "is" } else { "are" };
|
||||
write!(f, " {is_are} enabled")?;
|
||||
|
||||
if !self.disabled.is_empty() {
|
||||
f.write_str(" and ")?;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.disabled.is_empty() {
|
||||
for (i, atom) in self.disabled.iter().enumerate() {
|
||||
let sep = match i {
|
||||
0 => "",
|
||||
_ if i == self.disabled.len() - 1 => " and ",
|
||||
_ => ", ",
|
||||
};
|
||||
f.write_str(sep)?;
|
||||
|
||||
atom.fmt(f)?;
|
||||
}
|
||||
let is_are = if self.disabled.len() == 1 { "is" } else { "are" };
|
||||
write!(f, " {is_are} disabled")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
216
src/tools/rust-analyzer/crates/cfg/src/tests.rs
Normal file
216
src/tools/rust-analyzer/crates/cfg/src/tests.rs
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use expect_test::{expect, Expect};
|
||||
use mbe::{syntax_node_to_token_tree, DummyTestSpanMap, DUMMY};
|
||||
use syntax::{ast, AstNode, Edition};
|
||||
|
||||
use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr};
|
||||
|
||||
fn assert_parse_result(input: &str, expected: CfgExpr) {
|
||||
let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap();
|
||||
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
|
||||
let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap, DUMMY);
|
||||
let cfg = CfgExpr::parse(&tt);
|
||||
assert_eq!(cfg, expected);
|
||||
}
|
||||
|
||||
fn check_dnf(input: &str, expect: Expect) {
|
||||
let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap();
|
||||
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
|
||||
let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap, DUMMY);
|
||||
let cfg = CfgExpr::parse(&tt);
|
||||
let actual = format!("#![cfg({})]", DnfExpr::new(cfg));
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) {
|
||||
let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap();
|
||||
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
|
||||
let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap, DUMMY);
|
||||
let cfg = CfgExpr::parse(&tt);
|
||||
let dnf = DnfExpr::new(cfg);
|
||||
let why_inactive = dnf.why_inactive(opts).unwrap().to_string();
|
||||
expect.assert_eq(&why_inactive);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) {
|
||||
let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap();
|
||||
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
|
||||
let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap, DUMMY);
|
||||
let cfg = CfgExpr::parse(&tt);
|
||||
let dnf = DnfExpr::new(cfg);
|
||||
let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>();
|
||||
assert_eq!(hints, expected_hints);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cfg_expr_parser() {
|
||||
assert_parse_result("#![cfg(foo)]", CfgAtom::Flag("foo".into()).into());
|
||||
assert_parse_result("#![cfg(foo,)]", CfgAtom::Flag("foo".into()).into());
|
||||
assert_parse_result(
|
||||
"#![cfg(not(foo))]",
|
||||
CfgExpr::Not(Box::new(CfgAtom::Flag("foo".into()).into())),
|
||||
);
|
||||
assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
|
||||
|
||||
// Only take the first
|
||||
assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgAtom::Flag("foo".into()).into());
|
||||
|
||||
assert_parse_result(
|
||||
r#"#![cfg(all(foo, bar = "baz"))]"#,
|
||||
CfgExpr::All(vec![
|
||||
CfgAtom::Flag("foo".into()).into(),
|
||||
CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
|
||||
]),
|
||||
);
|
||||
|
||||
assert_parse_result(
|
||||
r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
|
||||
CfgExpr::Any(vec![
|
||||
CfgExpr::Not(Box::new(CfgExpr::Invalid)),
|
||||
CfgExpr::All(vec![]),
|
||||
CfgExpr::Invalid,
|
||||
CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
check_dnf("#![cfg(test)]", expect![[r#"#![cfg(test)]"#]]);
|
||||
check_dnf("#![cfg(not(test))]", expect![[r#"#![cfg(not(test))]"#]]);
|
||||
check_dnf("#![cfg(not(not(test)))]", expect![[r#"#![cfg(test)]"#]]);
|
||||
|
||||
check_dnf("#![cfg(all(a, b))]", expect![[r#"#![cfg(all(a, b))]"#]]);
|
||||
check_dnf("#![cfg(any(a, b))]", expect![[r#"#![cfg(any(a, b))]"#]]);
|
||||
|
||||
check_dnf("#![cfg(not(a))]", expect![[r#"#![cfg(not(a))]"#]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distribute() {
|
||||
check_dnf("#![cfg(all(any(a, b), c))]", expect![[r#"#![cfg(any(all(a, c), all(b, c)))]"#]]);
|
||||
check_dnf("#![cfg(all(c, any(a, b)))]", expect![[r#"#![cfg(any(all(c, a), all(c, b)))]"#]]);
|
||||
check_dnf(
|
||||
"#![cfg(all(any(a, b), any(c, d)))]",
|
||||
expect![[r#"#![cfg(any(all(a, c), all(a, d), all(b, c), all(b, d)))]"#]],
|
||||
);
|
||||
|
||||
check_dnf(
|
||||
"#![cfg(all(any(a, b, c), any(d, e, f), g))]",
|
||||
expect![[
|
||||
r#"#![cfg(any(all(a, d, g), all(a, e, g), all(a, f, g), all(b, d, g), all(b, e, g), all(b, f, g), all(c, d, g), all(c, e, g), all(c, f, g)))]"#
|
||||
]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn demorgan() {
|
||||
check_dnf("#![cfg(not(all(a, b)))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
|
||||
check_dnf("#![cfg(not(any(a, b)))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
|
||||
|
||||
check_dnf("#![cfg(not(all(not(a), b)))]", expect![[r#"#![cfg(any(a, not(b)))]"#]]);
|
||||
check_dnf("#![cfg(not(any(a, not(b))))]", expect![[r#"#![cfg(all(not(a), b))]"#]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested() {
|
||||
check_dnf("#![cfg(all(any(a), not(all(any(b)))))]", expect![[r#"#![cfg(all(a, not(b)))]"#]]);
|
||||
|
||||
check_dnf("#![cfg(any(any(a, b)))]", expect![[r#"#![cfg(any(a, b))]"#]]);
|
||||
check_dnf("#![cfg(not(any(any(a, b))))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
|
||||
check_dnf("#![cfg(all(all(a, b)))]", expect![[r#"#![cfg(all(a, b))]"#]]);
|
||||
check_dnf("#![cfg(not(all(all(a, b))))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression() {
|
||||
check_dnf("#![cfg(all(not(not(any(any(any()))))))]", expect![[r##"#![cfg(any())]"##]]);
|
||||
check_dnf("#![cfg(all(any(all(any()))))]", expect![[r##"#![cfg(any())]"##]]);
|
||||
check_dnf("#![cfg(all(all(any())))]", expect![[r##"#![cfg(any())]"##]]);
|
||||
|
||||
check_dnf("#![cfg(all(all(any(), x)))]", expect![[r##"#![cfg(any())]"##]]);
|
||||
check_dnf("#![cfg(all(all(any()), x))]", expect![[r##"#![cfg(any())]"##]]);
|
||||
check_dnf("#![cfg(all(all(any(x))))]", expect![[r##"#![cfg(x)]"##]]);
|
||||
check_dnf("#![cfg(all(all(any(x), x)))]", expect![[r##"#![cfg(all(x, x))]"##]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hints() {
|
||||
let mut opts = CfgOptions::default();
|
||||
|
||||
check_enable_hints("#![cfg(test)]", &opts, &["enable test"]);
|
||||
check_enable_hints("#![cfg(not(test))]", &opts, &[]);
|
||||
|
||||
check_enable_hints("#![cfg(any(a, b))]", &opts, &["enable a", "enable b"]);
|
||||
check_enable_hints("#![cfg(any(b, a))]", &opts, &["enable b", "enable a"]);
|
||||
|
||||
check_enable_hints("#![cfg(all(a, b))]", &opts, &["enable a and b"]);
|
||||
|
||||
opts.insert_atom("test".into());
|
||||
|
||||
check_enable_hints("#![cfg(test)]", &opts, &[]);
|
||||
check_enable_hints("#![cfg(not(test))]", &opts, &["disable test"]);
|
||||
}
|
||||
|
||||
/// Tests that we don't suggest hints for cfgs that express an inconsistent formula.
|
||||
#[test]
|
||||
fn hints_impossible() {
|
||||
let mut opts = CfgOptions::default();
|
||||
|
||||
check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
|
||||
|
||||
opts.insert_atom("test".into());
|
||||
|
||||
check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn why_inactive() {
|
||||
let mut opts = CfgOptions::default();
|
||||
opts.insert_atom("test".into());
|
||||
opts.insert_atom("test2".into());
|
||||
|
||||
check_why_inactive("#![cfg(a)]", &opts, expect![["a is disabled"]]);
|
||||
check_why_inactive("#![cfg(not(test))]", &opts, expect![["test is enabled"]]);
|
||||
|
||||
check_why_inactive(
|
||||
"#![cfg(all(not(test), not(test2)))]",
|
||||
&opts,
|
||||
expect![["test and test2 are enabled"]],
|
||||
);
|
||||
check_why_inactive("#![cfg(all(a, b))]", &opts, expect![["a and b are disabled"]]);
|
||||
check_why_inactive(
|
||||
"#![cfg(all(not(test), a))]",
|
||||
&opts,
|
||||
expect![["test is enabled and a is disabled"]],
|
||||
);
|
||||
check_why_inactive(
|
||||
"#![cfg(all(not(test), test2, a))]",
|
||||
&opts,
|
||||
expect![["test is enabled and a is disabled"]],
|
||||
);
|
||||
check_why_inactive(
|
||||
"#![cfg(all(not(test), not(test2), a))]",
|
||||
&opts,
|
||||
expect![["test and test2 are enabled and a is disabled"]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proptest() {
|
||||
const REPEATS: usize = 512;
|
||||
|
||||
let mut rng = oorandom::Rand32::new(123456789);
|
||||
let mut buf = Vec::new();
|
||||
for _ in 0..REPEATS {
|
||||
buf.clear();
|
||||
while buf.len() < 512 {
|
||||
buf.extend(rng.rand_u32().to_ne_bytes());
|
||||
}
|
||||
|
||||
let mut u = Unstructured::new(&buf);
|
||||
let cfg = CfgExpr::arbitrary(&mut u).unwrap();
|
||||
DnfExpr::new(cfg);
|
||||
}
|
||||
}
|
||||
29
src/tools/rust-analyzer/crates/flycheck/Cargo.toml
Normal file
29
src/tools/rust-analyzer/crates/flycheck/Cargo.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "flycheck"
|
||||
version = "0.0.0"
|
||||
description = "TBD"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
cargo_metadata.workspace = true
|
||||
crossbeam-channel.workspace = true
|
||||
tracing.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
command-group.workspace = true
|
||||
|
||||
# local deps
|
||||
paths.workspace = true
|
||||
stdx.workspace = true
|
||||
toolchain.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
156
src/tools/rust-analyzer/crates/flycheck/src/command.rs
Normal file
156
src/tools/rust-analyzer/crates/flycheck/src/command.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
//! Utilities for running a cargo command like `cargo check` or `cargo test` in a separate thread and
|
||||
//! parse its stdout/stderr.
|
||||
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fmt, io,
|
||||
marker::PhantomData,
|
||||
path::PathBuf,
|
||||
process::{ChildStderr, ChildStdout, Command, Stdio},
|
||||
};
|
||||
|
||||
use command_group::{CommandGroup, GroupChild};
|
||||
use crossbeam_channel::Sender;
|
||||
use stdx::process::streaming_output;
|
||||
|
||||
/// Cargo output is structured as a one JSON per line. This trait abstracts parsing one line of
|
||||
/// cargo output into a Rust data type.
|
||||
pub(crate) trait ParseFromLine: Sized + Send + 'static {
|
||||
fn from_line(line: &str, error: &mut String) -> Option<Self>;
|
||||
fn from_eof() -> Option<Self>;
|
||||
}
|
||||
|
||||
struct CargoActor<T> {
|
||||
sender: Sender<T>,
|
||||
stdout: ChildStdout,
|
||||
stderr: ChildStderr,
|
||||
}
|
||||
|
||||
impl<T: ParseFromLine> CargoActor<T> {
|
||||
fn new(sender: Sender<T>, stdout: ChildStdout, stderr: ChildStderr) -> Self {
|
||||
CargoActor { sender, stdout, stderr }
|
||||
}
|
||||
|
||||
fn run(self) -> io::Result<(bool, String)> {
|
||||
// We manually read a line at a time, instead of using serde's
|
||||
// stream deserializers, because the deserializer cannot recover
|
||||
// from an error, resulting in it getting stuck, because we try to
|
||||
// be resilient against failures.
|
||||
//
|
||||
// Because cargo only outputs one JSON object per line, we can
|
||||
// simply skip a line if it doesn't parse, which just ignores any
|
||||
// erroneous output.
|
||||
|
||||
let mut stdout_errors = String::new();
|
||||
let mut stderr_errors = String::new();
|
||||
let mut read_at_least_one_stdout_message = false;
|
||||
let mut read_at_least_one_stderr_message = false;
|
||||
let process_line = |line: &str, error: &mut String| {
|
||||
// Try to deserialize a message from Cargo or Rustc.
|
||||
if let Some(t) = T::from_line(line, error) {
|
||||
self.sender.send(t).unwrap();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
let output = streaming_output(
|
||||
self.stdout,
|
||||
self.stderr,
|
||||
&mut |line| {
|
||||
if process_line(line, &mut stdout_errors) {
|
||||
read_at_least_one_stdout_message = true;
|
||||
}
|
||||
},
|
||||
&mut |line| {
|
||||
if process_line(line, &mut stderr_errors) {
|
||||
read_at_least_one_stderr_message = true;
|
||||
}
|
||||
},
|
||||
&mut || {
|
||||
if let Some(t) = T::from_eof() {
|
||||
self.sender.send(t).unwrap();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let read_at_least_one_message =
|
||||
read_at_least_one_stdout_message || read_at_least_one_stderr_message;
|
||||
let mut error = stdout_errors;
|
||||
error.push_str(&stderr_errors);
|
||||
match output {
|
||||
Ok(_) => Ok((read_at_least_one_message, error)),
|
||||
Err(e) => Err(io::Error::new(e.kind(), format!("{e:?}: {error}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct JodGroupChild(GroupChild);
|
||||
|
||||
impl Drop for JodGroupChild {
|
||||
fn drop(&mut self) {
|
||||
_ = self.0.kill();
|
||||
_ = self.0.wait();
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a cargo process used for fly-checking.
|
||||
pub(crate) struct CommandHandle<T> {
|
||||
/// The handle to the actual cargo process. As we cannot cancel directly from with
|
||||
/// a read syscall dropping and therefore terminating the process is our best option.
|
||||
child: JodGroupChild,
|
||||
thread: stdx::thread::JoinHandle<io::Result<(bool, String)>>,
|
||||
program: OsString,
|
||||
arguments: Vec<OsString>,
|
||||
current_dir: Option<PathBuf>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for CommandHandle<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("CommandHandle")
|
||||
.field("program", &self.program)
|
||||
.field("arguments", &self.arguments)
|
||||
.field("current_dir", &self.current_dir)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ParseFromLine> CommandHandle<T> {
|
||||
pub(crate) fn spawn(mut command: Command, sender: Sender<T>) -> std::io::Result<Self> {
|
||||
command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
|
||||
let mut child = command.group_spawn().map(JodGroupChild)?;
|
||||
|
||||
let program = command.get_program().into();
|
||||
let arguments = command.get_args().map(|arg| arg.into()).collect::<Vec<OsString>>();
|
||||
let current_dir = command.get_current_dir().map(|arg| arg.to_path_buf());
|
||||
|
||||
let stdout = child.0.inner().stdout.take().unwrap();
|
||||
let stderr = child.0.inner().stderr.take().unwrap();
|
||||
|
||||
let actor = CargoActor::<T>::new(sender, stdout, stderr);
|
||||
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
|
||||
.name("CommandHandle".to_owned())
|
||||
.spawn(move || actor.run())
|
||||
.expect("failed to spawn thread");
|
||||
Ok(CommandHandle { program, arguments, current_dir, child, thread, _phantom: PhantomData })
|
||||
}
|
||||
|
||||
pub(crate) fn cancel(mut self) {
|
||||
let _ = self.child.0.kill();
|
||||
let _ = self.child.0.wait();
|
||||
}
|
||||
|
||||
pub(crate) fn join(mut self) -> io::Result<()> {
|
||||
let _ = self.child.0.kill();
|
||||
let exit_status = self.child.0.wait()?;
|
||||
let (read_at_least_one_message, error) = self.thread.join()?;
|
||||
if read_at_least_one_message || exit_status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Other, format!(
|
||||
"Cargo watcher failed, the command produced no valid metadata (exit code: {exit_status:?}):\n{error}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
500
src/tools/rust-analyzer/crates/flycheck/src/lib.rs
Normal file
500
src/tools/rust-analyzer/crates/flycheck/src/lib.rs
Normal file
|
|
@ -0,0 +1,500 @@
|
|||
//! Flycheck provides the functionality needed to run `cargo check` or
|
||||
//! another compatible command (f.x. clippy) in a background thread and provide
|
||||
//! LSP diagnostics based on the output of the command.
|
||||
|
||||
// FIXME: This crate now handles running `cargo test` needed in the test explorer in
|
||||
// addition to `cargo check`. Either split it into 3 crates (one for test, one for check
|
||||
// and one common utilities) or change its name and docs to reflect the current state.
|
||||
|
||||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
|
||||
use std::{fmt, io, process::Command, time::Duration};
|
||||
|
||||
use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
|
||||
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub use cargo_metadata::diagnostic::{
|
||||
Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
|
||||
DiagnosticSpanMacroExpansion,
|
||||
};
|
||||
use toolchain::Tool;
|
||||
|
||||
mod command;
|
||||
mod test_runner;
|
||||
|
||||
use command::{CommandHandle, ParseFromLine};
|
||||
pub use test_runner::{CargoTestHandle, CargoTestMessage, TestState};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum InvocationStrategy {
|
||||
Once,
|
||||
#[default]
|
||||
PerWorkspace,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum InvocationLocation {
|
||||
Root(AbsPathBuf),
|
||||
#[default]
|
||||
Workspace,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CargoOptions {
|
||||
pub target_triples: Vec<String>,
|
||||
pub all_targets: bool,
|
||||
pub no_default_features: bool,
|
||||
pub all_features: bool,
|
||||
pub features: Vec<String>,
|
||||
pub extra_args: Vec<String>,
|
||||
pub extra_env: FxHashMap<String, String>,
|
||||
pub target_dir: Option<Utf8PathBuf>,
|
||||
}
|
||||
|
||||
impl CargoOptions {
|
||||
fn apply_on_command(&self, cmd: &mut Command) {
|
||||
for target in &self.target_triples {
|
||||
cmd.args(["--target", target.as_str()]);
|
||||
}
|
||||
if self.all_targets {
|
||||
cmd.arg("--all-targets");
|
||||
}
|
||||
if self.all_features {
|
||||
cmd.arg("--all-features");
|
||||
} else {
|
||||
if self.no_default_features {
|
||||
cmd.arg("--no-default-features");
|
||||
}
|
||||
if !self.features.is_empty() {
|
||||
cmd.arg("--features");
|
||||
cmd.arg(self.features.join(" "));
|
||||
}
|
||||
}
|
||||
if let Some(target_dir) = &self.target_dir {
|
||||
cmd.arg("--target-dir").arg(target_dir);
|
||||
}
|
||||
cmd.envs(&self.extra_env);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FlycheckConfig {
|
||||
CargoCommand {
|
||||
command: String,
|
||||
options: CargoOptions,
|
||||
ansi_color_output: bool,
|
||||
},
|
||||
CustomCommand {
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
extra_env: FxHashMap<String, String>,
|
||||
invocation_strategy: InvocationStrategy,
|
||||
invocation_location: InvocationLocation,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for FlycheckConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {command}"),
|
||||
FlycheckConfig::CustomCommand { command, args, .. } => {
|
||||
write!(f, "{command} {}", args.join(" "))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Flycheck wraps the shared state and communication machinery used for
|
||||
/// running `cargo check` (or other compatible command) and providing
|
||||
/// diagnostics based on the output.
|
||||
/// The spawned thread is shut down when this struct is dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct FlycheckHandle {
|
||||
// XXX: drop order is significant
|
||||
sender: Sender<StateChange>,
|
||||
_thread: stdx::thread::JoinHandle,
|
||||
id: usize,
|
||||
}
|
||||
|
||||
impl FlycheckHandle {
|
||||
pub fn spawn(
|
||||
id: usize,
|
||||
sender: Box<dyn Fn(Message) + Send>,
|
||||
config: FlycheckConfig,
|
||||
sysroot_root: Option<AbsPathBuf>,
|
||||
workspace_root: AbsPathBuf,
|
||||
) -> FlycheckHandle {
|
||||
let actor = FlycheckActor::new(id, sender, config, sysroot_root, workspace_root);
|
||||
let (sender, receiver) = unbounded::<StateChange>();
|
||||
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
|
||||
.name("Flycheck".to_owned())
|
||||
.spawn(move || actor.run(receiver))
|
||||
.expect("failed to spawn thread");
|
||||
FlycheckHandle { id, sender, _thread: thread }
|
||||
}
|
||||
|
||||
/// Schedule a re-start of the cargo check worker to do a workspace wide check.
|
||||
pub fn restart_workspace(&self, saved_file: Option<AbsPathBuf>) {
|
||||
self.sender.send(StateChange::Restart { package: None, saved_file }).unwrap();
|
||||
}
|
||||
|
||||
/// Schedule a re-start of the cargo check worker to do a package wide check.
|
||||
pub fn restart_for_package(&self, package: String) {
|
||||
self.sender
|
||||
.send(StateChange::Restart { package: Some(package), saved_file: None })
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Stop this cargo check worker.
|
||||
pub fn cancel(&self) {
|
||||
self.sender.send(StateChange::Cancel).unwrap();
|
||||
}
|
||||
|
||||
pub fn id(&self) -> usize {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
/// Request adding a diagnostic with fixes included to a file
|
||||
AddDiagnostic { id: usize, workspace_root: AbsPathBuf, diagnostic: Diagnostic },
|
||||
|
||||
/// Request check progress notification to client
|
||||
Progress {
|
||||
/// Flycheck instance ID
|
||||
id: usize,
|
||||
progress: Progress,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Debug for Message {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Message::AddDiagnostic { id, workspace_root, diagnostic } => f
|
||||
.debug_struct("AddDiagnostic")
|
||||
.field("id", id)
|
||||
.field("workspace_root", workspace_root)
|
||||
.field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
|
||||
.finish(),
|
||||
Message::Progress { id, progress } => {
|
||||
f.debug_struct("Progress").field("id", id).field("progress", progress).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Progress {
|
||||
DidStart,
|
||||
DidCheckCrate(String),
|
||||
DidFinish(io::Result<()>),
|
||||
DidCancel,
|
||||
DidFailToRestart(String),
|
||||
}
|
||||
|
||||
enum StateChange {
|
||||
Restart { package: Option<String>, saved_file: Option<AbsPathBuf> },
|
||||
Cancel,
|
||||
}
|
||||
|
||||
/// A [`FlycheckActor`] is a single check instance of a workspace.
|
||||
struct FlycheckActor {
|
||||
/// The workspace id of this flycheck instance.
|
||||
id: usize,
|
||||
sender: Box<dyn Fn(Message) + Send>,
|
||||
config: FlycheckConfig,
|
||||
/// Either the workspace root of the workspace we are flychecking,
|
||||
/// or the project root of the project.
|
||||
root: AbsPathBuf,
|
||||
sysroot_root: Option<AbsPathBuf>,
|
||||
/// CargoHandle exists to wrap around the communication needed to be able to
|
||||
/// run `cargo check` without blocking. Currently the Rust standard library
|
||||
/// doesn't provide a way to read sub-process output without blocking, so we
|
||||
/// have to wrap sub-processes output handling in a thread and pass messages
|
||||
/// back over a channel.
|
||||
command_handle: Option<CommandHandle<CargoCheckMessage>>,
|
||||
/// The receiver side of the channel mentioned above.
|
||||
command_receiver: Option<Receiver<CargoCheckMessage>>,
|
||||
}
|
||||
|
||||
enum Event {
|
||||
RequestStateChange(StateChange),
|
||||
CheckEvent(Option<CargoCheckMessage>),
|
||||
}
|
||||
|
||||
const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
|
||||
|
||||
impl FlycheckActor {
|
||||
fn new(
|
||||
id: usize,
|
||||
sender: Box<dyn Fn(Message) + Send>,
|
||||
config: FlycheckConfig,
|
||||
sysroot_root: Option<AbsPathBuf>,
|
||||
workspace_root: AbsPathBuf,
|
||||
) -> FlycheckActor {
|
||||
tracing::info!(%id, ?workspace_root, "Spawning flycheck");
|
||||
FlycheckActor {
|
||||
id,
|
||||
sender,
|
||||
config,
|
||||
sysroot_root,
|
||||
root: workspace_root,
|
||||
command_handle: None,
|
||||
command_receiver: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn report_progress(&self, progress: Progress) {
|
||||
self.send(Message::Progress { id: self.id, progress });
|
||||
}
|
||||
|
||||
fn next_event(&self, inbox: &Receiver<StateChange>) -> Option<Event> {
|
||||
if let Ok(msg) = inbox.try_recv() {
|
||||
// give restarts a preference so check outputs don't block a restart or stop
|
||||
return Some(Event::RequestStateChange(msg));
|
||||
}
|
||||
select! {
|
||||
recv(inbox) -> msg => msg.ok().map(Event::RequestStateChange),
|
||||
recv(self.command_receiver.as_ref().unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(mut self, inbox: Receiver<StateChange>) {
|
||||
'event: while let Some(event) = self.next_event(&inbox) {
|
||||
match event {
|
||||
Event::RequestStateChange(StateChange::Cancel) => {
|
||||
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
|
||||
self.cancel_check_process();
|
||||
}
|
||||
Event::RequestStateChange(StateChange::Restart { package, saved_file }) => {
|
||||
// Cancel the previously spawned process
|
||||
self.cancel_check_process();
|
||||
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
|
||||
// restart chained with a stop, so just cancel
|
||||
if let StateChange::Cancel = restart {
|
||||
continue 'event;
|
||||
}
|
||||
}
|
||||
|
||||
let command =
|
||||
match self.check_command(package.as_deref(), saved_file.as_deref()) {
|
||||
Some(c) => c,
|
||||
None => continue,
|
||||
};
|
||||
let formatted_command = format!("{:?}", command);
|
||||
|
||||
tracing::debug!(?command, "will restart flycheck");
|
||||
let (sender, receiver) = unbounded();
|
||||
match CommandHandle::spawn(command, sender) {
|
||||
Ok(command_handle) => {
|
||||
tracing::debug!(command = formatted_command, "did restart flycheck");
|
||||
self.command_handle = Some(command_handle);
|
||||
self.command_receiver = Some(receiver);
|
||||
self.report_progress(Progress::DidStart);
|
||||
}
|
||||
Err(error) => {
|
||||
self.report_progress(Progress::DidFailToRestart(format!(
|
||||
"Failed to run the following command: {} error={}",
|
||||
formatted_command, error
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::CheckEvent(None) => {
|
||||
tracing::debug!(flycheck_id = self.id, "flycheck finished");
|
||||
|
||||
// Watcher finished
|
||||
let command_handle = self.command_handle.take().unwrap();
|
||||
self.command_receiver.take();
|
||||
let formatted_handle = format!("{:?}", command_handle);
|
||||
|
||||
let res = command_handle.join();
|
||||
if let Err(error) = &res {
|
||||
tracing::error!(
|
||||
"Flycheck failed to run the following command: {}, error={}",
|
||||
formatted_handle,
|
||||
error
|
||||
);
|
||||
}
|
||||
self.report_progress(Progress::DidFinish(res));
|
||||
}
|
||||
Event::CheckEvent(Some(message)) => match message {
|
||||
CargoCheckMessage::CompilerArtifact(msg) => {
|
||||
tracing::trace!(
|
||||
flycheck_id = self.id,
|
||||
artifact = msg.target.name,
|
||||
"artifact received"
|
||||
);
|
||||
self.report_progress(Progress::DidCheckCrate(msg.target.name));
|
||||
}
|
||||
|
||||
CargoCheckMessage::Diagnostic(msg) => {
|
||||
tracing::trace!(
|
||||
flycheck_id = self.id,
|
||||
message = msg.message,
|
||||
"diagnostic received"
|
||||
);
|
||||
self.send(Message::AddDiagnostic {
|
||||
id: self.id,
|
||||
workspace_root: self.root.clone(),
|
||||
diagnostic: msg,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
// If we rerun the thread, we need to discard the previous check results first
|
||||
self.cancel_check_process();
|
||||
}
|
||||
|
||||
fn cancel_check_process(&mut self) {
|
||||
if let Some(command_handle) = self.command_handle.take() {
|
||||
tracing::debug!(
|
||||
command = ?command_handle,
|
||||
"did cancel flycheck"
|
||||
);
|
||||
command_handle.cancel();
|
||||
self.report_progress(Progress::DidCancel);
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a `Command` object for checking the user's code. If the user
|
||||
/// has specified a custom command with placeholders that we cannot fill,
|
||||
/// return None.
|
||||
fn check_command(
|
||||
&self,
|
||||
package: Option<&str>,
|
||||
saved_file: Option<&AbsPath>,
|
||||
) -> Option<Command> {
|
||||
let (mut cmd, args) = match &self.config {
|
||||
FlycheckConfig::CargoCommand { command, options, ansi_color_output } => {
|
||||
let mut cmd = Command::new(Tool::Cargo.path());
|
||||
if let Some(sysroot_root) = &self.sysroot_root {
|
||||
cmd.env("RUSTUP_TOOLCHAIN", AsRef::<std::path::Path>::as_ref(sysroot_root));
|
||||
}
|
||||
cmd.arg(command);
|
||||
cmd.current_dir(&self.root);
|
||||
|
||||
match package {
|
||||
Some(pkg) => cmd.arg("-p").arg(pkg),
|
||||
None => cmd.arg("--workspace"),
|
||||
};
|
||||
|
||||
cmd.arg(if *ansi_color_output {
|
||||
"--message-format=json-diagnostic-rendered-ansi"
|
||||
} else {
|
||||
"--message-format=json"
|
||||
});
|
||||
|
||||
cmd.arg("--manifest-path");
|
||||
cmd.arg(self.root.join("Cargo.toml"));
|
||||
|
||||
options.apply_on_command(&mut cmd);
|
||||
(cmd, options.extra_args.clone())
|
||||
}
|
||||
FlycheckConfig::CustomCommand {
|
||||
command,
|
||||
args,
|
||||
extra_env,
|
||||
invocation_strategy,
|
||||
invocation_location,
|
||||
} => {
|
||||
let mut cmd = Command::new(command);
|
||||
cmd.envs(extra_env);
|
||||
|
||||
match invocation_location {
|
||||
InvocationLocation::Workspace => {
|
||||
match invocation_strategy {
|
||||
InvocationStrategy::Once => {
|
||||
cmd.current_dir(&self.root);
|
||||
}
|
||||
InvocationStrategy::PerWorkspace => {
|
||||
// FIXME: cmd.current_dir(&affected_workspace);
|
||||
cmd.current_dir(&self.root);
|
||||
}
|
||||
}
|
||||
}
|
||||
InvocationLocation::Root(root) => {
|
||||
cmd.current_dir(root);
|
||||
}
|
||||
}
|
||||
|
||||
if args.contains(&SAVED_FILE_PLACEHOLDER.to_owned()) {
|
||||
// If the custom command has a $saved_file placeholder, and
|
||||
// we're saving a file, replace the placeholder in the arguments.
|
||||
if let Some(saved_file) = saved_file {
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
if arg == SAVED_FILE_PLACEHOLDER {
|
||||
saved_file.to_string()
|
||||
} else {
|
||||
arg.clone()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
(cmd, args)
|
||||
} else {
|
||||
// The custom command has a $saved_file placeholder,
|
||||
// but we had an IDE event that wasn't a file save. Do nothing.
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
(cmd, args.clone())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cmd.args(args);
|
||||
Some(cmd)
|
||||
}
|
||||
|
||||
fn send(&self, check_task: Message) {
|
||||
(self.sender)(check_task);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum CargoCheckMessage {
|
||||
CompilerArtifact(cargo_metadata::Artifact),
|
||||
Diagnostic(Diagnostic),
|
||||
}
|
||||
|
||||
impl ParseFromLine for CargoCheckMessage {
|
||||
fn from_line(line: &str, error: &mut String) -> Option<Self> {
|
||||
let mut deserializer = serde_json::Deserializer::from_str(line);
|
||||
deserializer.disable_recursion_limit();
|
||||
if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
|
||||
return match message {
|
||||
// Skip certain kinds of messages to only spend time on what's useful
|
||||
JsonMessage::Cargo(message) => match message {
|
||||
cargo_metadata::Message::CompilerArtifact(artifact) if !artifact.fresh => {
|
||||
Some(CargoCheckMessage::CompilerArtifact(artifact))
|
||||
}
|
||||
cargo_metadata::Message::CompilerMessage(msg) => {
|
||||
Some(CargoCheckMessage::Diagnostic(msg.message))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
JsonMessage::Rustc(message) => Some(CargoCheckMessage::Diagnostic(message)),
|
||||
};
|
||||
}
|
||||
|
||||
error.push_str(line);
|
||||
error.push('\n');
|
||||
None
|
||||
}
|
||||
|
||||
fn from_eof() -> Option<Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum JsonMessage {
|
||||
Cargo(cargo_metadata::Message),
|
||||
Rustc(Diagnostic),
|
||||
}
|
||||
88
src/tools/rust-analyzer/crates/flycheck/src/test_runner.rs
Normal file
88
src/tools/rust-analyzer/crates/flycheck/src/test_runner.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//! This module provides the functionality needed to run `cargo test` in a background
|
||||
//! thread and report the result of each test in a channel.
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
use paths::AbsPath;
|
||||
use serde::Deserialize;
|
||||
use toolchain::Tool;
|
||||
|
||||
use crate::{
|
||||
command::{CommandHandle, ParseFromLine},
|
||||
CargoOptions,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "event", rename_all = "camelCase")]
|
||||
pub enum TestState {
|
||||
Started,
|
||||
Ok,
|
||||
Ignored,
|
||||
Failed { stdout: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum CargoTestMessage {
|
||||
Test {
|
||||
name: String,
|
||||
#[serde(flatten)]
|
||||
state: TestState,
|
||||
},
|
||||
Suite,
|
||||
Finished,
|
||||
Custom {
|
||||
text: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl ParseFromLine for CargoTestMessage {
|
||||
fn from_line(line: &str, _: &mut String) -> Option<Self> {
|
||||
let mut deserializer = serde_json::Deserializer::from_str(line);
|
||||
deserializer.disable_recursion_limit();
|
||||
if let Ok(message) = CargoTestMessage::deserialize(&mut deserializer) {
|
||||
return Some(message);
|
||||
}
|
||||
|
||||
Some(CargoTestMessage::Custom { text: line.to_owned() })
|
||||
}
|
||||
|
||||
fn from_eof() -> Option<Self> {
|
||||
Some(CargoTestMessage::Finished)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CargoTestHandle {
|
||||
_handle: CommandHandle<CargoTestMessage>,
|
||||
}
|
||||
|
||||
// Example of a cargo test command:
|
||||
// cargo test --workspace --no-fail-fast -- module::func -Z unstable-options --format=json
|
||||
|
||||
impl CargoTestHandle {
|
||||
pub fn new(
|
||||
path: Option<&str>,
|
||||
options: CargoOptions,
|
||||
root: &AbsPath,
|
||||
sender: Sender<CargoTestMessage>,
|
||||
) -> std::io::Result<Self> {
|
||||
let mut cmd = Command::new(Tool::Cargo.path());
|
||||
cmd.env("RUSTC_BOOTSTRAP", "1");
|
||||
cmd.arg("test");
|
||||
cmd.arg("--workspace");
|
||||
// --no-fail-fast is needed to ensure that all requested tests will run
|
||||
cmd.arg("--no-fail-fast");
|
||||
cmd.arg("--manifest-path");
|
||||
cmd.arg(root.join("Cargo.toml"));
|
||||
options.apply_on_command(&mut cmd);
|
||||
cmd.arg("--");
|
||||
if let Some(path) = path {
|
||||
cmd.arg(path);
|
||||
}
|
||||
cmd.args(["-Z", "unstable-options"]);
|
||||
cmd.arg("--format=json");
|
||||
Ok(Self { _handle: CommandHandle::spawn(cmd, sender)? })
|
||||
}
|
||||
}
|
||||
60
src/tools/rust-analyzer/crates/hir-def/Cargo.toml
Normal file
60
src/tools/rust-analyzer/crates/hir-def/Cargo.toml
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
[package]
|
||||
name = "hir-def"
|
||||
version = "0.0.0"
|
||||
description = "TBD"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
arrayvec.workspace = true
|
||||
bitflags.workspace = true
|
||||
cov-mark = "2.0.0-pre.1"
|
||||
dashmap.workspace = true
|
||||
drop_bomb = "0.1.5"
|
||||
either.workspace = true
|
||||
fst = { version = "0.4.7", default-features = false }
|
||||
indexmap.workspace = true
|
||||
itertools.workspace = true
|
||||
la-arena.workspace = true
|
||||
once_cell = "1.17.0"
|
||||
rustc-hash.workspace = true
|
||||
tracing.workspace = true
|
||||
smallvec.workspace = true
|
||||
hashbrown.workspace = true
|
||||
triomphe.workspace = true
|
||||
|
||||
ra-ap-rustc_parse_format.workspace = true
|
||||
ra-ap-rustc_abi.workspace = true
|
||||
|
||||
# local deps
|
||||
stdx.workspace = true
|
||||
intern.workspace = true
|
||||
base-db.workspace = true
|
||||
syntax.workspace = true
|
||||
profile.workspace = true
|
||||
hir-expand.workspace = true
|
||||
mbe.workspace = true
|
||||
cfg.workspace = true
|
||||
tt.workspace = true
|
||||
limit.workspace = true
|
||||
span.workspace = true
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
expect-test.workspace = true
|
||||
|
||||
# local deps
|
||||
test-utils.workspace = true
|
||||
test-fixture.workspace = true
|
||||
|
||||
[features]
|
||||
in-rust-tree = ["hir-expand/in-rust-tree"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
648
src/tools/rust-analyzer/crates/hir-def/src/attr.rs
Normal file
648
src/tools/rust-analyzer/crates/hir-def/src/attr.rs
Normal file
|
|
@ -0,0 +1,648 @@
|
|||
//! A higher level attributes based on TokenTree, with also some shortcuts.
|
||||
|
||||
pub mod builtin;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::{borrow::Cow, hash::Hash, ops, slice::Iter as SliceIter};
|
||||
|
||||
use base_db::CrateId;
|
||||
use cfg::{CfgExpr, CfgOptions};
|
||||
use either::Either;
|
||||
use hir_expand::{
|
||||
attrs::{collect_attrs, Attr, AttrId, RawAttrs},
|
||||
HirFileId, InFile,
|
||||
};
|
||||
use la_arena::{ArenaMap, Idx, RawIdx};
|
||||
use mbe::DelimiterKind;
|
||||
use syntax::{
|
||||
ast::{self, HasAttrs},
|
||||
AstPtr, SmolStr,
|
||||
};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
item_tree::{AttrOwner, Fields, ItemTreeNode},
|
||||
lang_item::LangItem,
|
||||
nameres::{ModuleOrigin, ModuleSource},
|
||||
src::{HasChildSource, HasSource},
|
||||
AdtId, AttrDefId, GenericParamId, HasModule, ItemTreeLoc, LocalFieldId, Lookup, MacroId,
|
||||
VariantId,
|
||||
};
|
||||
|
||||
/// Desugared attributes of an item post `cfg_attr` expansion.
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Attrs(RawAttrs);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AttrsWithOwner {
|
||||
attrs: Attrs,
|
||||
owner: AttrDefId,
|
||||
}
|
||||
|
||||
impl Attrs {
|
||||
pub fn get(&self, id: AttrId) -> Option<&Attr> {
|
||||
(**self).iter().find(|attr| attr.id == id)
|
||||
}
|
||||
|
||||
pub(crate) fn filter(db: &dyn DefDatabase, krate: CrateId, raw_attrs: RawAttrs) -> Attrs {
|
||||
Attrs(raw_attrs.filter(db.upcast(), krate))
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Attrs {
|
||||
type Target = [Attr];
|
||||
|
||||
fn deref(&self) -> &[Attr] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for AttrsWithOwner {
|
||||
type Target = Attrs;
|
||||
|
||||
fn deref(&self) -> &Attrs {
|
||||
&self.attrs
|
||||
}
|
||||
}
|
||||
|
||||
impl Attrs {
|
||||
pub const EMPTY: Self = Self(RawAttrs::EMPTY);
|
||||
|
||||
pub(crate) fn fields_attrs_query(
|
||||
db: &dyn DefDatabase,
|
||||
v: VariantId,
|
||||
) -> Arc<ArenaMap<LocalFieldId, Attrs>> {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "fields_attrs_query").entered();
|
||||
// FIXME: There should be some proper form of mapping between item tree field ids and hir field ids
|
||||
let mut res = ArenaMap::default();
|
||||
|
||||
let crate_graph = db.crate_graph();
|
||||
let (fields, item_tree, krate) = match v {
|
||||
VariantId::EnumVariantId(it) => {
|
||||
let loc = it.lookup(db);
|
||||
let krate = loc.parent.lookup(db).container.krate;
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let variant = &item_tree[loc.id.value];
|
||||
(variant.fields.clone(), item_tree, krate)
|
||||
}
|
||||
VariantId::StructId(it) => {
|
||||
let loc = it.lookup(db);
|
||||
let krate = loc.container.krate;
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let struct_ = &item_tree[loc.id.value];
|
||||
(struct_.fields.clone(), item_tree, krate)
|
||||
}
|
||||
VariantId::UnionId(it) => {
|
||||
let loc = it.lookup(db);
|
||||
let krate = loc.container.krate;
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let union_ = &item_tree[loc.id.value];
|
||||
(union_.fields.clone(), item_tree, krate)
|
||||
}
|
||||
};
|
||||
|
||||
let fields = match fields {
|
||||
Fields::Record(fields) | Fields::Tuple(fields) => fields,
|
||||
Fields::Unit => return Arc::new(res),
|
||||
};
|
||||
|
||||
let cfg_options = &crate_graph[krate].cfg_options;
|
||||
|
||||
let mut idx = 0;
|
||||
for field in fields {
|
||||
let attrs = item_tree.attrs(db, krate, field.into());
|
||||
if attrs.is_cfg_enabled(cfg_options) {
|
||||
res.insert(Idx::from_raw(RawIdx::from(idx)), attrs);
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Arc::new(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Attrs {
|
||||
pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> {
|
||||
AttrQuery { attrs: self, key }
|
||||
}
|
||||
|
||||
pub fn cfg(&self) -> Option<CfgExpr> {
|
||||
let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse);
|
||||
let first = cfgs.next()?;
|
||||
match cfgs.next() {
|
||||
Some(second) => {
|
||||
let cfgs = [first, second].into_iter().chain(cfgs);
|
||||
Some(CfgExpr::All(cfgs.collect()))
|
||||
}
|
||||
None => Some(first),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cfgs(&self) -> impl Iterator<Item = CfgExpr> + '_ {
|
||||
self.by_key("cfg").tt_values().map(CfgExpr::parse)
|
||||
}
|
||||
|
||||
pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
|
||||
match self.cfg() {
|
||||
None => true,
|
||||
Some(cfg) => cfg_options.check(&cfg) != Some(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lang(&self) -> Option<&str> {
|
||||
self.by_key("lang").string_value()
|
||||
}
|
||||
|
||||
pub fn lang_item(&self) -> Option<LangItem> {
|
||||
self.by_key("lang").string_value().and_then(LangItem::from_str)
|
||||
}
|
||||
|
||||
pub fn has_doc_hidden(&self) -> bool {
|
||||
self.by_key("doc").tt_values().any(|tt| {
|
||||
tt.delimiter.kind == DelimiterKind::Parenthesis &&
|
||||
matches!(&*tt.token_trees, [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "hidden")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_doc_notable_trait(&self) -> bool {
|
||||
self.by_key("doc").tt_values().any(|tt| {
|
||||
tt.delimiter.kind == DelimiterKind::Parenthesis &&
|
||||
matches!(&*tt.token_trees, [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "notable_trait")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn doc_exprs(&self) -> impl Iterator<Item = DocExpr> + '_ {
|
||||
self.by_key("doc").tt_values().map(DocExpr::parse)
|
||||
}
|
||||
|
||||
pub fn doc_aliases(&self) -> impl Iterator<Item = SmolStr> + '_ {
|
||||
self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec())
|
||||
}
|
||||
|
||||
pub fn export_name(&self) -> Option<&str> {
|
||||
self.by_key("export_name").string_value()
|
||||
}
|
||||
|
||||
pub fn is_proc_macro(&self) -> bool {
|
||||
self.by_key("proc_macro").exists()
|
||||
}
|
||||
|
||||
pub fn is_proc_macro_attribute(&self) -> bool {
|
||||
self.by_key("proc_macro_attribute").exists()
|
||||
}
|
||||
|
||||
pub fn is_proc_macro_derive(&self) -> bool {
|
||||
self.by_key("proc_macro_derive").exists()
|
||||
}
|
||||
|
||||
pub fn is_test(&self) -> bool {
|
||||
self.iter().any(|it| {
|
||||
it.path()
|
||||
.segments()
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(["core", "prelude", "v1", "test"].iter().rev())
|
||||
.all(|it| it.0.as_str() == Some(it.1))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_ignore(&self) -> bool {
|
||||
self.by_key("ignore").exists()
|
||||
}
|
||||
|
||||
pub fn is_bench(&self) -> bool {
|
||||
self.by_key("bench").exists()
|
||||
}
|
||||
|
||||
pub fn is_unstable(&self) -> bool {
|
||||
self.by_key("unstable").exists()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub enum DocAtom {
|
||||
/// eg. `#[doc(hidden)]`
|
||||
Flag(SmolStr),
|
||||
/// eg. `#[doc(alias = "it")]`
|
||||
///
|
||||
/// Note that a key can have multiple values that are all considered "active" at the same time.
|
||||
/// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`.
|
||||
KeyValue { key: SmolStr, value: SmolStr },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum DocExpr {
|
||||
Invalid,
|
||||
/// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]`
|
||||
Atom(DocAtom),
|
||||
/// eg. `#[doc(alias("x", "y"))]`
|
||||
Alias(Vec<SmolStr>),
|
||||
}
|
||||
|
||||
impl From<DocAtom> for DocExpr {
|
||||
fn from(atom: DocAtom) -> Self {
|
||||
DocExpr::Atom(atom)
|
||||
}
|
||||
}
|
||||
|
||||
impl DocExpr {
|
||||
fn parse<S>(tt: &tt::Subtree<S>) -> DocExpr {
|
||||
next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid)
|
||||
}
|
||||
|
||||
pub fn aliases(&self) -> &[SmolStr] {
|
||||
match self {
|
||||
DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => {
|
||||
std::slice::from_ref(value)
|
||||
}
|
||||
DocExpr::Alias(aliases) => aliases,
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_doc_expr<S>(it: &mut SliceIter<'_, tt::TokenTree<S>>) -> Option<DocExpr> {
|
||||
let name = match it.next() {
|
||||
None => return None,
|
||||
Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(),
|
||||
Some(_) => return Some(DocExpr::Invalid),
|
||||
};
|
||||
|
||||
// Peek
|
||||
let ret = match it.as_slice().first() {
|
||||
Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
|
||||
match it.as_slice().get(1) {
|
||||
Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
|
||||
it.next();
|
||||
it.next();
|
||||
// FIXME: escape? raw string?
|
||||
let value =
|
||||
SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
|
||||
DocAtom::KeyValue { key: name, value }.into()
|
||||
}
|
||||
_ => return Some(DocExpr::Invalid),
|
||||
}
|
||||
}
|
||||
Some(tt::TokenTree::Subtree(subtree)) => {
|
||||
it.next();
|
||||
let subs = parse_comma_sep(subtree);
|
||||
match name.as_str() {
|
||||
"alias" => DocExpr::Alias(subs),
|
||||
_ => DocExpr::Invalid,
|
||||
}
|
||||
}
|
||||
_ => DocAtom::Flag(name).into(),
|
||||
};
|
||||
|
||||
// Eat comma separator
|
||||
if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
|
||||
if punct.char == ',' {
|
||||
it.next();
|
||||
}
|
||||
}
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
fn parse_comma_sep<S>(subtree: &tt::Subtree<S>) -> Vec<SmolStr> {
|
||||
subtree
|
||||
.token_trees
|
||||
.iter()
|
||||
.filter_map(|tt| match tt {
|
||||
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
|
||||
// FIXME: escape? raw string?
|
||||
Some(SmolStr::new(lit.text.trim_start_matches('"').trim_end_matches('"')))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl AttrsWithOwner {
|
||||
pub fn new(db: &dyn DefDatabase, owner: AttrDefId) -> Self {
|
||||
Self { attrs: db.attrs(owner), owner }
|
||||
}
|
||||
|
||||
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "attrs_query").entered();
|
||||
// FIXME: this should use `Trace` to avoid duplication in `source_map` below
|
||||
let raw_attrs = match def {
|
||||
AttrDefId::ModuleId(module) => {
|
||||
let def_map = module.def_map(db);
|
||||
let mod_data = &def_map[module.local_id];
|
||||
|
||||
match mod_data.origin {
|
||||
ModuleOrigin::File { definition, declaration_tree_id, .. } => {
|
||||
let decl_attrs = declaration_tree_id
|
||||
.item_tree(db)
|
||||
.raw_attrs(AttrOwner::ModItem(declaration_tree_id.value.into()))
|
||||
.clone();
|
||||
let tree = db.file_item_tree(definition.into());
|
||||
let def_attrs = tree.raw_attrs(AttrOwner::TopLevel).clone();
|
||||
decl_attrs.merge(def_attrs)
|
||||
}
|
||||
ModuleOrigin::CrateRoot { definition } => {
|
||||
let tree = db.file_item_tree(definition.into());
|
||||
tree.raw_attrs(AttrOwner::TopLevel).clone()
|
||||
}
|
||||
ModuleOrigin::Inline { definition_tree_id, .. } => definition_tree_id
|
||||
.item_tree(db)
|
||||
.raw_attrs(AttrOwner::ModItem(definition_tree_id.value.into()))
|
||||
.clone(),
|
||||
ModuleOrigin::BlockExpr { id, .. } => {
|
||||
let tree = db.block_item_tree(id);
|
||||
tree.raw_attrs(AttrOwner::TopLevel).clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
AttrDefId::FieldId(it) => {
|
||||
return db.fields_attrs(it.parent)[it.local_id].clone();
|
||||
}
|
||||
AttrDefId::EnumVariantId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::AdtId(it) => match it {
|
||||
AdtId::StructId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AdtId::EnumId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AdtId::UnionId(it) => attrs_from_item_tree_loc(db, it),
|
||||
},
|
||||
AttrDefId::TraitId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::TraitAliasId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::MacroId(it) => match it {
|
||||
MacroId::Macro2Id(it) => attrs_from_item_tree_loc(db, it),
|
||||
MacroId::MacroRulesId(it) => attrs_from_item_tree_loc(db, it),
|
||||
MacroId::ProcMacroId(it) => attrs_from_item_tree_loc(db, it),
|
||||
},
|
||||
AttrDefId::ImplId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::ConstId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::StaticId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::FunctionId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::TypeAliasId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::GenericParamId(it) => match it {
|
||||
GenericParamId::ConstParamId(it) => {
|
||||
let src = it.parent().child_source(db);
|
||||
// FIXME: We should be never getting `None` here.
|
||||
match src.value.get(it.local_id()) {
|
||||
Some(val) => RawAttrs::from_attrs_owner(
|
||||
db.upcast(),
|
||||
src.with_value(val),
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
),
|
||||
None => RawAttrs::EMPTY,
|
||||
}
|
||||
}
|
||||
GenericParamId::TypeParamId(it) => {
|
||||
let src = it.parent().child_source(db);
|
||||
// FIXME: We should be never getting `None` here.
|
||||
match src.value.get(it.local_id()) {
|
||||
Some(val) => RawAttrs::from_attrs_owner(
|
||||
db.upcast(),
|
||||
src.with_value(val),
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
),
|
||||
None => RawAttrs::EMPTY,
|
||||
}
|
||||
}
|
||||
GenericParamId::LifetimeParamId(it) => {
|
||||
let src = it.parent.child_source(db);
|
||||
// FIXME: We should be never getting `None` here.
|
||||
match src.value.get(it.local_id) {
|
||||
Some(val) => RawAttrs::from_attrs_owner(
|
||||
db.upcast(),
|
||||
src.with_value(val),
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
),
|
||||
None => RawAttrs::EMPTY,
|
||||
}
|
||||
}
|
||||
},
|
||||
AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::ExternCrateId(it) => attrs_from_item_tree_loc(db, it),
|
||||
AttrDefId::UseId(it) => attrs_from_item_tree_loc(db, it),
|
||||
};
|
||||
|
||||
let attrs = raw_attrs.filter(db.upcast(), def.krate(db));
|
||||
Attrs(attrs)
|
||||
}
|
||||
|
||||
pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap {
|
||||
let owner = match self.owner {
|
||||
AttrDefId::ModuleId(module) => {
|
||||
// Modules can have 2 attribute owners (the `mod x;` item, and the module file itself).
|
||||
|
||||
let def_map = module.def_map(db);
|
||||
let mod_data = &def_map[module.local_id];
|
||||
match mod_data.declaration_source(db) {
|
||||
Some(it) => {
|
||||
let mut map = AttrSourceMap::new(InFile::new(it.file_id, &it.value));
|
||||
if let InFile { file_id, value: ModuleSource::SourceFile(file) } =
|
||||
mod_data.definition_source(db)
|
||||
{
|
||||
map.append_module_inline_attrs(AttrSourceMap::new(InFile::new(
|
||||
file_id, &file,
|
||||
)));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
None => {
|
||||
let InFile { file_id, value } = mod_data.definition_source(db);
|
||||
let attrs_owner = match &value {
|
||||
ModuleSource::SourceFile(file) => file as &dyn ast::HasAttrs,
|
||||
ModuleSource::Module(module) => module as &dyn ast::HasAttrs,
|
||||
ModuleSource::BlockExpr(block) => block as &dyn ast::HasAttrs,
|
||||
};
|
||||
return AttrSourceMap::new(InFile::new(file_id, attrs_owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
AttrDefId::FieldId(id) => {
|
||||
let map = db.fields_attrs_source_map(id.parent);
|
||||
let file_id = id.parent.file_id(db);
|
||||
let root = db.parse_or_expand(file_id);
|
||||
let owner = ast::AnyHasAttrs::new(map[id.local_id].to_node(&root));
|
||||
InFile::new(file_id, owner)
|
||||
}
|
||||
AttrDefId::AdtId(adt) => match adt {
|
||||
AdtId::StructId(id) => any_has_attrs(db, id),
|
||||
AdtId::UnionId(id) => any_has_attrs(db, id),
|
||||
AdtId::EnumId(id) => any_has_attrs(db, id),
|
||||
},
|
||||
AttrDefId::FunctionId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::EnumVariantId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::StaticId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::ConstId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::TraitId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::TraitAliasId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::TypeAliasId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::MacroId(id) => match id {
|
||||
MacroId::Macro2Id(id) => any_has_attrs(db, id),
|
||||
MacroId::MacroRulesId(id) => any_has_attrs(db, id),
|
||||
MacroId::ProcMacroId(id) => any_has_attrs(db, id),
|
||||
},
|
||||
AttrDefId::ImplId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::GenericParamId(id) => match id {
|
||||
GenericParamId::ConstParamId(id) => id
|
||||
.parent()
|
||||
.child_source(db)
|
||||
.map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())),
|
||||
GenericParamId::TypeParamId(id) => id
|
||||
.parent()
|
||||
.child_source(db)
|
||||
.map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())),
|
||||
GenericParamId::LifetimeParamId(id) => id
|
||||
.parent
|
||||
.child_source(db)
|
||||
.map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())),
|
||||
},
|
||||
AttrDefId::ExternBlockId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::ExternCrateId(id) => any_has_attrs(db, id),
|
||||
AttrDefId::UseId(id) => any_has_attrs(db, id),
|
||||
};
|
||||
|
||||
AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AttrSourceMap {
|
||||
source: Vec<Either<ast::Attr, ast::Comment>>,
|
||||
file_id: HirFileId,
|
||||
/// If this map is for a module, this will be the [`HirFileId`] of the module's definition site,
|
||||
/// while `file_id` will be the one of the module declaration site.
|
||||
/// The usize is the index into `source` from which point on the entries reside in the def site
|
||||
/// file.
|
||||
mod_def_site_file_id: Option<(HirFileId, usize)>,
|
||||
}
|
||||
|
||||
impl AttrSourceMap {
|
||||
fn new(owner: InFile<&dyn ast::HasAttrs>) -> Self {
|
||||
Self {
|
||||
source: collect_attrs(owner.value).map(|(_, it)| it).collect(),
|
||||
file_id: owner.file_id,
|
||||
mod_def_site_file_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a second source map to this one, this is required for modules, whose outline and inline
|
||||
/// attributes can reside in different files
|
||||
fn append_module_inline_attrs(&mut self, other: Self) {
|
||||
assert!(self.mod_def_site_file_id.is_none() && other.mod_def_site_file_id.is_none());
|
||||
let len = self.source.len();
|
||||
self.source.extend(other.source);
|
||||
if other.file_id != self.file_id {
|
||||
self.mod_def_site_file_id = Some((other.file_id, len));
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps the lowered `Attr` back to its original syntax node.
|
||||
///
|
||||
/// `attr` must come from the `owner` used for AttrSourceMap
|
||||
///
|
||||
/// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
|
||||
/// the attribute represented by `Attr`.
|
||||
pub fn source_of(&self, attr: &Attr) -> InFile<&Either<ast::Attr, ast::Comment>> {
|
||||
self.source_of_id(attr.id)
|
||||
}
|
||||
|
||||
pub fn source_of_id(&self, id: AttrId) -> InFile<&Either<ast::Attr, ast::Comment>> {
|
||||
let ast_idx = id.ast_index();
|
||||
let file_id = match self.mod_def_site_file_id {
|
||||
Some((file_id, def_site_cut)) if def_site_cut <= ast_idx => file_id,
|
||||
_ => self.file_id,
|
||||
};
|
||||
|
||||
self.source
|
||||
.get(ast_idx)
|
||||
.map(|it| InFile::new(file_id, it))
|
||||
.unwrap_or_else(|| panic!("cannot find attr at index {id:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AttrQuery<'attr> {
|
||||
attrs: &'attr Attrs,
|
||||
key: &'static str,
|
||||
}
|
||||
|
||||
impl<'attr> AttrQuery<'attr> {
|
||||
pub fn tt_values(self) -> impl Iterator<Item = &'attr crate::tt::Subtree> {
|
||||
self.attrs().filter_map(|attr| attr.token_tree_value())
|
||||
}
|
||||
|
||||
pub fn string_value(self) -> Option<&'attr str> {
|
||||
self.attrs().find_map(|attr| attr.string_value())
|
||||
}
|
||||
|
||||
pub fn string_value_unescape(self) -> Option<Cow<'attr, str>> {
|
||||
self.attrs().find_map(|attr| attr.string_value_unescape())
|
||||
}
|
||||
|
||||
pub fn exists(self) -> bool {
|
||||
self.attrs().next().is_some()
|
||||
}
|
||||
|
||||
pub fn attrs(self) -> impl Iterator<Item = &'attr Attr> + Clone {
|
||||
let key = self.key;
|
||||
self.attrs
|
||||
.iter()
|
||||
.filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_smol_str() == key))
|
||||
}
|
||||
|
||||
/// Find string value for a specific key inside token tree
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[doc(html_root_url = "url")]
|
||||
/// ^^^^^^^^^^^^^ key
|
||||
/// ```
|
||||
pub fn find_string_value_in_tt(self, key: &'attr str) -> Option<&SmolStr> {
|
||||
self.tt_values().find_map(|tt| {
|
||||
let name = tt.token_trees.iter()
|
||||
.skip_while(|tt| !matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text, ..} )) if text == key))
|
||||
.nth(2);
|
||||
|
||||
match name {
|
||||
Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal{ ref text, ..}))) => Some(text),
|
||||
_ => None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn any_has_attrs<'db>(
|
||||
db: &(dyn DefDatabase + 'db),
|
||||
id: impl Lookup<
|
||||
Database<'db> = dyn DefDatabase + 'db,
|
||||
Data = impl HasSource<Value = impl ast::HasAttrs>,
|
||||
>,
|
||||
) -> InFile<ast::AnyHasAttrs> {
|
||||
id.lookup(db).source(db).map(ast::AnyHasAttrs::new)
|
||||
}
|
||||
|
||||
fn attrs_from_item_tree_loc<'db, N: ItemTreeNode>(
|
||||
db: &(dyn DefDatabase + 'db),
|
||||
lookup: impl Lookup<Database<'db> = dyn DefDatabase + 'db, Data = impl ItemTreeLoc<Id = N>>,
|
||||
) -> RawAttrs {
|
||||
let id = lookup.lookup(db).item_tree_id();
|
||||
let tree = id.item_tree(db);
|
||||
let attr_owner = N::attr_owner(id.value);
|
||||
tree.raw_attrs(attr_owner).clone()
|
||||
}
|
||||
|
||||
pub(crate) fn fields_attrs_source_map(
|
||||
db: &dyn DefDatabase,
|
||||
def: VariantId,
|
||||
) -> Arc<ArenaMap<LocalFieldId, AstPtr<Either<ast::TupleField, ast::RecordField>>>> {
|
||||
let mut res = ArenaMap::default();
|
||||
let child_source = def.child_source(db);
|
||||
|
||||
for (idx, variant) in child_source.value.iter() {
|
||||
res.insert(
|
||||
idx,
|
||||
variant
|
||||
.as_ref()
|
||||
.either(|l| AstPtr::new(l).wrap_left(), |r| AstPtr::new(r).wrap_right()),
|
||||
);
|
||||
}
|
||||
|
||||
Arc::new(res)
|
||||
}
|
||||
697
src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs
Normal file
697
src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs
Normal file
|
|
@ -0,0 +1,697 @@
|
|||
//! Builtin attributes resolved by nameres.
|
||||
//!
|
||||
//! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`.
|
||||
//!
|
||||
//! It was last synchronized with upstream commit c3def263a44e07e09ae6d57abfc8650227fb4972.
|
||||
//!
|
||||
//! The macros were adjusted to only expand to the attribute name, since that is all we need to do
|
||||
//! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to
|
||||
//! ease updating.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// Ignored attribute namespaces used by tools.
|
||||
pub const TOOL_MODULES: &[&str] = &["rustfmt", "clippy"];
|
||||
|
||||
pub struct BuiltinAttribute {
|
||||
pub name: &'static str,
|
||||
pub template: AttributeTemplate,
|
||||
}
|
||||
|
||||
/// A template that the attribute input must match.
|
||||
/// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AttributeTemplate {
|
||||
pub word: bool,
|
||||
pub list: Option<&'static str>,
|
||||
pub name_value_str: Option<&'static str>,
|
||||
}
|
||||
|
||||
pub fn find_builtin_attr_idx(name: &str) -> Option<usize> {
|
||||
static BUILTIN_LOOKUP_TABLE: OnceLock<FxHashMap<&'static str, usize>> = OnceLock::new();
|
||||
BUILTIN_LOOKUP_TABLE
|
||||
.get_or_init(|| {
|
||||
INERT_ATTRIBUTES.iter().map(|attr| attr.name).enumerate().map(|(a, b)| (b, a)).collect()
|
||||
})
|
||||
.get(name)
|
||||
.copied()
|
||||
}
|
||||
|
||||
// impl AttributeTemplate {
|
||||
// const DEFAULT: AttributeTemplate =
|
||||
// AttributeTemplate { word: false, list: None, name_value_str: None };
|
||||
// }
|
||||
|
||||
/// A convenience macro for constructing attribute templates.
|
||||
/// E.g., `template!(Word, List: "description")` means that the attribute
|
||||
/// supports forms `#[attr]` and `#[attr(description)]`.
|
||||
macro_rules! template {
|
||||
(Word) => { template!(@ true, None, None) };
|
||||
(List: $descr: expr) => { template!(@ false, Some($descr), None) };
|
||||
(NameValueStr: $descr: expr) => { template!(@ false, None, Some($descr)) };
|
||||
(Word, List: $descr: expr) => { template!(@ true, Some($descr), None) };
|
||||
(Word, NameValueStr: $descr: expr) => { template!(@ true, None, Some($descr)) };
|
||||
(List: $descr1: expr, NameValueStr: $descr2: expr) => {
|
||||
template!(@ false, Some($descr1), Some($descr2))
|
||||
};
|
||||
(Word, List: $descr1: expr, NameValueStr: $descr2: expr) => {
|
||||
template!(@ true, Some($descr1), Some($descr2))
|
||||
};
|
||||
(@ $word: expr, $list: expr, $name_value_str: expr) => {
|
||||
AttributeTemplate {
|
||||
word: $word, list: $list, name_value_str: $name_value_str
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! ungated {
|
||||
($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)? $(,)?) => {
|
||||
BuiltinAttribute { name: stringify!($attr), template: $tpl }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! gated {
|
||||
($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $gate:ident, $msg:expr $(,)?) => {
|
||||
BuiltinAttribute { name: stringify!($attr), template: $tpl }
|
||||
};
|
||||
($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $msg:expr $(,)?) => {
|
||||
BuiltinAttribute { name: stringify!($attr), template: $tpl }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! rustc_attr {
|
||||
(TEST, $attr:ident, $typ:expr, $tpl:expr, $duplicate:expr $(, @only_local: $only_local:expr)? $(,)?) => {
|
||||
rustc_attr!(
|
||||
$attr,
|
||||
$typ,
|
||||
$tpl,
|
||||
$duplicate,
|
||||
$(@only_local: $only_local,)?
|
||||
concat!(
|
||||
"the `#[",
|
||||
stringify!($attr),
|
||||
"]` attribute is just used for rustc unit tests \
|
||||
and will never be stable",
|
||||
),
|
||||
)
|
||||
};
|
||||
($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $msg:expr $(,)?) => {
|
||||
BuiltinAttribute { name: stringify!($attr), template: $tpl }
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! experimental {
|
||||
($attr:ident) => {
|
||||
concat!("the `#[", stringify!($attr), "]` attribute is an experimental feature")
|
||||
};
|
||||
}
|
||||
|
||||
/// Attributes that have a special meaning to rustc or rustdoc.
|
||||
#[rustfmt::skip]
|
||||
pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
||||
// ==========================================================================
|
||||
// Stable attributes:
|
||||
// ==========================================================================
|
||||
|
||||
// Conditional compilation:
|
||||
ungated!(cfg, Normal, template!(List: "predicate"), DuplicatesOk),
|
||||
ungated!(cfg_attr, Normal, template!(List: "predicate, attr1, attr2, ..."), DuplicatesOk),
|
||||
|
||||
// Testing:
|
||||
ungated!(ignore, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing),
|
||||
ungated!(
|
||||
should_panic, Normal,
|
||||
template!(Word, List: r#"expected = "reason""#, NameValueStr: "reason"), FutureWarnFollowing,
|
||||
),
|
||||
// FIXME(Centril): This can be used on stable but shouldn't.
|
||||
ungated!(reexport_test_harness_main, CrateLevel, template!(NameValueStr: "name"), ErrorFollowing),
|
||||
|
||||
// Macros:
|
||||
ungated!(automatically_derived, Normal, template!(Word), WarnFollowing),
|
||||
ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ..."), WarnFollowingWordOnly),
|
||||
ungated!(macro_escape, Normal, template!(Word), WarnFollowing), // Deprecated synonym for `macro_use`.
|
||||
ungated!(macro_export, Normal, template!(Word, List: "local_inner_macros"), WarnFollowing),
|
||||
ungated!(proc_macro, Normal, template!(Word), ErrorFollowing),
|
||||
ungated!(
|
||||
proc_macro_derive, Normal,
|
||||
template!(List: "TraitName, /*opt*/ attributes(name1, name2, ...)"), ErrorFollowing,
|
||||
),
|
||||
ungated!(proc_macro_attribute, Normal, template!(Word), ErrorFollowing),
|
||||
|
||||
// Lints:
|
||||
ungated!(
|
||||
warn, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#),
|
||||
DuplicatesOk, @only_local: true,
|
||||
),
|
||||
ungated!(
|
||||
allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#),
|
||||
DuplicatesOk, @only_local: true,
|
||||
),
|
||||
gated!(
|
||||
expect, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk,
|
||||
lint_reasons, experimental!(expect)
|
||||
),
|
||||
ungated!(
|
||||
forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#),
|
||||
DuplicatesOk, @only_local: true,
|
||||
),
|
||||
ungated!(
|
||||
deny, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#),
|
||||
DuplicatesOk, @only_local: true,
|
||||
),
|
||||
ungated!(must_use, Normal, template!(Word, NameValueStr: "reason"), FutureWarnFollowing),
|
||||
gated!(
|
||||
must_not_suspend, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing,
|
||||
experimental!(must_not_suspend)
|
||||
),
|
||||
ungated!(
|
||||
deprecated, Normal,
|
||||
template!(
|
||||
Word,
|
||||
List: r#"/*opt*/ since = "version", /*opt*/ note = "reason""#,
|
||||
NameValueStr: "reason"
|
||||
),
|
||||
ErrorFollowing
|
||||
),
|
||||
|
||||
// Crate properties:
|
||||
ungated!(crate_name, CrateLevel, template!(NameValueStr: "name"), FutureWarnFollowing),
|
||||
ungated!(crate_type, CrateLevel, template!(NameValueStr: "bin|lib|..."), DuplicatesOk),
|
||||
// crate_id is deprecated
|
||||
ungated!(crate_id, CrateLevel, template!(NameValueStr: "ignored"), FutureWarnFollowing),
|
||||
|
||||
// ABI, linking, symbols, and FFI
|
||||
ungated!(
|
||||
link, Normal,
|
||||
template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...", /*opt*/ import_name_type = "decorated|noprefix|undecorated""#),
|
||||
DuplicatesOk,
|
||||
),
|
||||
ungated!(link_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding),
|
||||
ungated!(no_link, Normal, template!(Word), WarnFollowing),
|
||||
ungated!(repr, Normal, template!(List: "C"), DuplicatesOk, @only_local: true),
|
||||
ungated!(export_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding),
|
||||
ungated!(link_section, Normal, template!(NameValueStr: "name"), FutureWarnPreceding),
|
||||
ungated!(no_mangle, Normal, template!(Word), WarnFollowing, @only_local: true),
|
||||
ungated!(used, Normal, template!(Word, List: "compiler|linker"), WarnFollowing, @only_local: true),
|
||||
ungated!(link_ordinal, Normal, template!(List: "ordinal"), ErrorPreceding),
|
||||
|
||||
// Limits:
|
||||
ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing),
|
||||
ungated!(type_length_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing),
|
||||
gated!(
|
||||
move_size_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing,
|
||||
large_assignments, experimental!(move_size_limit)
|
||||
),
|
||||
|
||||
// Entry point:
|
||||
gated!(unix_sigpipe, Normal, template!(Word, NameValueStr: "inherit|sig_ign|sig_dfl"), ErrorFollowing, experimental!(unix_sigpipe)),
|
||||
ungated!(start, Normal, template!(Word), WarnFollowing),
|
||||
ungated!(no_start, CrateLevel, template!(Word), WarnFollowing),
|
||||
ungated!(no_main, CrateLevel, template!(Word), WarnFollowing),
|
||||
|
||||
// Modules, prelude, and resolution:
|
||||
ungated!(path, Normal, template!(NameValueStr: "file"), FutureWarnFollowing),
|
||||
ungated!(no_std, CrateLevel, template!(Word), WarnFollowing),
|
||||
ungated!(no_implicit_prelude, Normal, template!(Word), WarnFollowing),
|
||||
ungated!(non_exhaustive, Normal, template!(Word), WarnFollowing),
|
||||
|
||||
// Runtime
|
||||
ungated!(
|
||||
windows_subsystem, CrateLevel,
|
||||
template!(NameValueStr: "windows|console"), FutureWarnFollowing
|
||||
),
|
||||
ungated!(panic_handler, Normal, template!(Word), WarnFollowing), // RFC 2070
|
||||
|
||||
// Code generation:
|
||||
ungated!(inline, Normal, template!(Word, List: "always|never"), FutureWarnFollowing, @only_local: true),
|
||||
ungated!(cold, Normal, template!(Word), WarnFollowing, @only_local: true),
|
||||
ungated!(no_builtins, CrateLevel, template!(Word), WarnFollowing),
|
||||
ungated!(
|
||||
target_feature, Normal, template!(List: r#"enable = "name""#),
|
||||
DuplicatesOk, @only_local: true,
|
||||
),
|
||||
ungated!(track_caller, Normal, template!(Word), WarnFollowing),
|
||||
ungated!(instruction_set, Normal, template!(List: "set"), ErrorPreceding),
|
||||
gated!(
|
||||
no_sanitize, Normal,
|
||||
template!(List: "address, kcfi, memory, thread"), DuplicatesOk,
|
||||
experimental!(no_sanitize)
|
||||
),
|
||||
gated!(coverage, Normal, template!(Word, List: "on|off"), WarnFollowing, coverage_attribute, experimental!(coverage)),
|
||||
|
||||
ungated!(
|
||||
doc, Normal, template!(List: "hidden|inline|...", NameValueStr: "string"), DuplicatesOk
|
||||
),
|
||||
|
||||
// Debugging
|
||||
ungated!(
|
||||
debugger_visualizer, Normal,
|
||||
template!(List: r#"natvis_file = "...", gdb_script_file = "...""#), DuplicatesOk
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Unstable attributes:
|
||||
// ==========================================================================
|
||||
|
||||
// Linking:
|
||||
gated!(
|
||||
naked, Normal, template!(Word), WarnFollowing, @only_local: true,
|
||||
naked_functions, experimental!(naked)
|
||||
),
|
||||
|
||||
// Testing:
|
||||
gated!(
|
||||
test_runner, CrateLevel, template!(List: "path"), ErrorFollowing, custom_test_frameworks,
|
||||
"custom test frameworks are an unstable feature",
|
||||
),
|
||||
// RFC #1268
|
||||
gated!(
|
||||
marker, Normal, template!(Word), WarnFollowing, @only_local: true,
|
||||
marker_trait_attr, experimental!(marker)
|
||||
),
|
||||
gated!(
|
||||
thread_local, Normal, template!(Word), WarnFollowing,
|
||||
"`#[thread_local]` is an experimental feature, and does not currently handle destructors",
|
||||
),
|
||||
gated!(no_core, CrateLevel, template!(Word), WarnFollowing, experimental!(no_core)),
|
||||
// RFC 2412
|
||||
gated!(
|
||||
optimize, Normal, template!(List: "size|speed"), ErrorPreceding, optimize_attribute,
|
||||
experimental!(optimize),
|
||||
),
|
||||
|
||||
gated!(ffi_pure, Normal, template!(Word), WarnFollowing, experimental!(ffi_pure)),
|
||||
gated!(ffi_const, Normal, template!(Word), WarnFollowing, experimental!(ffi_const)),
|
||||
gated!(
|
||||
register_tool, CrateLevel, template!(List: "tool1, tool2, ..."), DuplicatesOk,
|
||||
experimental!(register_tool),
|
||||
),
|
||||
|
||||
gated!(
|
||||
cmse_nonsecure_entry, Normal, template!(Word), WarnFollowing,
|
||||
experimental!(cmse_nonsecure_entry)
|
||||
),
|
||||
// RFC 2632
|
||||
gated!(
|
||||
const_trait, Normal, template!(Word), WarnFollowing, const_trait_impl,
|
||||
"`const_trait` is a temporary placeholder for marking a trait that is suitable for `const` \
|
||||
`impls` and all default bodies as `const`, which may be removed or renamed in the \
|
||||
future."
|
||||
),
|
||||
// lang-team MCP 147
|
||||
gated!(
|
||||
deprecated_safe, Normal, template!(List: r#"since = "version", note = "...""#), ErrorFollowing,
|
||||
experimental!(deprecated_safe),
|
||||
),
|
||||
|
||||
// `#[collapse_debuginfo]`
|
||||
gated!(
|
||||
collapse_debuginfo, Normal, template!(Word), WarnFollowing,
|
||||
experimental!(collapse_debuginfo)
|
||||
),
|
||||
|
||||
// RFC 2397
|
||||
gated!(do_not_recommend, Normal, template!(Word), WarnFollowing, experimental!(do_not_recommend)),
|
||||
|
||||
// `#[cfi_encoding = ""]`
|
||||
gated!(
|
||||
cfi_encoding, Normal, template!(NameValueStr: "encoding"), ErrorPreceding,
|
||||
experimental!(cfi_encoding)
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes: Stability, deprecation, and unsafe:
|
||||
// ==========================================================================
|
||||
|
||||
ungated!(
|
||||
feature, CrateLevel,
|
||||
template!(List: "name1, name2, ..."), DuplicatesOk, @only_local: true,
|
||||
),
|
||||
// DuplicatesOk since it has its own validation
|
||||
ungated!(
|
||||
stable, Normal,
|
||||
template!(List: r#"feature = "name", since = "version""#), DuplicatesOk, @only_local: true,
|
||||
),
|
||||
ungated!(
|
||||
unstable, Normal,
|
||||
template!(List: r#"feature = "name", reason = "...", issue = "N""#), DuplicatesOk,
|
||||
),
|
||||
ungated!(rustc_const_unstable, Normal, template!(List: r#"feature = "name""#), DuplicatesOk),
|
||||
ungated!(
|
||||
rustc_const_stable, Normal,
|
||||
template!(List: r#"feature = "name""#), DuplicatesOk, @only_local: true,
|
||||
),
|
||||
ungated!(
|
||||
rustc_default_body_unstable, Normal,
|
||||
template!(List: r#"feature = "name", reason = "...", issue = "N""#), DuplicatesOk
|
||||
),
|
||||
gated!(
|
||||
allow_internal_unstable, Normal, template!(Word, List: "feat1, feat2, ..."), DuplicatesOk,
|
||||
"allow_internal_unstable side-steps feature gating and stability checks",
|
||||
),
|
||||
gated!(
|
||||
rustc_allow_const_fn_unstable, Normal,
|
||||
template!(Word, List: "feat1, feat2, ..."), DuplicatesOk,
|
||||
"rustc_allow_const_fn_unstable side-steps feature gating and stability checks"
|
||||
),
|
||||
gated!(
|
||||
allow_internal_unsafe, Normal, template!(Word), WarnFollowing,
|
||||
"allow_internal_unsafe side-steps the unsafe_code lint",
|
||||
),
|
||||
rustc_attr!(rustc_allowed_through_unstable_modules, Normal, template!(Word), WarnFollowing,
|
||||
"rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \
|
||||
through unstable paths"),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes: Type system related:
|
||||
// ==========================================================================
|
||||
|
||||
gated!(fundamental, Normal, template!(Word), WarnFollowing, experimental!(fundamental)),
|
||||
gated!(
|
||||
may_dangle, Normal, template!(Word), WarnFollowing, dropck_eyepatch,
|
||||
"`may_dangle` has unstable semantics and may be removed in the future",
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes: Runtime related:
|
||||
// ==========================================================================
|
||||
|
||||
rustc_attr!(rustc_allocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL),
|
||||
rustc_attr!(rustc_nounwind, Normal, template!(Word), WarnFollowing, IMPL_DETAIL),
|
||||
rustc_attr!(rustc_reallocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL),
|
||||
rustc_attr!(rustc_deallocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL),
|
||||
rustc_attr!(rustc_allocator_zeroed, Normal, template!(Word), WarnFollowing, IMPL_DETAIL),
|
||||
gated!(
|
||||
default_lib_allocator, Normal, template!(Word), WarnFollowing, allocator_internals,
|
||||
experimental!(default_lib_allocator),
|
||||
),
|
||||
gated!(
|
||||
needs_allocator, Normal, template!(Word), WarnFollowing, allocator_internals,
|
||||
experimental!(needs_allocator),
|
||||
),
|
||||
gated!(panic_runtime, Normal, template!(Word), WarnFollowing, experimental!(panic_runtime)),
|
||||
gated!(
|
||||
needs_panic_runtime, Normal, template!(Word), WarnFollowing,
|
||||
experimental!(needs_panic_runtime)
|
||||
),
|
||||
gated!(
|
||||
compiler_builtins, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[compiler_builtins]` attribute is used to identify the `compiler_builtins` crate \
|
||||
which contains compiler-rt intrinsics and will never be stable",
|
||||
),
|
||||
gated!(
|
||||
profiler_runtime, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[profiler_runtime]` attribute is used to identify the `profiler_builtins` crate \
|
||||
which contains the profiler runtime and will never be stable",
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes, Linkage:
|
||||
// ==========================================================================
|
||||
|
||||
gated!(
|
||||
linkage, Normal, template!(NameValueStr: "external|internal|..."), ErrorPreceding, @only_local: true,
|
||||
"the `linkage` attribute is experimental and not portable across platforms",
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_std_internal_symbol, Normal, template!(Word), WarnFollowing, @only_local: true, INTERNAL_UNSTABLE
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes, Macro related:
|
||||
// ==========================================================================
|
||||
|
||||
rustc_attr!(
|
||||
rustc_builtin_macro, Normal,
|
||||
template!(Word, List: "name, /*opt*/ attributes(name1, name2, ...)"), ErrorFollowing,
|
||||
IMPL_DETAIL,
|
||||
),
|
||||
rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE),
|
||||
rustc_attr!(
|
||||
rustc_macro_transparency, Normal,
|
||||
template!(NameValueStr: "transparent|semitransparent|opaque"), ErrorFollowing,
|
||||
"used internally for testing macro hygiene",
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes, Diagnostics related:
|
||||
// ==========================================================================
|
||||
|
||||
rustc_attr!(
|
||||
rustc_on_unimplemented, Normal,
|
||||
template!(
|
||||
List: r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#,
|
||||
NameValueStr: "message"
|
||||
),
|
||||
ErrorFollowing,
|
||||
INTERNAL_UNSTABLE
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_confusables, Normal,
|
||||
template!(List: r#""name1", "name2", ..."#),
|
||||
ErrorFollowing,
|
||||
INTERNAL_UNSTABLE,
|
||||
),
|
||||
// Enumerates "identity-like" conversion methods to suggest on type mismatch.
|
||||
rustc_attr!(
|
||||
rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
|
||||
),
|
||||
// Prevents field reads in the marked trait or method to be considered
|
||||
// during dead code analysis.
|
||||
rustc_attr!(
|
||||
rustc_trivial_field_reads, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
|
||||
),
|
||||
// Used by the `rustc::potential_query_instability` lint to warn methods which
|
||||
// might not be stable during incremental compilation.
|
||||
rustc_attr!(rustc_lint_query_instability, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE),
|
||||
// Used by the `rustc::untranslatable_diagnostic` and `rustc::diagnostic_outside_of_impl` lints
|
||||
// to assist in changes to diagnostic APIs.
|
||||
rustc_attr!(rustc_lint_diagnostics, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE),
|
||||
// Used by the `rustc::bad_opt_access` lint to identify `DebuggingOptions` and `CodegenOptions`
|
||||
// types (as well as any others in future).
|
||||
rustc_attr!(rustc_lint_opt_ty, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE),
|
||||
// Used by the `rustc::bad_opt_access` lint on fields
|
||||
// types (as well as any others in future).
|
||||
rustc_attr!(rustc_lint_opt_deny_field_access, Normal, template!(List: "message"), WarnFollowing, INTERNAL_UNSTABLE),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes, Const related:
|
||||
// ==========================================================================
|
||||
|
||||
rustc_attr!(rustc_promotable, Normal, template!(Word), WarnFollowing, IMPL_DETAIL),
|
||||
rustc_attr!(
|
||||
rustc_legacy_const_generics, Normal, template!(List: "N"), ErrorFollowing,
|
||||
INTERNAL_UNSTABLE
|
||||
),
|
||||
// Do not const-check this function's body. It will always get replaced during CTFE.
|
||||
rustc_attr!(
|
||||
rustc_do_not_const_check, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
|
||||
),
|
||||
// Ensure the argument to this function is &&str during const-check.
|
||||
rustc_attr!(
|
||||
rustc_const_panic_str, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes, Layout related:
|
||||
// ==========================================================================
|
||||
|
||||
rustc_attr!(
|
||||
rustc_layout_scalar_valid_range_start, Normal, template!(List: "value"), ErrorFollowing,
|
||||
"the `#[rustc_layout_scalar_valid_range_start]` attribute is just used to enable \
|
||||
niche optimizations in libcore and libstd and will never be stable",
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_layout_scalar_valid_range_end, Normal, template!(List: "value"), ErrorFollowing,
|
||||
"the `#[rustc_layout_scalar_valid_range_end]` attribute is just used to enable \
|
||||
niche optimizations in libcore and libstd and will never be stable",
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_nonnull_optimization_guaranteed, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to enable \
|
||||
niche optimizations in libcore and libstd and will never be stable",
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes, Misc:
|
||||
// ==========================================================================
|
||||
gated!(
|
||||
lang, Normal, template!(NameValueStr: "name"), DuplicatesOk, @only_local: true, lang_items,
|
||||
"language items are subject to change",
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_pass_by_value, Normal, template!(Word), ErrorFollowing,
|
||||
"#[rustc_pass_by_value] is used to mark types that must be passed by value instead of reference."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_never_returns_null_ptr, Normal, template!(Word), ErrorFollowing,
|
||||
"#[rustc_never_returns_null_ptr] is used to mark functions returning non-null pointers."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, @only_local: true,
|
||||
"#![rustc_coherence_is_core] allows inherent methods on builtin types, only intended to be used in `core`."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_coinductive, AttributeType::Normal, template!(Word), WarnFollowing, @only_local: true,
|
||||
"#![rustc_coinductive] changes a trait to be coinductive, allowing cycles in the trait solver."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: true,
|
||||
"#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_deny_explicit_impl,
|
||||
AttributeType::Normal,
|
||||
template!(List: "implement_via_object = (true|false)"),
|
||||
ErrorFollowing,
|
||||
@only_local: true,
|
||||
"#[rustc_deny_explicit_impl] enforces that a trait can have no user-provided impls"
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_has_incoherent_inherent_impls, AttributeType::Normal, template!(Word), ErrorFollowing,
|
||||
"#[rustc_has_incoherent_inherent_impls] allows the addition of incoherent inherent impls for \
|
||||
the given type by annotating all impl items with #[rustc_allow_incoherent_impl]."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_box, AttributeType::Normal, template!(Word), ErrorFollowing,
|
||||
"#[rustc_box] allows creating boxes \
|
||||
and it is only intended to be used in `alloc`."
|
||||
),
|
||||
|
||||
BuiltinAttribute {
|
||||
// name: sym::rustc_diagnostic_item,
|
||||
name: "rustc_diagnostic_item",
|
||||
// FIXME: This can be `true` once we always use `tcx.is_diagnostic_item`.
|
||||
// only_local: false,
|
||||
// type_: Normal,
|
||||
template: template!(NameValueStr: "name"),
|
||||
// duplicates: ErrorFollowing,
|
||||
// gate: Gated(
|
||||
// Stability::Unstable,
|
||||
// sym::rustc_attrs,
|
||||
// "diagnostic items compiler internal support for linting",
|
||||
// cfg_fn!(rustc_attrs),
|
||||
// ),
|
||||
},
|
||||
gated!(
|
||||
// Used in resolve:
|
||||
prelude_import, Normal, template!(Word), WarnFollowing,
|
||||
"`#[prelude_import]` is for use by rustc only",
|
||||
),
|
||||
gated!(
|
||||
rustc_paren_sugar, Normal, template!(Word), WarnFollowing, unboxed_closures,
|
||||
"unboxed_closures are still evolving",
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_inherit_overflow_checks, Normal, template!(Word), WarnFollowing, @only_local: true,
|
||||
"the `#[rustc_inherit_overflow_checks]` attribute is just used to control \
|
||||
overflow checking behavior of several libcore functions that are inlined \
|
||||
across crates and will never be stable",
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_reservation_impl, Normal,
|
||||
template!(NameValueStr: "reservation message"), ErrorFollowing,
|
||||
"the `#[rustc_reservation_impl]` attribute is internally used \
|
||||
for reserving for `for<T> From<!> for T` impl"
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_test_marker, Normal, template!(NameValueStr: "name"), WarnFollowing,
|
||||
"the `#[rustc_test_marker]` attribute is used internally to track tests",
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_unsafe_specialization_marker, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[rustc_unsafe_specialization_marker]` attribute is used to check specializations"
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_specialization_trait, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[rustc_specialization_trait]` attribute is used to check specializations"
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_main, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[rustc_main]` attribute is used internally to specify test entry point function",
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_skip_array_during_method_dispatch, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \
|
||||
from method dispatch when the receiver is an array, for compatibility in editions < 2021."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."), ErrorFollowing,
|
||||
"the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \
|
||||
definition of a trait, it's currently in experimental form and should be changed before \
|
||||
being exposed outside of the std"
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_doc_primitive, Normal, template!(NameValueStr: "primitive name"), ErrorFollowing,
|
||||
r#"`rustc_doc_primitive` is a rustc internal attribute"#,
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_safe_intrinsic, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[rustc_safe_intrinsic]` attribute is used internally to mark intrinsics as safe"
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes, Testing:
|
||||
// ==========================================================================
|
||||
|
||||
rustc_attr!(TEST, rustc_effective_visibility, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_outlives, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_strict_coherence, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_variance, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_variance_of_opaques, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_hidden_type_of_opaques, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ..."), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_abi, Normal, template!(List: "field1, field2, ..."), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_regions, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(
|
||||
TEST, rustc_error, Normal,
|
||||
template!(Word, List: "delayed_bug_from_inside_query"), WarnFollowingWordOnly
|
||||
),
|
||||
rustc_attr!(TEST, rustc_dump_user_args, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_evaluate_where_clauses, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(
|
||||
TEST, rustc_if_this_changed, Normal, template!(Word, List: "DepNode"), DuplicatesOk
|
||||
),
|
||||
rustc_attr!(
|
||||
TEST, rustc_then_this_would_need, Normal, template!(List: "DepNode"), DuplicatesOk
|
||||
),
|
||||
rustc_attr!(
|
||||
TEST, rustc_clean, Normal,
|
||||
template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#),
|
||||
DuplicatesOk,
|
||||
),
|
||||
rustc_attr!(
|
||||
TEST, rustc_partition_reused, Normal,
|
||||
template!(List: r#"cfg = "...", module = "...""#), DuplicatesOk,
|
||||
),
|
||||
rustc_attr!(
|
||||
TEST, rustc_partition_codegened, Normal,
|
||||
template!(List: r#"cfg = "...", module = "...""#), DuplicatesOk,
|
||||
),
|
||||
rustc_attr!(
|
||||
TEST, rustc_expected_cgu_reuse, Normal,
|
||||
template!(List: r#"cfg = "...", module = "...", kind = "...""#), DuplicatesOk,
|
||||
),
|
||||
rustc_attr!(TEST, rustc_symbol_name, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_polymorphize_error, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_def_path, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_mir, Normal, template!(List: "arg1, arg2, ..."), DuplicatesOk),
|
||||
gated!(
|
||||
custom_mir, Normal, template!(List: r#"dialect = "...", phase = "...""#),
|
||||
ErrorFollowing, "the `#[custom_mir]` attribute is just used for the Rust test suite",
|
||||
),
|
||||
rustc_attr!(TEST, rustc_dump_program_clauses, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_dump_env_program_clauses, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_object_lifetime_default, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_dump_vtable, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_dummy, Normal, template!(Word /* doesn't matter*/), DuplicatesOk),
|
||||
gated!(
|
||||
omit_gdb_pretty_printer_section, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[omit_gdb_pretty_printer_section]` attribute is just used for the Rust test suite",
|
||||
),
|
||||
];
|
||||
47
src/tools/rust-analyzer/crates/hir-def/src/attr/tests.rs
Normal file
47
src/tools/rust-analyzer/crates/hir-def/src/attr/tests.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
//! This module contains tests for doc-expression parsing.
|
||||
//! Currently, it tests `#[doc(hidden)]` and `#[doc(alias)]`.
|
||||
|
||||
use triomphe::Arc;
|
||||
|
||||
use base_db::FileId;
|
||||
use hir_expand::span_map::{RealSpanMap, SpanMap};
|
||||
use mbe::syntax_node_to_token_tree;
|
||||
use syntax::{ast, AstNode, TextRange};
|
||||
|
||||
use crate::attr::{DocAtom, DocExpr};
|
||||
|
||||
fn assert_parse_result(input: &str, expected: DocExpr) {
|
||||
let source_file = ast::SourceFile::parse(input, span::Edition::CURRENT).ok().unwrap();
|
||||
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
|
||||
let map = SpanMap::RealSpanMap(Arc::new(RealSpanMap::absolute(FileId::from_raw(0))));
|
||||
let tt = syntax_node_to_token_tree(
|
||||
tt.syntax(),
|
||||
map.as_ref(),
|
||||
map.span_for_range(TextRange::empty(0.into())),
|
||||
);
|
||||
let cfg = DocExpr::parse(&tt);
|
||||
assert_eq!(cfg, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_doc_expr_parser() {
|
||||
assert_parse_result("#![doc(hidden)]", DocAtom::Flag("hidden".into()).into());
|
||||
|
||||
assert_parse_result(
|
||||
r#"#![doc(alias = "foo")]"#,
|
||||
DocAtom::KeyValue { key: "alias".into(), value: "foo".into() }.into(),
|
||||
);
|
||||
|
||||
assert_parse_result(r#"#![doc(alias("foo"))]"#, DocExpr::Alias(["foo".into()].into()));
|
||||
assert_parse_result(
|
||||
r#"#![doc(alias("foo", "bar", "baz"))]"#,
|
||||
DocExpr::Alias(["foo".into(), "bar".into(), "baz".into()].into()),
|
||||
);
|
||||
|
||||
assert_parse_result(
|
||||
r#"
|
||||
#[doc(alias("Bar", "Qux"))]
|
||||
struct Foo;"#,
|
||||
DocExpr::Alias(["Bar".into(), "Qux".into()].into()),
|
||||
);
|
||||
}
|
||||
431
src/tools/rust-analyzer/crates/hir-def/src/body.rs
Normal file
431
src/tools/rust-analyzer/crates/hir-def/src/body.rs
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
//! Defines `Body`: a lowered representation of bodies of functions, statics and
|
||||
//! consts.
|
||||
mod lower;
|
||||
mod pretty;
|
||||
pub mod scope;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::ops::Index;
|
||||
|
||||
use base_db::CrateId;
|
||||
use cfg::{CfgExpr, CfgOptions};
|
||||
use hir_expand::{name::Name, HirFileId, InFile};
|
||||
use la_arena::{Arena, ArenaMap};
|
||||
use rustc_hash::FxHashMap;
|
||||
use syntax::{ast, AstPtr, SyntaxNodePtr};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
expander::Expander,
|
||||
hir::{
|
||||
dummy_expr_id, Binding, BindingId, Expr, ExprId, Label, LabelId, Pat, PatId, RecordFieldPat,
|
||||
},
|
||||
nameres::DefMap,
|
||||
path::{ModPath, Path},
|
||||
src::HasSource,
|
||||
BlockId, DefWithBodyId, HasModule, Lookup,
|
||||
};
|
||||
|
||||
/// The body of an item (function, const etc.).
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Body {
|
||||
pub exprs: Arena<Expr>,
|
||||
pub pats: Arena<Pat>,
|
||||
pub bindings: Arena<Binding>,
|
||||
pub labels: Arena<Label>,
|
||||
/// Id of the closure/coroutine that owns the corresponding binding. If a binding is owned by the
|
||||
/// top level expression, it will not be listed in here.
|
||||
pub binding_owners: FxHashMap<BindingId, ExprId>,
|
||||
/// The patterns for the function's parameters. While the parameter types are
|
||||
/// part of the function signature, the patterns are not (they don't change
|
||||
/// the external type of the function).
|
||||
///
|
||||
/// If this `Body` is for the body of a constant, this will just be
|
||||
/// empty.
|
||||
pub params: Box<[PatId]>,
|
||||
pub self_param: Option<BindingId>,
|
||||
/// The `ExprId` of the actual body expression.
|
||||
pub body_expr: ExprId,
|
||||
/// Block expressions in this body that may contain inner items.
|
||||
block_scopes: Vec<BlockId>,
|
||||
}
|
||||
|
||||
pub type ExprPtr = AstPtr<ast::Expr>;
|
||||
pub type ExprSource = InFile<ExprPtr>;
|
||||
|
||||
pub type PatPtr = AstPtr<ast::Pat>;
|
||||
pub type PatSource = InFile<PatPtr>;
|
||||
|
||||
pub type LabelPtr = AstPtr<ast::Label>;
|
||||
pub type LabelSource = InFile<LabelPtr>;
|
||||
|
||||
pub type FieldPtr = AstPtr<ast::RecordExprField>;
|
||||
pub type FieldSource = InFile<FieldPtr>;
|
||||
|
||||
pub type PatFieldPtr = AstPtr<ast::RecordPatField>;
|
||||
pub type PatFieldSource = InFile<PatFieldPtr>;
|
||||
|
||||
/// An item body together with the mapping from syntax nodes to HIR expression
|
||||
/// IDs. This is needed to go from e.g. a position in a file to the HIR
|
||||
/// expression containing it; but for type inference etc., we want to operate on
|
||||
/// a structure that is agnostic to the actual positions of expressions in the
|
||||
/// file, so that we don't recompute types whenever some whitespace is typed.
|
||||
///
|
||||
/// One complication here is that, due to macro expansion, a single `Body` might
|
||||
/// be spread across several files. So, for each ExprId and PatId, we record
|
||||
/// both the HirFileId and the position inside the file. However, we only store
|
||||
/// AST -> ExprId mapping for non-macro files, as it is not clear how to handle
|
||||
/// this properly for macros.
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
pub struct BodySourceMap {
|
||||
expr_map: FxHashMap<ExprSource, ExprId>,
|
||||
expr_map_back: ArenaMap<ExprId, ExprSource>,
|
||||
|
||||
pat_map: FxHashMap<PatSource, PatId>,
|
||||
pat_map_back: ArenaMap<PatId, PatSource>,
|
||||
|
||||
label_map: FxHashMap<LabelSource, LabelId>,
|
||||
label_map_back: ArenaMap<LabelId, LabelSource>,
|
||||
|
||||
self_param: Option<InFile<AstPtr<ast::SelfParam>>>,
|
||||
|
||||
/// We don't create explicit nodes for record fields (`S { record_field: 92 }`).
|
||||
/// Instead, we use id of expression (`92`) to identify the field.
|
||||
field_map_back: FxHashMap<ExprId, FieldSource>,
|
||||
pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
|
||||
|
||||
format_args_template_map: FxHashMap<ExprId, Vec<(syntax::TextRange, Name)>>,
|
||||
|
||||
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
|
||||
|
||||
/// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
|
||||
/// the source map (since they're just as volatile).
|
||||
diagnostics: Vec<BodyDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct SyntheticSyntax;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum BodyDiagnostic {
|
||||
InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
|
||||
MacroError { node: InFile<AstPtr<ast::MacroCall>>, message: String },
|
||||
UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>>, krate: CrateId },
|
||||
UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
|
||||
UnreachableLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
|
||||
UndeclaredLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
|
||||
}
|
||||
|
||||
impl Body {
|
||||
pub(crate) fn body_with_source_map_query(
|
||||
db: &dyn DefDatabase,
|
||||
def: DefWithBodyId,
|
||||
) -> (Arc<Body>, Arc<BodySourceMap>) {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "body_with_source_map_query").entered();
|
||||
let mut params = None;
|
||||
|
||||
let mut is_async_fn = false;
|
||||
let InFile { file_id, value: body } = {
|
||||
match def {
|
||||
DefWithBodyId::FunctionId(f) => {
|
||||
let data = db.function_data(f);
|
||||
let f = f.lookup(db);
|
||||
let src = f.source(db);
|
||||
params = src.value.param_list().map(|param_list| {
|
||||
let item_tree = f.id.item_tree(db);
|
||||
let func = &item_tree[f.id.value];
|
||||
let krate = f.container.module(db).krate;
|
||||
let crate_graph = db.crate_graph();
|
||||
(
|
||||
param_list,
|
||||
func.params.clone().map(move |param| {
|
||||
item_tree
|
||||
.attrs(db, krate, param.into())
|
||||
.is_cfg_enabled(&crate_graph[krate].cfg_options)
|
||||
}),
|
||||
)
|
||||
});
|
||||
is_async_fn = data.has_async_kw();
|
||||
src.map(|it| it.body().map(ast::Expr::from))
|
||||
}
|
||||
DefWithBodyId::ConstId(c) => {
|
||||
let c = c.lookup(db);
|
||||
let src = c.source(db);
|
||||
src.map(|it| it.body())
|
||||
}
|
||||
DefWithBodyId::StaticId(s) => {
|
||||
let s = s.lookup(db);
|
||||
let src = s.source(db);
|
||||
src.map(|it| it.body())
|
||||
}
|
||||
DefWithBodyId::VariantId(v) => {
|
||||
let s = v.lookup(db);
|
||||
let src = s.source(db);
|
||||
src.map(|it| it.expr())
|
||||
}
|
||||
DefWithBodyId::InTypeConstId(c) => c.lookup(db).id.map(|_| c.source(db).expr()),
|
||||
}
|
||||
};
|
||||
let module = def.module(db);
|
||||
let expander = Expander::new(db, file_id, module);
|
||||
let (mut body, mut source_map) =
|
||||
Body::new(db, def, expander, params, body, module.krate, is_async_fn);
|
||||
body.shrink_to_fit();
|
||||
source_map.shrink_to_fit();
|
||||
|
||||
(Arc::new(body), Arc::new(source_map))
|
||||
}
|
||||
|
||||
pub(crate) fn body_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<Body> {
|
||||
db.body_with_source_map(def).0
|
||||
}
|
||||
|
||||
/// Returns an iterator over all block expressions in this body that define inner items.
|
||||
pub fn blocks<'a>(
|
||||
&'a self,
|
||||
db: &'a dyn DefDatabase,
|
||||
) -> impl Iterator<Item = (BlockId, Arc<DefMap>)> + '_ {
|
||||
self.block_scopes.iter().map(move |&block| (block, db.block_def_map(block)))
|
||||
}
|
||||
|
||||
pub fn pretty_print(&self, db: &dyn DefDatabase, owner: DefWithBodyId) -> String {
|
||||
pretty::print_body_hir(db, self, owner)
|
||||
}
|
||||
|
||||
pub fn pretty_print_expr(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
owner: DefWithBodyId,
|
||||
expr: ExprId,
|
||||
) -> String {
|
||||
pretty::print_expr_hir(db, self, owner, expr)
|
||||
}
|
||||
|
||||
fn new(
|
||||
db: &dyn DefDatabase,
|
||||
owner: DefWithBodyId,
|
||||
expander: Expander,
|
||||
params: Option<(ast::ParamList, impl Iterator<Item = bool>)>,
|
||||
body: Option<ast::Expr>,
|
||||
krate: CrateId,
|
||||
is_async_fn: bool,
|
||||
) -> (Body, BodySourceMap) {
|
||||
lower::lower(db, owner, expander, params, body, krate, is_async_fn)
|
||||
}
|
||||
|
||||
fn shrink_to_fit(&mut self) {
|
||||
let Self {
|
||||
body_expr: _,
|
||||
params: _,
|
||||
self_param: _,
|
||||
block_scopes,
|
||||
exprs,
|
||||
labels,
|
||||
pats,
|
||||
bindings,
|
||||
binding_owners,
|
||||
} = self;
|
||||
block_scopes.shrink_to_fit();
|
||||
exprs.shrink_to_fit();
|
||||
labels.shrink_to_fit();
|
||||
pats.shrink_to_fit();
|
||||
bindings.shrink_to_fit();
|
||||
binding_owners.shrink_to_fit();
|
||||
}
|
||||
|
||||
pub fn walk_bindings_in_pat(&self, pat_id: PatId, mut f: impl FnMut(BindingId)) {
|
||||
self.walk_pats(pat_id, &mut |pat| {
|
||||
if let Pat::Bind { id, .. } = &self[pat] {
|
||||
f(*id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn walk_pats_shallow(&self, pat_id: PatId, mut f: impl FnMut(PatId)) {
|
||||
let pat = &self[pat_id];
|
||||
match pat {
|
||||
Pat::Range { .. }
|
||||
| Pat::Lit(..)
|
||||
| Pat::Path(..)
|
||||
| Pat::ConstBlock(..)
|
||||
| Pat::Wild
|
||||
| Pat::Missing => {}
|
||||
&Pat::Bind { subpat, .. } => {
|
||||
if let Some(subpat) = subpat {
|
||||
f(subpat);
|
||||
}
|
||||
}
|
||||
Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => {
|
||||
args.iter().copied().for_each(f);
|
||||
}
|
||||
Pat::Ref { pat, .. } => f(*pat),
|
||||
Pat::Slice { prefix, slice, suffix } => {
|
||||
let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter());
|
||||
total_iter.copied().for_each(f);
|
||||
}
|
||||
Pat::Record { args, .. } => {
|
||||
args.iter().for_each(|RecordFieldPat { pat, .. }| f(*pat));
|
||||
}
|
||||
Pat::Box { inner } => f(*inner),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_pats(&self, pat_id: PatId, f: &mut impl FnMut(PatId)) {
|
||||
f(pat_id);
|
||||
self.walk_pats_shallow(pat_id, |p| self.walk_pats(p, f));
|
||||
}
|
||||
|
||||
pub fn is_binding_upvar(&self, binding: BindingId, relative_to: ExprId) -> bool {
|
||||
match self.binding_owners.get(&binding) {
|
||||
Some(it) => {
|
||||
// We assign expression ids in a way that outer closures will receive
|
||||
// a lower id
|
||||
it.into_raw() < relative_to.into_raw()
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Body {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
body_expr: dummy_expr_id(),
|
||||
exprs: Default::default(),
|
||||
pats: Default::default(),
|
||||
bindings: Default::default(),
|
||||
labels: Default::default(),
|
||||
params: Default::default(),
|
||||
block_scopes: Default::default(),
|
||||
binding_owners: Default::default(),
|
||||
self_param: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<ExprId> for Body {
|
||||
type Output = Expr;
|
||||
|
||||
fn index(&self, expr: ExprId) -> &Expr {
|
||||
&self.exprs[expr]
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<PatId> for Body {
|
||||
type Output = Pat;
|
||||
|
||||
fn index(&self, pat: PatId) -> &Pat {
|
||||
&self.pats[pat]
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<LabelId> for Body {
|
||||
type Output = Label;
|
||||
|
||||
fn index(&self, label: LabelId) -> &Label {
|
||||
&self.labels[label]
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<BindingId> for Body {
|
||||
type Output = Binding;
|
||||
|
||||
fn index(&self, b: BindingId) -> &Binding {
|
||||
&self.bindings[b]
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Change `node_` prefix to something more reasonable.
|
||||
// Perhaps `expr_syntax` and `expr_id`?
|
||||
impl BodySourceMap {
|
||||
pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprSource, SyntheticSyntax> {
|
||||
self.expr_map_back.get(expr).cloned().ok_or(SyntheticSyntax)
|
||||
}
|
||||
|
||||
pub fn node_expr(&self, node: InFile<&ast::Expr>) -> Option<ExprId> {
|
||||
let src = node.map(AstPtr::new);
|
||||
self.expr_map.get(&src).cloned()
|
||||
}
|
||||
|
||||
pub fn node_macro_file(&self, node: InFile<&ast::MacroCall>) -> Option<HirFileId> {
|
||||
let src = node.map(AstPtr::new);
|
||||
self.expansions.get(&src).cloned()
|
||||
}
|
||||
|
||||
pub fn pat_syntax(&self, pat: PatId) -> Result<PatSource, SyntheticSyntax> {
|
||||
self.pat_map_back.get(pat).cloned().ok_or(SyntheticSyntax)
|
||||
}
|
||||
|
||||
pub fn self_param_syntax(&self) -> Option<InFile<AstPtr<ast::SelfParam>>> {
|
||||
self.self_param
|
||||
}
|
||||
|
||||
pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<PatId> {
|
||||
self.pat_map.get(&node.map(AstPtr::new)).cloned()
|
||||
}
|
||||
|
||||
pub fn label_syntax(&self, label: LabelId) -> LabelSource {
|
||||
self.label_map_back[label]
|
||||
}
|
||||
|
||||
pub fn node_label(&self, node: InFile<&ast::Label>) -> Option<LabelId> {
|
||||
let src = node.map(AstPtr::new);
|
||||
self.label_map.get(&src).cloned()
|
||||
}
|
||||
|
||||
pub fn field_syntax(&self, expr: ExprId) -> FieldSource {
|
||||
self.field_map_back[&expr]
|
||||
}
|
||||
|
||||
pub fn pat_field_syntax(&self, pat: PatId) -> PatFieldSource {
|
||||
self.pat_field_map_back[&pat]
|
||||
}
|
||||
|
||||
pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroExpr>) -> Option<ExprId> {
|
||||
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::MacroExpr>).map(AstPtr::upcast);
|
||||
self.expr_map.get(&src).copied()
|
||||
}
|
||||
|
||||
pub fn implicit_format_args(
|
||||
&self,
|
||||
node: InFile<&ast::FormatArgsExpr>,
|
||||
) -> Option<&[(syntax::TextRange, Name)]> {
|
||||
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
|
||||
self.format_args_template_map.get(self.expr_map.get(&src)?).map(std::ops::Deref::deref)
|
||||
}
|
||||
|
||||
/// Get a reference to the body source map's diagnostics.
|
||||
pub fn diagnostics(&self) -> &[BodyDiagnostic] {
|
||||
&self.diagnostics
|
||||
}
|
||||
|
||||
fn shrink_to_fit(&mut self) {
|
||||
let Self {
|
||||
self_param: _,
|
||||
expr_map,
|
||||
expr_map_back,
|
||||
pat_map,
|
||||
pat_map_back,
|
||||
label_map,
|
||||
label_map_back,
|
||||
field_map_back,
|
||||
pat_field_map_back,
|
||||
expansions,
|
||||
format_args_template_map,
|
||||
diagnostics,
|
||||
} = self;
|
||||
format_args_template_map.shrink_to_fit();
|
||||
expr_map.shrink_to_fit();
|
||||
expr_map_back.shrink_to_fit();
|
||||
pat_map.shrink_to_fit();
|
||||
pat_map_back.shrink_to_fit();
|
||||
label_map.shrink_to_fit();
|
||||
label_map_back.shrink_to_fit();
|
||||
field_map_back.shrink_to_fit();
|
||||
pat_field_map_back.shrink_to_fit();
|
||||
expansions.shrink_to_fit();
|
||||
diagnostics.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
2042
src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
Normal file
2042
src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
Normal file
File diff suppressed because it is too large
Load diff
697
src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs
Normal file
697
src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs
Normal file
|
|
@ -0,0 +1,697 @@
|
|||
//! A pretty-printer for HIR.
|
||||
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
hir::{
|
||||
Array, BindingAnnotation, CaptureBy, ClosureKind, Literal, LiteralOrConst, Movability,
|
||||
Statement,
|
||||
},
|
||||
pretty::{print_generic_args, print_path, print_type_ref},
|
||||
type_ref::TypeRef,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBodyId) -> String {
|
||||
let header = match owner {
|
||||
DefWithBodyId::FunctionId(it) => {
|
||||
it.lookup(db).id.resolved(db, |it| format!("fn {}", it.name.display(db.upcast())))
|
||||
}
|
||||
DefWithBodyId::StaticId(it) => it
|
||||
.lookup(db)
|
||||
.id
|
||||
.resolved(db, |it| format!("static {} = ", it.name.display(db.upcast()))),
|
||||
DefWithBodyId::ConstId(it) => it.lookup(db).id.resolved(db, |it| {
|
||||
format!(
|
||||
"const {} = ",
|
||||
match &it.name {
|
||||
Some(name) => name.display(db.upcast()).to_string(),
|
||||
None => "_".to_owned(),
|
||||
}
|
||||
)
|
||||
}),
|
||||
DefWithBodyId::InTypeConstId(_) => "In type const = ".to_owned(),
|
||||
DefWithBodyId::VariantId(it) => {
|
||||
let loc = it.lookup(db);
|
||||
let enum_loc = loc.parent.lookup(db);
|
||||
format!(
|
||||
"enum {}::{}",
|
||||
enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast()),
|
||||
loc.id.item_tree(db)[loc.id.value].name.display(db.upcast()),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false };
|
||||
if let DefWithBodyId::FunctionId(it) = owner {
|
||||
p.buf.push('(');
|
||||
let params = &db.function_data(it).params;
|
||||
let mut params = params.iter();
|
||||
if let Some(self_param) = body.self_param {
|
||||
p.print_binding(self_param);
|
||||
p.buf.push(':');
|
||||
if let Some(ty) = params.next() {
|
||||
p.print_type_ref(ty);
|
||||
}
|
||||
}
|
||||
body.params.iter().zip(params).for_each(|(¶m, ty)| {
|
||||
p.print_pat(param);
|
||||
p.buf.push(':');
|
||||
p.print_type_ref(ty);
|
||||
});
|
||||
p.buf.push(')');
|
||||
p.buf.push(' ');
|
||||
}
|
||||
p.print_expr(body.body_expr);
|
||||
if matches!(owner, DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_)) {
|
||||
p.buf.push(';');
|
||||
}
|
||||
p.buf
|
||||
}
|
||||
|
||||
pub(super) fn print_expr_hir(
|
||||
db: &dyn DefDatabase,
|
||||
body: &Body,
|
||||
_owner: DefWithBodyId,
|
||||
expr: ExprId,
|
||||
) -> String {
|
||||
let mut p = Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false };
|
||||
p.print_expr(expr);
|
||||
p.buf
|
||||
}
|
||||
|
||||
macro_rules! w {
|
||||
($dst:expr, $($arg:tt)*) => {
|
||||
{ let _ = write!($dst, $($arg)*); }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! wln {
|
||||
($dst:expr) => {
|
||||
{ let _ = writeln!($dst); }
|
||||
};
|
||||
($dst:expr, $($arg:tt)*) => {
|
||||
{ let _ = writeln!($dst, $($arg)*); }
|
||||
};
|
||||
}
|
||||
|
||||
struct Printer<'a> {
|
||||
db: &'a dyn DefDatabase,
|
||||
body: &'a Body,
|
||||
buf: String,
|
||||
indent_level: usize,
|
||||
needs_indent: bool,
|
||||
}
|
||||
|
||||
impl Write for Printer<'_> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for line in s.split_inclusive('\n') {
|
||||
if self.needs_indent {
|
||||
match self.buf.chars().rev().find(|ch| *ch != ' ') {
|
||||
Some('\n') | None => {}
|
||||
_ => self.buf.push('\n'),
|
||||
}
|
||||
self.buf.push_str(&" ".repeat(self.indent_level));
|
||||
self.needs_indent = false;
|
||||
}
|
||||
|
||||
self.buf.push_str(line);
|
||||
self.needs_indent = line.ends_with('\n');
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Printer<'_> {
|
||||
fn indented(&mut self, f: impl FnOnce(&mut Self)) {
|
||||
self.indent_level += 1;
|
||||
wln!(self);
|
||||
f(self);
|
||||
self.indent_level -= 1;
|
||||
self.buf = self.buf.trim_end_matches('\n').to_owned();
|
||||
}
|
||||
|
||||
fn whitespace(&mut self) {
|
||||
match self.buf.chars().next_back() {
|
||||
None | Some('\n' | ' ') => {}
|
||||
_ => self.buf.push(' '),
|
||||
}
|
||||
}
|
||||
|
||||
fn newline(&mut self) {
|
||||
match self.buf.chars().rev().find_position(|ch| *ch != ' ') {
|
||||
Some((_, '\n')) | None => {}
|
||||
Some((idx, _)) => {
|
||||
if idx != 0 {
|
||||
self.buf.drain(self.buf.len() - idx..);
|
||||
}
|
||||
writeln!(self).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_expr(&mut self, expr: ExprId) {
|
||||
let expr = &self.body[expr];
|
||||
|
||||
match expr {
|
||||
Expr::Missing => w!(self, "<EFBFBD>"),
|
||||
Expr::Underscore => w!(self, "_"),
|
||||
Expr::InlineAsm(_) => w!(self, "builtin#asm(_)"),
|
||||
Expr::OffsetOf(offset_of) => {
|
||||
w!(self, "builtin#offset_of(");
|
||||
self.print_type_ref(&offset_of.container);
|
||||
w!(
|
||||
self,
|
||||
", {})",
|
||||
offset_of
|
||||
.fields
|
||||
.iter()
|
||||
.format_with(".", |field, f| f(&field.display(self.db.upcast())))
|
||||
);
|
||||
}
|
||||
Expr::Path(path) => self.print_path(path),
|
||||
Expr::If { condition, then_branch, else_branch } => {
|
||||
w!(self, "if ");
|
||||
self.print_expr(*condition);
|
||||
w!(self, " ");
|
||||
self.print_expr(*then_branch);
|
||||
if let Some(els) = *else_branch {
|
||||
w!(self, " else ");
|
||||
self.print_expr(els);
|
||||
}
|
||||
}
|
||||
Expr::Let { pat, expr } => {
|
||||
w!(self, "let ");
|
||||
self.print_pat(*pat);
|
||||
w!(self, " = ");
|
||||
self.print_expr(*expr);
|
||||
}
|
||||
Expr::Loop { body, label } => {
|
||||
if let Some(lbl) = label {
|
||||
w!(self, "{}: ", self.body[*lbl].name.display(self.db.upcast()));
|
||||
}
|
||||
w!(self, "loop ");
|
||||
self.print_expr(*body);
|
||||
}
|
||||
Expr::Call { callee, args, is_assignee_expr: _ } => {
|
||||
self.print_expr(*callee);
|
||||
w!(self, "(");
|
||||
if !args.is_empty() {
|
||||
self.indented(|p| {
|
||||
for arg in &**args {
|
||||
p.print_expr(*arg);
|
||||
wln!(p, ",");
|
||||
}
|
||||
});
|
||||
}
|
||||
w!(self, ")");
|
||||
}
|
||||
Expr::MethodCall { receiver, method_name, args, generic_args } => {
|
||||
self.print_expr(*receiver);
|
||||
w!(self, ".{}", method_name.display(self.db.upcast()));
|
||||
if let Some(args) = generic_args {
|
||||
w!(self, "::<");
|
||||
print_generic_args(self.db, args, self).unwrap();
|
||||
w!(self, ">");
|
||||
}
|
||||
w!(self, "(");
|
||||
if !args.is_empty() {
|
||||
self.indented(|p| {
|
||||
for arg in &**args {
|
||||
p.print_expr(*arg);
|
||||
wln!(p, ",");
|
||||
}
|
||||
});
|
||||
}
|
||||
w!(self, ")");
|
||||
}
|
||||
Expr::Match { expr, arms } => {
|
||||
w!(self, "match ");
|
||||
self.print_expr(*expr);
|
||||
w!(self, " {{");
|
||||
self.indented(|p| {
|
||||
for arm in &**arms {
|
||||
p.print_pat(arm.pat);
|
||||
if let Some(guard) = arm.guard {
|
||||
w!(p, " if ");
|
||||
p.print_expr(guard);
|
||||
}
|
||||
w!(p, " => ");
|
||||
p.print_expr(arm.expr);
|
||||
wln!(p, ",");
|
||||
}
|
||||
});
|
||||
wln!(self, "}}");
|
||||
}
|
||||
Expr::Continue { label } => {
|
||||
w!(self, "continue");
|
||||
if let Some(lbl) = label {
|
||||
w!(self, " {}", self.body[*lbl].name.display(self.db.upcast()));
|
||||
}
|
||||
}
|
||||
Expr::Break { expr, label } => {
|
||||
w!(self, "break");
|
||||
if let Some(lbl) = label {
|
||||
w!(self, " {}", self.body[*lbl].name.display(self.db.upcast()));
|
||||
}
|
||||
if let Some(expr) = expr {
|
||||
self.whitespace();
|
||||
self.print_expr(*expr);
|
||||
}
|
||||
}
|
||||
Expr::Return { expr } => {
|
||||
w!(self, "return");
|
||||
if let Some(expr) = expr {
|
||||
self.whitespace();
|
||||
self.print_expr(*expr);
|
||||
}
|
||||
}
|
||||
Expr::Become { expr } => {
|
||||
w!(self, "become");
|
||||
self.whitespace();
|
||||
self.print_expr(*expr);
|
||||
}
|
||||
Expr::Yield { expr } => {
|
||||
w!(self, "yield");
|
||||
if let Some(expr) = expr {
|
||||
self.whitespace();
|
||||
self.print_expr(*expr);
|
||||
}
|
||||
}
|
||||
Expr::Yeet { expr } => {
|
||||
w!(self, "do");
|
||||
self.whitespace();
|
||||
w!(self, "yeet");
|
||||
if let Some(expr) = expr {
|
||||
self.whitespace();
|
||||
self.print_expr(*expr);
|
||||
}
|
||||
}
|
||||
Expr::RecordLit { path, fields, spread, ellipsis, is_assignee_expr: _ } => {
|
||||
match path {
|
||||
Some(path) => self.print_path(path),
|
||||
None => w!(self, "<EFBFBD>"),
|
||||
}
|
||||
|
||||
w!(self, "{{");
|
||||
self.indented(|p| {
|
||||
for field in &**fields {
|
||||
w!(p, "{}: ", field.name.display(self.db.upcast()));
|
||||
p.print_expr(field.expr);
|
||||
wln!(p, ",");
|
||||
}
|
||||
if let Some(spread) = spread {
|
||||
w!(p, "..");
|
||||
p.print_expr(*spread);
|
||||
wln!(p);
|
||||
}
|
||||
if *ellipsis {
|
||||
wln!(p, "..");
|
||||
}
|
||||
});
|
||||
w!(self, "}}");
|
||||
}
|
||||
Expr::Field { expr, name } => {
|
||||
self.print_expr(*expr);
|
||||
w!(self, ".{}", name.display(self.db.upcast()));
|
||||
}
|
||||
Expr::Await { expr } => {
|
||||
self.print_expr(*expr);
|
||||
w!(self, ".await");
|
||||
}
|
||||
Expr::Cast { expr, type_ref } => {
|
||||
self.print_expr(*expr);
|
||||
w!(self, " as ");
|
||||
self.print_type_ref(type_ref);
|
||||
}
|
||||
Expr::Ref { expr, rawness, mutability } => {
|
||||
w!(self, "&");
|
||||
if rawness.is_raw() {
|
||||
w!(self, "raw ");
|
||||
}
|
||||
if mutability.is_mut() {
|
||||
w!(self, "mut ");
|
||||
}
|
||||
self.print_expr(*expr);
|
||||
}
|
||||
Expr::Box { expr } => {
|
||||
w!(self, "box ");
|
||||
self.print_expr(*expr);
|
||||
}
|
||||
Expr::UnaryOp { expr, op } => {
|
||||
let op = match op {
|
||||
ast::UnaryOp::Deref => "*",
|
||||
ast::UnaryOp::Not => "!",
|
||||
ast::UnaryOp::Neg => "-",
|
||||
};
|
||||
w!(self, "{}", op);
|
||||
self.print_expr(*expr);
|
||||
}
|
||||
Expr::BinaryOp { lhs, rhs, op } => {
|
||||
let (bra, ket) = match op {
|
||||
None | Some(ast::BinaryOp::Assignment { .. }) => ("", ""),
|
||||
_ => ("(", ")"),
|
||||
};
|
||||
w!(self, "{}", bra);
|
||||
self.print_expr(*lhs);
|
||||
w!(self, "{} ", ket);
|
||||
match op {
|
||||
Some(op) => w!(self, "{}", op),
|
||||
None => w!(self, "<EFBFBD>"), // :)
|
||||
}
|
||||
w!(self, " {}", bra);
|
||||
self.print_expr(*rhs);
|
||||
w!(self, "{}", ket);
|
||||
}
|
||||
Expr::Range { lhs, rhs, range_type } => {
|
||||
if let Some(lhs) = lhs {
|
||||
w!(self, "(");
|
||||
self.print_expr(*lhs);
|
||||
w!(self, ") ");
|
||||
}
|
||||
let range = match range_type {
|
||||
ast::RangeOp::Exclusive => "..",
|
||||
ast::RangeOp::Inclusive => "..=",
|
||||
};
|
||||
w!(self, "{}", range);
|
||||
if let Some(rhs) = rhs {
|
||||
w!(self, "(");
|
||||
self.print_expr(*rhs);
|
||||
w!(self, ") ");
|
||||
}
|
||||
}
|
||||
Expr::Index { base, index, is_assignee_expr: _ } => {
|
||||
self.print_expr(*base);
|
||||
w!(self, "[");
|
||||
self.print_expr(*index);
|
||||
w!(self, "]");
|
||||
}
|
||||
Expr::Closure { args, arg_types, ret_type, body, closure_kind, capture_by } => {
|
||||
match closure_kind {
|
||||
ClosureKind::Coroutine(Movability::Static) => {
|
||||
w!(self, "static ");
|
||||
}
|
||||
ClosureKind::Async => {
|
||||
w!(self, "async ");
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
match capture_by {
|
||||
CaptureBy::Value => {
|
||||
w!(self, "move ");
|
||||
}
|
||||
CaptureBy::Ref => (),
|
||||
}
|
||||
w!(self, "|");
|
||||
for (i, (pat, ty)) in args.iter().zip(arg_types.iter()).enumerate() {
|
||||
if i != 0 {
|
||||
w!(self, ", ");
|
||||
}
|
||||
self.print_pat(*pat);
|
||||
if let Some(ty) = ty {
|
||||
w!(self, ": ");
|
||||
self.print_type_ref(ty);
|
||||
}
|
||||
}
|
||||
w!(self, "|");
|
||||
if let Some(ret_ty) = ret_type {
|
||||
w!(self, " -> ");
|
||||
self.print_type_ref(ret_ty);
|
||||
}
|
||||
self.whitespace();
|
||||
self.print_expr(*body);
|
||||
}
|
||||
Expr::Tuple { exprs, is_assignee_expr: _ } => {
|
||||
w!(self, "(");
|
||||
for expr in exprs.iter() {
|
||||
self.print_expr(*expr);
|
||||
w!(self, ", ");
|
||||
}
|
||||
w!(self, ")");
|
||||
}
|
||||
Expr::Array(arr) => {
|
||||
w!(self, "[");
|
||||
if !matches!(arr, Array::ElementList { elements, .. } if elements.is_empty()) {
|
||||
self.indented(|p| match arr {
|
||||
Array::ElementList { elements, is_assignee_expr: _ } => {
|
||||
for elem in elements.iter() {
|
||||
p.print_expr(*elem);
|
||||
w!(p, ", ");
|
||||
}
|
||||
}
|
||||
Array::Repeat { initializer, repeat } => {
|
||||
p.print_expr(*initializer);
|
||||
w!(p, "; ");
|
||||
p.print_expr(*repeat);
|
||||
}
|
||||
});
|
||||
self.newline();
|
||||
}
|
||||
w!(self, "]");
|
||||
}
|
||||
Expr::Literal(lit) => self.print_literal(lit),
|
||||
Expr::Block { id: _, statements, tail, label } => {
|
||||
let label =
|
||||
label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db.upcast())));
|
||||
self.print_block(label.as_deref(), statements, tail);
|
||||
}
|
||||
Expr::Unsafe { id: _, statements, tail } => {
|
||||
self.print_block(Some("unsafe "), statements, tail);
|
||||
}
|
||||
Expr::Async { id: _, statements, tail } => {
|
||||
self.print_block(Some("async "), statements, tail);
|
||||
}
|
||||
Expr::Const(id) => {
|
||||
w!(self, "const {{ /* {id:?} */ }}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_block(
|
||||
&mut self,
|
||||
label: Option<&str>,
|
||||
statements: &[Statement],
|
||||
tail: &Option<la_arena::Idx<Expr>>,
|
||||
) {
|
||||
self.whitespace();
|
||||
if let Some(lbl) = label {
|
||||
w!(self, "{}", lbl);
|
||||
}
|
||||
w!(self, "{{");
|
||||
if !statements.is_empty() || tail.is_some() {
|
||||
self.indented(|p| {
|
||||
for stmt in statements {
|
||||
p.print_stmt(stmt);
|
||||
}
|
||||
if let Some(tail) = tail {
|
||||
p.print_expr(*tail);
|
||||
}
|
||||
p.newline();
|
||||
});
|
||||
}
|
||||
w!(self, "}}");
|
||||
}
|
||||
|
||||
fn print_pat(&mut self, pat: PatId) {
|
||||
let pat = &self.body[pat];
|
||||
|
||||
match pat {
|
||||
Pat::Missing => w!(self, "<EFBFBD>"),
|
||||
Pat::Wild => w!(self, "_"),
|
||||
Pat::Tuple { args, ellipsis } => {
|
||||
w!(self, "(");
|
||||
for (i, pat) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
w!(self, ", ");
|
||||
}
|
||||
if *ellipsis == Some(i) {
|
||||
w!(self, ".., ");
|
||||
}
|
||||
self.print_pat(*pat);
|
||||
}
|
||||
w!(self, ")");
|
||||
}
|
||||
Pat::Or(pats) => {
|
||||
for (i, pat) in pats.iter().enumerate() {
|
||||
if i != 0 {
|
||||
w!(self, " | ");
|
||||
}
|
||||
self.print_pat(*pat);
|
||||
}
|
||||
}
|
||||
Pat::Record { path, args, ellipsis } => {
|
||||
match path {
|
||||
Some(path) => self.print_path(path),
|
||||
None => w!(self, "<EFBFBD>"),
|
||||
}
|
||||
|
||||
w!(self, " {{");
|
||||
self.indented(|p| {
|
||||
for arg in args.iter() {
|
||||
w!(p, "{}: ", arg.name.display(self.db.upcast()));
|
||||
p.print_pat(arg.pat);
|
||||
wln!(p, ",");
|
||||
}
|
||||
if *ellipsis {
|
||||
wln!(p, "..");
|
||||
}
|
||||
});
|
||||
w!(self, "}}");
|
||||
}
|
||||
Pat::Range { start, end } => {
|
||||
if let Some(start) = start {
|
||||
self.print_literal_or_const(start);
|
||||
}
|
||||
w!(self, "..=");
|
||||
if let Some(end) = end {
|
||||
self.print_literal_or_const(end);
|
||||
}
|
||||
}
|
||||
Pat::Slice { prefix, slice, suffix } => {
|
||||
w!(self, "[");
|
||||
for pat in prefix.iter() {
|
||||
self.print_pat(*pat);
|
||||
w!(self, ", ");
|
||||
}
|
||||
if let Some(pat) = slice {
|
||||
self.print_pat(*pat);
|
||||
w!(self, ", ");
|
||||
}
|
||||
for pat in suffix.iter() {
|
||||
self.print_pat(*pat);
|
||||
w!(self, ", ");
|
||||
}
|
||||
w!(self, "]");
|
||||
}
|
||||
Pat::Path(path) => self.print_path(path),
|
||||
Pat::Lit(expr) => self.print_expr(*expr),
|
||||
Pat::Bind { id, subpat } => {
|
||||
self.print_binding(*id);
|
||||
if let Some(pat) = subpat {
|
||||
self.whitespace();
|
||||
self.print_pat(*pat);
|
||||
}
|
||||
}
|
||||
Pat::TupleStruct { path, args, ellipsis } => {
|
||||
match path {
|
||||
Some(path) => self.print_path(path),
|
||||
None => w!(self, "<EFBFBD>"),
|
||||
}
|
||||
w!(self, "(");
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
w!(self, ", ");
|
||||
}
|
||||
if *ellipsis == Some(i) {
|
||||
w!(self, ", ..");
|
||||
}
|
||||
self.print_pat(*arg);
|
||||
}
|
||||
w!(self, ")");
|
||||
}
|
||||
Pat::Ref { pat, mutability } => {
|
||||
w!(self, "&");
|
||||
if mutability.is_mut() {
|
||||
w!(self, "mut ");
|
||||
}
|
||||
self.print_pat(*pat);
|
||||
}
|
||||
Pat::Box { inner } => {
|
||||
w!(self, "box ");
|
||||
self.print_pat(*inner);
|
||||
}
|
||||
Pat::ConstBlock(c) => {
|
||||
w!(self, "const ");
|
||||
self.print_expr(*c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_stmt(&mut self, stmt: &Statement) {
|
||||
match stmt {
|
||||
Statement::Let { pat, type_ref, initializer, else_branch } => {
|
||||
w!(self, "let ");
|
||||
self.print_pat(*pat);
|
||||
if let Some(ty) = type_ref {
|
||||
w!(self, ": ");
|
||||
self.print_type_ref(ty);
|
||||
}
|
||||
if let Some(init) = initializer {
|
||||
w!(self, " = ");
|
||||
self.print_expr(*init);
|
||||
}
|
||||
if let Some(els) = else_branch {
|
||||
w!(self, " else ");
|
||||
self.print_expr(*els);
|
||||
}
|
||||
wln!(self, ";");
|
||||
}
|
||||
Statement::Expr { expr, has_semi } => {
|
||||
self.print_expr(*expr);
|
||||
if *has_semi {
|
||||
w!(self, ";");
|
||||
}
|
||||
wln!(self);
|
||||
}
|
||||
Statement::Item => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_literal_or_const(&mut self, literal_or_const: &LiteralOrConst) {
|
||||
match literal_or_const {
|
||||
LiteralOrConst::Literal(l) => self.print_literal(l),
|
||||
LiteralOrConst::Const(c) => self.print_pat(*c),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_literal(&mut self, literal: &Literal) {
|
||||
match literal {
|
||||
Literal::String(it) => w!(self, "{:?}", it),
|
||||
Literal::ByteString(it) => w!(self, "\"{}\"", it.escape_ascii()),
|
||||
Literal::CString(it) => w!(self, "\"{}\\0\"", it.escape_ascii()),
|
||||
Literal::Char(it) => w!(self, "'{}'", it.escape_debug()),
|
||||
Literal::Bool(it) => w!(self, "{}", it),
|
||||
Literal::Int(i, suffix) => {
|
||||
w!(self, "{}", i);
|
||||
if let Some(suffix) = suffix {
|
||||
w!(self, "{}", suffix);
|
||||
}
|
||||
}
|
||||
Literal::Uint(i, suffix) => {
|
||||
w!(self, "{}", i);
|
||||
if let Some(suffix) = suffix {
|
||||
w!(self, "{}", suffix);
|
||||
}
|
||||
}
|
||||
Literal::Float(f, suffix) => {
|
||||
w!(self, "{}", f);
|
||||
if let Some(suffix) = suffix {
|
||||
w!(self, "{}", suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_type_ref(&mut self, ty: &TypeRef) {
|
||||
print_type_ref(self.db, ty, self).unwrap();
|
||||
}
|
||||
|
||||
fn print_path(&mut self, path: &Path) {
|
||||
print_path(self.db, path, self).unwrap();
|
||||
}
|
||||
|
||||
fn print_binding(&mut self, id: BindingId) {
|
||||
let Binding { name, mode, .. } = &self.body.bindings[id];
|
||||
let mode = match mode {
|
||||
BindingAnnotation::Unannotated => "",
|
||||
BindingAnnotation::Mutable => "mut ",
|
||||
BindingAnnotation::Ref => "ref ",
|
||||
BindingAnnotation::RefMut => "ref mut ",
|
||||
};
|
||||
w!(self, "{}{}", mode, name.display(self.db.upcast()));
|
||||
}
|
||||
}
|
||||
605
src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs
Normal file
605
src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs
Normal file
|
|
@ -0,0 +1,605 @@
|
|||
//! Name resolution for expressions.
|
||||
use hir_expand::name::Name;
|
||||
use la_arena::{Arena, ArenaMap, Idx, IdxRange, RawIdx};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
body::Body,
|
||||
db::DefDatabase,
|
||||
hir::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement},
|
||||
BlockId, DefWithBodyId,
|
||||
};
|
||||
|
||||
pub type ScopeId = Idx<ScopeData>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ExprScopes {
|
||||
scopes: Arena<ScopeData>,
|
||||
scope_entries: Arena<ScopeEntry>,
|
||||
scope_by_expr: ArenaMap<ExprId, ScopeId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ScopeEntry {
|
||||
name: Name,
|
||||
binding: BindingId,
|
||||
}
|
||||
|
||||
impl ScopeEntry {
|
||||
pub fn name(&self) -> &Name {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn binding(&self) -> BindingId {
|
||||
self.binding
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ScopeData {
|
||||
parent: Option<ScopeId>,
|
||||
block: Option<BlockId>,
|
||||
label: Option<(LabelId, Name)>,
|
||||
entries: IdxRange<ScopeEntry>,
|
||||
}
|
||||
|
||||
impl ExprScopes {
|
||||
pub(crate) fn expr_scopes_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<ExprScopes> {
|
||||
let body = db.body(def);
|
||||
let mut scopes = ExprScopes::new(&body);
|
||||
scopes.shrink_to_fit();
|
||||
Arc::new(scopes)
|
||||
}
|
||||
|
||||
pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
|
||||
&self.scope_entries[self.scopes[scope].entries.clone()]
|
||||
}
|
||||
|
||||
/// If `scope` refers to a block expression scope, returns the corresponding `BlockId`.
|
||||
pub fn block(&self, scope: ScopeId) -> Option<BlockId> {
|
||||
self.scopes[scope].block
|
||||
}
|
||||
|
||||
/// If `scope` refers to a labeled expression scope, returns the corresponding `Label`.
|
||||
pub fn label(&self, scope: ScopeId) -> Option<(LabelId, Name)> {
|
||||
self.scopes[scope].label.clone()
|
||||
}
|
||||
|
||||
/// Returns the scopes in ascending order.
|
||||
pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
|
||||
std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
|
||||
}
|
||||
|
||||
pub fn resolve_name_in_scope(&self, scope: ScopeId, name: &Name) -> Option<&ScopeEntry> {
|
||||
self.scope_chain(Some(scope))
|
||||
.find_map(|scope| self.entries(scope).iter().find(|it| it.name == *name))
|
||||
}
|
||||
|
||||
pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
|
||||
self.scope_by_expr.get(expr).copied()
|
||||
}
|
||||
|
||||
pub fn scope_by_expr(&self) -> &ArenaMap<ExprId, ScopeId> {
|
||||
&self.scope_by_expr
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_entries(idx: usize) -> IdxRange<ScopeEntry> {
|
||||
IdxRange::new(Idx::from_raw(RawIdx::from(idx as u32))..Idx::from_raw(RawIdx::from(idx as u32)))
|
||||
}
|
||||
|
||||
impl ExprScopes {
|
||||
fn new(body: &Body) -> ExprScopes {
|
||||
let mut scopes = ExprScopes {
|
||||
scopes: Arena::default(),
|
||||
scope_entries: Arena::default(),
|
||||
scope_by_expr: ArenaMap::with_capacity(body.exprs.len()),
|
||||
};
|
||||
let mut root = scopes.root_scope();
|
||||
if let Some(self_param) = body.self_param {
|
||||
scopes.add_bindings(body, root, self_param);
|
||||
}
|
||||
scopes.add_params_bindings(body, root, &body.params);
|
||||
compute_expr_scopes(body.body_expr, body, &mut scopes, &mut root);
|
||||
scopes
|
||||
}
|
||||
|
||||
fn root_scope(&mut self) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData {
|
||||
parent: None,
|
||||
block: None,
|
||||
label: None,
|
||||
entries: empty_entries(self.scope_entries.len()),
|
||||
})
|
||||
}
|
||||
|
||||
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData {
|
||||
parent: Some(parent),
|
||||
block: None,
|
||||
label: None,
|
||||
entries: empty_entries(self.scope_entries.len()),
|
||||
})
|
||||
}
|
||||
|
||||
fn new_labeled_scope(&mut self, parent: ScopeId, label: Option<(LabelId, Name)>) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData {
|
||||
parent: Some(parent),
|
||||
block: None,
|
||||
label,
|
||||
entries: empty_entries(self.scope_entries.len()),
|
||||
})
|
||||
}
|
||||
|
||||
fn new_block_scope(
|
||||
&mut self,
|
||||
parent: ScopeId,
|
||||
block: Option<BlockId>,
|
||||
label: Option<(LabelId, Name)>,
|
||||
) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData {
|
||||
parent: Some(parent),
|
||||
block,
|
||||
label,
|
||||
entries: empty_entries(self.scope_entries.len()),
|
||||
})
|
||||
}
|
||||
|
||||
fn add_bindings(&mut self, body: &Body, scope: ScopeId, binding: BindingId) {
|
||||
let Binding { name, .. } = &body.bindings[binding];
|
||||
let entry = self.scope_entries.alloc(ScopeEntry { name: name.clone(), binding });
|
||||
self.scopes[scope].entries =
|
||||
IdxRange::new_inclusive(self.scopes[scope].entries.start()..=entry);
|
||||
}
|
||||
|
||||
fn add_pat_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
|
||||
let pattern = &body[pat];
|
||||
if let Pat::Bind { id, .. } = pattern {
|
||||
self.add_bindings(body, scope, *id);
|
||||
}
|
||||
|
||||
pattern.walk_child_pats(|pat| self.add_pat_bindings(body, scope, pat));
|
||||
}
|
||||
|
||||
fn add_params_bindings(&mut self, body: &Body, scope: ScopeId, params: &[PatId]) {
|
||||
params.iter().for_each(|pat| self.add_pat_bindings(body, scope, *pat));
|
||||
}
|
||||
|
||||
fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
|
||||
self.scope_by_expr.insert(node, scope);
|
||||
}
|
||||
|
||||
fn shrink_to_fit(&mut self) {
|
||||
let ExprScopes { scopes, scope_entries, scope_by_expr } = self;
|
||||
scopes.shrink_to_fit();
|
||||
scope_entries.shrink_to_fit();
|
||||
scope_by_expr.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_block_scopes(
|
||||
statements: &[Statement],
|
||||
tail: Option<ExprId>,
|
||||
body: &Body,
|
||||
scopes: &mut ExprScopes,
|
||||
scope: &mut ScopeId,
|
||||
) {
|
||||
for stmt in statements {
|
||||
match stmt {
|
||||
Statement::Let { pat, initializer, else_branch, .. } => {
|
||||
if let Some(expr) = initializer {
|
||||
compute_expr_scopes(*expr, body, scopes, scope);
|
||||
}
|
||||
if let Some(expr) = else_branch {
|
||||
compute_expr_scopes(*expr, body, scopes, scope);
|
||||
}
|
||||
|
||||
*scope = scopes.new_scope(*scope);
|
||||
scopes.add_pat_bindings(body, *scope, *pat);
|
||||
}
|
||||
Statement::Expr { expr, .. } => {
|
||||
compute_expr_scopes(*expr, body, scopes, scope);
|
||||
}
|
||||
Statement::Item => (),
|
||||
}
|
||||
}
|
||||
if let Some(expr) = tail {
|
||||
compute_expr_scopes(expr, body, scopes, scope);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: &mut ScopeId) {
|
||||
let make_label =
|
||||
|label: &Option<LabelId>| label.map(|label| (label, body.labels[label].name.clone()));
|
||||
|
||||
scopes.set_scope(expr, *scope);
|
||||
match &body[expr] {
|
||||
Expr::Block { statements, tail, id, label } => {
|
||||
let mut scope = scopes.new_block_scope(*scope, *id, make_label(label));
|
||||
// Overwrite the old scope for the block expr, so that every block scope can be found
|
||||
// via the block itself (important for blocks that only contain items, no expressions).
|
||||
scopes.set_scope(expr, scope);
|
||||
compute_block_scopes(statements, *tail, body, scopes, &mut scope);
|
||||
}
|
||||
Expr::Const(_) => {
|
||||
// FIXME: This is broken.
|
||||
}
|
||||
Expr::Unsafe { id, statements, tail } | Expr::Async { id, statements, tail } => {
|
||||
let mut scope = scopes.new_block_scope(*scope, *id, None);
|
||||
// Overwrite the old scope for the block expr, so that every block scope can be found
|
||||
// via the block itself (important for blocks that only contain items, no expressions).
|
||||
scopes.set_scope(expr, scope);
|
||||
compute_block_scopes(statements, *tail, body, scopes, &mut scope);
|
||||
}
|
||||
Expr::Loop { body: body_expr, label } => {
|
||||
let mut scope = scopes.new_labeled_scope(*scope, make_label(label));
|
||||
compute_expr_scopes(*body_expr, body, scopes, &mut scope);
|
||||
}
|
||||
Expr::Closure { args, body: body_expr, .. } => {
|
||||
let mut scope = scopes.new_scope(*scope);
|
||||
scopes.add_params_bindings(body, scope, args);
|
||||
compute_expr_scopes(*body_expr, body, scopes, &mut scope);
|
||||
}
|
||||
Expr::Match { expr, arms } => {
|
||||
compute_expr_scopes(*expr, body, scopes, scope);
|
||||
for arm in arms.iter() {
|
||||
let mut scope = scopes.new_scope(*scope);
|
||||
scopes.add_pat_bindings(body, scope, arm.pat);
|
||||
if let Some(guard) = arm.guard {
|
||||
scope = scopes.new_scope(scope);
|
||||
compute_expr_scopes(guard, body, scopes, &mut scope);
|
||||
}
|
||||
compute_expr_scopes(arm.expr, body, scopes, &mut scope);
|
||||
}
|
||||
}
|
||||
&Expr::If { condition, then_branch, else_branch } => {
|
||||
let mut then_branch_scope = scopes.new_scope(*scope);
|
||||
compute_expr_scopes(condition, body, scopes, &mut then_branch_scope);
|
||||
compute_expr_scopes(then_branch, body, scopes, &mut then_branch_scope);
|
||||
if let Some(else_branch) = else_branch {
|
||||
compute_expr_scopes(else_branch, body, scopes, scope);
|
||||
}
|
||||
}
|
||||
&Expr::Let { pat, expr } => {
|
||||
compute_expr_scopes(expr, body, scopes, scope);
|
||||
*scope = scopes.new_scope(*scope);
|
||||
scopes.add_pat_bindings(body, *scope, pat);
|
||||
}
|
||||
e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use base_db::{FileId, SourceDatabase};
|
||||
use hir_expand::{name::AsName, InFile};
|
||||
use syntax::{algo::find_node_at_offset, ast, AstNode};
|
||||
use test_fixture::WithFixture;
|
||||
use test_utils::{assert_eq_text, extract_offset};
|
||||
|
||||
use crate::{db::DefDatabase, test_db::TestDB, FunctionId, ModuleDefId};
|
||||
|
||||
fn find_function(db: &TestDB, file_id: FileId) -> FunctionId {
|
||||
let krate = db.test_crate();
|
||||
let crate_def_map = db.crate_def_map(krate);
|
||||
|
||||
let module = crate_def_map.modules_for_file(file_id).next().unwrap();
|
||||
let (_, def) = crate_def_map[module].scope.entries().next().unwrap();
|
||||
match def.take_values().unwrap() {
|
||||
ModuleDefId::FunctionId(it) => it,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_check(ra_fixture: &str, expected: &[&str]) {
|
||||
let (offset, code) = extract_offset(ra_fixture);
|
||||
let code = {
|
||||
let mut buf = String::new();
|
||||
let off: usize = offset.into();
|
||||
buf.push_str(&code[..off]);
|
||||
buf.push_str("$0marker");
|
||||
buf.push_str(&code[off..]);
|
||||
buf
|
||||
};
|
||||
|
||||
let (db, position) = TestDB::with_position(&code);
|
||||
let file_id = position.file_id;
|
||||
let offset = position.offset;
|
||||
|
||||
let file_syntax = db.parse(file_id).syntax_node();
|
||||
let marker: ast::PathExpr = find_node_at_offset(&file_syntax, offset).unwrap();
|
||||
let function = find_function(&db, file_id);
|
||||
|
||||
let scopes = db.expr_scopes(function.into());
|
||||
let (_body, source_map) = db.body_with_source_map(function.into());
|
||||
|
||||
let expr_id = source_map
|
||||
.node_expr(InFile { file_id: file_id.into(), value: &marker.into() })
|
||||
.unwrap();
|
||||
let scope = scopes.scope_for(expr_id);
|
||||
|
||||
let actual = scopes
|
||||
.scope_chain(scope)
|
||||
.flat_map(|scope| scopes.entries(scope))
|
||||
.map(|it| it.name().to_smol_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let expected = expected.join("\n");
|
||||
assert_eq_text!(&expected, &actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda_scope() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux(foo: i32) {
|
||||
let f = |bar, baz: i32| {
|
||||
$0
|
||||
};
|
||||
}",
|
||||
&["bar", "baz", "foo"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_scope() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux() {
|
||||
f(|x| $0 );
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_method_call_scope() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux() {
|
||||
z.f(|x| $0 );
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loop_scope() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux() {
|
||||
loop {
|
||||
let x = ();
|
||||
$0
|
||||
};
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux() {
|
||||
match () {
|
||||
Some(x) => {
|
||||
$0
|
||||
}
|
||||
};
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shadow_variable() {
|
||||
do_check(
|
||||
r"
|
||||
fn foo(x: String) {
|
||||
let x : &str = &x$0;
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bindings_after_at() {
|
||||
do_check(
|
||||
r"
|
||||
fn foo() {
|
||||
match Some(()) {
|
||||
opt @ Some(unit) => {
|
||||
$0
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
",
|
||||
&["opt", "unit"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_inner_item() {
|
||||
do_check(
|
||||
r"
|
||||
macro_rules! mac {
|
||||
() => {{
|
||||
fn inner() {}
|
||||
inner();
|
||||
}};
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
mac!();
|
||||
$0
|
||||
}
|
||||
",
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broken_inner_item() {
|
||||
do_check(
|
||||
r"
|
||||
fn foo() {
|
||||
trait {}
|
||||
$0
|
||||
}
|
||||
",
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
fn do_check_local_name(ra_fixture: &str, expected_offset: u32) {
|
||||
let (db, position) = TestDB::with_position(ra_fixture);
|
||||
let file_id = position.file_id;
|
||||
let offset = position.offset;
|
||||
|
||||
let file = db.parse(file_id).ok().unwrap();
|
||||
let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into())
|
||||
.expect("failed to find a name at the target offset");
|
||||
let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset).unwrap();
|
||||
|
||||
let function = find_function(&db, file_id);
|
||||
|
||||
let scopes = db.expr_scopes(function.into());
|
||||
let (body, source_map) = db.body_with_source_map(function.into());
|
||||
|
||||
let expr_scope = {
|
||||
let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap();
|
||||
let expr_id =
|
||||
source_map.node_expr(InFile { file_id: file_id.into(), value: &expr_ast }).unwrap();
|
||||
scopes.scope_for(expr_id).unwrap()
|
||||
};
|
||||
|
||||
let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap();
|
||||
let pat_src = source_map
|
||||
.pat_syntax(*body.bindings[resolved.binding()].definitions.first().unwrap())
|
||||
.unwrap();
|
||||
|
||||
let local_name = pat_src.value.syntax_node_ptr().to_node(file.syntax());
|
||||
assert_eq!(local_name.text_range(), expected_name.syntax().text_range());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_local_name() {
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn foo(x: i32, y: u32) {
|
||||
{
|
||||
let z = x * 2;
|
||||
}
|
||||
{
|
||||
let t = x$0 * 3;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
7,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_local_name_declaration() {
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn foo(x: String) {
|
||||
let x : &str = &x$0;
|
||||
}
|
||||
"#,
|
||||
7,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_local_name_shadow() {
|
||||
do_check_local_name(
|
||||
r"
|
||||
fn foo(x: String) {
|
||||
let x : &str = &x;
|
||||
x$0
|
||||
}
|
||||
",
|
||||
28,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_patterns_contribute_bindings() {
|
||||
do_check_local_name(
|
||||
r"
|
||||
fn foo() {
|
||||
if let Some(&from) = bar() {
|
||||
from$0;
|
||||
}
|
||||
}
|
||||
",
|
||||
28,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn while_let_adds_binding() {
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn test() {
|
||||
let foo: Option<f32> = None;
|
||||
while let Option::Some(spam) = foo {
|
||||
spam$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
75,
|
||||
);
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn test() {
|
||||
let foo: Option<f32> = None;
|
||||
while (((let Option::Some(_) = foo))) && let Option::Some(spam) = foo {
|
||||
spam$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
107,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_guard_if_let() {
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn test() {
|
||||
let foo: Option<f32> = None;
|
||||
match foo {
|
||||
_ if let Option::Some(spam) = foo => spam$0,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
93,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_chains_can_reference_previous_lets() {
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn test() {
|
||||
let foo: Option<i32> = None;
|
||||
if let Some(spam) = foo && spa$0m > 1 && let Some(spam) = foo && spam > 1 {}
|
||||
}
|
||||
"#,
|
||||
61,
|
||||
);
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn test() {
|
||||
let foo: Option<i32> = None;
|
||||
if let Some(spam) = foo && spam > 1 && let Some(spam) = foo && sp$0am > 1 {}
|
||||
}
|
||||
"#,
|
||||
100,
|
||||
);
|
||||
}
|
||||
}
|
||||
335
src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs
Normal file
335
src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
mod block;
|
||||
|
||||
use base_db::SourceDatabase;
|
||||
use expect_test::{expect, Expect};
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
use crate::{test_db::TestDB, ModuleDefId};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn lower(ra_fixture: &str) -> (TestDB, Arc<Body>, DefWithBodyId) {
|
||||
let db = TestDB::with_files(ra_fixture);
|
||||
|
||||
let krate = db.crate_graph().iter().next().unwrap();
|
||||
let def_map = db.crate_def_map(krate);
|
||||
let mut fn_def = None;
|
||||
'outer: for (_, module) in def_map.modules() {
|
||||
for decl in module.scope.declarations() {
|
||||
if let ModuleDefId::FunctionId(it) = decl {
|
||||
fn_def = Some(it);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
let fn_def = fn_def.unwrap().into();
|
||||
|
||||
let body = db.body(fn_def);
|
||||
(db, body, fn_def)
|
||||
}
|
||||
|
||||
fn def_map_at(ra_fixture: &str) -> String {
|
||||
let (db, position) = TestDB::with_position(ra_fixture);
|
||||
|
||||
let module = db.module_at_position(position);
|
||||
module.def_map(&db).dump(&db)
|
||||
}
|
||||
|
||||
fn check_block_scopes_at(ra_fixture: &str, expect: Expect) {
|
||||
let (db, position) = TestDB::with_position(ra_fixture);
|
||||
|
||||
let module = db.module_at_position(position);
|
||||
let actual = module.def_map(&db).dump_block_scopes(&db);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
fn check_at(ra_fixture: &str, expect: Expect) {
|
||||
let actual = def_map_at(ra_fixture);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn your_stack_belongs_to_me() {
|
||||
cov_mark::check!(your_stack_belongs_to_me);
|
||||
lower(
|
||||
r#"
|
||||
macro_rules! n_nuple {
|
||||
($e:tt) => ();
|
||||
($($rest:tt)*) => {{
|
||||
(n_nuple!($($rest)*)None,)
|
||||
}};
|
||||
}
|
||||
fn main() { n_nuple!(1,2,3); }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn your_stack_belongs_to_me2() {
|
||||
cov_mark::check!(overflow_but_not_me);
|
||||
lower(
|
||||
r#"
|
||||
macro_rules! foo {
|
||||
() => {{ foo!(); foo!(); }}
|
||||
}
|
||||
fn main() { foo!(); }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursion_limit() {
|
||||
cov_mark::check!(your_stack_belongs_to_me);
|
||||
|
||||
lower(
|
||||
r#"
|
||||
#![recursion_limit = "2"]
|
||||
macro_rules! n_nuple {
|
||||
($e:tt) => ();
|
||||
($first:tt $($rest:tt)*) => {{
|
||||
n_nuple!($($rest)*)
|
||||
}};
|
||||
}
|
||||
fn main() { n_nuple!(1,2,3); }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_3642_bad_macro_stackover() {
|
||||
lower(
|
||||
r#"
|
||||
#[macro_export]
|
||||
macro_rules! match_ast {
|
||||
(match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
|
||||
|
||||
(match ($node:expr) {
|
||||
$( ast::$ast:ident($it:ident) => $res:expr, )*
|
||||
_ => $catch_all:expr $(,)?
|
||||
}) => {{
|
||||
$( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )*
|
||||
{ $catch_all }
|
||||
}};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let anchor = match_ast! {
|
||||
match parent {
|
||||
as => {},
|
||||
_ => return None
|
||||
}
|
||||
};
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_resolve() {
|
||||
// Regression test for a path resolution bug introduced with inner item handling.
|
||||
lower(
|
||||
r#"
|
||||
macro_rules! vec {
|
||||
() => { () };
|
||||
($elem:expr; $n:expr) => { () };
|
||||
($($x:expr),+ $(,)?) => { () };
|
||||
}
|
||||
mod m {
|
||||
fn outer() {
|
||||
let _ = vec![FileSet::default(); self.len()];
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desugar_builtin_format_args() {
|
||||
let (db, body, def) = lower(
|
||||
r#"
|
||||
//- minicore: fmt
|
||||
fn main() {
|
||||
let are = "are";
|
||||
let count = 10;
|
||||
builtin#format_args("hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", last = "!");
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
expect![[r#"
|
||||
fn main() {
|
||||
let are = "are";
|
||||
let count = 10;
|
||||
builtin#lang(Arguments::new_v1_formatted)(
|
||||
&[
|
||||
"hello ", " ", " friends, we ", " ", "",
|
||||
],
|
||||
&[
|
||||
builtin#lang(Argument::new_display)(
|
||||
&count,
|
||||
), builtin#lang(Argument::new_display)(
|
||||
&"fancy",
|
||||
), builtin#lang(Argument::new_debug)(
|
||||
&are,
|
||||
), builtin#lang(Argument::new_display)(
|
||||
&"!",
|
||||
),
|
||||
],
|
||||
&[
|
||||
builtin#lang(Placeholder::new)(
|
||||
0usize,
|
||||
' ',
|
||||
builtin#lang(Alignment::Unknown),
|
||||
8u32,
|
||||
builtin#lang(Count::Implied),
|
||||
builtin#lang(Count::Is)(
|
||||
2usize,
|
||||
),
|
||||
), builtin#lang(Placeholder::new)(
|
||||
1usize,
|
||||
' ',
|
||||
builtin#lang(Alignment::Unknown),
|
||||
0u32,
|
||||
builtin#lang(Count::Implied),
|
||||
builtin#lang(Count::Implied),
|
||||
), builtin#lang(Placeholder::new)(
|
||||
2usize,
|
||||
' ',
|
||||
builtin#lang(Alignment::Unknown),
|
||||
0u32,
|
||||
builtin#lang(Count::Implied),
|
||||
builtin#lang(Count::Implied),
|
||||
), builtin#lang(Placeholder::new)(
|
||||
1usize,
|
||||
' ',
|
||||
builtin#lang(Alignment::Unknown),
|
||||
0u32,
|
||||
builtin#lang(Count::Implied),
|
||||
builtin#lang(Count::Implied),
|
||||
), builtin#lang(Placeholder::new)(
|
||||
3usize,
|
||||
' ',
|
||||
builtin#lang(Alignment::Unknown),
|
||||
0u32,
|
||||
builtin#lang(Count::Implied),
|
||||
builtin#lang(Count::Implied),
|
||||
),
|
||||
],
|
||||
unsafe {
|
||||
builtin#lang(UnsafeArg::new)()
|
||||
},
|
||||
);
|
||||
}"#]]
|
||||
.assert_eq(&body.pretty_print(&db, def))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_hygiene() {
|
||||
let (db, body, def) = lower(
|
||||
r##"
|
||||
//- minicore: fmt, from
|
||||
//- /main.rs
|
||||
mod error;
|
||||
|
||||
use crate::error::error;
|
||||
|
||||
fn main() {
|
||||
// _ = forces body expansion instead of block def map expansion
|
||||
_ = error!("Failed to resolve path `{}`", node.text());
|
||||
}
|
||||
//- /error.rs
|
||||
macro_rules! _error {
|
||||
($fmt:expr, $($arg:tt)+) => {$crate::error::intermediate!(format_args!($fmt, $($arg)+))}
|
||||
}
|
||||
pub(crate) use _error as error;
|
||||
macro_rules! _intermediate {
|
||||
($arg:expr) => {$crate::error::SsrError::new($arg)}
|
||||
}
|
||||
pub(crate) use _intermediate as intermediate;
|
||||
|
||||
pub struct SsrError(pub(crate) core::fmt::Arguments);
|
||||
|
||||
impl SsrError {
|
||||
pub(crate) fn new(message: impl Into<core::fmt::Arguments>) -> SsrError {
|
||||
SsrError(message.into())
|
||||
}
|
||||
}
|
||||
"##,
|
||||
);
|
||||
|
||||
assert_eq!(db.body_with_source_map(def).1.diagnostics(), &[]);
|
||||
expect![[r#"
|
||||
fn main() {
|
||||
_ = $crate::error::SsrError::new(
|
||||
builtin#lang(Arguments::new_v1_formatted)(
|
||||
&[
|
||||
"Failed to resolve path `", "`",
|
||||
],
|
||||
&[
|
||||
builtin#lang(Argument::new_display)(
|
||||
&node.text(),
|
||||
),
|
||||
],
|
||||
&[
|
||||
builtin#lang(Placeholder::new)(
|
||||
0usize,
|
||||
' ',
|
||||
builtin#lang(Alignment::Unknown),
|
||||
0u32,
|
||||
builtin#lang(Count::Implied),
|
||||
builtin#lang(Count::Implied),
|
||||
),
|
||||
],
|
||||
unsafe {
|
||||
builtin#lang(UnsafeArg::new)()
|
||||
},
|
||||
),
|
||||
);
|
||||
}"#]]
|
||||
.assert_eq(&body.pretty_print(&db, def))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_10300() {
|
||||
let (db, body, def) = lower(
|
||||
r#"
|
||||
//- minicore: concat, panic
|
||||
mod private {
|
||||
pub use core::concat;
|
||||
}
|
||||
|
||||
macro_rules! m {
|
||||
() => {
|
||||
panic!(concat!($crate::private::concat!("cc")));
|
||||
};
|
||||
}
|
||||
|
||||
fn f() {
|
||||
m!();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
let (_, source_map) = db.body_with_source_map(def);
|
||||
assert_eq!(source_map.diagnostics(), &[]);
|
||||
|
||||
for (_, def_map) in body.blocks(&db) {
|
||||
assert_eq!(def_map.diagnostics(), &[]);
|
||||
}
|
||||
|
||||
expect![[r#"
|
||||
fn f() {
|
||||
$crate::panicking::panic_fmt(
|
||||
builtin#lang(Arguments::new_v1_formatted)(
|
||||
&[
|
||||
"cc",
|
||||
],
|
||||
&[],
|
||||
&[],
|
||||
unsafe {
|
||||
builtin#lang(UnsafeArg::new)()
|
||||
},
|
||||
),
|
||||
);
|
||||
}"#]]
|
||||
.assert_eq(&body.pretty_print(&db, def))
|
||||
}
|
||||
530
src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs
Normal file
530
src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
use super::*;
|
||||
use expect_test::expect;
|
||||
|
||||
#[test]
|
||||
fn inner_item_smoke() {
|
||||
check_at(
|
||||
r#"
|
||||
struct inner {}
|
||||
fn outer() {
|
||||
$0
|
||||
fn inner() {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
inner: v
|
||||
|
||||
crate
|
||||
inner: t
|
||||
outer: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_from_crate() {
|
||||
check_at(
|
||||
r#"
|
||||
struct Struct {}
|
||||
fn outer() {
|
||||
fn Struct() {}
|
||||
use Struct as PlainStruct;
|
||||
use crate::Struct as CrateStruct;
|
||||
use self::Struct as SelfStruct;
|
||||
use super::Struct as SuperStruct;
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
CrateStruct: ti
|
||||
PlainStruct: ti vi
|
||||
SelfStruct: ti
|
||||
Struct: v
|
||||
SuperStruct: _
|
||||
|
||||
crate
|
||||
Struct: t
|
||||
outer: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_namespaces() {
|
||||
check_at(
|
||||
r#"
|
||||
struct name {}
|
||||
fn outer() {
|
||||
fn name() {}
|
||||
|
||||
use name as imported; // should import both `name`s
|
||||
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
imported: ti vi
|
||||
name: v
|
||||
|
||||
crate
|
||||
name: t
|
||||
outer: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_blocks() {
|
||||
check_at(
|
||||
r#"
|
||||
fn outer() {
|
||||
struct inner1 {}
|
||||
fn inner() {
|
||||
use inner1;
|
||||
use outer;
|
||||
fn inner2() {}
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
inner1: ti
|
||||
inner2: v
|
||||
outer: vi
|
||||
|
||||
block scope
|
||||
inner: v
|
||||
inner1: t
|
||||
|
||||
crate
|
||||
outer: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn super_imports() {
|
||||
check_at(
|
||||
r#"
|
||||
mod module {
|
||||
fn f() {
|
||||
use super::Struct;
|
||||
$0
|
||||
}
|
||||
}
|
||||
|
||||
struct Struct {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
Struct: ti
|
||||
|
||||
crate
|
||||
Struct: t
|
||||
module: t
|
||||
|
||||
crate::module
|
||||
f: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn super_imports_2() {
|
||||
check_at(
|
||||
r#"
|
||||
fn outer() {
|
||||
mod m {
|
||||
struct ResolveMe {}
|
||||
fn middle() {
|
||||
mod m2 {
|
||||
fn inner() {
|
||||
use super::ResolveMe;
|
||||
$0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
ResolveMe: ti
|
||||
|
||||
block scope
|
||||
m2: t
|
||||
|
||||
block scope::m2
|
||||
inner: v
|
||||
|
||||
block scope
|
||||
m: t
|
||||
|
||||
block scope::m
|
||||
ResolveMe: t
|
||||
middle: v
|
||||
|
||||
crate
|
||||
outer: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_module_scoping() {
|
||||
check_block_scopes_at(
|
||||
r#"
|
||||
fn f() {
|
||||
mod module {
|
||||
struct Struct {}
|
||||
fn f() {
|
||||
use self::Struct;
|
||||
$0
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
BlockId(1) in BlockRelativeModuleId { block: Some(BlockId(0)), local_id: Idx::<ModuleData>(1) }
|
||||
BlockId(0) in BlockRelativeModuleId { block: None, local_id: Idx::<ModuleData>(0) }
|
||||
crate scope
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_imports() {
|
||||
check_at(
|
||||
r#"
|
||||
fn f() {
|
||||
mod m {
|
||||
struct ResolveMe {}
|
||||
fn g() {
|
||||
fn h() {
|
||||
use self::ResolveMe;
|
||||
$0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
ResolveMe: ti
|
||||
|
||||
block scope
|
||||
h: v
|
||||
|
||||
block scope
|
||||
m: t
|
||||
|
||||
block scope::m
|
||||
ResolveMe: t
|
||||
g: v
|
||||
|
||||
crate
|
||||
f: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_macro_items() {
|
||||
// Checks that legacy-scoped `macro_rules!` from parent namespaces are resolved and expanded
|
||||
// correctly.
|
||||
check_at(
|
||||
r#"
|
||||
macro_rules! mark {
|
||||
() => {
|
||||
struct Hit {}
|
||||
}
|
||||
}
|
||||
|
||||
fn f() {
|
||||
mark!();
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
Hit: t
|
||||
|
||||
crate
|
||||
f: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_resolve() {
|
||||
check_at(
|
||||
r#"
|
||||
//- /lib.rs crate:lib deps:core
|
||||
use core::cov_mark;
|
||||
|
||||
fn f() {
|
||||
fn nested() {
|
||||
cov_mark::mark!(Hit);
|
||||
$0
|
||||
}
|
||||
}
|
||||
//- /core.rs crate:core
|
||||
pub mod cov_mark {
|
||||
#[macro_export]
|
||||
macro_rules! _mark {
|
||||
($name:ident) => {
|
||||
struct $name {}
|
||||
}
|
||||
}
|
||||
|
||||
pub use crate::_mark as mark;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
Hit: t
|
||||
|
||||
block scope
|
||||
nested: v
|
||||
|
||||
crate
|
||||
cov_mark: ti
|
||||
f: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_exported_in_block_mod() {
|
||||
check_at(
|
||||
r#"
|
||||
#[macro_export]
|
||||
macro_rules! foo {
|
||||
() => { pub struct FooWorks; };
|
||||
}
|
||||
macro_rules! bar {
|
||||
() => { pub struct BarWorks; };
|
||||
}
|
||||
fn main() {
|
||||
mod module {
|
||||
foo!();
|
||||
bar!();
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
module: t
|
||||
|
||||
block scope::module
|
||||
BarWorks: t v
|
||||
FooWorks: t v
|
||||
|
||||
crate
|
||||
foo: m
|
||||
main: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_resolve_legacy() {
|
||||
check_at(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
mod module;
|
||||
|
||||
//- /module.rs
|
||||
macro_rules! m {
|
||||
() => {
|
||||
struct Def {}
|
||||
};
|
||||
}
|
||||
|
||||
fn f() {
|
||||
{
|
||||
m!();
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
Def: t
|
||||
|
||||
crate
|
||||
module: t
|
||||
|
||||
crate::module
|
||||
f: v
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn super_does_not_resolve_to_block_module() {
|
||||
check_at(
|
||||
r#"
|
||||
fn main() {
|
||||
struct Struct {}
|
||||
mod module {
|
||||
use super::Struct;
|
||||
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
Struct: t
|
||||
module: t
|
||||
|
||||
block scope::module
|
||||
Struct: _
|
||||
|
||||
crate
|
||||
main: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn underscore_import() {
|
||||
// This used to panic, because the default (private) visibility inside block expressions would
|
||||
// point into the containing `DefMap`, which visibilities should never be able to do.
|
||||
cov_mark::check!(adjust_vis_in_block_def_map);
|
||||
check_at(
|
||||
r#"
|
||||
mod m {
|
||||
fn main() {
|
||||
use Tr as _;
|
||||
trait Tr {}
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
_: t
|
||||
Tr: t
|
||||
|
||||
crate
|
||||
m: t
|
||||
|
||||
crate::m
|
||||
main: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_macro_item_decl() {
|
||||
cov_mark::check!(macro_call_in_macro_stmts_is_added_to_item_tree);
|
||||
check_at(
|
||||
r#"
|
||||
macro_rules! inner_declare {
|
||||
($ident:ident) => {
|
||||
static $ident: u32 = 0;
|
||||
};
|
||||
}
|
||||
macro_rules! declare {
|
||||
($ident:ident) => {
|
||||
inner_declare!($ident);
|
||||
};
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
declare!(bar);
|
||||
bar;
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
bar: v
|
||||
|
||||
crate
|
||||
foo: v
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_visible_from_same_def_map() {
|
||||
// Regression test for https://github.com/rust-lang/rust-analyzer/issues/9481
|
||||
cov_mark::check!(is_visible_from_same_block_def_map);
|
||||
check_at(
|
||||
r#"
|
||||
fn outer() {
|
||||
mod tests {
|
||||
use super::*;
|
||||
}
|
||||
use crate::name;
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
name: _
|
||||
tests: t
|
||||
|
||||
block scope::tests
|
||||
name: _
|
||||
outer: v
|
||||
|
||||
crate
|
||||
outer: v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stmt_macro_expansion_with_trailing_expr() {
|
||||
cov_mark::check!(macro_stmt_with_trailing_macro_expr);
|
||||
check_at(
|
||||
r#"
|
||||
macro_rules! mac {
|
||||
() => { mac!($) };
|
||||
($x:tt) => { fn inner() {} };
|
||||
}
|
||||
fn foo() {
|
||||
mac!();
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
inner: v
|
||||
|
||||
crate
|
||||
foo: v
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_expr_macro_expands_stmts() {
|
||||
check_at(
|
||||
r#"
|
||||
macro_rules! foo {
|
||||
() => { const FOO: u32 = 0;const BAR: u32 = 0; };
|
||||
}
|
||||
fn f() {$0
|
||||
foo!{}
|
||||
};
|
||||
"#,
|
||||
expect![[r#"
|
||||
block scope
|
||||
BAR: v
|
||||
FOO: v
|
||||
|
||||
crate
|
||||
f: v
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
199
src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs
Normal file
199
src/tools/rust-analyzer/crates/hir-def/src/builtin_type.rs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
//! This module defines built-in types.
|
||||
//!
|
||||
//! A peculiarity of built-in types is that they are always available and are
|
||||
//! not associated with any particular crate.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use hir_expand::name::{name, AsName, Name};
|
||||
/// Different signed int types.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum BuiltinInt {
|
||||
Isize,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
}
|
||||
|
||||
/// Different unsigned int types.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum BuiltinUint {
|
||||
Usize,
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum BuiltinFloat {
|
||||
F32,
|
||||
F64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum BuiltinType {
|
||||
Char,
|
||||
Bool,
|
||||
Str,
|
||||
Int(BuiltinInt),
|
||||
Uint(BuiltinUint),
|
||||
Float(BuiltinFloat),
|
||||
}
|
||||
|
||||
impl BuiltinType {
|
||||
#[rustfmt::skip]
|
||||
pub const ALL: &'static [(Name, BuiltinType)] = &[
|
||||
(name![char], BuiltinType::Char),
|
||||
(name![bool], BuiltinType::Bool),
|
||||
(name![str], BuiltinType::Str),
|
||||
|
||||
(name![isize], BuiltinType::Int(BuiltinInt::Isize)),
|
||||
(name![i8], BuiltinType::Int(BuiltinInt::I8)),
|
||||
(name![i16], BuiltinType::Int(BuiltinInt::I16)),
|
||||
(name![i32], BuiltinType::Int(BuiltinInt::I32)),
|
||||
(name![i64], BuiltinType::Int(BuiltinInt::I64)),
|
||||
(name![i128], BuiltinType::Int(BuiltinInt::I128)),
|
||||
|
||||
(name![usize], BuiltinType::Uint(BuiltinUint::Usize)),
|
||||
(name![u8], BuiltinType::Uint(BuiltinUint::U8)),
|
||||
(name![u16], BuiltinType::Uint(BuiltinUint::U16)),
|
||||
(name![u32], BuiltinType::Uint(BuiltinUint::U32)),
|
||||
(name![u64], BuiltinType::Uint(BuiltinUint::U64)),
|
||||
(name![u128], BuiltinType::Uint(BuiltinUint::U128)),
|
||||
|
||||
(name![f32], BuiltinType::Float(BuiltinFloat::F32)),
|
||||
(name![f64], BuiltinType::Float(BuiltinFloat::F64)),
|
||||
];
|
||||
|
||||
pub fn by_name(name: &Name) -> Option<Self> {
|
||||
Self::ALL.iter().find_map(|(n, ty)| if n == name { Some(*ty) } else { None })
|
||||
}
|
||||
}
|
||||
|
||||
impl AsName for BuiltinType {
|
||||
fn as_name(&self) -> Name {
|
||||
match self {
|
||||
BuiltinType::Char => name![char],
|
||||
BuiltinType::Bool => name![bool],
|
||||
BuiltinType::Str => name![str],
|
||||
BuiltinType::Int(it) => match it {
|
||||
BuiltinInt::Isize => name![isize],
|
||||
BuiltinInt::I8 => name![i8],
|
||||
BuiltinInt::I16 => name![i16],
|
||||
BuiltinInt::I32 => name![i32],
|
||||
BuiltinInt::I64 => name![i64],
|
||||
BuiltinInt::I128 => name![i128],
|
||||
},
|
||||
BuiltinType::Uint(it) => match it {
|
||||
BuiltinUint::Usize => name![usize],
|
||||
BuiltinUint::U8 => name![u8],
|
||||
BuiltinUint::U16 => name![u16],
|
||||
BuiltinUint::U32 => name![u32],
|
||||
BuiltinUint::U64 => name![u64],
|
||||
BuiltinUint::U128 => name![u128],
|
||||
},
|
||||
BuiltinType::Float(it) => match it {
|
||||
BuiltinFloat::F32 => name![f32],
|
||||
BuiltinFloat::F64 => name![f64],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BuiltinType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BuiltinType::Char => f.write_str("char"),
|
||||
BuiltinType::Bool => f.write_str("bool"),
|
||||
BuiltinType::Str => f.write_str("str"),
|
||||
BuiltinType::Int(it) => it.fmt(f),
|
||||
BuiltinType::Uint(it) => it.fmt(f),
|
||||
BuiltinType::Float(it) => it.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl BuiltinInt {
|
||||
pub fn from_suffix(suffix: &str) -> Option<BuiltinInt> {
|
||||
let res = match suffix {
|
||||
"isize" => Self::Isize,
|
||||
"i8" => Self::I8,
|
||||
"i16" => Self::I16,
|
||||
"i32" => Self::I32,
|
||||
"i64" => Self::I64,
|
||||
"i128" => Self::I128,
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl BuiltinUint {
|
||||
pub fn from_suffix(suffix: &str) -> Option<BuiltinUint> {
|
||||
let res = match suffix {
|
||||
"usize" => Self::Usize,
|
||||
"u8" => Self::U8,
|
||||
"u16" => Self::U16,
|
||||
"u32" => Self::U32,
|
||||
"u64" => Self::U64,
|
||||
"u128" => Self::U128,
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl BuiltinFloat {
|
||||
pub fn from_suffix(suffix: &str) -> Option<BuiltinFloat> {
|
||||
let res = match suffix {
|
||||
"f32" => BuiltinFloat::F32,
|
||||
"f64" => BuiltinFloat::F64,
|
||||
_ => return None,
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BuiltinInt {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
BuiltinInt::Isize => "isize",
|
||||
BuiltinInt::I8 => "i8",
|
||||
BuiltinInt::I16 => "i16",
|
||||
BuiltinInt::I32 => "i32",
|
||||
BuiltinInt::I64 => "i64",
|
||||
BuiltinInt::I128 => "i128",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BuiltinUint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
BuiltinUint::Usize => "usize",
|
||||
BuiltinUint::U8 => "u8",
|
||||
BuiltinUint::U16 => "u16",
|
||||
BuiltinUint::U32 => "u32",
|
||||
BuiltinUint::U64 => "u64",
|
||||
BuiltinUint::U128 => "u128",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BuiltinFloat {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
BuiltinFloat::F32 => "f32",
|
||||
BuiltinFloat::F64 => "f64",
|
||||
})
|
||||
}
|
||||
}
|
||||
259
src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs
Normal file
259
src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
//! When *constructing* `hir`, we start at some parent syntax node and recursively
|
||||
//! lower the children.
|
||||
//!
|
||||
//! This module allows one to go in the opposite direction: start with a syntax
|
||||
//! node for a *child*, and get its hir.
|
||||
|
||||
use either::Either;
|
||||
use hir_expand::{attrs::collect_attrs, HirFileId};
|
||||
use syntax::ast;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
dyn_map::{
|
||||
keys::{self, Key},
|
||||
DynMap,
|
||||
},
|
||||
item_scope::ItemScope,
|
||||
item_tree::ItemTreeNode,
|
||||
nameres::DefMap,
|
||||
src::{HasChildSource, HasSource},
|
||||
AdtId, AssocItemId, DefWithBodyId, EnumId, FieldId, GenericDefId, ImplId, ItemTreeLoc,
|
||||
LifetimeParamId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId, TypeOrConstParamId,
|
||||
VariantId,
|
||||
};
|
||||
|
||||
pub trait ChildBySource {
|
||||
fn child_by_source(&self, db: &dyn DefDatabase, file_id: HirFileId) -> DynMap {
|
||||
let mut res = DynMap::default();
|
||||
self.child_by_source_to(db, &mut res, file_id);
|
||||
res
|
||||
}
|
||||
fn child_by_source_to(&self, db: &dyn DefDatabase, map: &mut DynMap, file_id: HirFileId);
|
||||
}
|
||||
|
||||
impl ChildBySource for TraitId {
|
||||
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
|
||||
let data = db.trait_data(*self);
|
||||
|
||||
data.attribute_calls().filter(|(ast_id, _)| ast_id.file_id == file_id).for_each(
|
||||
|(ast_id, call_id)| {
|
||||
res[keys::ATTR_MACRO_CALL].insert(ast_id.to_node(db.upcast()), call_id);
|
||||
},
|
||||
);
|
||||
data.items.iter().for_each(|&(_, item)| {
|
||||
add_assoc_item(db, res, file_id, item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildBySource for ImplId {
|
||||
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
|
||||
let data = db.impl_data(*self);
|
||||
data.attribute_calls().filter(|(ast_id, _)| ast_id.file_id == file_id).for_each(
|
||||
|(ast_id, call_id)| {
|
||||
res[keys::ATTR_MACRO_CALL].insert(ast_id.to_node(db.upcast()), call_id);
|
||||
},
|
||||
);
|
||||
data.items.iter().for_each(|&item| {
|
||||
add_assoc_item(db, res, file_id, item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildBySource for ModuleId {
|
||||
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
|
||||
let def_map = self.def_map(db);
|
||||
let module_data = &def_map[self.local_id];
|
||||
module_data.scope.child_by_source_to(db, res, file_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildBySource for ItemScope {
|
||||
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
|
||||
self.declarations().for_each(|item| add_module_def(db, res, file_id, item));
|
||||
self.impls().for_each(|imp| insert_item_loc(db, res, file_id, imp, keys::IMPL));
|
||||
self.extern_crate_decls()
|
||||
.for_each(|ext| insert_item_loc(db, res, file_id, ext, keys::EXTERN_CRATE));
|
||||
self.use_decls().for_each(|ext| insert_item_loc(db, res, file_id, ext, keys::USE));
|
||||
self.unnamed_consts()
|
||||
.for_each(|konst| insert_item_loc(db, res, file_id, konst, keys::CONST));
|
||||
self.attr_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each(
|
||||
|(ast_id, call_id)| {
|
||||
res[keys::ATTR_MACRO_CALL].insert(ast_id.to_node(db.upcast()), call_id);
|
||||
},
|
||||
);
|
||||
self.legacy_macros().for_each(|(_, ids)| {
|
||||
ids.iter().for_each(|&id| {
|
||||
if let MacroId::MacroRulesId(id) = id {
|
||||
let loc = id.lookup(db);
|
||||
if loc.id.file_id() == file_id {
|
||||
res[keys::MACRO_RULES].insert(loc.source(db).value, id);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
self.derive_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each(
|
||||
|(ast_id, calls)| {
|
||||
let adt = ast_id.to_node(db.upcast());
|
||||
calls.for_each(|(attr_id, call_id, calls)| {
|
||||
if let Some((_, Either::Left(attr))) =
|
||||
collect_attrs(&adt).nth(attr_id.ast_index())
|
||||
{
|
||||
res[keys::DERIVE_MACRO_CALL].insert(attr, (attr_id, call_id, calls.into()));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
fn add_module_def(
|
||||
db: &dyn DefDatabase,
|
||||
map: &mut DynMap,
|
||||
file_id: HirFileId,
|
||||
item: ModuleDefId,
|
||||
) {
|
||||
match item {
|
||||
ModuleDefId::FunctionId(id) => {
|
||||
insert_item_loc(db, map, file_id, id, keys::FUNCTION)
|
||||
}
|
||||
ModuleDefId::ConstId(id) => insert_item_loc(db, map, file_id, id, keys::CONST),
|
||||
ModuleDefId::TypeAliasId(id) => {
|
||||
insert_item_loc(db, map, file_id, id, keys::TYPE_ALIAS)
|
||||
}
|
||||
ModuleDefId::StaticId(id) => insert_item_loc(db, map, file_id, id, keys::STATIC),
|
||||
ModuleDefId::TraitId(id) => insert_item_loc(db, map, file_id, id, keys::TRAIT),
|
||||
ModuleDefId::TraitAliasId(id) => {
|
||||
insert_item_loc(db, map, file_id, id, keys::TRAIT_ALIAS)
|
||||
}
|
||||
ModuleDefId::AdtId(adt) => match adt {
|
||||
AdtId::StructId(id) => insert_item_loc(db, map, file_id, id, keys::STRUCT),
|
||||
AdtId::UnionId(id) => insert_item_loc(db, map, file_id, id, keys::UNION),
|
||||
AdtId::EnumId(id) => insert_item_loc(db, map, file_id, id, keys::ENUM),
|
||||
},
|
||||
ModuleDefId::MacroId(id) => match id {
|
||||
MacroId::Macro2Id(id) => insert_item_loc(db, map, file_id, id, keys::MACRO2),
|
||||
MacroId::MacroRulesId(id) => {
|
||||
insert_item_loc(db, map, file_id, id, keys::MACRO_RULES)
|
||||
}
|
||||
MacroId::ProcMacroId(id) => {
|
||||
insert_item_loc(db, map, file_id, id, keys::PROC_MACRO)
|
||||
}
|
||||
},
|
||||
ModuleDefId::ModuleId(_)
|
||||
| ModuleDefId::EnumVariantId(_)
|
||||
| ModuleDefId::BuiltinType(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildBySource for VariantId {
|
||||
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, _: HirFileId) {
|
||||
let arena_map = self.child_source(db);
|
||||
let arena_map = arena_map.as_ref();
|
||||
let parent = *self;
|
||||
for (local_id, source) in arena_map.value.iter() {
|
||||
let id = FieldId { parent, local_id };
|
||||
match source.clone() {
|
||||
Either::Left(source) => res[keys::TUPLE_FIELD].insert(source, id),
|
||||
Either::Right(source) => res[keys::RECORD_FIELD].insert(source, id),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildBySource for EnumId {
|
||||
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
|
||||
let loc = &self.lookup(db);
|
||||
if file_id != loc.id.file_id() {
|
||||
return;
|
||||
}
|
||||
|
||||
let tree = loc.id.item_tree(db);
|
||||
let ast_id_map = db.ast_id_map(loc.id.file_id());
|
||||
let root = db.parse_or_expand(loc.id.file_id());
|
||||
|
||||
db.enum_data(*self).variants.iter().for_each(|&(variant, _)| {
|
||||
res[keys::ENUM_VARIANT].insert(
|
||||
ast_id_map.get(tree[variant.lookup(db).id.value].ast_id).to_node(&root),
|
||||
variant,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildBySource for DefWithBodyId {
|
||||
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
|
||||
let body = db.body(*self);
|
||||
if let &DefWithBodyId::VariantId(v) = self {
|
||||
VariantId::EnumVariantId(v).child_by_source_to(db, res, file_id)
|
||||
}
|
||||
|
||||
for (block, def_map) in body.blocks(db) {
|
||||
// All block expressions are merged into the same map, because they logically all add
|
||||
// inner items to the containing `DefWithBodyId`.
|
||||
def_map[DefMap::ROOT].scope.child_by_source_to(db, res, file_id);
|
||||
res[keys::BLOCK].insert(block.lookup(db).ast_id.to_node(db.upcast()), block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildBySource for GenericDefId {
|
||||
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
|
||||
let (gfile_id, generic_params_list) = self.file_id_and_params_of(db);
|
||||
if gfile_id != file_id {
|
||||
return;
|
||||
}
|
||||
|
||||
let generic_params = db.generic_params(*self);
|
||||
let mut toc_idx_iter = generic_params.type_or_consts.iter().map(|(idx, _)| idx);
|
||||
let lts_idx_iter = generic_params.lifetimes.iter().map(|(idx, _)| idx);
|
||||
|
||||
// For traits the first type index is `Self`, skip it.
|
||||
if let GenericDefId::TraitId(_) = *self {
|
||||
toc_idx_iter.next().unwrap(); // advance_by(1);
|
||||
}
|
||||
|
||||
if let Some(generic_params_list) = generic_params_list {
|
||||
for (local_id, ast_param) in
|
||||
toc_idx_iter.zip(generic_params_list.type_or_const_params())
|
||||
{
|
||||
let id = TypeOrConstParamId { parent: *self, local_id };
|
||||
match ast_param {
|
||||
ast::TypeOrConstParam::Type(a) => res[keys::TYPE_PARAM].insert(a, id),
|
||||
ast::TypeOrConstParam::Const(a) => res[keys::CONST_PARAM].insert(a, id),
|
||||
}
|
||||
}
|
||||
for (local_id, ast_param) in lts_idx_iter.zip(generic_params_list.lifetime_params()) {
|
||||
let id = LifetimeParamId { parent: *self, local_id };
|
||||
res[keys::LIFETIME_PARAM].insert(ast_param, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_item_loc<ID, N, Data>(
|
||||
db: &dyn DefDatabase,
|
||||
res: &mut DynMap,
|
||||
file_id: HirFileId,
|
||||
id: ID,
|
||||
key: Key<N::Source, ID>,
|
||||
) where
|
||||
ID: for<'db> Lookup<Database<'db> = dyn DefDatabase + 'db, Data = Data> + 'static,
|
||||
Data: ItemTreeLoc<Id = N>,
|
||||
N: ItemTreeNode,
|
||||
N::Source: 'static,
|
||||
{
|
||||
let loc = id.lookup(db);
|
||||
if loc.item_tree_id().file_id() == file_id {
|
||||
res[key].insert(loc.source(db).value, id)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_assoc_item(db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId, item: AssocItemId) {
|
||||
match item {
|
||||
AssocItemId::FunctionId(func) => insert_item_loc(db, res, file_id, func, keys::FUNCTION),
|
||||
AssocItemId::ConstId(konst) => insert_item_loc(db, res, file_id, konst, keys::CONST),
|
||||
AssocItemId::TypeAliasId(ty) => insert_item_loc(db, res, file_id, ty, keys::TYPE_ALIAS),
|
||||
}
|
||||
}
|
||||
821
src/tools/rust-analyzer/crates/hir-def/src/data.rs
Normal file
821
src/tools/rust-analyzer/crates/hir-def/src/data.rs
Normal file
|
|
@ -0,0 +1,821 @@
|
|||
//! Contains basic data about various HIR declarations.
|
||||
|
||||
pub mod adt;
|
||||
|
||||
use base_db::CrateId;
|
||||
use hir_expand::{
|
||||
name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefKind,
|
||||
};
|
||||
use intern::Interned;
|
||||
use smallvec::SmallVec;
|
||||
use syntax::{ast, Parse};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
attr::Attrs,
|
||||
db::DefDatabase,
|
||||
expander::{Expander, Mark},
|
||||
item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, MacroCall, ModItem, TreeId},
|
||||
macro_call_as_call_id,
|
||||
nameres::{
|
||||
attr_resolution::ResolvedAttr,
|
||||
diagnostics::{DefDiagnostic, DefDiagnostics},
|
||||
proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroKind},
|
||||
DefMap, MacroSubNs,
|
||||
},
|
||||
path::ImportAlias,
|
||||
type_ref::{TraitRef, TypeBound, TypeRef},
|
||||
visibility::RawVisibility,
|
||||
AssocItemId, AstIdWithPath, ConstId, ConstLoc, ExternCrateId, FunctionId, FunctionLoc,
|
||||
HasModule, ImplId, Intern, ItemContainerId, ItemLoc, Lookup, Macro2Id, MacroRulesId, ModuleId,
|
||||
ProcMacroId, StaticId, TraitAliasId, TraitId, TypeAliasId, TypeAliasLoc,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FunctionData {
|
||||
pub name: Name,
|
||||
pub params: Box<[Interned<TypeRef>]>,
|
||||
pub ret_type: Interned<TypeRef>,
|
||||
pub attrs: Attrs,
|
||||
pub visibility: RawVisibility,
|
||||
pub abi: Option<Interned<str>>,
|
||||
pub legacy_const_generics_indices: Box<[u32]>,
|
||||
pub rustc_allow_incoherent_impl: bool,
|
||||
flags: FnFlags,
|
||||
}
|
||||
|
||||
impl FunctionData {
|
||||
pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> {
|
||||
let loc = func.lookup(db);
|
||||
let krate = loc.container.module(db).krate;
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let func = &item_tree[loc.id.value];
|
||||
let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container {
|
||||
trait_vis(db, trait_id)
|
||||
} else {
|
||||
item_tree[func.visibility].clone()
|
||||
};
|
||||
|
||||
let crate_graph = db.crate_graph();
|
||||
let cfg_options = &crate_graph[krate].cfg_options;
|
||||
let enabled_params = func
|
||||
.params
|
||||
.clone()
|
||||
.filter(|¶m| item_tree.attrs(db, krate, param.into()).is_cfg_enabled(cfg_options));
|
||||
|
||||
// If last cfg-enabled param is a `...` param, it's a varargs function.
|
||||
let is_varargs = enabled_params
|
||||
.clone()
|
||||
.next_back()
|
||||
.map_or(false, |param| item_tree[param].type_ref.is_none());
|
||||
|
||||
let mut flags = func.flags;
|
||||
if is_varargs {
|
||||
flags |= FnFlags::IS_VARARGS;
|
||||
}
|
||||
if flags.contains(FnFlags::HAS_SELF_PARAM) {
|
||||
// If there's a self param in the syntax, but it is cfg'd out, remove the flag.
|
||||
let is_cfgd_out = match func.params.clone().next() {
|
||||
Some(param) => {
|
||||
!item_tree.attrs(db, krate, param.into()).is_cfg_enabled(cfg_options)
|
||||
}
|
||||
None => {
|
||||
stdx::never!("fn HAS_SELF_PARAM but no parameters allocated");
|
||||
true
|
||||
}
|
||||
};
|
||||
if is_cfgd_out {
|
||||
cov_mark::hit!(cfgd_out_self_param);
|
||||
flags.remove(FnFlags::HAS_SELF_PARAM);
|
||||
}
|
||||
}
|
||||
|
||||
let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into());
|
||||
let legacy_const_generics_indices = attrs
|
||||
.by_key("rustc_legacy_const_generics")
|
||||
.tt_values()
|
||||
.next()
|
||||
.map(parse_rustc_legacy_const_generics)
|
||||
.unwrap_or_default();
|
||||
let rustc_allow_incoherent_impl = attrs.by_key("rustc_allow_incoherent_impl").exists();
|
||||
|
||||
Arc::new(FunctionData {
|
||||
name: func.name.clone(),
|
||||
params: enabled_params
|
||||
.clone()
|
||||
.filter_map(|id| item_tree[id].type_ref.clone())
|
||||
.collect(),
|
||||
ret_type: func.ret_type.clone(),
|
||||
attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()),
|
||||
visibility,
|
||||
abi: func.abi.clone(),
|
||||
legacy_const_generics_indices,
|
||||
flags,
|
||||
rustc_allow_incoherent_impl,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_body(&self) -> bool {
|
||||
self.flags.contains(FnFlags::HAS_BODY)
|
||||
}
|
||||
|
||||
/// True if the first param is `self`. This is relevant to decide whether this
|
||||
/// can be called as a method.
|
||||
pub fn has_self_param(&self) -> bool {
|
||||
self.flags.contains(FnFlags::HAS_SELF_PARAM)
|
||||
}
|
||||
|
||||
pub fn has_default_kw(&self) -> bool {
|
||||
self.flags.contains(FnFlags::HAS_DEFAULT_KW)
|
||||
}
|
||||
|
||||
pub fn has_const_kw(&self) -> bool {
|
||||
self.flags.contains(FnFlags::HAS_CONST_KW)
|
||||
}
|
||||
|
||||
pub fn has_async_kw(&self) -> bool {
|
||||
self.flags.contains(FnFlags::HAS_ASYNC_KW)
|
||||
}
|
||||
|
||||
pub fn has_unsafe_kw(&self) -> bool {
|
||||
self.flags.contains(FnFlags::HAS_UNSAFE_KW)
|
||||
}
|
||||
|
||||
pub fn is_varargs(&self) -> bool {
|
||||
self.flags.contains(FnFlags::IS_VARARGS)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_rustc_legacy_const_generics(tt: &crate::tt::Subtree) -> Box<[u32]> {
|
||||
let mut indices = Vec::new();
|
||||
for args in tt.token_trees.chunks(2) {
|
||||
match &args[0] {
|
||||
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => match lit.text.parse() {
|
||||
Ok(index) => indices.push(index),
|
||||
Err(_) => break,
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
|
||||
if let Some(comma) = args.get(1) {
|
||||
match comma {
|
||||
tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if punct.char == ',' => {}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indices.into_boxed_slice()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TypeAliasData {
|
||||
pub name: Name,
|
||||
pub type_ref: Option<Interned<TypeRef>>,
|
||||
pub visibility: RawVisibility,
|
||||
pub is_extern: bool,
|
||||
pub rustc_has_incoherent_inherent_impls: bool,
|
||||
pub rustc_allow_incoherent_impl: bool,
|
||||
/// Bounds restricting the type alias itself (eg. `type Ty: Bound;` in a trait or impl).
|
||||
pub bounds: Box<[Interned<TypeBound>]>,
|
||||
}
|
||||
|
||||
impl TypeAliasData {
|
||||
pub(crate) fn type_alias_data_query(
|
||||
db: &dyn DefDatabase,
|
||||
typ: TypeAliasId,
|
||||
) -> Arc<TypeAliasData> {
|
||||
let loc = typ.lookup(db);
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let typ = &item_tree[loc.id.value];
|
||||
let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container {
|
||||
trait_vis(db, trait_id)
|
||||
} else {
|
||||
item_tree[typ.visibility].clone()
|
||||
};
|
||||
|
||||
let attrs = item_tree.attrs(
|
||||
db,
|
||||
loc.container.module(db).krate(),
|
||||
ModItem::from(loc.id.value).into(),
|
||||
);
|
||||
let rustc_has_incoherent_inherent_impls =
|
||||
attrs.by_key("rustc_has_incoherent_inherent_impls").exists();
|
||||
let rustc_allow_incoherent_impl = attrs.by_key("rustc_allow_incoherent_impl").exists();
|
||||
|
||||
Arc::new(TypeAliasData {
|
||||
name: typ.name.clone(),
|
||||
type_ref: typ.type_ref.clone(),
|
||||
visibility,
|
||||
is_extern: matches!(loc.container, ItemContainerId::ExternBlockId(_)),
|
||||
rustc_has_incoherent_inherent_impls,
|
||||
rustc_allow_incoherent_impl,
|
||||
bounds: typ.bounds.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TraitData {
|
||||
pub name: Name,
|
||||
pub items: Vec<(Name, AssocItemId)>,
|
||||
pub is_auto: bool,
|
||||
pub is_unsafe: bool,
|
||||
pub rustc_has_incoherent_inherent_impls: bool,
|
||||
pub skip_array_during_method_dispatch: bool,
|
||||
pub fundamental: bool,
|
||||
pub visibility: RawVisibility,
|
||||
/// Whether the trait has `#[rust_skip_array_during_method_dispatch]`. `hir_ty` will ignore
|
||||
/// method calls to this trait's methods when the receiver is an array and the crate edition is
|
||||
/// 2015 or 2018.
|
||||
// box it as the vec is usually empty anyways
|
||||
pub attribute_calls: Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>,
|
||||
}
|
||||
|
||||
impl TraitData {
|
||||
#[inline]
|
||||
pub(crate) fn trait_data_query(db: &dyn DefDatabase, tr: TraitId) -> Arc<TraitData> {
|
||||
db.trait_data_with_diagnostics(tr).0
|
||||
}
|
||||
|
||||
pub(crate) fn trait_data_with_diagnostics_query(
|
||||
db: &dyn DefDatabase,
|
||||
tr: TraitId,
|
||||
) -> (Arc<TraitData>, DefDiagnostics) {
|
||||
let ItemLoc { container: module_id, id: tree_id } = tr.lookup(db);
|
||||
let item_tree = tree_id.item_tree(db);
|
||||
let tr_def = &item_tree[tree_id.value];
|
||||
let name = tr_def.name.clone();
|
||||
let is_auto = tr_def.is_auto;
|
||||
let is_unsafe = tr_def.is_unsafe;
|
||||
let visibility = item_tree[tr_def.visibility].clone();
|
||||
let attrs = item_tree.attrs(db, module_id.krate(), ModItem::from(tree_id.value).into());
|
||||
let skip_array_during_method_dispatch =
|
||||
attrs.by_key("rustc_skip_array_during_method_dispatch").exists();
|
||||
let rustc_has_incoherent_inherent_impls =
|
||||
attrs.by_key("rustc_has_incoherent_inherent_impls").exists();
|
||||
let fundamental = attrs.by_key("fundamental").exists();
|
||||
let mut collector =
|
||||
AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::TraitId(tr));
|
||||
collector.collect(&item_tree, tree_id.tree_id(), &tr_def.items);
|
||||
let (items, attribute_calls, diagnostics) = collector.finish();
|
||||
|
||||
(
|
||||
Arc::new(TraitData {
|
||||
name,
|
||||
attribute_calls,
|
||||
items,
|
||||
is_auto,
|
||||
is_unsafe,
|
||||
visibility,
|
||||
skip_array_during_method_dispatch,
|
||||
rustc_has_incoherent_inherent_impls,
|
||||
fundamental,
|
||||
}),
|
||||
DefDiagnostics::new(diagnostics),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn associated_types(&self) -> impl Iterator<Item = TypeAliasId> + '_ {
|
||||
self.items.iter().filter_map(|(_name, item)| match item {
|
||||
AssocItemId::TypeAliasId(t) => Some(*t),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn associated_type_by_name(&self, name: &Name) -> Option<TypeAliasId> {
|
||||
self.items.iter().find_map(|(item_name, item)| match item {
|
||||
AssocItemId::TypeAliasId(t) if item_name == name => Some(*t),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn method_by_name(&self, name: &Name) -> Option<FunctionId> {
|
||||
self.items.iter().find_map(|(item_name, item)| match item {
|
||||
AssocItemId::FunctionId(t) if item_name == name => Some(*t),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn attribute_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ {
|
||||
self.attribute_calls.iter().flat_map(|it| it.iter()).copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TraitAliasData {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibility,
|
||||
}
|
||||
|
||||
impl TraitAliasData {
|
||||
pub(crate) fn trait_alias_query(db: &dyn DefDatabase, id: TraitAliasId) -> Arc<TraitAliasData> {
|
||||
let loc = id.lookup(db);
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let alias = &item_tree[loc.id.value];
|
||||
let visibility = item_tree[alias.visibility].clone();
|
||||
|
||||
Arc::new(TraitAliasData { name: alias.name.clone(), visibility })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ImplData {
|
||||
pub target_trait: Option<Interned<TraitRef>>,
|
||||
pub self_ty: Interned<TypeRef>,
|
||||
pub items: Vec<AssocItemId>,
|
||||
pub is_negative: bool,
|
||||
pub is_unsafe: bool,
|
||||
// box it as the vec is usually empty anyways
|
||||
pub attribute_calls: Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>,
|
||||
}
|
||||
|
||||
impl ImplData {
|
||||
#[inline]
|
||||
pub(crate) fn impl_data_query(db: &dyn DefDatabase, id: ImplId) -> Arc<ImplData> {
|
||||
db.impl_data_with_diagnostics(id).0
|
||||
}
|
||||
|
||||
pub(crate) fn impl_data_with_diagnostics_query(
|
||||
db: &dyn DefDatabase,
|
||||
id: ImplId,
|
||||
) -> (Arc<ImplData>, DefDiagnostics) {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "impl_data_with_diagnostics_query").entered();
|
||||
let ItemLoc { container: module_id, id: tree_id } = id.lookup(db);
|
||||
|
||||
let item_tree = tree_id.item_tree(db);
|
||||
let impl_def = &item_tree[tree_id.value];
|
||||
let target_trait = impl_def.target_trait.clone();
|
||||
let self_ty = impl_def.self_ty.clone();
|
||||
let is_negative = impl_def.is_negative;
|
||||
let is_unsafe = impl_def.is_unsafe;
|
||||
|
||||
let mut collector =
|
||||
AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::ImplId(id));
|
||||
collector.collect(&item_tree, tree_id.tree_id(), &impl_def.items);
|
||||
|
||||
let (items, attribute_calls, diagnostics) = collector.finish();
|
||||
let items = items.into_iter().map(|(_, item)| item).collect();
|
||||
|
||||
(
|
||||
Arc::new(ImplData {
|
||||
target_trait,
|
||||
self_ty,
|
||||
items,
|
||||
is_negative,
|
||||
is_unsafe,
|
||||
attribute_calls,
|
||||
}),
|
||||
DefDiagnostics::new(diagnostics),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn attribute_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ {
|
||||
self.attribute_calls.iter().flat_map(|it| it.iter()).copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Macro2Data {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibility,
|
||||
// It's a bit wasteful as currently this is only for builtin `Default` derive macro, but macro2
|
||||
// are rarely used in practice so I think it's okay for now.
|
||||
/// Derive helpers, if this is a derive rustc_builtin_macro
|
||||
pub helpers: Option<Box<[Name]>>,
|
||||
}
|
||||
|
||||
impl Macro2Data {
|
||||
pub(crate) fn macro2_data_query(db: &dyn DefDatabase, makro: Macro2Id) -> Arc<Macro2Data> {
|
||||
let loc = makro.lookup(db);
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let makro = &item_tree[loc.id.value];
|
||||
|
||||
let helpers = item_tree
|
||||
.attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into())
|
||||
.by_key("rustc_builtin_macro")
|
||||
.tt_values()
|
||||
.next()
|
||||
.and_then(|attr| parse_macro_name_and_helper_attrs(&attr.token_trees))
|
||||
.map(|(_, helpers)| helpers);
|
||||
|
||||
Arc::new(Macro2Data {
|
||||
name: makro.name.clone(),
|
||||
visibility: item_tree[makro.visibility].clone(),
|
||||
helpers,
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MacroRulesData {
|
||||
pub name: Name,
|
||||
pub macro_export: bool,
|
||||
}
|
||||
|
||||
impl MacroRulesData {
|
||||
pub(crate) fn macro_rules_data_query(
|
||||
db: &dyn DefDatabase,
|
||||
makro: MacroRulesId,
|
||||
) -> Arc<MacroRulesData> {
|
||||
let loc = makro.lookup(db);
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let makro = &item_tree[loc.id.value];
|
||||
|
||||
let macro_export = item_tree
|
||||
.attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into())
|
||||
.by_key("macro_export")
|
||||
.exists();
|
||||
|
||||
Arc::new(MacroRulesData { name: makro.name.clone(), macro_export })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProcMacroData {
|
||||
pub name: Name,
|
||||
/// Derive helpers, if this is a derive
|
||||
pub helpers: Option<Box<[Name]>>,
|
||||
}
|
||||
|
||||
impl ProcMacroData {
|
||||
pub(crate) fn proc_macro_data_query(
|
||||
db: &dyn DefDatabase,
|
||||
makro: ProcMacroId,
|
||||
) -> Arc<ProcMacroData> {
|
||||
let loc = makro.lookup(db);
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let makro = &item_tree[loc.id.value];
|
||||
|
||||
let (name, helpers) = if let Some(def) = item_tree
|
||||
.attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into())
|
||||
.parse_proc_macro_decl(&makro.name)
|
||||
{
|
||||
(
|
||||
def.name,
|
||||
match def.kind {
|
||||
ProcMacroKind::Derive { helpers } => Some(helpers),
|
||||
ProcMacroKind::Bang | ProcMacroKind::Attr => None,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
// eeeh...
|
||||
stdx::never!("proc macro declaration is not a proc macro");
|
||||
(makro.name.clone(), None)
|
||||
};
|
||||
Arc::new(ProcMacroData { name, helpers })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExternCrateDeclData {
|
||||
pub name: Name,
|
||||
pub alias: Option<ImportAlias>,
|
||||
pub visibility: RawVisibility,
|
||||
pub crate_id: Option<CrateId>,
|
||||
}
|
||||
|
||||
impl ExternCrateDeclData {
|
||||
pub(crate) fn extern_crate_decl_data_query(
|
||||
db: &dyn DefDatabase,
|
||||
extern_crate: ExternCrateId,
|
||||
) -> Arc<ExternCrateDeclData> {
|
||||
let loc = extern_crate.lookup(db);
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let extern_crate = &item_tree[loc.id.value];
|
||||
|
||||
let name = extern_crate.name.clone();
|
||||
let krate = loc.container.krate();
|
||||
let crate_id = if name == hir_expand::name![self] {
|
||||
Some(krate)
|
||||
} else {
|
||||
db.crate_def_map(krate)
|
||||
.extern_prelude()
|
||||
.find(|&(prelude_name, ..)| *prelude_name == name)
|
||||
.map(|(_, (root, _))| root.krate())
|
||||
};
|
||||
|
||||
Arc::new(Self {
|
||||
name: extern_crate.name.clone(),
|
||||
visibility: item_tree[extern_crate.visibility].clone(),
|
||||
alias: extern_crate.alias.clone(),
|
||||
crate_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConstData {
|
||||
/// `None` for `const _: () = ();`
|
||||
pub name: Option<Name>,
|
||||
pub type_ref: Interned<TypeRef>,
|
||||
pub visibility: RawVisibility,
|
||||
pub rustc_allow_incoherent_impl: bool,
|
||||
pub has_body: bool,
|
||||
}
|
||||
|
||||
impl ConstData {
|
||||
pub(crate) fn const_data_query(db: &dyn DefDatabase, konst: ConstId) -> Arc<ConstData> {
|
||||
let loc = konst.lookup(db);
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let konst = &item_tree[loc.id.value];
|
||||
let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container {
|
||||
trait_vis(db, trait_id)
|
||||
} else {
|
||||
item_tree[konst.visibility].clone()
|
||||
};
|
||||
|
||||
let rustc_allow_incoherent_impl = item_tree
|
||||
.attrs(db, loc.container.module(db).krate(), ModItem::from(loc.id.value).into())
|
||||
.by_key("rustc_allow_incoherent_impl")
|
||||
.exists();
|
||||
|
||||
Arc::new(ConstData {
|
||||
name: konst.name.clone(),
|
||||
type_ref: konst.type_ref.clone(),
|
||||
visibility,
|
||||
rustc_allow_incoherent_impl,
|
||||
has_body: konst.has_body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StaticData {
|
||||
pub name: Name,
|
||||
pub type_ref: Interned<TypeRef>,
|
||||
pub visibility: RawVisibility,
|
||||
pub mutable: bool,
|
||||
pub is_extern: bool,
|
||||
}
|
||||
|
||||
impl StaticData {
|
||||
pub(crate) fn static_data_query(db: &dyn DefDatabase, konst: StaticId) -> Arc<StaticData> {
|
||||
let loc = konst.lookup(db);
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let statik = &item_tree[loc.id.value];
|
||||
|
||||
Arc::new(StaticData {
|
||||
name: statik.name.clone(),
|
||||
type_ref: statik.type_ref.clone(),
|
||||
visibility: item_tree[statik.visibility].clone(),
|
||||
mutable: statik.mutable,
|
||||
is_extern: matches!(loc.container, ItemContainerId::ExternBlockId(_)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct AssocItemCollector<'a> {
|
||||
db: &'a dyn DefDatabase,
|
||||
module_id: ModuleId,
|
||||
def_map: Arc<DefMap>,
|
||||
diagnostics: Vec<DefDiagnostic>,
|
||||
container: ItemContainerId,
|
||||
expander: Expander,
|
||||
|
||||
items: Vec<(Name, AssocItemId)>,
|
||||
attr_calls: Vec<(AstId<ast::Item>, MacroCallId)>,
|
||||
}
|
||||
|
||||
impl<'a> AssocItemCollector<'a> {
|
||||
fn new(
|
||||
db: &'a dyn DefDatabase,
|
||||
module_id: ModuleId,
|
||||
file_id: HirFileId,
|
||||
container: ItemContainerId,
|
||||
) -> Self {
|
||||
Self {
|
||||
db,
|
||||
module_id,
|
||||
def_map: module_id.def_map(db),
|
||||
container,
|
||||
expander: Expander::new(db, file_id, module_id),
|
||||
items: Vec::new(),
|
||||
attr_calls: Vec::new(),
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(
|
||||
self,
|
||||
) -> (
|
||||
Vec<(Name, AssocItemId)>,
|
||||
Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>,
|
||||
Vec<DefDiagnostic>,
|
||||
) {
|
||||
(
|
||||
self.items,
|
||||
if self.attr_calls.is_empty() { None } else { Some(Box::new(self.attr_calls)) },
|
||||
self.diagnostics,
|
||||
)
|
||||
}
|
||||
|
||||
fn collect(&mut self, item_tree: &ItemTree, tree_id: TreeId, assoc_items: &[AssocItem]) {
|
||||
let container = self.container;
|
||||
self.items.reserve(assoc_items.len());
|
||||
|
||||
'items: for &item in assoc_items {
|
||||
let attrs = item_tree.attrs(self.db, self.module_id.krate, ModItem::from(item).into());
|
||||
if !attrs.is_cfg_enabled(self.expander.cfg_options()) {
|
||||
self.diagnostics.push(DefDiagnostic::unconfigured_code(
|
||||
self.module_id.local_id,
|
||||
InFile::new(self.expander.current_file_id(), item.ast_id(item_tree).erase()),
|
||||
attrs.cfg().unwrap(),
|
||||
self.expander.cfg_options().clone(),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
'attrs: for attr in &*attrs {
|
||||
let ast_id =
|
||||
AstId::new(self.expander.current_file_id(), item.ast_id(item_tree).upcast());
|
||||
let ast_id_with_path = AstIdWithPath { path: (*attr.path).clone(), ast_id };
|
||||
|
||||
match self.def_map.resolve_attr_macro(
|
||||
self.db,
|
||||
self.module_id.local_id,
|
||||
ast_id_with_path,
|
||||
attr,
|
||||
) {
|
||||
Ok(ResolvedAttr::Macro(call_id)) => {
|
||||
// If proc attribute macro expansion is disabled, skip expanding it here
|
||||
if !self.db.expand_proc_attr_macros() {
|
||||
continue 'attrs;
|
||||
}
|
||||
let loc = self.db.lookup_intern_macro_call(call_id);
|
||||
if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind {
|
||||
// If there's no expander for the proc macro (e.g. the
|
||||
// proc macro is ignored, or building the proc macro
|
||||
// crate failed), skip expansion like we would if it was
|
||||
// disabled. This is analogous to the handling in
|
||||
// `DefCollector::collect_macros`.
|
||||
if exp.is_dummy() {
|
||||
self.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
|
||||
self.module_id.local_id,
|
||||
loc.kind,
|
||||
loc.def.krate,
|
||||
));
|
||||
|
||||
continue 'attrs;
|
||||
}
|
||||
if exp.is_disabled() {
|
||||
continue 'attrs;
|
||||
}
|
||||
}
|
||||
|
||||
self.attr_calls.push((ast_id, call_id));
|
||||
|
||||
let res =
|
||||
self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id);
|
||||
self.collect_macro_items(res, &|| loc.kind.clone());
|
||||
continue 'items;
|
||||
}
|
||||
Ok(_) => (),
|
||||
Err(_) => {
|
||||
self.diagnostics.push(DefDiagnostic::unresolved_macro_call(
|
||||
self.module_id.local_id,
|
||||
MacroCallKind::Attr {
|
||||
ast_id,
|
||||
attr_args: None,
|
||||
invoc_attr_index: attr.id,
|
||||
},
|
||||
attr.path().clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.collect_item(item_tree, tree_id, container, item);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_item(
|
||||
&mut self,
|
||||
item_tree: &ItemTree,
|
||||
tree_id: TreeId,
|
||||
container: ItemContainerId,
|
||||
item: AssocItem,
|
||||
) {
|
||||
match item {
|
||||
AssocItem::Function(id) => {
|
||||
let item = &item_tree[id];
|
||||
|
||||
let def =
|
||||
FunctionLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db);
|
||||
self.items.push((item.name.clone(), def.into()));
|
||||
}
|
||||
AssocItem::Const(id) => {
|
||||
let item = &item_tree[id];
|
||||
let Some(name) = item.name.clone() else { return };
|
||||
let def = ConstLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db);
|
||||
self.items.push((name, def.into()));
|
||||
}
|
||||
AssocItem::TypeAlias(id) => {
|
||||
let item = &item_tree[id];
|
||||
|
||||
let def =
|
||||
TypeAliasLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db);
|
||||
self.items.push((item.name.clone(), def.into()));
|
||||
}
|
||||
AssocItem::MacroCall(call) => {
|
||||
let file_id = self.expander.current_file_id();
|
||||
let MacroCall { ast_id, expand_to, ctxt, ref path } = item_tree[call];
|
||||
let module = self.expander.module.local_id;
|
||||
|
||||
let resolver = |path| {
|
||||
self.def_map
|
||||
.resolve_path(
|
||||
self.db,
|
||||
module,
|
||||
&path,
|
||||
crate::item_scope::BuiltinShadowMode::Other,
|
||||
Some(MacroSubNs::Bang),
|
||||
)
|
||||
.0
|
||||
.take_macros()
|
||||
.map(|it| self.db.macro_def(it))
|
||||
};
|
||||
match macro_call_as_call_id(
|
||||
self.db.upcast(),
|
||||
&AstIdWithPath::new(file_id, ast_id, Clone::clone(path)),
|
||||
ctxt,
|
||||
expand_to,
|
||||
self.expander.krate(),
|
||||
resolver,
|
||||
) {
|
||||
Ok(Some(call_id)) => {
|
||||
let res =
|
||||
self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id);
|
||||
self.collect_macro_items(res, &|| hir_expand::MacroCallKind::FnLike {
|
||||
ast_id: InFile::new(file_id, ast_id),
|
||||
expand_to: hir_expand::ExpandTo::Items,
|
||||
eager: None,
|
||||
});
|
||||
}
|
||||
Ok(None) => (),
|
||||
Err(_) => {
|
||||
self.diagnostics.push(DefDiagnostic::unresolved_macro_call(
|
||||
self.module_id.local_id,
|
||||
MacroCallKind::FnLike {
|
||||
ast_id: InFile::new(file_id, ast_id),
|
||||
expand_to,
|
||||
eager: None,
|
||||
},
|
||||
Clone::clone(path),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_macro_items(
|
||||
&mut self,
|
||||
ExpandResult { value, err }: ExpandResult<Option<(Mark, Parse<ast::MacroItems>)>>,
|
||||
error_call_kind: &dyn Fn() -> hir_expand::MacroCallKind,
|
||||
) {
|
||||
let Some((mark, parse)) = value else { return };
|
||||
|
||||
if let Some(err) = err {
|
||||
let diag = match err {
|
||||
// why is this reported here?
|
||||
hir_expand::ExpandError::UnresolvedProcMacro(krate) => {
|
||||
DefDiagnostic::unresolved_proc_macro(
|
||||
self.module_id.local_id,
|
||||
error_call_kind(),
|
||||
krate,
|
||||
)
|
||||
}
|
||||
_ => DefDiagnostic::macro_error(
|
||||
self.module_id.local_id,
|
||||
error_call_kind(),
|
||||
err.to_string(),
|
||||
),
|
||||
};
|
||||
self.diagnostics.push(diag);
|
||||
}
|
||||
let errors = parse.errors();
|
||||
if !errors.is_empty() {
|
||||
self.diagnostics.push(DefDiagnostic::macro_expansion_parse_error(
|
||||
self.module_id.local_id,
|
||||
error_call_kind(),
|
||||
errors.into_boxed_slice(),
|
||||
));
|
||||
}
|
||||
|
||||
let tree_id = item_tree::TreeId::new(self.expander.current_file_id(), None);
|
||||
let item_tree = tree_id.item_tree(self.db);
|
||||
let iter: SmallVec<[_; 2]> =
|
||||
item_tree.top_level_items().iter().filter_map(ModItem::as_assoc_item).collect();
|
||||
|
||||
self.collect(&item_tree, tree_id, &iter);
|
||||
|
||||
self.expander.exit(mark);
|
||||
}
|
||||
}
|
||||
|
||||
fn trait_vis(db: &dyn DefDatabase, trait_id: TraitId) -> RawVisibility {
|
||||
let ItemLoc { id: tree_id, .. } = trait_id.lookup(db);
|
||||
let item_tree = tree_id.item_tree(db);
|
||||
let tr_def = &item_tree[tree_id.value];
|
||||
item_tree[tr_def.visibility].clone()
|
||||
}
|
||||
528
src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs
Normal file
528
src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
//! Defines hir-level representation of structs, enums and unions
|
||||
|
||||
use base_db::CrateId;
|
||||
use bitflags::bitflags;
|
||||
use cfg::CfgOptions;
|
||||
use either::Either;
|
||||
|
||||
use hir_expand::{
|
||||
name::{AsName, Name},
|
||||
HirFileId, InFile,
|
||||
};
|
||||
use intern::Interned;
|
||||
use la_arena::Arena;
|
||||
use rustc_abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions};
|
||||
use syntax::ast::{self, HasName, HasVisibility};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
builtin_type::{BuiltinInt, BuiltinUint},
|
||||
db::DefDatabase,
|
||||
item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId},
|
||||
lang_item::LangItem,
|
||||
lower::LowerCtx,
|
||||
nameres::diagnostics::{DefDiagnostic, DefDiagnostics},
|
||||
trace::Trace,
|
||||
tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree},
|
||||
type_ref::TypeRef,
|
||||
visibility::RawVisibility,
|
||||
EnumId, EnumVariantId, LocalFieldId, LocalModuleId, Lookup, StructId, UnionId, VariantId,
|
||||
};
|
||||
|
||||
/// Note that we use `StructData` for unions as well!
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StructData {
|
||||
pub name: Name,
|
||||
pub variant_data: Arc<VariantData>,
|
||||
pub repr: Option<ReprOptions>,
|
||||
pub visibility: RawVisibility,
|
||||
pub flags: StructFlags,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct StructFlags: u8 {
|
||||
const NO_FLAGS = 0;
|
||||
/// Indicates whether the struct is `PhantomData`.
|
||||
const IS_PHANTOM_DATA = 1 << 2;
|
||||
/// Indicates whether the struct has a `#[fundamental]` attribute.
|
||||
const IS_FUNDAMENTAL = 1 << 3;
|
||||
// FIXME: should this be a flag?
|
||||
/// Indicates whether the struct has a `#[rustc_has_incoherent_inherent_impls]` attribute.
|
||||
const IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL = 1 << 4;
|
||||
/// Indicates whether this struct is `Box`.
|
||||
const IS_BOX = 1 << 5;
|
||||
/// Indicates whether this struct is `ManuallyDrop`.
|
||||
const IS_MANUALLY_DROP = 1 << 6;
|
||||
/// Indicates whether this struct is `UnsafeCell`.
|
||||
const IS_UNSAFE_CELL = 1 << 7;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EnumData {
|
||||
pub name: Name,
|
||||
pub variants: Box<[(EnumVariantId, Name)]>,
|
||||
pub repr: Option<ReprOptions>,
|
||||
pub visibility: RawVisibility,
|
||||
pub rustc_has_incoherent_inherent_impls: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EnumVariantData {
|
||||
pub name: Name,
|
||||
pub variant_data: Arc<VariantData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VariantData {
|
||||
Record(Arena<FieldData>),
|
||||
Tuple(Arena<FieldData>),
|
||||
Unit,
|
||||
}
|
||||
|
||||
/// A single field of an enum variant or struct
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FieldData {
|
||||
pub name: Name,
|
||||
pub type_ref: Interned<TypeRef>,
|
||||
pub visibility: RawVisibility,
|
||||
}
|
||||
|
||||
fn repr_from_value(
|
||||
db: &dyn DefDatabase,
|
||||
krate: CrateId,
|
||||
item_tree: &ItemTree,
|
||||
of: AttrOwner,
|
||||
) -> Option<ReprOptions> {
|
||||
item_tree.attrs(db, krate, of).by_key("repr").tt_values().find_map(parse_repr_tt)
|
||||
}
|
||||
|
||||
fn parse_repr_tt(tt: &Subtree) -> Option<ReprOptions> {
|
||||
match tt.delimiter {
|
||||
Delimiter { kind: DelimiterKind::Parenthesis, .. } => {}
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
let mut flags = ReprFlags::empty();
|
||||
let mut int = None;
|
||||
let mut max_align: Option<Align> = None;
|
||||
let mut min_pack: Option<Align> = None;
|
||||
|
||||
let mut tts = tt.token_trees.iter().peekable();
|
||||
while let Some(tt) = tts.next() {
|
||||
if let TokenTree::Leaf(Leaf::Ident(ident)) = tt {
|
||||
flags.insert(match &*ident.text {
|
||||
"packed" => {
|
||||
let pack = if let Some(TokenTree::Subtree(tt)) = tts.peek() {
|
||||
tts.next();
|
||||
if let Some(TokenTree::Leaf(Leaf::Literal(lit))) = tt.token_trees.first() {
|
||||
lit.text.parse().unwrap_or_default()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let pack = Align::from_bytes(pack).unwrap_or(Align::ONE);
|
||||
min_pack =
|
||||
Some(if let Some(min_pack) = min_pack { min_pack.min(pack) } else { pack });
|
||||
ReprFlags::empty()
|
||||
}
|
||||
"align" => {
|
||||
if let Some(TokenTree::Subtree(tt)) = tts.peek() {
|
||||
tts.next();
|
||||
if let Some(TokenTree::Leaf(Leaf::Literal(lit))) = tt.token_trees.first() {
|
||||
if let Ok(align) = lit.text.parse() {
|
||||
let align = Align::from_bytes(align).ok();
|
||||
max_align = max_align.max(align);
|
||||
}
|
||||
}
|
||||
}
|
||||
ReprFlags::empty()
|
||||
}
|
||||
"C" => ReprFlags::IS_C,
|
||||
"transparent" => ReprFlags::IS_TRANSPARENT,
|
||||
"simd" => ReprFlags::IS_SIMD,
|
||||
repr => {
|
||||
if let Some(builtin) = BuiltinInt::from_suffix(repr)
|
||||
.map(Either::Left)
|
||||
.or_else(|| BuiltinUint::from_suffix(repr).map(Either::Right))
|
||||
{
|
||||
int = Some(match builtin {
|
||||
Either::Left(bi) => match bi {
|
||||
BuiltinInt::Isize => IntegerType::Pointer(true),
|
||||
BuiltinInt::I8 => IntegerType::Fixed(Integer::I8, true),
|
||||
BuiltinInt::I16 => IntegerType::Fixed(Integer::I16, true),
|
||||
BuiltinInt::I32 => IntegerType::Fixed(Integer::I32, true),
|
||||
BuiltinInt::I64 => IntegerType::Fixed(Integer::I64, true),
|
||||
BuiltinInt::I128 => IntegerType::Fixed(Integer::I128, true),
|
||||
},
|
||||
Either::Right(bu) => match bu {
|
||||
BuiltinUint::Usize => IntegerType::Pointer(false),
|
||||
BuiltinUint::U8 => IntegerType::Fixed(Integer::I8, false),
|
||||
BuiltinUint::U16 => IntegerType::Fixed(Integer::I16, false),
|
||||
BuiltinUint::U32 => IntegerType::Fixed(Integer::I32, false),
|
||||
BuiltinUint::U64 => IntegerType::Fixed(Integer::I64, false),
|
||||
BuiltinUint::U128 => IntegerType::Fixed(Integer::I128, false),
|
||||
},
|
||||
});
|
||||
}
|
||||
ReprFlags::empty()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Some(ReprOptions { int, align: max_align, pack: min_pack, flags, field_shuffle_seed: 0 })
|
||||
}
|
||||
|
||||
impl StructData {
|
||||
#[inline]
|
||||
pub(crate) fn struct_data_query(db: &dyn DefDatabase, id: StructId) -> Arc<StructData> {
|
||||
db.struct_data_with_diagnostics(id).0
|
||||
}
|
||||
|
||||
pub(crate) fn struct_data_with_diagnostics_query(
|
||||
db: &dyn DefDatabase,
|
||||
id: StructId,
|
||||
) -> (Arc<StructData>, DefDiagnostics) {
|
||||
let loc = id.lookup(db);
|
||||
let krate = loc.container.krate;
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
|
||||
let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into());
|
||||
|
||||
let mut flags = StructFlags::NO_FLAGS;
|
||||
if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() {
|
||||
flags |= StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL;
|
||||
}
|
||||
if attrs.by_key("fundamental").exists() {
|
||||
flags |= StructFlags::IS_FUNDAMENTAL;
|
||||
}
|
||||
if let Some(lang) = attrs.lang_item() {
|
||||
match lang {
|
||||
LangItem::PhantomData => flags |= StructFlags::IS_PHANTOM_DATA,
|
||||
LangItem::OwnedBox => flags |= StructFlags::IS_BOX,
|
||||
LangItem::ManuallyDrop => flags |= StructFlags::IS_MANUALLY_DROP,
|
||||
LangItem::UnsafeCell => flags |= StructFlags::IS_UNSAFE_CELL,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let strukt = &item_tree[loc.id.value];
|
||||
let (variant_data, diagnostics) = lower_fields(
|
||||
db,
|
||||
krate,
|
||||
loc.id.file_id(),
|
||||
loc.container.local_id,
|
||||
&item_tree,
|
||||
&db.crate_graph()[krate].cfg_options,
|
||||
&strukt.fields,
|
||||
None,
|
||||
);
|
||||
(
|
||||
Arc::new(StructData {
|
||||
name: strukt.name.clone(),
|
||||
variant_data: Arc::new(variant_data),
|
||||
repr,
|
||||
visibility: item_tree[strukt.visibility].clone(),
|
||||
flags,
|
||||
}),
|
||||
DefDiagnostics::new(diagnostics),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn union_data_query(db: &dyn DefDatabase, id: UnionId) -> Arc<StructData> {
|
||||
db.union_data_with_diagnostics(id).0
|
||||
}
|
||||
|
||||
pub(crate) fn union_data_with_diagnostics_query(
|
||||
db: &dyn DefDatabase,
|
||||
id: UnionId,
|
||||
) -> (Arc<StructData>, DefDiagnostics) {
|
||||
let loc = id.lookup(db);
|
||||
let krate = loc.container.krate;
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
|
||||
let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into());
|
||||
let mut flags = StructFlags::NO_FLAGS;
|
||||
if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() {
|
||||
flags |= StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL;
|
||||
}
|
||||
if attrs.by_key("fundamental").exists() {
|
||||
flags |= StructFlags::IS_FUNDAMENTAL;
|
||||
}
|
||||
|
||||
let union = &item_tree[loc.id.value];
|
||||
let (variant_data, diagnostics) = lower_fields(
|
||||
db,
|
||||
krate,
|
||||
loc.id.file_id(),
|
||||
loc.container.local_id,
|
||||
&item_tree,
|
||||
&db.crate_graph()[krate].cfg_options,
|
||||
&union.fields,
|
||||
None,
|
||||
);
|
||||
(
|
||||
Arc::new(StructData {
|
||||
name: union.name.clone(),
|
||||
variant_data: Arc::new(variant_data),
|
||||
repr,
|
||||
visibility: item_tree[union.visibility].clone(),
|
||||
flags,
|
||||
}),
|
||||
DefDiagnostics::new(diagnostics),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EnumData {
|
||||
pub(crate) fn enum_data_query(db: &dyn DefDatabase, e: EnumId) -> Arc<EnumData> {
|
||||
let loc = e.lookup(db);
|
||||
let krate = loc.container.krate;
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
|
||||
let rustc_has_incoherent_inherent_impls = item_tree
|
||||
.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into())
|
||||
.by_key("rustc_has_incoherent_inherent_impls")
|
||||
.exists();
|
||||
|
||||
let enum_ = &item_tree[loc.id.value];
|
||||
|
||||
Arc::new(EnumData {
|
||||
name: enum_.name.clone(),
|
||||
variants: loc.container.def_map(db).enum_definitions[&e]
|
||||
.iter()
|
||||
.map(|&id| (id, item_tree[id.lookup(db).id.value].name.clone()))
|
||||
.collect(),
|
||||
repr,
|
||||
visibility: item_tree[enum_.visibility].clone(),
|
||||
rustc_has_incoherent_inherent_impls,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn variant(&self, name: &Name) -> Option<EnumVariantId> {
|
||||
let &(id, _) = self.variants.iter().find(|(_id, n)| n == name)?;
|
||||
Some(id)
|
||||
}
|
||||
|
||||
pub fn variant_body_type(&self) -> IntegerType {
|
||||
match self.repr {
|
||||
Some(ReprOptions { int: Some(builtin), .. }) => builtin,
|
||||
_ => IntegerType::Pointer(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EnumVariantData {
|
||||
#[inline]
|
||||
pub(crate) fn enum_variant_data_query(
|
||||
db: &dyn DefDatabase,
|
||||
e: EnumVariantId,
|
||||
) -> Arc<EnumVariantData> {
|
||||
db.enum_variant_data_with_diagnostics(e).0
|
||||
}
|
||||
|
||||
pub(crate) fn enum_variant_data_with_diagnostics_query(
|
||||
db: &dyn DefDatabase,
|
||||
e: EnumVariantId,
|
||||
) -> (Arc<EnumVariantData>, DefDiagnostics) {
|
||||
let loc = e.lookup(db);
|
||||
let container = loc.parent.lookup(db).container;
|
||||
let krate = container.krate;
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let variant = &item_tree[loc.id.value];
|
||||
|
||||
let (var_data, diagnostics) = lower_fields(
|
||||
db,
|
||||
krate,
|
||||
loc.id.file_id(),
|
||||
container.local_id,
|
||||
&item_tree,
|
||||
&db.crate_graph()[krate].cfg_options,
|
||||
&variant.fields,
|
||||
Some(item_tree[loc.parent.lookup(db).id.value].visibility),
|
||||
);
|
||||
|
||||
(
|
||||
Arc::new(EnumVariantData {
|
||||
name: variant.name.clone(),
|
||||
variant_data: Arc::new(var_data),
|
||||
}),
|
||||
DefDiagnostics::new(diagnostics),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl VariantData {
|
||||
pub fn fields(&self) -> &Arena<FieldData> {
|
||||
const EMPTY: &Arena<FieldData> = &Arena::new();
|
||||
match &self {
|
||||
VariantData::Record(fields) | VariantData::Tuple(fields) => fields,
|
||||
_ => EMPTY,
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Linear lookup
|
||||
pub fn field(&self, name: &Name) -> Option<LocalFieldId> {
|
||||
self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None })
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> StructKind {
|
||||
match self {
|
||||
VariantData::Record(_) => StructKind::Record,
|
||||
VariantData::Tuple(_) => StructKind::Tuple,
|
||||
VariantData::Unit => StructKind::Unit,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::self_named_constructors)]
|
||||
pub(crate) fn variant_data(db: &dyn DefDatabase, id: VariantId) -> Arc<VariantData> {
|
||||
match id {
|
||||
VariantId::StructId(it) => db.struct_data(it).variant_data.clone(),
|
||||
VariantId::EnumVariantId(it) => db.enum_variant_data(it).variant_data.clone(),
|
||||
VariantId::UnionId(it) => db.union_data(it).variant_data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum StructKind {
|
||||
Tuple,
|
||||
Record,
|
||||
Unit,
|
||||
}
|
||||
|
||||
pub(crate) fn lower_struct(
|
||||
db: &dyn DefDatabase,
|
||||
trace: &mut Trace<FieldData, Either<ast::TupleField, ast::RecordField>>,
|
||||
ast: &InFile<ast::StructKind>,
|
||||
krate: CrateId,
|
||||
item_tree: &ItemTree,
|
||||
fields: &Fields,
|
||||
) -> StructKind {
|
||||
let ctx = LowerCtx::new(db, ast.file_id);
|
||||
|
||||
match (&ast.value, fields) {
|
||||
(ast::StructKind::Tuple(fl), Fields::Tuple(fields)) => {
|
||||
let cfg_options = &db.crate_graph()[krate].cfg_options;
|
||||
for ((i, fd), item_tree_id) in fl.fields().enumerate().zip(fields.clone()) {
|
||||
if !item_tree.attrs(db, krate, item_tree_id.into()).is_cfg_enabled(cfg_options) {
|
||||
continue;
|
||||
}
|
||||
|
||||
trace.alloc(
|
||||
|| Either::Left(fd.clone()),
|
||||
|| FieldData {
|
||||
name: Name::new_tuple_field(i),
|
||||
type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())),
|
||||
visibility: RawVisibility::from_ast(db, fd.visibility(), &mut |range| {
|
||||
ctx.span_map().span_for_range(range).ctx
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
StructKind::Tuple
|
||||
}
|
||||
(ast::StructKind::Record(fl), Fields::Record(fields)) => {
|
||||
let cfg_options = &db.crate_graph()[krate].cfg_options;
|
||||
for (fd, item_tree_id) in fl.fields().zip(fields.clone()) {
|
||||
if !item_tree.attrs(db, krate, item_tree_id.into()).is_cfg_enabled(cfg_options) {
|
||||
continue;
|
||||
}
|
||||
|
||||
trace.alloc(
|
||||
|| Either::Right(fd.clone()),
|
||||
|| FieldData {
|
||||
name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing),
|
||||
type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())),
|
||||
visibility: RawVisibility::from_ast(db, fd.visibility(), &mut |range| {
|
||||
ctx.span_map().span_for_range(range).ctx
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
StructKind::Record
|
||||
}
|
||||
_ => StructKind::Unit,
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_fields(
|
||||
db: &dyn DefDatabase,
|
||||
krate: CrateId,
|
||||
current_file_id: HirFileId,
|
||||
container: LocalModuleId,
|
||||
item_tree: &ItemTree,
|
||||
cfg_options: &CfgOptions,
|
||||
fields: &Fields,
|
||||
override_visibility: Option<RawVisibilityId>,
|
||||
) -> (VariantData, Vec<DefDiagnostic>) {
|
||||
let mut diagnostics = Vec::new();
|
||||
match fields {
|
||||
Fields::Record(flds) => {
|
||||
let mut arena = Arena::new();
|
||||
for field_id in flds.clone() {
|
||||
let attrs = item_tree.attrs(db, krate, field_id.into());
|
||||
let field = &item_tree[field_id];
|
||||
if attrs.is_cfg_enabled(cfg_options) {
|
||||
arena.alloc(lower_field(item_tree, field, override_visibility));
|
||||
} else {
|
||||
diagnostics.push(DefDiagnostic::unconfigured_code(
|
||||
container,
|
||||
InFile::new(
|
||||
current_file_id,
|
||||
match field.ast_id {
|
||||
FieldAstId::Record(it) => it.erase(),
|
||||
FieldAstId::Tuple(it) => it.erase(),
|
||||
},
|
||||
),
|
||||
attrs.cfg().unwrap(),
|
||||
cfg_options.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
(VariantData::Record(arena), diagnostics)
|
||||
}
|
||||
Fields::Tuple(flds) => {
|
||||
let mut arena = Arena::new();
|
||||
for field_id in flds.clone() {
|
||||
let attrs = item_tree.attrs(db, krate, field_id.into());
|
||||
let field = &item_tree[field_id];
|
||||
if attrs.is_cfg_enabled(cfg_options) {
|
||||
arena.alloc(lower_field(item_tree, field, override_visibility));
|
||||
} else {
|
||||
diagnostics.push(DefDiagnostic::unconfigured_code(
|
||||
container,
|
||||
InFile::new(
|
||||
current_file_id,
|
||||
match field.ast_id {
|
||||
FieldAstId::Record(it) => it.erase(),
|
||||
FieldAstId::Tuple(it) => it.erase(),
|
||||
},
|
||||
),
|
||||
attrs.cfg().unwrap(),
|
||||
cfg_options.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
(VariantData::Tuple(arena), diagnostics)
|
||||
}
|
||||
Fields::Unit => (VariantData::Unit, diagnostics),
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_field(
|
||||
item_tree: &ItemTree,
|
||||
field: &Field,
|
||||
override_visibility: Option<RawVisibilityId>,
|
||||
) -> FieldData {
|
||||
FieldData {
|
||||
name: field.name.clone(),
|
||||
type_ref: field.type_ref.clone(),
|
||||
visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(),
|
||||
}
|
||||
}
|
||||
351
src/tools/rust-analyzer/crates/hir-def/src/db.rs
Normal file
351
src/tools/rust-analyzer/crates/hir-def/src/db.rs
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
//! Defines database & queries for name resolution.
|
||||
use base_db::{salsa, CrateId, FileId, SourceDatabase, Upcast};
|
||||
use either::Either;
|
||||
use hir_expand::{db::ExpandDatabase, HirFileId, MacroDefId};
|
||||
use intern::Interned;
|
||||
use la_arena::ArenaMap;
|
||||
use span::MacroCallId;
|
||||
use syntax::{ast, AstPtr};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
attr::{Attrs, AttrsWithOwner},
|
||||
body::{scope::ExprScopes, Body, BodySourceMap},
|
||||
data::{
|
||||
adt::{EnumData, EnumVariantData, StructData, VariantData},
|
||||
ConstData, ExternCrateDeclData, FunctionData, ImplData, Macro2Data, MacroRulesData,
|
||||
ProcMacroData, StaticData, TraitAliasData, TraitData, TypeAliasData,
|
||||
},
|
||||
generics::GenericParams,
|
||||
import_map::ImportMap,
|
||||
item_tree::{AttrOwner, ItemTree},
|
||||
lang_item::{self, LangItem, LangItemTarget, LangItems},
|
||||
nameres::{diagnostics::DefDiagnostics, DefMap},
|
||||
visibility::{self, Visibility},
|
||||
AttrDefId, BlockId, BlockLoc, ConstBlockId, ConstBlockLoc, ConstId, ConstLoc, DefWithBodyId,
|
||||
EnumId, EnumLoc, EnumVariantId, EnumVariantLoc, ExternBlockId, ExternBlockLoc, ExternCrateId,
|
||||
ExternCrateLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, InTypeConstId,
|
||||
InTypeConstLoc, LocalFieldId, Macro2Id, Macro2Loc, MacroId, MacroRulesId, MacroRulesLoc,
|
||||
MacroRulesLocFlags, ProcMacroId, ProcMacroLoc, StaticId, StaticLoc, StructId, StructLoc,
|
||||
TraitAliasId, TraitAliasLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc,
|
||||
UseId, UseLoc, VariantId,
|
||||
};
|
||||
|
||||
#[salsa::query_group(InternDatabaseStorage)]
|
||||
pub trait InternDatabase: SourceDatabase {
|
||||
// region: items
|
||||
#[salsa::interned]
|
||||
fn intern_use(&self, loc: UseLoc) -> UseId;
|
||||
#[salsa::interned]
|
||||
fn intern_extern_crate(&self, loc: ExternCrateLoc) -> ExternCrateId;
|
||||
#[salsa::interned]
|
||||
fn intern_function(&self, loc: FunctionLoc) -> FunctionId;
|
||||
#[salsa::interned]
|
||||
fn intern_struct(&self, loc: StructLoc) -> StructId;
|
||||
#[salsa::interned]
|
||||
fn intern_union(&self, loc: UnionLoc) -> UnionId;
|
||||
#[salsa::interned]
|
||||
fn intern_enum(&self, loc: EnumLoc) -> EnumId;
|
||||
#[salsa::interned]
|
||||
fn intern_enum_variant(&self, loc: EnumVariantLoc) -> EnumVariantId;
|
||||
#[salsa::interned]
|
||||
fn intern_const(&self, loc: ConstLoc) -> ConstId;
|
||||
#[salsa::interned]
|
||||
fn intern_static(&self, loc: StaticLoc) -> StaticId;
|
||||
#[salsa::interned]
|
||||
fn intern_trait(&self, loc: TraitLoc) -> TraitId;
|
||||
#[salsa::interned]
|
||||
fn intern_trait_alias(&self, loc: TraitAliasLoc) -> TraitAliasId;
|
||||
#[salsa::interned]
|
||||
fn intern_type_alias(&self, loc: TypeAliasLoc) -> TypeAliasId;
|
||||
#[salsa::interned]
|
||||
fn intern_impl(&self, loc: ImplLoc) -> ImplId;
|
||||
#[salsa::interned]
|
||||
fn intern_extern_block(&self, loc: ExternBlockLoc) -> ExternBlockId;
|
||||
#[salsa::interned]
|
||||
fn intern_macro2(&self, loc: Macro2Loc) -> Macro2Id;
|
||||
#[salsa::interned]
|
||||
fn intern_proc_macro(&self, loc: ProcMacroLoc) -> ProcMacroId;
|
||||
#[salsa::interned]
|
||||
fn intern_macro_rules(&self, loc: MacroRulesLoc) -> MacroRulesId;
|
||||
// endregion: items
|
||||
|
||||
#[salsa::interned]
|
||||
fn intern_block(&self, loc: BlockLoc) -> BlockId;
|
||||
#[salsa::interned]
|
||||
fn intern_anonymous_const(&self, id: ConstBlockLoc) -> ConstBlockId;
|
||||
#[salsa::interned]
|
||||
fn intern_in_type_const(&self, id: InTypeConstLoc) -> InTypeConstId;
|
||||
}
|
||||
|
||||
#[salsa::query_group(DefDatabaseStorage)]
|
||||
pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDatabase> {
|
||||
#[salsa::input]
|
||||
fn expand_proc_attr_macros(&self) -> bool;
|
||||
|
||||
#[salsa::invoke(ItemTree::file_item_tree_query)]
|
||||
fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>;
|
||||
|
||||
#[salsa::invoke(ItemTree::block_item_tree_query)]
|
||||
fn block_item_tree(&self, block_id: BlockId) -> Arc<ItemTree>;
|
||||
|
||||
#[salsa::invoke(DefMap::crate_def_map_query)]
|
||||
fn crate_def_map(&self, krate: CrateId) -> Arc<DefMap>;
|
||||
|
||||
/// Computes the block-level `DefMap`.
|
||||
#[salsa::invoke(DefMap::block_def_map_query)]
|
||||
fn block_def_map(&self, block: BlockId) -> Arc<DefMap>;
|
||||
|
||||
fn macro_def(&self, m: MacroId) -> MacroDefId;
|
||||
|
||||
// region:data
|
||||
|
||||
#[salsa::transparent]
|
||||
#[salsa::invoke(StructData::struct_data_query)]
|
||||
fn struct_data(&self, id: StructId) -> Arc<StructData>;
|
||||
|
||||
#[salsa::invoke(StructData::struct_data_with_diagnostics_query)]
|
||||
fn struct_data_with_diagnostics(&self, id: StructId) -> (Arc<StructData>, DefDiagnostics);
|
||||
|
||||
#[salsa::transparent]
|
||||
#[salsa::invoke(StructData::union_data_query)]
|
||||
fn union_data(&self, id: UnionId) -> Arc<StructData>;
|
||||
|
||||
#[salsa::invoke(StructData::union_data_with_diagnostics_query)]
|
||||
fn union_data_with_diagnostics(&self, id: UnionId) -> (Arc<StructData>, DefDiagnostics);
|
||||
|
||||
#[salsa::invoke(EnumData::enum_data_query)]
|
||||
fn enum_data(&self, e: EnumId) -> Arc<EnumData>;
|
||||
|
||||
#[salsa::transparent]
|
||||
#[salsa::invoke(EnumVariantData::enum_variant_data_query)]
|
||||
fn enum_variant_data(&self, id: EnumVariantId) -> Arc<EnumVariantData>;
|
||||
|
||||
#[salsa::invoke(EnumVariantData::enum_variant_data_with_diagnostics_query)]
|
||||
fn enum_variant_data_with_diagnostics(
|
||||
&self,
|
||||
id: EnumVariantId,
|
||||
) -> (Arc<EnumVariantData>, DefDiagnostics);
|
||||
|
||||
#[salsa::transparent]
|
||||
#[salsa::invoke(VariantData::variant_data)]
|
||||
fn variant_data(&self, id: VariantId) -> Arc<VariantData>;
|
||||
#[salsa::transparent]
|
||||
#[salsa::invoke(ImplData::impl_data_query)]
|
||||
fn impl_data(&self, e: ImplId) -> Arc<ImplData>;
|
||||
|
||||
#[salsa::invoke(ImplData::impl_data_with_diagnostics_query)]
|
||||
fn impl_data_with_diagnostics(&self, e: ImplId) -> (Arc<ImplData>, DefDiagnostics);
|
||||
|
||||
#[salsa::transparent]
|
||||
#[salsa::invoke(TraitData::trait_data_query)]
|
||||
fn trait_data(&self, e: TraitId) -> Arc<TraitData>;
|
||||
|
||||
#[salsa::invoke(TraitData::trait_data_with_diagnostics_query)]
|
||||
fn trait_data_with_diagnostics(&self, tr: TraitId) -> (Arc<TraitData>, DefDiagnostics);
|
||||
|
||||
#[salsa::invoke(TraitAliasData::trait_alias_query)]
|
||||
fn trait_alias_data(&self, e: TraitAliasId) -> Arc<TraitAliasData>;
|
||||
|
||||
#[salsa::invoke(TypeAliasData::type_alias_data_query)]
|
||||
fn type_alias_data(&self, e: TypeAliasId) -> Arc<TypeAliasData>;
|
||||
|
||||
#[salsa::invoke(FunctionData::fn_data_query)]
|
||||
fn function_data(&self, func: FunctionId) -> Arc<FunctionData>;
|
||||
|
||||
#[salsa::invoke(ConstData::const_data_query)]
|
||||
fn const_data(&self, konst: ConstId) -> Arc<ConstData>;
|
||||
|
||||
#[salsa::invoke(StaticData::static_data_query)]
|
||||
fn static_data(&self, konst: StaticId) -> Arc<StaticData>;
|
||||
|
||||
#[salsa::invoke(Macro2Data::macro2_data_query)]
|
||||
fn macro2_data(&self, makro: Macro2Id) -> Arc<Macro2Data>;
|
||||
|
||||
#[salsa::invoke(MacroRulesData::macro_rules_data_query)]
|
||||
fn macro_rules_data(&self, makro: MacroRulesId) -> Arc<MacroRulesData>;
|
||||
|
||||
#[salsa::invoke(ProcMacroData::proc_macro_data_query)]
|
||||
fn proc_macro_data(&self, makro: ProcMacroId) -> Arc<ProcMacroData>;
|
||||
|
||||
#[salsa::invoke(ExternCrateDeclData::extern_crate_decl_data_query)]
|
||||
fn extern_crate_decl_data(&self, extern_crate: ExternCrateId) -> Arc<ExternCrateDeclData>;
|
||||
|
||||
// endregion:data
|
||||
|
||||
#[salsa::invoke(Body::body_with_source_map_query)]
|
||||
fn body_with_source_map(&self, def: DefWithBodyId) -> (Arc<Body>, Arc<BodySourceMap>);
|
||||
|
||||
#[salsa::invoke(Body::body_query)]
|
||||
fn body(&self, def: DefWithBodyId) -> Arc<Body>;
|
||||
|
||||
#[salsa::invoke(ExprScopes::expr_scopes_query)]
|
||||
fn expr_scopes(&self, def: DefWithBodyId) -> Arc<ExprScopes>;
|
||||
|
||||
#[salsa::invoke(GenericParams::generic_params_query)]
|
||||
fn generic_params(&self, def: GenericDefId) -> Interned<GenericParams>;
|
||||
|
||||
// region:attrs
|
||||
|
||||
#[salsa::invoke(Attrs::fields_attrs_query)]
|
||||
fn fields_attrs(&self, def: VariantId) -> Arc<ArenaMap<LocalFieldId, Attrs>>;
|
||||
|
||||
#[salsa::invoke(crate::attr::fields_attrs_source_map)]
|
||||
fn fields_attrs_source_map(
|
||||
&self,
|
||||
def: VariantId,
|
||||
) -> Arc<ArenaMap<LocalFieldId, AstPtr<Either<ast::TupleField, ast::RecordField>>>>;
|
||||
|
||||
#[salsa::invoke(AttrsWithOwner::attrs_query)]
|
||||
fn attrs(&self, def: AttrDefId) -> Attrs;
|
||||
|
||||
#[salsa::transparent]
|
||||
#[salsa::invoke(lang_item::lang_attr)]
|
||||
fn lang_attr(&self, def: AttrDefId) -> Option<LangItem>;
|
||||
|
||||
// endregion:attrs
|
||||
|
||||
#[salsa::invoke(LangItems::lang_item_query)]
|
||||
fn lang_item(&self, start_crate: CrateId, item: LangItem) -> Option<LangItemTarget>;
|
||||
|
||||
#[salsa::invoke(ImportMap::import_map_query)]
|
||||
fn import_map(&self, krate: CrateId) -> Arc<ImportMap>;
|
||||
|
||||
// region:visibilities
|
||||
|
||||
#[salsa::invoke(visibility::field_visibilities_query)]
|
||||
fn field_visibilities(&self, var: VariantId) -> Arc<ArenaMap<LocalFieldId, Visibility>>;
|
||||
|
||||
// FIXME: unify function_visibility and const_visibility?
|
||||
#[salsa::invoke(visibility::function_visibility_query)]
|
||||
fn function_visibility(&self, def: FunctionId) -> Visibility;
|
||||
|
||||
#[salsa::invoke(visibility::const_visibility_query)]
|
||||
fn const_visibility(&self, def: ConstId) -> Visibility;
|
||||
|
||||
// endregion:visibilities
|
||||
|
||||
#[salsa::invoke(LangItems::crate_lang_items_query)]
|
||||
fn crate_lang_items(&self, krate: CrateId) -> Option<Arc<LangItems>>;
|
||||
|
||||
#[salsa::invoke(crate::lang_item::notable_traits_in_deps)]
|
||||
fn notable_traits_in_deps(&self, krate: CrateId) -> Arc<[Arc<[TraitId]>]>;
|
||||
#[salsa::invoke(crate::lang_item::crate_notable_traits)]
|
||||
fn crate_notable_traits(&self, krate: CrateId) -> Option<Arc<[TraitId]>>;
|
||||
|
||||
fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;
|
||||
|
||||
fn include_macro_invoc(&self, crate_id: CrateId) -> Vec<(MacroCallId, FileId)>;
|
||||
}
|
||||
|
||||
// return: macro call id and include file id
|
||||
fn include_macro_invoc(db: &dyn DefDatabase, krate: CrateId) -> Vec<(MacroCallId, FileId)> {
|
||||
db.crate_def_map(krate)
|
||||
.modules
|
||||
.values()
|
||||
.flat_map(|m| m.scope.iter_macro_invoc())
|
||||
.filter_map(|invoc| {
|
||||
db.lookup_intern_macro_call(*invoc.1)
|
||||
.include_file_id(db.upcast(), *invoc.1)
|
||||
.map(|x| (*invoc.1, x))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: CrateId) -> bool {
|
||||
let file = db.crate_graph()[crate_id].root_file_id;
|
||||
let item_tree = db.file_item_tree(file.into());
|
||||
let attrs = item_tree.raw_attrs(AttrOwner::TopLevel);
|
||||
for attr in &**attrs {
|
||||
match attr.path().as_ident().and_then(|id| id.as_text()) {
|
||||
Some(ident) if ident == "no_std" => return true,
|
||||
Some(ident) if ident == "cfg_attr" => {}
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
// This is a `cfg_attr`; check if it could possibly expand to `no_std`.
|
||||
// Syntax is: `#[cfg_attr(condition(cfg, style), attr0, attr1, <...>)]`
|
||||
let tt = match attr.token_tree_value() {
|
||||
Some(tt) => &tt.token_trees,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let segments =
|
||||
tt.split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ','));
|
||||
for output in segments.skip(1) {
|
||||
match output {
|
||||
[tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "no_std" => {
|
||||
return true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId {
|
||||
use hir_expand::InFile;
|
||||
|
||||
use crate::{Lookup, MacroDefKind, MacroExpander};
|
||||
|
||||
let kind = |expander, file_id, m| {
|
||||
let in_file = InFile::new(file_id, m);
|
||||
match expander {
|
||||
MacroExpander::Declarative => MacroDefKind::Declarative(in_file),
|
||||
MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(it, in_file),
|
||||
MacroExpander::BuiltInAttr(it) => MacroDefKind::BuiltInAttr(it, in_file),
|
||||
MacroExpander::BuiltInDerive(it) => MacroDefKind::BuiltInDerive(it, in_file),
|
||||
MacroExpander::BuiltInEager(it) => MacroDefKind::BuiltInEager(it, in_file),
|
||||
}
|
||||
};
|
||||
|
||||
match id {
|
||||
MacroId::Macro2Id(it) => {
|
||||
let loc: Macro2Loc = it.lookup(db);
|
||||
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let makro = &item_tree[loc.id.value];
|
||||
MacroDefId {
|
||||
krate: loc.container.krate,
|
||||
kind: kind(loc.expander, loc.id.file_id(), makro.ast_id.upcast()),
|
||||
local_inner: false,
|
||||
allow_internal_unsafe: loc.allow_internal_unsafe,
|
||||
edition: loc.edition,
|
||||
}
|
||||
}
|
||||
MacroId::MacroRulesId(it) => {
|
||||
let loc: MacroRulesLoc = it.lookup(db);
|
||||
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let makro = &item_tree[loc.id.value];
|
||||
MacroDefId {
|
||||
krate: loc.container.krate,
|
||||
kind: kind(loc.expander, loc.id.file_id(), makro.ast_id.upcast()),
|
||||
local_inner: loc.flags.contains(MacroRulesLocFlags::LOCAL_INNER),
|
||||
allow_internal_unsafe: loc
|
||||
.flags
|
||||
.contains(MacroRulesLocFlags::ALLOW_INTERNAL_UNSAFE),
|
||||
edition: loc.edition,
|
||||
}
|
||||
}
|
||||
MacroId::ProcMacroId(it) => {
|
||||
let loc = it.lookup(db);
|
||||
|
||||
let item_tree = loc.id.item_tree(db);
|
||||
let makro = &item_tree[loc.id.value];
|
||||
MacroDefId {
|
||||
krate: loc.container.krate,
|
||||
kind: MacroDefKind::ProcMacro(
|
||||
loc.expander,
|
||||
loc.kind,
|
||||
InFile::new(loc.id.file_id(), makro.ast_id),
|
||||
),
|
||||
local_inner: false,
|
||||
allow_internal_unsafe: false,
|
||||
edition: loc.edition,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs
Normal file
118
src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
//! This module defines a `DynMap` -- a container for heterogeneous maps.
|
||||
//!
|
||||
//! This means that `DynMap` stores a bunch of hash maps inside, and those maps
|
||||
//! can be of different types.
|
||||
//!
|
||||
//! It is used like this:
|
||||
//!
|
||||
//! ```
|
||||
//! // keys define submaps of a `DynMap`
|
||||
//! const STRING_TO_U32: Key<String, u32> = Key::new();
|
||||
//! const U32_TO_VEC: Key<u32, Vec<bool>> = Key::new();
|
||||
//!
|
||||
//! // Note: concrete type, no type params!
|
||||
//! let mut map = DynMap::new();
|
||||
//!
|
||||
//! // To access a specific map, index the `DynMap` by `Key`:
|
||||
//! map[STRING_TO_U32].insert("hello".to_string(), 92);
|
||||
//! let value = map[U32_TO_VEC].get(92);
|
||||
//! assert!(value.is_none());
|
||||
//! ```
|
||||
//!
|
||||
//! This is a work of fiction. Any similarities to Kotlin's `BindingContext` are
|
||||
//! a coincidence.
|
||||
pub mod keys;
|
||||
|
||||
use std::{
|
||||
hash::Hash,
|
||||
marker::PhantomData,
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use stdx::anymap::Map;
|
||||
|
||||
pub struct Key<K, V, P = (K, V)> {
|
||||
_phantom: PhantomData<(K, V, P)>,
|
||||
}
|
||||
|
||||
impl<K, V, P> Key<K, V, P> {
|
||||
pub(crate) const fn new() -> Key<K, V, P> {
|
||||
Key { _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, P> Copy for Key<K, V, P> {}
|
||||
|
||||
impl<K, V, P> Clone for Key<K, V, P> {
|
||||
fn clone(&self) -> Key<K, V, P> {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Policy {
|
||||
type K;
|
||||
type V;
|
||||
|
||||
fn insert(map: &mut DynMap, key: Self::K, value: Self::V);
|
||||
fn get<'a>(map: &'a DynMap, key: &Self::K) -> Option<&'a Self::V>;
|
||||
fn is_empty(map: &DynMap) -> bool;
|
||||
}
|
||||
|
||||
impl<K: Hash + Eq + 'static, V: 'static> Policy for (K, V) {
|
||||
type K = K;
|
||||
type V = V;
|
||||
fn insert(map: &mut DynMap, key: K, value: V) {
|
||||
map.map.entry::<FxHashMap<K, V>>().or_insert_with(Default::default).insert(key, value);
|
||||
}
|
||||
fn get<'a>(map: &'a DynMap, key: &K) -> Option<&'a V> {
|
||||
map.map.get::<FxHashMap<K, V>>()?.get(key)
|
||||
}
|
||||
fn is_empty(map: &DynMap) -> bool {
|
||||
map.map.get::<FxHashMap<K, V>>().map_or(true, |it| it.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DynMap {
|
||||
pub(crate) map: Map,
|
||||
}
|
||||
|
||||
impl Default for DynMap {
|
||||
fn default() -> Self {
|
||||
DynMap { map: Map::new() }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct KeyMap<KEY> {
|
||||
map: DynMap,
|
||||
_phantom: PhantomData<KEY>,
|
||||
}
|
||||
|
||||
impl<P: Policy> KeyMap<Key<P::K, P::V, P>> {
|
||||
pub fn insert(&mut self, key: P::K, value: P::V) {
|
||||
P::insert(&mut self.map, key, value)
|
||||
}
|
||||
pub fn get(&self, key: &P::K) -> Option<&P::V> {
|
||||
P::get(&self.map, key)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
P::is_empty(&self.map)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Policy> Index<Key<P::K, P::V, P>> for DynMap {
|
||||
type Output = KeyMap<Key<P::K, P::V, P>>;
|
||||
fn index(&self, _key: Key<P::K, P::V, P>) -> &Self::Output {
|
||||
// Safe due to `#[repr(transparent)]`.
|
||||
unsafe { std::mem::transmute::<&DynMap, &KeyMap<Key<P::K, P::V, P>>>(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Policy> IndexMut<Key<P::K, P::V, P>> for DynMap {
|
||||
fn index_mut(&mut self, _key: Key<P::K, P::V, P>) -> &mut Self::Output {
|
||||
// Safe due to `#[repr(transparent)]`.
|
||||
unsafe { std::mem::transmute::<&mut DynMap, &mut KeyMap<Key<P::K, P::V, P>>>(self) }
|
||||
}
|
||||
}
|
||||
73
src/tools/rust-analyzer/crates/hir-def/src/dyn_map/keys.rs
Normal file
73
src/tools/rust-analyzer/crates/hir-def/src/dyn_map/keys.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
//! keys to be used with `DynMap`
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use hir_expand::{attrs::AttrId, MacroCallId};
|
||||
use rustc_hash::FxHashMap;
|
||||
use syntax::{ast, AstNode, AstPtr};
|
||||
|
||||
use crate::{
|
||||
dyn_map::{DynMap, Policy},
|
||||
BlockId, ConstId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, ImplId,
|
||||
LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId,
|
||||
TraitId, TypeAliasId, TypeOrConstParamId, UnionId, UseId,
|
||||
};
|
||||
|
||||
pub type Key<K, V> = crate::dyn_map::Key<K, V, AstPtrPolicy<K, V>>;
|
||||
|
||||
pub const BLOCK: Key<ast::BlockExpr, BlockId> = Key::new();
|
||||
pub const FUNCTION: Key<ast::Fn, FunctionId> = Key::new();
|
||||
pub const CONST: Key<ast::Const, ConstId> = Key::new();
|
||||
pub const STATIC: Key<ast::Static, StaticId> = Key::new();
|
||||
pub const TYPE_ALIAS: Key<ast::TypeAlias, TypeAliasId> = Key::new();
|
||||
pub const IMPL: Key<ast::Impl, ImplId> = Key::new();
|
||||
pub const TRAIT: Key<ast::Trait, TraitId> = Key::new();
|
||||
pub const TRAIT_ALIAS: Key<ast::TraitAlias, TraitAliasId> = Key::new();
|
||||
pub const STRUCT: Key<ast::Struct, StructId> = Key::new();
|
||||
pub const UNION: Key<ast::Union, UnionId> = Key::new();
|
||||
pub const ENUM: Key<ast::Enum, EnumId> = Key::new();
|
||||
pub const EXTERN_CRATE: Key<ast::ExternCrate, ExternCrateId> = Key::new();
|
||||
pub const USE: Key<ast::Use, UseId> = Key::new();
|
||||
|
||||
pub const ENUM_VARIANT: Key<ast::Variant, EnumVariantId> = Key::new();
|
||||
pub const TUPLE_FIELD: Key<ast::TupleField, FieldId> = Key::new();
|
||||
pub const RECORD_FIELD: Key<ast::RecordField, FieldId> = Key::new();
|
||||
pub const TYPE_PARAM: Key<ast::TypeParam, TypeOrConstParamId> = Key::new();
|
||||
pub const CONST_PARAM: Key<ast::ConstParam, TypeOrConstParamId> = Key::new();
|
||||
pub const LIFETIME_PARAM: Key<ast::LifetimeParam, LifetimeParamId> = Key::new();
|
||||
|
||||
pub const MACRO_RULES: Key<ast::MacroRules, MacroRulesId> = Key::new();
|
||||
pub const MACRO2: Key<ast::MacroDef, Macro2Id> = Key::new();
|
||||
pub const PROC_MACRO: Key<ast::Fn, ProcMacroId> = Key::new();
|
||||
pub const ATTR_MACRO_CALL: Key<ast::Item, MacroCallId> = Key::new();
|
||||
pub const DERIVE_MACRO_CALL: Key<ast::Attr, (AttrId, MacroCallId, Box<[Option<MacroCallId>]>)> =
|
||||
Key::new();
|
||||
|
||||
/// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are
|
||||
/// equal if they point to exactly the same object.
|
||||
///
|
||||
/// In general, we do not guarantee that we have exactly one instance of a
|
||||
/// syntax tree for each file. We probably should add such guarantee, but, for
|
||||
/// the time being, we will use identity-less AstPtr comparison.
|
||||
pub struct AstPtrPolicy<AST, ID> {
|
||||
_phantom: PhantomData<(AST, ID)>,
|
||||
}
|
||||
|
||||
impl<AST: AstNode + 'static, ID: 'static> Policy for AstPtrPolicy<AST, ID> {
|
||||
type K = AST;
|
||||
type V = ID;
|
||||
fn insert(map: &mut DynMap, key: AST, value: ID) {
|
||||
let key = AstPtr::new(&key);
|
||||
map.map
|
||||
.entry::<FxHashMap<AstPtr<AST>, ID>>()
|
||||
.or_insert_with(Default::default)
|
||||
.insert(key, value);
|
||||
}
|
||||
fn get<'a>(map: &'a DynMap, key: &AST) -> Option<&'a ID> {
|
||||
let key = AstPtr::new(key);
|
||||
map.map.get::<FxHashMap<AstPtr<AST>, ID>>()?.get(&key)
|
||||
}
|
||||
fn is_empty(map: &DynMap) -> bool {
|
||||
map.map.get::<FxHashMap<AstPtr<AST>, ID>>().map_or(true, |it| it.is_empty())
|
||||
}
|
||||
}
|
||||
206
src/tools/rust-analyzer/crates/hir-def/src/expander.rs
Normal file
206
src/tools/rust-analyzer/crates/hir-def/src/expander.rs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
//! Macro expansion utilities.
|
||||
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use base_db::CrateId;
|
||||
use cfg::CfgOptions;
|
||||
use drop_bomb::DropBomb;
|
||||
use hir_expand::{
|
||||
attrs::RawAttrs, mod_path::ModPath, span_map::SpanMap, ExpandError, ExpandResult, HirFileId,
|
||||
InFile, MacroCallId,
|
||||
};
|
||||
use limit::Limit;
|
||||
use syntax::{ast, Parse};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
attr::Attrs, db::DefDatabase, lower::LowerCtx, path::Path, AsMacroCall, MacroId, ModuleId,
|
||||
UnresolvedMacro,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Expander {
|
||||
cfg_options: Arc<CfgOptions>,
|
||||
span_map: OnceCell<SpanMap>,
|
||||
current_file_id: HirFileId,
|
||||
pub(crate) module: ModuleId,
|
||||
/// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached.
|
||||
recursion_depth: u32,
|
||||
recursion_limit: Limit,
|
||||
}
|
||||
|
||||
impl Expander {
|
||||
pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander {
|
||||
let recursion_limit = module.def_map(db).recursion_limit() as usize;
|
||||
let recursion_limit = Limit::new(if cfg!(test) {
|
||||
// Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
|
||||
std::cmp::min(32, recursion_limit)
|
||||
} else {
|
||||
recursion_limit
|
||||
});
|
||||
Expander {
|
||||
current_file_id,
|
||||
module,
|
||||
recursion_depth: 0,
|
||||
recursion_limit,
|
||||
cfg_options: db.crate_graph()[module.krate].cfg_options.clone(),
|
||||
span_map: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn krate(&self) -> CrateId {
|
||||
self.module.krate
|
||||
}
|
||||
|
||||
pub fn enter_expand<T: ast::AstNode>(
|
||||
&mut self,
|
||||
db: &dyn DefDatabase,
|
||||
macro_call: ast::MacroCall,
|
||||
resolver: impl Fn(ModPath) -> Option<MacroId>,
|
||||
) -> Result<ExpandResult<Option<(Mark, Parse<T>)>>, UnresolvedMacro> {
|
||||
// FIXME: within_limit should support this, instead of us having to extract the error
|
||||
let mut unresolved_macro_err = None;
|
||||
|
||||
let result = self.within_limit(db, |this| {
|
||||
let macro_call = this.in_file(¯o_call);
|
||||
match macro_call.as_call_id_with_errors(db.upcast(), this.module.krate(), |path| {
|
||||
resolver(path).map(|it| db.macro_def(it))
|
||||
}) {
|
||||
Ok(call_id) => call_id,
|
||||
Err(resolve_err) => {
|
||||
unresolved_macro_err = Some(resolve_err);
|
||||
ExpandResult { value: None, err: None }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(err) = unresolved_macro_err {
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enter_expand_id<T: ast::AstNode>(
|
||||
&mut self,
|
||||
db: &dyn DefDatabase,
|
||||
call_id: MacroCallId,
|
||||
) -> ExpandResult<Option<(Mark, Parse<T>)>> {
|
||||
self.within_limit(db, |_this| ExpandResult::ok(Some(call_id)))
|
||||
}
|
||||
|
||||
pub fn exit(&mut self, mut mark: Mark) {
|
||||
self.span_map = mark.span_map;
|
||||
self.current_file_id = mark.file_id;
|
||||
if self.recursion_depth == u32::MAX {
|
||||
// Recursion limit has been reached somewhere in the macro expansion tree. Reset the
|
||||
// depth only when we get out of the tree.
|
||||
if !self.current_file_id.is_macro() {
|
||||
self.recursion_depth = 0;
|
||||
}
|
||||
} else {
|
||||
self.recursion_depth -= 1;
|
||||
}
|
||||
mark.bomb.defuse();
|
||||
}
|
||||
|
||||
pub fn ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a> {
|
||||
LowerCtx::with_span_map_cell(db, self.current_file_id, self.span_map.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn in_file<T>(&self, value: T) -> InFile<T> {
|
||||
InFile { file_id: self.current_file_id, value }
|
||||
}
|
||||
|
||||
pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
|
||||
Attrs::filter(
|
||||
db,
|
||||
self.krate(),
|
||||
RawAttrs::new(
|
||||
db.upcast(),
|
||||
owner,
|
||||
self.span_map.get_or_init(|| db.span_map(self.current_file_id)).as_ref(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn cfg_options(&self) -> &CfgOptions {
|
||||
&self.cfg_options
|
||||
}
|
||||
|
||||
pub fn current_file_id(&self) -> HirFileId {
|
||||
self.current_file_id
|
||||
}
|
||||
|
||||
pub(crate) fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> {
|
||||
let ctx = LowerCtx::with_span_map_cell(db, self.current_file_id, self.span_map.clone());
|
||||
Path::from_src(&ctx, path)
|
||||
}
|
||||
|
||||
fn within_limit<F, T: ast::AstNode>(
|
||||
&mut self,
|
||||
db: &dyn DefDatabase,
|
||||
op: F,
|
||||
) -> ExpandResult<Option<(Mark, Parse<T>)>>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>,
|
||||
{
|
||||
if self.recursion_depth == u32::MAX {
|
||||
// Recursion limit has been reached somewhere in the macro expansion tree. We should
|
||||
// stop expanding other macro calls in this tree, or else this may result in
|
||||
// exponential number of macro expansions, leading to a hang.
|
||||
//
|
||||
// The overflow error should have been reported when it occurred (see the next branch),
|
||||
// so don't return overflow error here to avoid diagnostics duplication.
|
||||
cov_mark::hit!(overflow_but_not_me);
|
||||
return ExpandResult::ok(None);
|
||||
} else if self.recursion_limit.check(self.recursion_depth as usize + 1).is_err() {
|
||||
self.recursion_depth = u32::MAX;
|
||||
cov_mark::hit!(your_stack_belongs_to_me);
|
||||
return ExpandResult::only_err(ExpandError::RecursionOverflow);
|
||||
}
|
||||
|
||||
let ExpandResult { value, err } = op(self);
|
||||
let Some(call_id) = value else {
|
||||
return ExpandResult { value: None, err };
|
||||
};
|
||||
|
||||
let macro_file = call_id.as_macro_file();
|
||||
let res = db.parse_macro_expansion(macro_file);
|
||||
|
||||
let err = err.or(res.err);
|
||||
ExpandResult {
|
||||
value: match err {
|
||||
// If proc-macro is disabled or unresolved, we want to expand to a missing expression
|
||||
// instead of an empty tree which might end up in an empty block.
|
||||
Some(ExpandError::UnresolvedProcMacro(_)) => None,
|
||||
_ => (|| {
|
||||
let parse = res.value.0.cast::<T>()?;
|
||||
|
||||
self.recursion_depth += 1;
|
||||
let old_span_map = OnceCell::new();
|
||||
if let Some(prev) = self.span_map.take() {
|
||||
_ = old_span_map.set(prev);
|
||||
};
|
||||
_ = self.span_map.set(SpanMap::ExpansionSpanMap(res.value.1));
|
||||
let old_file_id =
|
||||
std::mem::replace(&mut self.current_file_id, macro_file.into());
|
||||
let mark = Mark {
|
||||
file_id: old_file_id,
|
||||
span_map: old_span_map,
|
||||
bomb: DropBomb::new("expansion mark dropped"),
|
||||
};
|
||||
Some((mark, parse))
|
||||
})(),
|
||||
},
|
||||
err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mark {
|
||||
file_id: HirFileId,
|
||||
span_map: OnceCell<SpanMap>,
|
||||
bomb: DropBomb,
|
||||
}
|
||||
1588
src/tools/rust-analyzer/crates/hir-def/src/find_path.rs
Normal file
1588
src/tools/rust-analyzer/crates/hir-def/src/find_path.rs
Normal file
File diff suppressed because it is too large
Load diff
604
src/tools/rust-analyzer/crates/hir-def/src/generics.rs
Normal file
604
src/tools/rust-analyzer/crates/hir-def/src/generics.rs
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
//! Many kinds of items or constructs can have generic parameters: functions,
|
||||
//! structs, impls, traits, etc. This module provides a common HIR for these
|
||||
//! generic parameters. See also the `Generics` type and the `generics_of` query
|
||||
//! in rustc.
|
||||
|
||||
use std::ops;
|
||||
|
||||
use either::Either;
|
||||
use hir_expand::{
|
||||
name::{AsName, Name},
|
||||
ExpandResult,
|
||||
};
|
||||
use intern::Interned;
|
||||
use la_arena::Arena;
|
||||
use once_cell::unsync::Lazy;
|
||||
use stdx::impl_from;
|
||||
use syntax::ast::{self, HasGenericParams, HasName, HasTypeBounds};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
expander::Expander,
|
||||
item_tree::{GenericsItemTreeNode, ItemTree},
|
||||
lower::LowerCtx,
|
||||
nameres::{DefMap, MacroSubNs},
|
||||
type_ref::{ConstRef, LifetimeRef, TypeBound, TypeRef},
|
||||
AdtId, ConstParamId, GenericDefId, HasModule, ItemTreeLoc, LifetimeParamId,
|
||||
LocalLifetimeParamId, LocalTypeOrConstParamId, Lookup, TypeOrConstParamId, TypeParamId,
|
||||
};
|
||||
|
||||
/// Data about a generic type parameter (to a function, struct, impl, ...).
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct TypeParamData {
|
||||
/// [`None`] only if the type ref is an [`TypeRef::ImplTrait`]. FIXME: Might be better to just
|
||||
/// make it always be a value, giving impl trait a special name.
|
||||
pub name: Option<Name>,
|
||||
pub default: Option<Interned<TypeRef>>,
|
||||
pub provenance: TypeParamProvenance,
|
||||
}
|
||||
|
||||
/// Data about a generic lifetime parameter (to a function, struct, impl, ...).
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct LifetimeParamData {
|
||||
pub name: Name,
|
||||
}
|
||||
|
||||
/// Data about a generic const parameter (to a function, struct, impl, ...).
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct ConstParamData {
|
||||
pub name: Name,
|
||||
pub ty: Interned<TypeRef>,
|
||||
pub default: Option<ConstRef>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum TypeParamProvenance {
|
||||
TypeParamList,
|
||||
TraitSelf,
|
||||
ArgumentImplTrait,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum TypeOrConstParamData {
|
||||
TypeParamData(TypeParamData),
|
||||
ConstParamData(ConstParamData),
|
||||
}
|
||||
|
||||
impl TypeOrConstParamData {
|
||||
pub fn name(&self) -> Option<&Name> {
|
||||
match self {
|
||||
TypeOrConstParamData::TypeParamData(it) => it.name.as_ref(),
|
||||
TypeOrConstParamData::ConstParamData(it) => Some(&it.name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_default(&self) -> bool {
|
||||
match self {
|
||||
TypeOrConstParamData::TypeParamData(it) => it.default.is_some(),
|
||||
TypeOrConstParamData::ConstParamData(it) => it.default.is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_param(&self) -> Option<&TypeParamData> {
|
||||
match self {
|
||||
TypeOrConstParamData::TypeParamData(it) => Some(it),
|
||||
TypeOrConstParamData::ConstParamData(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn const_param(&self) -> Option<&ConstParamData> {
|
||||
match self {
|
||||
TypeOrConstParamData::TypeParamData(_) => None,
|
||||
TypeOrConstParamData::ConstParamData(it) => Some(it),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_trait_self(&self) -> bool {
|
||||
match self {
|
||||
TypeOrConstParamData::TypeParamData(it) => {
|
||||
it.provenance == TypeParamProvenance::TraitSelf
|
||||
}
|
||||
TypeOrConstParamData::ConstParamData(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_from!(TypeParamData, ConstParamData for TypeOrConstParamData);
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum GenericParamData {
|
||||
TypeParamData(TypeParamData),
|
||||
ConstParamData(ConstParamData),
|
||||
LifetimeParamData(LifetimeParamData),
|
||||
}
|
||||
|
||||
impl GenericParamData {
|
||||
pub fn name(&self) -> Option<&Name> {
|
||||
match self {
|
||||
GenericParamData::TypeParamData(it) => it.name.as_ref(),
|
||||
GenericParamData::ConstParamData(it) => Some(&it.name),
|
||||
GenericParamData::LifetimeParamData(it) => Some(&it.name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_param(&self) -> Option<&TypeParamData> {
|
||||
match self {
|
||||
GenericParamData::TypeParamData(it) => Some(it),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn const_param(&self) -> Option<&ConstParamData> {
|
||||
match self {
|
||||
GenericParamData::ConstParamData(it) => Some(it),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lifetime_param(&self) -> Option<&LifetimeParamData> {
|
||||
match self {
|
||||
GenericParamData::LifetimeParamData(it) => Some(it),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_from!(TypeParamData, ConstParamData, LifetimeParamData for GenericParamData);
|
||||
|
||||
pub enum GenericParamDataRef<'a> {
|
||||
TypeParamData(&'a TypeParamData),
|
||||
ConstParamData(&'a ConstParamData),
|
||||
LifetimeParamData(&'a LifetimeParamData),
|
||||
}
|
||||
|
||||
/// Data about the generic parameters of a function, struct, impl, etc.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct GenericParams {
|
||||
pub type_or_consts: Arena<TypeOrConstParamData>,
|
||||
pub lifetimes: Arena<LifetimeParamData>,
|
||||
pub where_predicates: Box<[WherePredicate]>,
|
||||
}
|
||||
|
||||
impl ops::Index<LocalTypeOrConstParamId> for GenericParams {
|
||||
type Output = TypeOrConstParamData;
|
||||
fn index(&self, index: LocalTypeOrConstParamId) -> &TypeOrConstParamData {
|
||||
&self.type_or_consts[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<LocalLifetimeParamId> for GenericParams {
|
||||
type Output = LifetimeParamData;
|
||||
fn index(&self, index: LocalLifetimeParamId) -> &LifetimeParamData {
|
||||
&self.lifetimes[index]
|
||||
}
|
||||
}
|
||||
|
||||
/// A single predicate from a where clause, i.e. `where Type: Trait`. Combined
|
||||
/// where clauses like `where T: Foo + Bar` are turned into multiple of these.
|
||||
/// It might still result in multiple actual predicates though, because of
|
||||
/// associated type bindings like `Iterator<Item = u32>`.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum WherePredicate {
|
||||
TypeBound {
|
||||
target: WherePredicateTypeTarget,
|
||||
bound: Interned<TypeBound>,
|
||||
},
|
||||
Lifetime {
|
||||
target: LifetimeRef,
|
||||
bound: LifetimeRef,
|
||||
},
|
||||
ForLifetime {
|
||||
lifetimes: Box<[Name]>,
|
||||
target: WherePredicateTypeTarget,
|
||||
bound: Interned<TypeBound>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum WherePredicateTypeTarget {
|
||||
TypeRef(Interned<TypeRef>),
|
||||
/// For desugared where predicates that can directly refer to a type param.
|
||||
TypeOrConstParam(LocalTypeOrConstParamId),
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct GenericParamsCollector {
|
||||
pub(crate) type_or_consts: Arena<TypeOrConstParamData>,
|
||||
lifetimes: Arena<LifetimeParamData>,
|
||||
where_predicates: Vec<WherePredicate>,
|
||||
}
|
||||
|
||||
impl GenericParamsCollector {
|
||||
pub(crate) fn fill(
|
||||
&mut self,
|
||||
lower_ctx: &LowerCtx<'_>,
|
||||
node: &dyn HasGenericParams,
|
||||
add_param_attrs: impl FnMut(
|
||||
Either<LocalTypeOrConstParamId, LocalLifetimeParamId>,
|
||||
ast::GenericParam,
|
||||
),
|
||||
) {
|
||||
if let Some(params) = node.generic_param_list() {
|
||||
self.fill_params(lower_ctx, params, add_param_attrs)
|
||||
}
|
||||
if let Some(where_clause) = node.where_clause() {
|
||||
self.fill_where_predicates(lower_ctx, where_clause);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fill_bounds(
|
||||
&mut self,
|
||||
lower_ctx: &LowerCtx<'_>,
|
||||
type_bounds: Option<ast::TypeBoundList>,
|
||||
target: Either<TypeRef, LifetimeRef>,
|
||||
) {
|
||||
for bound in type_bounds.iter().flat_map(|type_bound_list| type_bound_list.bounds()) {
|
||||
self.add_where_predicate_from_bound(lower_ctx, bound, None, target.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_params(
|
||||
&mut self,
|
||||
lower_ctx: &LowerCtx<'_>,
|
||||
params: ast::GenericParamList,
|
||||
mut add_param_attrs: impl FnMut(
|
||||
Either<LocalTypeOrConstParamId, LocalLifetimeParamId>,
|
||||
ast::GenericParam,
|
||||
),
|
||||
) {
|
||||
for type_or_const_param in params.type_or_const_params() {
|
||||
match type_or_const_param {
|
||||
ast::TypeOrConstParam::Type(type_param) => {
|
||||
let name = type_param.name().map_or_else(Name::missing, |it| it.as_name());
|
||||
// FIXME: Use `Path::from_src`
|
||||
let default = type_param
|
||||
.default_type()
|
||||
.map(|it| Interned::new(TypeRef::from_ast(lower_ctx, it)));
|
||||
let param = TypeParamData {
|
||||
name: Some(name.clone()),
|
||||
default,
|
||||
provenance: TypeParamProvenance::TypeParamList,
|
||||
};
|
||||
let idx = self.type_or_consts.alloc(param.into());
|
||||
let type_ref = TypeRef::Path(name.into());
|
||||
self.fill_bounds(
|
||||
lower_ctx,
|
||||
type_param.type_bound_list(),
|
||||
Either::Left(type_ref),
|
||||
);
|
||||
add_param_attrs(Either::Left(idx), ast::GenericParam::TypeParam(type_param));
|
||||
}
|
||||
ast::TypeOrConstParam::Const(const_param) => {
|
||||
let name = const_param.name().map_or_else(Name::missing, |it| it.as_name());
|
||||
let ty = const_param
|
||||
.ty()
|
||||
.map_or(TypeRef::Error, |it| TypeRef::from_ast(lower_ctx, it));
|
||||
let param = ConstParamData {
|
||||
name,
|
||||
ty: Interned::new(ty),
|
||||
default: ConstRef::from_const_param(lower_ctx, &const_param),
|
||||
};
|
||||
let idx = self.type_or_consts.alloc(param.into());
|
||||
add_param_attrs(Either::Left(idx), ast::GenericParam::ConstParam(const_param));
|
||||
}
|
||||
}
|
||||
}
|
||||
for lifetime_param in params.lifetime_params() {
|
||||
let name =
|
||||
lifetime_param.lifetime().map_or_else(Name::missing, |lt| Name::new_lifetime(<));
|
||||
let param = LifetimeParamData { name: name.clone() };
|
||||
let idx = self.lifetimes.alloc(param);
|
||||
let lifetime_ref = LifetimeRef::new_name(name);
|
||||
self.fill_bounds(
|
||||
lower_ctx,
|
||||
lifetime_param.type_bound_list(),
|
||||
Either::Right(lifetime_ref),
|
||||
);
|
||||
add_param_attrs(Either::Right(idx), ast::GenericParam::LifetimeParam(lifetime_param));
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_where_predicates(&mut self, lower_ctx: &LowerCtx<'_>, where_clause: ast::WhereClause) {
|
||||
for pred in where_clause.predicates() {
|
||||
let target = if let Some(type_ref) = pred.ty() {
|
||||
Either::Left(TypeRef::from_ast(lower_ctx, type_ref))
|
||||
} else if let Some(lifetime) = pred.lifetime() {
|
||||
Either::Right(LifetimeRef::new(&lifetime))
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let lifetimes: Option<Box<_>> = pred.generic_param_list().map(|param_list| {
|
||||
// Higher-Ranked Trait Bounds
|
||||
param_list
|
||||
.lifetime_params()
|
||||
.map(|lifetime_param| {
|
||||
lifetime_param
|
||||
.lifetime()
|
||||
.map_or_else(Name::missing, |lt| Name::new_lifetime(<))
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
for bound in pred.type_bound_list().iter().flat_map(|l| l.bounds()) {
|
||||
self.add_where_predicate_from_bound(
|
||||
lower_ctx,
|
||||
bound,
|
||||
lifetimes.as_deref(),
|
||||
target.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_where_predicate_from_bound(
|
||||
&mut self,
|
||||
lower_ctx: &LowerCtx<'_>,
|
||||
bound: ast::TypeBound,
|
||||
hrtb_lifetimes: Option<&[Name]>,
|
||||
target: Either<TypeRef, LifetimeRef>,
|
||||
) {
|
||||
let bound = TypeBound::from_ast(lower_ctx, bound);
|
||||
let predicate = match (target, bound) {
|
||||
(Either::Left(type_ref), bound) => match hrtb_lifetimes {
|
||||
Some(hrtb_lifetimes) => WherePredicate::ForLifetime {
|
||||
lifetimes: hrtb_lifetimes.to_vec().into_boxed_slice(),
|
||||
target: WherePredicateTypeTarget::TypeRef(Interned::new(type_ref)),
|
||||
bound: Interned::new(bound),
|
||||
},
|
||||
None => WherePredicate::TypeBound {
|
||||
target: WherePredicateTypeTarget::TypeRef(Interned::new(type_ref)),
|
||||
bound: Interned::new(bound),
|
||||
},
|
||||
},
|
||||
(Either::Right(lifetime), TypeBound::Lifetime(bound)) => {
|
||||
WherePredicate::Lifetime { target: lifetime, bound }
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
self.where_predicates.push(predicate);
|
||||
}
|
||||
|
||||
pub(crate) fn fill_implicit_impl_trait_args(
|
||||
&mut self,
|
||||
db: &dyn DefDatabase,
|
||||
exp: &mut Lazy<(Arc<DefMap>, Expander), impl FnOnce() -> (Arc<DefMap>, Expander)>,
|
||||
type_ref: &TypeRef,
|
||||
) {
|
||||
type_ref.walk(&mut |type_ref| {
|
||||
if let TypeRef::ImplTrait(bounds) = type_ref {
|
||||
let param = TypeParamData {
|
||||
name: None,
|
||||
default: None,
|
||||
provenance: TypeParamProvenance::ArgumentImplTrait,
|
||||
};
|
||||
let param_id = self.type_or_consts.alloc(param.into());
|
||||
for bound in bounds {
|
||||
self.where_predicates.push(WherePredicate::TypeBound {
|
||||
target: WherePredicateTypeTarget::TypeOrConstParam(param_id),
|
||||
bound: bound.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
if let TypeRef::Macro(mc) = type_ref {
|
||||
let macro_call = mc.to_node(db.upcast());
|
||||
let (def_map, expander) = &mut **exp;
|
||||
|
||||
let module = expander.module.local_id;
|
||||
let resolver = |path| {
|
||||
def_map
|
||||
.resolve_path(
|
||||
db,
|
||||
module,
|
||||
&path,
|
||||
crate::item_scope::BuiltinShadowMode::Other,
|
||||
Some(MacroSubNs::Bang),
|
||||
)
|
||||
.0
|
||||
.take_macros()
|
||||
};
|
||||
if let Ok(ExpandResult { value: Some((mark, expanded)), .. }) =
|
||||
expander.enter_expand(db, macro_call, resolver)
|
||||
{
|
||||
let ctx = expander.ctx(db);
|
||||
let type_ref = TypeRef::from_ast(&ctx, expanded.tree());
|
||||
self.fill_implicit_impl_trait_args(db, &mut *exp, &type_ref);
|
||||
exp.1.exit(mark);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn finish(self) -> GenericParams {
|
||||
let Self { mut lifetimes, mut type_or_consts, where_predicates } = self;
|
||||
lifetimes.shrink_to_fit();
|
||||
type_or_consts.shrink_to_fit();
|
||||
GenericParams {
|
||||
type_or_consts,
|
||||
lifetimes,
|
||||
where_predicates: where_predicates.into_boxed_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericParams {
|
||||
/// Number of Generic parameters (type_or_consts + lifetimes)
|
||||
pub fn len(&self) -> usize {
|
||||
self.type_or_consts.len() + self.lifetimes.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Iterator of type_or_consts field
|
||||
pub fn iter_type_or_consts(
|
||||
&self,
|
||||
) -> impl DoubleEndedIterator<Item = (LocalTypeOrConstParamId, &TypeOrConstParamData)> {
|
||||
self.type_or_consts.iter()
|
||||
}
|
||||
|
||||
/// Iterator of lifetimes field
|
||||
pub fn iter_lt(
|
||||
&self,
|
||||
) -> impl DoubleEndedIterator<Item = (LocalLifetimeParamId, &LifetimeParamData)> {
|
||||
self.lifetimes.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn generic_params_query(
|
||||
db: &dyn DefDatabase,
|
||||
def: GenericDefId,
|
||||
) -> Interned<GenericParams> {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "generic_params_query").entered();
|
||||
|
||||
let krate = def.module(db).krate;
|
||||
let cfg_options = db.crate_graph();
|
||||
let cfg_options = &cfg_options[krate].cfg_options;
|
||||
|
||||
// Returns the generic parameters that are enabled under the current `#[cfg]` options
|
||||
let enabled_params = |params: &Interned<GenericParams>, item_tree: &ItemTree| {
|
||||
let enabled = |param| item_tree.attrs(db, krate, param).is_cfg_enabled(cfg_options);
|
||||
|
||||
// In the common case, no parameters will by disabled by `#[cfg]` attributes.
|
||||
// Therefore, make a first pass to check if all parameters are enabled and, if so,
|
||||
// clone the `Interned<GenericParams>` instead of recreating an identical copy.
|
||||
let all_type_or_consts_enabled =
|
||||
params.type_or_consts.iter().all(|(idx, _)| enabled(idx.into()));
|
||||
let all_lifetimes_enabled = params.lifetimes.iter().all(|(idx, _)| enabled(idx.into()));
|
||||
|
||||
if all_type_or_consts_enabled && all_lifetimes_enabled {
|
||||
params.clone()
|
||||
} else {
|
||||
Interned::new(GenericParams {
|
||||
type_or_consts: all_type_or_consts_enabled
|
||||
.then(|| params.type_or_consts.clone())
|
||||
.unwrap_or_else(|| {
|
||||
params
|
||||
.type_or_consts
|
||||
.iter()
|
||||
.filter(|(idx, _)| enabled((*idx).into()))
|
||||
.map(|(_, param)| param.clone())
|
||||
.collect()
|
||||
}),
|
||||
lifetimes: all_lifetimes_enabled
|
||||
.then(|| params.lifetimes.clone())
|
||||
.unwrap_or_else(|| {
|
||||
params
|
||||
.lifetimes
|
||||
.iter()
|
||||
.filter(|(idx, _)| enabled((*idx).into()))
|
||||
.map(|(_, param)| param.clone())
|
||||
.collect()
|
||||
}),
|
||||
where_predicates: params.where_predicates.clone(),
|
||||
})
|
||||
}
|
||||
};
|
||||
fn id_to_generics<Id: GenericsItemTreeNode>(
|
||||
db: &dyn DefDatabase,
|
||||
id: impl for<'db> Lookup<
|
||||
Database<'db> = dyn DefDatabase + 'db,
|
||||
Data = impl ItemTreeLoc<Id = Id>,
|
||||
>,
|
||||
enabled_params: impl Fn(&Interned<GenericParams>, &ItemTree) -> Interned<GenericParams>,
|
||||
) -> Interned<GenericParams> {
|
||||
let id = id.lookup(db).item_tree_id();
|
||||
let tree = id.item_tree(db);
|
||||
let item = &tree[id.value];
|
||||
enabled_params(item.generic_params(), &tree)
|
||||
}
|
||||
|
||||
match def {
|
||||
GenericDefId::FunctionId(id) => {
|
||||
let loc = id.lookup(db);
|
||||
let tree = loc.id.item_tree(db);
|
||||
let item = &tree[loc.id.value];
|
||||
|
||||
let enabled_params = enabled_params(&item.explicit_generic_params, &tree);
|
||||
|
||||
let module = loc.container.module(db);
|
||||
let func_data = db.function_data(id);
|
||||
if func_data.params.is_empty() {
|
||||
enabled_params
|
||||
} else {
|
||||
let mut generic_params = GenericParamsCollector {
|
||||
type_or_consts: enabled_params.type_or_consts.clone(),
|
||||
lifetimes: enabled_params.lifetimes.clone(),
|
||||
where_predicates: enabled_params.where_predicates.clone().into(),
|
||||
};
|
||||
|
||||
// Don't create an `Expander` if not needed since this
|
||||
// could cause a reparse after the `ItemTree` has been created due to the spanmap.
|
||||
let mut expander = Lazy::new(|| {
|
||||
(module.def_map(db), Expander::new(db, loc.id.file_id(), module))
|
||||
});
|
||||
for param in func_data.params.iter() {
|
||||
generic_params.fill_implicit_impl_trait_args(db, &mut expander, param);
|
||||
}
|
||||
Interned::new(generic_params.finish())
|
||||
}
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::StructId(id)) => id_to_generics(db, id, enabled_params),
|
||||
GenericDefId::AdtId(AdtId::EnumId(id)) => id_to_generics(db, id, enabled_params),
|
||||
GenericDefId::AdtId(AdtId::UnionId(id)) => id_to_generics(db, id, enabled_params),
|
||||
GenericDefId::TraitId(id) => id_to_generics(db, id, enabled_params),
|
||||
GenericDefId::TraitAliasId(id) => id_to_generics(db, id, enabled_params),
|
||||
GenericDefId::TypeAliasId(id) => id_to_generics(db, id, enabled_params),
|
||||
GenericDefId::ImplId(id) => id_to_generics(db, id, enabled_params),
|
||||
GenericDefId::EnumVariantId(_) | GenericDefId::ConstId(_) => {
|
||||
Interned::new(GenericParams {
|
||||
type_or_consts: Default::default(),
|
||||
lifetimes: Default::default(),
|
||||
where_predicates: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_type_by_name(&self, name: &Name, parent: GenericDefId) -> Option<TypeParamId> {
|
||||
self.type_or_consts.iter().find_map(|(id, p)| {
|
||||
if p.name().as_ref() == Some(&name) && p.type_param().is_some() {
|
||||
Some(TypeParamId::from_unchecked(TypeOrConstParamId { local_id: id, parent }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_const_by_name(&self, name: &Name, parent: GenericDefId) -> Option<ConstParamId> {
|
||||
self.type_or_consts.iter().find_map(|(id, p)| {
|
||||
if p.name().as_ref() == Some(&name) && p.const_param().is_some() {
|
||||
Some(ConstParamId::from_unchecked(TypeOrConstParamId { local_id: id, parent }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_trait_self_param(&self) -> Option<LocalTypeOrConstParamId> {
|
||||
self.type_or_consts.iter().find_map(|(id, p)| {
|
||||
matches!(
|
||||
p,
|
||||
TypeOrConstParamData::TypeParamData(TypeParamData {
|
||||
provenance: TypeParamProvenance::TraitSelf,
|
||||
..
|
||||
})
|
||||
)
|
||||
.then(|| id)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_lifetime_by_name(
|
||||
&self,
|
||||
name: &Name,
|
||||
parent: GenericDefId,
|
||||
) -> Option<LifetimeParamId> {
|
||||
self.lifetimes.iter().find_map(|(id, p)| {
|
||||
if &p.name == name {
|
||||
Some(LifetimeParamId { local_id: id, parent })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
573
src/tools/rust-analyzer/crates/hir-def/src/hir.rs
Normal file
573
src/tools/rust-analyzer/crates/hir-def/src/hir.rs
Normal file
|
|
@ -0,0 +1,573 @@
|
|||
//! This module describes hir-level representation of expressions.
|
||||
//!
|
||||
//! This representation is:
|
||||
//!
|
||||
//! 1. Identity-based. Each expression has an `id`, so we can distinguish
|
||||
//! between different `1` in `1 + 1`.
|
||||
//! 2. Independent of syntax. Though syntactic provenance information can be
|
||||
//! attached separately via id-based side map.
|
||||
//! 3. Unresolved. Paths are stored as sequences of names, and not as defs the
|
||||
//! names refer to.
|
||||
//! 4. Desugared. There's no `if let`.
|
||||
//!
|
||||
//! See also a neighboring `body` module.
|
||||
|
||||
pub mod format_args;
|
||||
pub mod type_ref;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use hir_expand::name::Name;
|
||||
use intern::Interned;
|
||||
use la_arena::{Idx, RawIdx};
|
||||
use smallvec::SmallVec;
|
||||
use syntax::ast;
|
||||
|
||||
use crate::{
|
||||
builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
|
||||
path::{GenericArgs, Path},
|
||||
type_ref::{Mutability, Rawness, TypeRef},
|
||||
BlockId, ConstBlockId,
|
||||
};
|
||||
|
||||
pub use syntax::ast::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp};
|
||||
|
||||
pub type BindingId = Idx<Binding>;
|
||||
|
||||
pub type ExprId = Idx<Expr>;
|
||||
|
||||
/// FIXME: this is a hacky function which should be removed
|
||||
pub(crate) fn dummy_expr_id() -> ExprId {
|
||||
ExprId::from_raw(RawIdx::from(u32::MAX))
|
||||
}
|
||||
|
||||
pub type PatId = Idx<Pat>;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
pub enum ExprOrPatId {
|
||||
ExprId(ExprId),
|
||||
PatId(PatId),
|
||||
}
|
||||
stdx::impl_from!(ExprId, PatId for ExprOrPatId);
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Label {
|
||||
pub name: Name,
|
||||
}
|
||||
pub type LabelId = Idx<Label>;
|
||||
|
||||
// We convert float values into bits and that's how we don't need to deal with f32 and f64.
|
||||
// For PartialEq, bits comparison should work, as ordering is not important
|
||||
// https://github.com/rust-lang/rust-analyzer/issues/12380#issuecomment-1137284360
|
||||
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct FloatTypeWrapper(u64);
|
||||
|
||||
impl FloatTypeWrapper {
|
||||
pub fn new(value: f64) -> Self {
|
||||
Self(value.to_bits())
|
||||
}
|
||||
|
||||
pub fn into_f64(self) -> f64 {
|
||||
f64::from_bits(self.0)
|
||||
}
|
||||
|
||||
pub fn into_f32(self) -> f32 {
|
||||
f64::from_bits(self.0) as f32
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FloatTypeWrapper {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", f64::from_bits(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Literal {
|
||||
String(Box<str>),
|
||||
ByteString(Box<[u8]>),
|
||||
CString(Box<[u8]>),
|
||||
Char(char),
|
||||
Bool(bool),
|
||||
Int(i128, Option<BuiltinInt>),
|
||||
Uint(u128, Option<BuiltinUint>),
|
||||
// Here we are using a wrapper around float because f32 and f64 do not implement Eq, so they
|
||||
// could not be used directly here, to understand how the wrapper works go to definition of
|
||||
// FloatTypeWrapper
|
||||
Float(FloatTypeWrapper, Option<BuiltinFloat>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
/// Used in range patterns.
|
||||
pub enum LiteralOrConst {
|
||||
Literal(Literal),
|
||||
Const(PatId),
|
||||
}
|
||||
|
||||
impl Literal {
|
||||
pub fn negate(self) -> Option<Self> {
|
||||
if let Literal::Int(i, k) = self {
|
||||
Some(Literal::Int(-i, k))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ast::LiteralKind> for Literal {
|
||||
fn from(ast_lit_kind: ast::LiteralKind) -> Self {
|
||||
use ast::LiteralKind;
|
||||
match ast_lit_kind {
|
||||
LiteralKind::IntNumber(lit) => {
|
||||
if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
|
||||
Literal::Float(
|
||||
FloatTypeWrapper::new(lit.float_value().unwrap_or(Default::default())),
|
||||
builtin,
|
||||
)
|
||||
} else if let builtin @ Some(_) = lit.suffix().and_then(BuiltinUint::from_suffix) {
|
||||
Literal::Uint(lit.value().unwrap_or(0), builtin)
|
||||
} else {
|
||||
let builtin = lit.suffix().and_then(BuiltinInt::from_suffix);
|
||||
Literal::Int(lit.value().unwrap_or(0) as i128, builtin)
|
||||
}
|
||||
}
|
||||
LiteralKind::FloatNumber(lit) => {
|
||||
let ty = lit.suffix().and_then(BuiltinFloat::from_suffix);
|
||||
Literal::Float(FloatTypeWrapper::new(lit.value().unwrap_or(Default::default())), ty)
|
||||
}
|
||||
LiteralKind::ByteString(bs) => {
|
||||
let text = bs.value().map(Box::from).unwrap_or_else(Default::default);
|
||||
Literal::ByteString(text)
|
||||
}
|
||||
LiteralKind::String(s) => {
|
||||
let text = s.value().map(Box::from).unwrap_or_else(Default::default);
|
||||
Literal::String(text)
|
||||
}
|
||||
LiteralKind::CString(s) => {
|
||||
let text = s.value().map(Box::from).unwrap_or_else(Default::default);
|
||||
Literal::CString(text)
|
||||
}
|
||||
LiteralKind::Byte(b) => {
|
||||
Literal::Uint(b.value().unwrap_or_default() as u128, Some(BuiltinUint::U8))
|
||||
}
|
||||
LiteralKind::Char(c) => Literal::Char(c.value().unwrap_or_default()),
|
||||
LiteralKind::Bool(val) => Literal::Bool(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Expr {
|
||||
/// This is produced if the syntax tree does not have a required expression piece.
|
||||
Missing,
|
||||
Path(Path),
|
||||
If {
|
||||
condition: ExprId,
|
||||
then_branch: ExprId,
|
||||
else_branch: Option<ExprId>,
|
||||
},
|
||||
Let {
|
||||
pat: PatId,
|
||||
expr: ExprId,
|
||||
},
|
||||
Block {
|
||||
id: Option<BlockId>,
|
||||
statements: Box<[Statement]>,
|
||||
tail: Option<ExprId>,
|
||||
label: Option<LabelId>,
|
||||
},
|
||||
Async {
|
||||
id: Option<BlockId>,
|
||||
statements: Box<[Statement]>,
|
||||
tail: Option<ExprId>,
|
||||
},
|
||||
Const(ConstBlockId),
|
||||
// FIXME: Fold this into Block with an unsafe flag?
|
||||
Unsafe {
|
||||
id: Option<BlockId>,
|
||||
statements: Box<[Statement]>,
|
||||
tail: Option<ExprId>,
|
||||
},
|
||||
Loop {
|
||||
body: ExprId,
|
||||
label: Option<LabelId>,
|
||||
},
|
||||
Call {
|
||||
callee: ExprId,
|
||||
args: Box<[ExprId]>,
|
||||
is_assignee_expr: bool,
|
||||
},
|
||||
MethodCall {
|
||||
receiver: ExprId,
|
||||
method_name: Name,
|
||||
args: Box<[ExprId]>,
|
||||
generic_args: Option<Box<GenericArgs>>,
|
||||
},
|
||||
Match {
|
||||
expr: ExprId,
|
||||
arms: Box<[MatchArm]>,
|
||||
},
|
||||
Continue {
|
||||
label: Option<LabelId>,
|
||||
},
|
||||
Break {
|
||||
expr: Option<ExprId>,
|
||||
label: Option<LabelId>,
|
||||
},
|
||||
Return {
|
||||
expr: Option<ExprId>,
|
||||
},
|
||||
Become {
|
||||
expr: ExprId,
|
||||
},
|
||||
Yield {
|
||||
expr: Option<ExprId>,
|
||||
},
|
||||
Yeet {
|
||||
expr: Option<ExprId>,
|
||||
},
|
||||
RecordLit {
|
||||
path: Option<Box<Path>>,
|
||||
fields: Box<[RecordLitField]>,
|
||||
spread: Option<ExprId>,
|
||||
ellipsis: bool,
|
||||
is_assignee_expr: bool,
|
||||
},
|
||||
Field {
|
||||
expr: ExprId,
|
||||
name: Name,
|
||||
},
|
||||
Await {
|
||||
expr: ExprId,
|
||||
},
|
||||
Cast {
|
||||
expr: ExprId,
|
||||
type_ref: Interned<TypeRef>,
|
||||
},
|
||||
Ref {
|
||||
expr: ExprId,
|
||||
rawness: Rawness,
|
||||
mutability: Mutability,
|
||||
},
|
||||
Box {
|
||||
expr: ExprId,
|
||||
},
|
||||
UnaryOp {
|
||||
expr: ExprId,
|
||||
op: UnaryOp,
|
||||
},
|
||||
BinaryOp {
|
||||
lhs: ExprId,
|
||||
rhs: ExprId,
|
||||
op: Option<BinaryOp>,
|
||||
},
|
||||
Range {
|
||||
lhs: Option<ExprId>,
|
||||
rhs: Option<ExprId>,
|
||||
range_type: RangeOp,
|
||||
},
|
||||
Index {
|
||||
base: ExprId,
|
||||
index: ExprId,
|
||||
is_assignee_expr: bool,
|
||||
},
|
||||
Closure {
|
||||
args: Box<[PatId]>,
|
||||
arg_types: Box<[Option<Interned<TypeRef>>]>,
|
||||
ret_type: Option<Interned<TypeRef>>,
|
||||
body: ExprId,
|
||||
closure_kind: ClosureKind,
|
||||
capture_by: CaptureBy,
|
||||
},
|
||||
Tuple {
|
||||
exprs: Box<[ExprId]>,
|
||||
is_assignee_expr: bool,
|
||||
},
|
||||
Array(Array),
|
||||
Literal(Literal),
|
||||
Underscore,
|
||||
OffsetOf(OffsetOf),
|
||||
InlineAsm(InlineAsm),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OffsetOf {
|
||||
pub container: Interned<TypeRef>,
|
||||
pub fields: Box<[Name]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct InlineAsm {
|
||||
pub e: ExprId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ClosureKind {
|
||||
Closure,
|
||||
Coroutine(Movability),
|
||||
Async,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CaptureBy {
|
||||
/// `move |x| y + x`.
|
||||
Value,
|
||||
/// `move` keyword was not specified.
|
||||
Ref,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Movability {
|
||||
Static,
|
||||
Movable,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Array {
|
||||
ElementList { elements: Box<[ExprId]>, is_assignee_expr: bool },
|
||||
Repeat { initializer: ExprId, repeat: ExprId },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct MatchArm {
|
||||
pub pat: PatId,
|
||||
pub guard: Option<ExprId>,
|
||||
pub expr: ExprId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct RecordLitField {
|
||||
pub name: Name,
|
||||
pub expr: ExprId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Statement {
|
||||
Let {
|
||||
pat: PatId,
|
||||
type_ref: Option<Interned<TypeRef>>,
|
||||
initializer: Option<ExprId>,
|
||||
else_branch: Option<ExprId>,
|
||||
},
|
||||
Expr {
|
||||
expr: ExprId,
|
||||
has_semi: bool,
|
||||
},
|
||||
// At the moment, we only use this to figure out if a return expression
|
||||
// is really the last statement of a block. See #16566
|
||||
Item,
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
|
||||
match self {
|
||||
Expr::Missing => {}
|
||||
Expr::Path(_) | Expr::OffsetOf(_) => {}
|
||||
Expr::InlineAsm(it) => f(it.e),
|
||||
Expr::If { condition, then_branch, else_branch } => {
|
||||
f(*condition);
|
||||
f(*then_branch);
|
||||
if let &Some(else_branch) = else_branch {
|
||||
f(else_branch);
|
||||
}
|
||||
}
|
||||
Expr::Let { expr, .. } => {
|
||||
f(*expr);
|
||||
}
|
||||
Expr::Const(_) => (),
|
||||
Expr::Block { statements, tail, .. }
|
||||
| Expr::Unsafe { statements, tail, .. }
|
||||
| Expr::Async { statements, tail, .. } => {
|
||||
for stmt in statements.iter() {
|
||||
match stmt {
|
||||
Statement::Let { initializer, else_branch, .. } => {
|
||||
if let &Some(expr) = initializer {
|
||||
f(expr);
|
||||
}
|
||||
if let &Some(expr) = else_branch {
|
||||
f(expr);
|
||||
}
|
||||
}
|
||||
Statement::Expr { expr: expression, .. } => f(*expression),
|
||||
Statement::Item => (),
|
||||
}
|
||||
}
|
||||
if let &Some(expr) = tail {
|
||||
f(expr);
|
||||
}
|
||||
}
|
||||
Expr::Loop { body, .. } => f(*body),
|
||||
Expr::Call { callee, args, .. } => {
|
||||
f(*callee);
|
||||
args.iter().copied().for_each(f);
|
||||
}
|
||||
Expr::MethodCall { receiver, args, .. } => {
|
||||
f(*receiver);
|
||||
args.iter().copied().for_each(f);
|
||||
}
|
||||
Expr::Match { expr, arms } => {
|
||||
f(*expr);
|
||||
arms.iter().map(|arm| arm.expr).for_each(f);
|
||||
}
|
||||
Expr::Continue { .. } => {}
|
||||
Expr::Break { expr, .. }
|
||||
| Expr::Return { expr }
|
||||
| Expr::Yield { expr }
|
||||
| Expr::Yeet { expr } => {
|
||||
if let &Some(expr) = expr {
|
||||
f(expr);
|
||||
}
|
||||
}
|
||||
Expr::Become { expr } => f(*expr),
|
||||
Expr::RecordLit { fields, spread, .. } => {
|
||||
for field in fields.iter() {
|
||||
f(field.expr);
|
||||
}
|
||||
if let &Some(expr) = spread {
|
||||
f(expr);
|
||||
}
|
||||
}
|
||||
Expr::Closure { body, .. } => {
|
||||
f(*body);
|
||||
}
|
||||
Expr::BinaryOp { lhs, rhs, .. } => {
|
||||
f(*lhs);
|
||||
f(*rhs);
|
||||
}
|
||||
Expr::Range { lhs, rhs, .. } => {
|
||||
if let &Some(lhs) = rhs {
|
||||
f(lhs);
|
||||
}
|
||||
if let &Some(rhs) = lhs {
|
||||
f(rhs);
|
||||
}
|
||||
}
|
||||
Expr::Index { base, index, .. } => {
|
||||
f(*base);
|
||||
f(*index);
|
||||
}
|
||||
Expr::Field { expr, .. }
|
||||
| Expr::Await { expr }
|
||||
| Expr::Cast { expr, .. }
|
||||
| Expr::Ref { expr, .. }
|
||||
| Expr::UnaryOp { expr, .. }
|
||||
| Expr::Box { expr } => {
|
||||
f(*expr);
|
||||
}
|
||||
Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f),
|
||||
Expr::Array(a) => match a {
|
||||
Array::ElementList { elements, .. } => elements.iter().copied().for_each(f),
|
||||
Array::Repeat { initializer, repeat } => {
|
||||
f(*initializer);
|
||||
f(*repeat)
|
||||
}
|
||||
},
|
||||
Expr::Literal(_) => {}
|
||||
Expr::Underscore => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicit binding annotations given in the HIR for a binding. Note
|
||||
/// that this is not the final binding *mode* that we infer after type
|
||||
/// inference.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Copy)]
|
||||
pub enum BindingAnnotation {
|
||||
/// No binding annotation given: this means that the final binding mode
|
||||
/// will depend on whether we have skipped through a `&` reference
|
||||
/// when matching. For example, the `x` in `Some(x)` will have binding
|
||||
/// mode `None`; if you do `let Some(x) = &Some(22)`, it will
|
||||
/// ultimately be inferred to be by-reference.
|
||||
Unannotated,
|
||||
|
||||
/// Annotated with `mut x` -- could be either ref or not, similar to `None`.
|
||||
Mutable,
|
||||
|
||||
/// Annotated as `ref`, like `ref x`
|
||||
Ref,
|
||||
|
||||
/// Annotated as `ref mut x`.
|
||||
RefMut,
|
||||
}
|
||||
|
||||
impl BindingAnnotation {
|
||||
pub fn new(is_mutable: bool, is_ref: bool) -> Self {
|
||||
match (is_mutable, is_ref) {
|
||||
(true, true) => BindingAnnotation::RefMut,
|
||||
(false, true) => BindingAnnotation::Ref,
|
||||
(true, false) => BindingAnnotation::Mutable,
|
||||
(false, false) => BindingAnnotation::Unannotated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BindingProblems {
|
||||
/// https://doc.rust-lang.org/stable/error_codes/E0416.html
|
||||
BoundMoreThanOnce,
|
||||
/// https://doc.rust-lang.org/stable/error_codes/E0409.html
|
||||
BoundInconsistently,
|
||||
/// https://doc.rust-lang.org/stable/error_codes/E0408.html
|
||||
NotBoundAcrossAll,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Binding {
|
||||
pub name: Name,
|
||||
pub mode: BindingAnnotation,
|
||||
pub definitions: SmallVec<[PatId; 1]>,
|
||||
pub problems: Option<BindingProblems>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct RecordFieldPat {
|
||||
pub name: Name,
|
||||
pub pat: PatId,
|
||||
}
|
||||
|
||||
/// Close relative to rustc's hir::PatKind
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Pat {
|
||||
Missing,
|
||||
Wild,
|
||||
Tuple { args: Box<[PatId]>, ellipsis: Option<usize> },
|
||||
Or(Box<[PatId]>),
|
||||
Record { path: Option<Box<Path>>, args: Box<[RecordFieldPat]>, ellipsis: bool },
|
||||
Range { start: Option<Box<LiteralOrConst>>, end: Option<Box<LiteralOrConst>> },
|
||||
Slice { prefix: Box<[PatId]>, slice: Option<PatId>, suffix: Box<[PatId]> },
|
||||
Path(Box<Path>),
|
||||
Lit(ExprId),
|
||||
Bind { id: BindingId, subpat: Option<PatId> },
|
||||
TupleStruct { path: Option<Box<Path>>, args: Box<[PatId]>, ellipsis: Option<usize> },
|
||||
Ref { pat: PatId, mutability: Mutability },
|
||||
Box { inner: PatId },
|
||||
ConstBlock(ExprId),
|
||||
}
|
||||
|
||||
impl Pat {
|
||||
pub fn walk_child_pats(&self, mut f: impl FnMut(PatId)) {
|
||||
match self {
|
||||
Pat::Range { .. }
|
||||
| Pat::Lit(..)
|
||||
| Pat::Path(..)
|
||||
| Pat::ConstBlock(..)
|
||||
| Pat::Wild
|
||||
| Pat::Missing => {}
|
||||
Pat::Bind { subpat, .. } => {
|
||||
subpat.iter().copied().for_each(f);
|
||||
}
|
||||
Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => {
|
||||
args.iter().copied().for_each(f);
|
||||
}
|
||||
Pat::Ref { pat, .. } => f(*pat),
|
||||
Pat::Slice { prefix, slice, suffix } => {
|
||||
let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter());
|
||||
total_iter.copied().for_each(f);
|
||||
}
|
||||
Pat::Record { args, .. } => {
|
||||
args.iter().map(|f| f.pat).for_each(f);
|
||||
}
|
||||
Pat::Box { inner } => f(*inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
519
src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs
Normal file
519
src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
//! Parses `format_args` input.
|
||||
use std::mem;
|
||||
|
||||
use hir_expand::name::Name;
|
||||
use rustc_parse_format as parse;
|
||||
use stdx::TupleExt;
|
||||
use syntax::{
|
||||
ast::{self, IsString},
|
||||
SmolStr, TextRange, TextSize,
|
||||
};
|
||||
|
||||
use crate::hir::ExprId;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FormatArgs {
|
||||
pub template: Box<[FormatArgsPiece]>,
|
||||
pub arguments: FormatArguments,
|
||||
pub orphans: Vec<ExprId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FormatArguments {
|
||||
pub arguments: Box<[FormatArgument]>,
|
||||
pub num_unnamed_args: usize,
|
||||
pub num_explicit_args: usize,
|
||||
pub names: Box<[(Name, usize)]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FormatArgsPiece {
|
||||
Literal(Box<str>),
|
||||
Placeholder(FormatPlaceholder),
|
||||
}
|
||||
|
||||
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FormatPlaceholder {
|
||||
/// Index into [`FormatArgs::arguments`].
|
||||
pub argument: FormatArgPosition,
|
||||
/// The span inside the format string for the full `{…}` placeholder.
|
||||
pub span: Option<TextRange>,
|
||||
/// `{}`, `{:?}`, or `{:x}`, etc.
|
||||
pub format_trait: FormatTrait,
|
||||
/// `{}` or `{:.5}` or `{:-^20}`, etc.
|
||||
pub format_options: FormatOptions,
|
||||
}
|
||||
|
||||
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FormatArgPosition {
|
||||
/// Which argument this position refers to (Ok),
|
||||
/// or would've referred to if it existed (Err).
|
||||
pub index: Result<usize, usize>,
|
||||
/// What kind of position this is. See [`FormatArgPositionKind`].
|
||||
pub kind: FormatArgPositionKind,
|
||||
/// The span of the name or number.
|
||||
pub span: Option<TextRange>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FormatArgPositionKind {
|
||||
/// `{}` or `{:.*}`
|
||||
Implicit,
|
||||
/// `{1}` or `{:1$}` or `{:.1$}`
|
||||
Number,
|
||||
/// `{a}` or `{:a$}` or `{:.a$}`
|
||||
Named,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum FormatTrait {
|
||||
/// `{}`
|
||||
Display,
|
||||
/// `{:?}`
|
||||
Debug,
|
||||
/// `{:e}`
|
||||
LowerExp,
|
||||
/// `{:E}`
|
||||
UpperExp,
|
||||
/// `{:o}`
|
||||
Octal,
|
||||
/// `{:p}`
|
||||
Pointer,
|
||||
/// `{:b}`
|
||||
Binary,
|
||||
/// `{:x}`
|
||||
LowerHex,
|
||||
/// `{:X}`
|
||||
UpperHex,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub struct FormatOptions {
|
||||
/// The width. E.g. `{:5}` or `{:width$}`.
|
||||
pub width: Option<FormatCount>,
|
||||
/// The precision. E.g. `{:.5}` or `{:.precision$}`.
|
||||
pub precision: Option<FormatCount>,
|
||||
/// The alignment. E.g. `{:>}` or `{:<}` or `{:^}`.
|
||||
pub alignment: Option<FormatAlignment>,
|
||||
/// The fill character. E.g. the `.` in `{:.>10}`.
|
||||
pub fill: Option<char>,
|
||||
/// The `+` or `-` flag.
|
||||
pub sign: Option<FormatSign>,
|
||||
/// The `#` flag.
|
||||
pub alternate: bool,
|
||||
/// The `0` flag. E.g. the `0` in `{:02x}`.
|
||||
pub zero_pad: bool,
|
||||
/// The `x` or `X` flag (for `Debug` only). E.g. the `x` in `{:x?}`.
|
||||
pub debug_hex: Option<FormatDebugHex>,
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FormatSign {
|
||||
/// The `+` flag.
|
||||
Plus,
|
||||
/// The `-` flag.
|
||||
Minus,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FormatDebugHex {
|
||||
/// The `x` flag in `{:x?}`.
|
||||
Lower,
|
||||
/// The `X` flag in `{:X?}`.
|
||||
Upper,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FormatAlignment {
|
||||
/// `{:<}`
|
||||
Left,
|
||||
/// `{:>}`
|
||||
Right,
|
||||
/// `{:^}`
|
||||
Center,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FormatCount {
|
||||
/// `{:5}` or `{:.5}`
|
||||
Literal(usize),
|
||||
/// `{:.*}`, `{:.5$}`, or `{:a$}`, etc.
|
||||
Argument(FormatArgPosition),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FormatArgument {
|
||||
pub kind: FormatArgumentKind,
|
||||
pub expr: ExprId,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum FormatArgumentKind {
|
||||
/// `format_args(…, arg)`
|
||||
Normal,
|
||||
/// `format_args(…, arg = 1)`
|
||||
Named(Name),
|
||||
/// `format_args("… {arg} …")`
|
||||
Captured(Name),
|
||||
}
|
||||
|
||||
// Only used in parse_args and report_invalid_references,
|
||||
// to indicate how a referred argument was used.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum PositionUsedAs {
|
||||
Placeholder(Option<TextRange>),
|
||||
Precision,
|
||||
Width,
|
||||
}
|
||||
use PositionUsedAs::*;
|
||||
|
||||
#[allow(clippy::unnecessary_lazy_evaluations)]
|
||||
pub(crate) fn parse(
|
||||
s: &ast::String,
|
||||
fmt_snippet: Option<String>,
|
||||
mut args: FormatArgumentsCollector,
|
||||
is_direct_literal: bool,
|
||||
mut synth: impl FnMut(Name) -> ExprId,
|
||||
mut record_usage: impl FnMut(Name, Option<TextRange>),
|
||||
) -> FormatArgs {
|
||||
let text = s.text_without_quotes();
|
||||
let str_style = match s.quote_offsets() {
|
||||
Some(offsets) => {
|
||||
let raw = usize::from(offsets.quotes.0.len()) - 1;
|
||||
// subtract 1 for the `r` prefix
|
||||
(raw != 0).then(|| raw - 1)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let mut parser =
|
||||
parse::Parser::new(text, str_style, fmt_snippet, false, parse::ParseMode::Format);
|
||||
|
||||
let mut pieces = Vec::new();
|
||||
while let Some(piece) = parser.next() {
|
||||
if !parser.errors.is_empty() {
|
||||
break;
|
||||
} else {
|
||||
pieces.push(piece);
|
||||
}
|
||||
}
|
||||
let is_source_literal = parser.is_source_literal;
|
||||
if !parser.errors.is_empty() {
|
||||
// FIXME: Diagnose
|
||||
return FormatArgs {
|
||||
template: Default::default(),
|
||||
arguments: args.finish(),
|
||||
orphans: vec![],
|
||||
};
|
||||
}
|
||||
|
||||
let to_span = |inner_span: parse::InnerSpan| {
|
||||
is_source_literal.then(|| {
|
||||
TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap())
|
||||
- TextSize::from(str_style.map(|it| it + 1).unwrap_or(0) as u32 + 1)
|
||||
})
|
||||
};
|
||||
|
||||
let mut used = vec![false; args.explicit_args().len()];
|
||||
let mut invalid_refs = Vec::new();
|
||||
let mut numeric_references_to_named_arg = Vec::new();
|
||||
|
||||
enum ArgRef<'a> {
|
||||
Index(usize),
|
||||
Name(&'a str, Option<TextRange>),
|
||||
}
|
||||
let mut lookup_arg = |arg: ArgRef<'_>,
|
||||
span: Option<TextRange>,
|
||||
used_as: PositionUsedAs,
|
||||
kind: FormatArgPositionKind|
|
||||
-> FormatArgPosition {
|
||||
let index = match arg {
|
||||
ArgRef::Index(index) => {
|
||||
if let Some(arg) = args.by_index(index) {
|
||||
used[index] = true;
|
||||
if arg.kind.ident().is_some() {
|
||||
// This was a named argument, but it was used as a positional argument.
|
||||
numeric_references_to_named_arg.push((index, span, used_as));
|
||||
}
|
||||
Ok(index)
|
||||
} else {
|
||||
// Doesn't exist as an explicit argument.
|
||||
invalid_refs.push((index, span, used_as, kind));
|
||||
Err(index)
|
||||
}
|
||||
}
|
||||
ArgRef::Name(name, span) => {
|
||||
let name = Name::new_text_dont_use(SmolStr::new(name));
|
||||
if let Some((index, _)) = args.by_name(&name) {
|
||||
record_usage(name, span);
|
||||
// Name found in `args`, so we resolve it to its index.
|
||||
if index < args.explicit_args().len() {
|
||||
// Mark it as used, if it was an explicit argument.
|
||||
used[index] = true;
|
||||
}
|
||||
Ok(index)
|
||||
} else {
|
||||
// Name not found in `args`, so we add it as an implicitly captured argument.
|
||||
if !is_direct_literal {
|
||||
// For the moment capturing variables from format strings expanded from macros is
|
||||
// disabled (see RFC #2795)
|
||||
// FIXME: Diagnose
|
||||
}
|
||||
record_usage(name.clone(), span);
|
||||
Ok(args.add(FormatArgument {
|
||||
kind: FormatArgumentKind::Captured(name.clone()),
|
||||
// FIXME: This is problematic, we might want to synthesize a dummy
|
||||
// expression proper and/or desugar these.
|
||||
expr: synth(name),
|
||||
}))
|
||||
}
|
||||
}
|
||||
};
|
||||
FormatArgPosition { index, kind, span }
|
||||
};
|
||||
|
||||
let mut template = Vec::new();
|
||||
let mut unfinished_literal = String::new();
|
||||
let mut placeholder_index = 0;
|
||||
|
||||
for piece in pieces {
|
||||
match piece {
|
||||
parse::Piece::String(s) => {
|
||||
unfinished_literal.push_str(s);
|
||||
}
|
||||
parse::Piece::NextArgument(arg) => {
|
||||
let parse::Argument { position, position_span, format } = *arg;
|
||||
if !unfinished_literal.is_empty() {
|
||||
template.push(FormatArgsPiece::Literal(
|
||||
mem::take(&mut unfinished_literal).into_boxed_str(),
|
||||
));
|
||||
}
|
||||
|
||||
let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s));
|
||||
placeholder_index += 1;
|
||||
|
||||
let position_span = to_span(position_span);
|
||||
let argument = match position {
|
||||
parse::ArgumentImplicitlyIs(i) => lookup_arg(
|
||||
ArgRef::Index(i),
|
||||
position_span,
|
||||
Placeholder(span),
|
||||
FormatArgPositionKind::Implicit,
|
||||
),
|
||||
parse::ArgumentIs(i) => lookup_arg(
|
||||
ArgRef::Index(i),
|
||||
position_span,
|
||||
Placeholder(span),
|
||||
FormatArgPositionKind::Number,
|
||||
),
|
||||
parse::ArgumentNamed(name) => lookup_arg(
|
||||
ArgRef::Name(name, position_span),
|
||||
position_span,
|
||||
Placeholder(span),
|
||||
FormatArgPositionKind::Named,
|
||||
),
|
||||
};
|
||||
|
||||
let alignment = match format.align {
|
||||
parse::AlignUnknown => None,
|
||||
parse::AlignLeft => Some(FormatAlignment::Left),
|
||||
parse::AlignRight => Some(FormatAlignment::Right),
|
||||
parse::AlignCenter => Some(FormatAlignment::Center),
|
||||
};
|
||||
|
||||
let format_trait = match format.ty {
|
||||
"" => FormatTrait::Display,
|
||||
"?" => FormatTrait::Debug,
|
||||
"e" => FormatTrait::LowerExp,
|
||||
"E" => FormatTrait::UpperExp,
|
||||
"o" => FormatTrait::Octal,
|
||||
"p" => FormatTrait::Pointer,
|
||||
"b" => FormatTrait::Binary,
|
||||
"x" => FormatTrait::LowerHex,
|
||||
"X" => FormatTrait::UpperHex,
|
||||
_ => {
|
||||
// FIXME: Diagnose
|
||||
FormatTrait::Display
|
||||
}
|
||||
};
|
||||
|
||||
let precision_span = format.precision_span.and_then(to_span);
|
||||
let precision = match format.precision {
|
||||
parse::CountIs(n) => Some(FormatCount::Literal(n)),
|
||||
parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
|
||||
ArgRef::Name(name, to_span(name_span)),
|
||||
precision_span,
|
||||
Precision,
|
||||
FormatArgPositionKind::Named,
|
||||
))),
|
||||
parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
|
||||
ArgRef::Index(i),
|
||||
precision_span,
|
||||
Precision,
|
||||
FormatArgPositionKind::Number,
|
||||
))),
|
||||
parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
|
||||
ArgRef::Index(i),
|
||||
precision_span,
|
||||
Precision,
|
||||
FormatArgPositionKind::Implicit,
|
||||
))),
|
||||
parse::CountImplied => None,
|
||||
};
|
||||
|
||||
let width_span = format.width_span.and_then(to_span);
|
||||
let width = match format.width {
|
||||
parse::CountIs(n) => Some(FormatCount::Literal(n)),
|
||||
parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
|
||||
ArgRef::Name(name, to_span(name_span)),
|
||||
width_span,
|
||||
Width,
|
||||
FormatArgPositionKind::Named,
|
||||
))),
|
||||
parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
|
||||
ArgRef::Index(i),
|
||||
width_span,
|
||||
Width,
|
||||
FormatArgPositionKind::Number,
|
||||
))),
|
||||
parse::CountIsStar(_) => unreachable!(),
|
||||
parse::CountImplied => None,
|
||||
};
|
||||
|
||||
template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
argument,
|
||||
span,
|
||||
format_trait,
|
||||
format_options: FormatOptions {
|
||||
fill: format.fill,
|
||||
alignment,
|
||||
sign: format.sign.map(|s| match s {
|
||||
parse::Sign::Plus => FormatSign::Plus,
|
||||
parse::Sign::Minus => FormatSign::Minus,
|
||||
}),
|
||||
alternate: format.alternate,
|
||||
zero_pad: format.zero_pad,
|
||||
debug_hex: format.debug_hex.map(|s| match s {
|
||||
parse::DebugHex::Lower => FormatDebugHex::Lower,
|
||||
parse::DebugHex::Upper => FormatDebugHex::Upper,
|
||||
}),
|
||||
precision,
|
||||
width,
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !unfinished_literal.is_empty() {
|
||||
template.push(FormatArgsPiece::Literal(unfinished_literal.into_boxed_str()));
|
||||
}
|
||||
|
||||
if !invalid_refs.is_empty() {
|
||||
// FIXME: Diagnose
|
||||
}
|
||||
|
||||
let unused = used
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(_, used)| !used)
|
||||
.map(|(i, _)| {
|
||||
let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
|
||||
(args.explicit_args()[i].expr, named)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !unused.is_empty() {
|
||||
// FIXME: Diagnose
|
||||
}
|
||||
|
||||
FormatArgs {
|
||||
template: template.into_boxed_slice(),
|
||||
arguments: args.finish(),
|
||||
orphans: unused.into_iter().map(TupleExt::head).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct FormatArgumentsCollector {
|
||||
arguments: Vec<FormatArgument>,
|
||||
num_unnamed_args: usize,
|
||||
num_explicit_args: usize,
|
||||
names: Vec<(Name, usize)>,
|
||||
}
|
||||
|
||||
impl FormatArgumentsCollector {
|
||||
pub(crate) fn finish(self) -> FormatArguments {
|
||||
FormatArguments {
|
||||
arguments: self.arguments.into_boxed_slice(),
|
||||
num_unnamed_args: self.num_unnamed_args,
|
||||
num_explicit_args: self.num_explicit_args,
|
||||
names: self.names.into_boxed_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn add(&mut self, arg: FormatArgument) -> usize {
|
||||
let index = self.arguments.len();
|
||||
if let Some(name) = arg.kind.ident() {
|
||||
self.names.push((name.clone(), index));
|
||||
} else if self.names.is_empty() {
|
||||
// Only count the unnamed args before the first named arg.
|
||||
// (Any later ones are errors.)
|
||||
self.num_unnamed_args += 1;
|
||||
}
|
||||
if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
|
||||
// This is an explicit argument.
|
||||
// Make sure that all arguments so far are explicit.
|
||||
assert_eq!(
|
||||
self.num_explicit_args,
|
||||
self.arguments.len(),
|
||||
"captured arguments must be added last"
|
||||
);
|
||||
self.num_explicit_args += 1;
|
||||
}
|
||||
self.arguments.push(arg);
|
||||
index
|
||||
}
|
||||
|
||||
pub fn by_name(&self, name: &Name) -> Option<(usize, &FormatArgument)> {
|
||||
let &(_, i) = self.names.iter().find(|(n, _)| n == name)?;
|
||||
Some((i, &self.arguments[i]))
|
||||
}
|
||||
|
||||
pub fn by_index(&self, i: usize) -> Option<&FormatArgument> {
|
||||
(i < self.num_explicit_args).then(|| &self.arguments[i])
|
||||
}
|
||||
|
||||
pub fn unnamed_args(&self) -> &[FormatArgument] {
|
||||
&self.arguments[..self.num_unnamed_args]
|
||||
}
|
||||
|
||||
pub fn named_args(&self) -> &[FormatArgument] {
|
||||
&self.arguments[self.num_unnamed_args..self.num_explicit_args]
|
||||
}
|
||||
|
||||
pub fn explicit_args(&self) -> &[FormatArgument] {
|
||||
&self.arguments[..self.num_explicit_args]
|
||||
}
|
||||
|
||||
pub fn all_args(&self) -> &[FormatArgument] {
|
||||
&self.arguments[..]
|
||||
}
|
||||
|
||||
pub fn all_args_mut(&mut self) -> &mut Vec<FormatArgument> {
|
||||
&mut self.arguments
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatArgumentKind {
|
||||
pub fn ident(&self) -> Option<&Name> {
|
||||
match self {
|
||||
Self::Normal => None,
|
||||
Self::Named(id) => Some(id),
|
||||
Self::Captured(id) => Some(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
524
src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs
Normal file
524
src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
//! HIR for references to types. Paths in these are not yet resolved. They can
|
||||
//! be directly created from an ast::TypeRef, without further queries.
|
||||
|
||||
use core::fmt;
|
||||
use std::fmt::Write;
|
||||
|
||||
use hir_expand::{
|
||||
db::ExpandDatabase,
|
||||
name::{AsName, Name},
|
||||
AstId,
|
||||
};
|
||||
use intern::Interned;
|
||||
use syntax::ast::{self, HasName, IsString};
|
||||
|
||||
use crate::{
|
||||
builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
|
||||
hir::Literal,
|
||||
lower::LowerCtx,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum Mutability {
|
||||
Shared,
|
||||
Mut,
|
||||
}
|
||||
|
||||
impl Mutability {
|
||||
pub fn from_mutable(mutable: bool) -> Mutability {
|
||||
if mutable {
|
||||
Mutability::Mut
|
||||
} else {
|
||||
Mutability::Shared
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_keyword_for_ref(self) -> &'static str {
|
||||
match self {
|
||||
Mutability::Shared => "",
|
||||
Mutability::Mut => "mut ",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_keyword_for_ptr(self) -> &'static str {
|
||||
match self {
|
||||
Mutability::Shared => "const ",
|
||||
Mutability::Mut => "mut ",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the mutability is [`Mut`].
|
||||
///
|
||||
/// [`Mut`]: Mutability::Mut
|
||||
#[must_use]
|
||||
pub fn is_mut(&self) -> bool {
|
||||
matches!(self, Self::Mut)
|
||||
}
|
||||
|
||||
/// Returns `true` if the mutability is [`Shared`].
|
||||
///
|
||||
/// [`Shared`]: Mutability::Shared
|
||||
#[must_use]
|
||||
pub fn is_shared(&self) -> bool {
|
||||
matches!(self, Self::Shared)
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_raw(&self) -> bool {
|
||||
matches!(self, Self::RawPtr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct TraitRef {
|
||||
pub path: Path,
|
||||
}
|
||||
|
||||
impl TraitRef {
|
||||
/// Converts an `ast::PathType` to a `hir::TraitRef`.
|
||||
pub(crate) fn from_ast(ctx: &LowerCtx<'_>, node: ast::Type) -> Option<Self> {
|
||||
// FIXME: Use `Path::from_src`
|
||||
match node {
|
||||
ast::Type::PathType(path) => {
|
||||
path.path().and_then(|it| ctx.lower_path(it)).map(|path| TraitRef { path })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare ty::Ty
|
||||
///
|
||||
/// Note: Most users of `TypeRef` that end up in the salsa database intern it using
|
||||
/// `Interned<TypeRef>` to save space. But notably, nested `TypeRef`s are not interned, since that
|
||||
/// does not seem to save any noticeable amount of memory.
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum TypeRef {
|
||||
Never,
|
||||
Placeholder,
|
||||
Tuple(Vec<TypeRef>),
|
||||
Path(Path),
|
||||
RawPtr(Box<TypeRef>, Mutability),
|
||||
Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability),
|
||||
// FIXME: This should be Array(Box<TypeRef>, Ast<ConstArg>),
|
||||
Array(Box<TypeRef>, ConstRef),
|
||||
Slice(Box<TypeRef>),
|
||||
/// A fn pointer. Last element of the vector is the return type.
|
||||
Fn(
|
||||
Vec<(Option<Name>, TypeRef)>,
|
||||
bool, /*varargs*/
|
||||
bool, /*is_unsafe*/
|
||||
Option<Interned<str>>, /* abi */
|
||||
),
|
||||
ImplTrait(Vec<Interned<TypeBound>>),
|
||||
DynTrait(Vec<Interned<TypeBound>>),
|
||||
Macro(AstId<ast::MacroCall>),
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct LifetimeRef {
|
||||
pub name: Name,
|
||||
}
|
||||
|
||||
impl LifetimeRef {
|
||||
pub(crate) fn new_name(name: Name) -> Self {
|
||||
LifetimeRef { name }
|
||||
}
|
||||
|
||||
pub(crate) fn new(lifetime: &ast::Lifetime) -> Self {
|
||||
LifetimeRef { name: Name::new_lifetime(lifetime) }
|
||||
}
|
||||
|
||||
pub fn missing() -> LifetimeRef {
|
||||
LifetimeRef { name: Name::missing() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum TypeBound {
|
||||
Path(Path, TraitBoundModifier),
|
||||
ForLifetime(Box<[Name]>, Path),
|
||||
Lifetime(LifetimeRef),
|
||||
Error,
|
||||
}
|
||||
|
||||
/// A modifier on a bound, currently this is only used for `?Sized`, where the
|
||||
/// modifier is `Maybe`.
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum TraitBoundModifier {
|
||||
None,
|
||||
Maybe,
|
||||
}
|
||||
|
||||
impl TypeRef {
|
||||
/// Converts an `ast::TypeRef` to a `hir::TypeRef`.
|
||||
pub fn from_ast(ctx: &LowerCtx<'_>, node: ast::Type) -> Self {
|
||||
match node {
|
||||
ast::Type::ParenType(inner) => TypeRef::from_ast_opt(ctx, inner.ty()),
|
||||
ast::Type::TupleType(inner) => {
|
||||
TypeRef::Tuple(inner.fields().map(|it| TypeRef::from_ast(ctx, it)).collect())
|
||||
}
|
||||
ast::Type::NeverType(..) => TypeRef::Never,
|
||||
ast::Type::PathType(inner) => {
|
||||
// FIXME: Use `Path::from_src`
|
||||
inner
|
||||
.path()
|
||||
.and_then(|it| ctx.lower_path(it))
|
||||
.map(TypeRef::Path)
|
||||
.unwrap_or(TypeRef::Error)
|
||||
}
|
||||
ast::Type::PtrType(inner) => {
|
||||
let inner_ty = TypeRef::from_ast_opt(ctx, inner.ty());
|
||||
let mutability = Mutability::from_mutable(inner.mut_token().is_some());
|
||||
TypeRef::RawPtr(Box::new(inner_ty), mutability)
|
||||
}
|
||||
ast::Type::ArrayType(inner) => {
|
||||
let len = ConstRef::from_const_arg(ctx, inner.const_arg());
|
||||
TypeRef::Array(Box::new(TypeRef::from_ast_opt(ctx, inner.ty())), len)
|
||||
}
|
||||
ast::Type::SliceType(inner) => {
|
||||
TypeRef::Slice(Box::new(TypeRef::from_ast_opt(ctx, inner.ty())))
|
||||
}
|
||||
ast::Type::RefType(inner) => {
|
||||
let inner_ty = TypeRef::from_ast_opt(ctx, inner.ty());
|
||||
let lifetime = inner.lifetime().map(|lt| LifetimeRef::new(<));
|
||||
let mutability = Mutability::from_mutable(inner.mut_token().is_some());
|
||||
TypeRef::Reference(Box::new(inner_ty), lifetime, mutability)
|
||||
}
|
||||
ast::Type::InferType(_inner) => TypeRef::Placeholder,
|
||||
ast::Type::FnPtrType(inner) => {
|
||||
let ret_ty = inner
|
||||
.ret_type()
|
||||
.and_then(|rt| rt.ty())
|
||||
.map(|it| TypeRef::from_ast(ctx, it))
|
||||
.unwrap_or_else(|| TypeRef::Tuple(Vec::new()));
|
||||
let mut is_varargs = false;
|
||||
let mut params = if let Some(pl) = inner.param_list() {
|
||||
if let Some(param) = pl.params().last() {
|
||||
is_varargs = param.dotdotdot_token().is_some();
|
||||
}
|
||||
|
||||
pl.params()
|
||||
.map(|it| {
|
||||
let type_ref = TypeRef::from_ast_opt(ctx, it.ty());
|
||||
let name = match it.pat() {
|
||||
Some(ast::Pat::IdentPat(it)) => Some(
|
||||
it.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing),
|
||||
),
|
||||
_ => None,
|
||||
};
|
||||
(name, type_ref)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
fn lower_abi(abi: ast::Abi) -> Interned<str> {
|
||||
match abi.abi_string() {
|
||||
Some(tok) => Interned::new_str(tok.text_without_quotes()),
|
||||
// `extern` default to be `extern "C"`.
|
||||
_ => Interned::new_str("C"),
|
||||
}
|
||||
}
|
||||
|
||||
let abi = inner.abi().map(lower_abi);
|
||||
params.push((None, ret_ty));
|
||||
TypeRef::Fn(params, is_varargs, inner.unsafe_token().is_some(), abi)
|
||||
}
|
||||
// for types are close enough for our purposes to the inner type for now...
|
||||
ast::Type::ForType(inner) => TypeRef::from_ast_opt(ctx, inner.ty()),
|
||||
ast::Type::ImplTraitType(inner) => {
|
||||
TypeRef::ImplTrait(type_bounds_from_ast(ctx, inner.type_bound_list()))
|
||||
}
|
||||
ast::Type::DynTraitType(inner) => {
|
||||
TypeRef::DynTrait(type_bounds_from_ast(ctx, inner.type_bound_list()))
|
||||
}
|
||||
ast::Type::MacroType(mt) => match mt.macro_call() {
|
||||
Some(mc) => TypeRef::Macro(ctx.ast_id(&mc)),
|
||||
None => TypeRef::Error,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_ast_opt(ctx: &LowerCtx<'_>, node: Option<ast::Type>) -> Self {
|
||||
match node {
|
||||
Some(node) => TypeRef::from_ast(ctx, node),
|
||||
None => TypeRef::Error,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unit() -> TypeRef {
|
||||
TypeRef::Tuple(Vec::new())
|
||||
}
|
||||
|
||||
pub fn walk(&self, f: &mut impl FnMut(&TypeRef)) {
|
||||
go(self, f);
|
||||
|
||||
fn go(type_ref: &TypeRef, f: &mut impl FnMut(&TypeRef)) {
|
||||
f(type_ref);
|
||||
match type_ref {
|
||||
TypeRef::Fn(params, _, _, _) => {
|
||||
params.iter().for_each(|(_, param_type)| go(param_type, f))
|
||||
}
|
||||
TypeRef::Tuple(types) => types.iter().for_each(|t| go(t, f)),
|
||||
TypeRef::RawPtr(type_ref, _)
|
||||
| TypeRef::Reference(type_ref, ..)
|
||||
| TypeRef::Array(type_ref, _)
|
||||
| TypeRef::Slice(type_ref) => go(type_ref, f),
|
||||
TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => {
|
||||
for bound in bounds {
|
||||
match bound.as_ref() {
|
||||
TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
|
||||
go_path(path, f)
|
||||
}
|
||||
TypeBound::Lifetime(_) | TypeBound::Error => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeRef::Path(path) => go_path(path, f),
|
||||
TypeRef::Never | TypeRef::Placeholder | TypeRef::Macro(_) | TypeRef::Error => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn go_path(path: &Path, f: &mut impl FnMut(&TypeRef)) {
|
||||
if let Some(type_ref) = path.type_anchor() {
|
||||
go(type_ref, f);
|
||||
}
|
||||
for segment in path.segments().iter() {
|
||||
if let Some(args_and_bindings) = segment.args_and_bindings {
|
||||
for arg in args_and_bindings.args.iter() {
|
||||
match arg {
|
||||
crate::path::GenericArg::Type(type_ref) => {
|
||||
go(type_ref, f);
|
||||
}
|
||||
crate::path::GenericArg::Const(_)
|
||||
| crate::path::GenericArg::Lifetime(_) => {}
|
||||
}
|
||||
}
|
||||
for binding in args_and_bindings.bindings.iter() {
|
||||
if let Some(type_ref) = &binding.type_ref {
|
||||
go(type_ref, f);
|
||||
}
|
||||
for bound in binding.bounds.iter() {
|
||||
match bound.as_ref() {
|
||||
TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
|
||||
go_path(path, f)
|
||||
}
|
||||
TypeBound::Lifetime(_) | TypeBound::Error => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn type_bounds_from_ast(
|
||||
lower_ctx: &LowerCtx<'_>,
|
||||
type_bounds_opt: Option<ast::TypeBoundList>,
|
||||
) -> Vec<Interned<TypeBound>> {
|
||||
if let Some(type_bounds) = type_bounds_opt {
|
||||
type_bounds.bounds().map(|it| Interned::new(TypeBound::from_ast(lower_ctx, it))).collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeBound {
|
||||
pub(crate) fn from_ast(ctx: &LowerCtx<'_>, node: ast::TypeBound) -> Self {
|
||||
let lower_path_type = |path_type: ast::PathType| ctx.lower_path(path_type.path()?);
|
||||
|
||||
match node.kind() {
|
||||
ast::TypeBoundKind::PathType(path_type) => {
|
||||
let m = match node.question_mark_token() {
|
||||
Some(_) => TraitBoundModifier::Maybe,
|
||||
None => TraitBoundModifier::None,
|
||||
};
|
||||
lower_path_type(path_type)
|
||||
.map(|p| TypeBound::Path(p, m))
|
||||
.unwrap_or(TypeBound::Error)
|
||||
}
|
||||
ast::TypeBoundKind::ForType(for_type) => {
|
||||
let lt_refs = match for_type.generic_param_list() {
|
||||
Some(gpl) => gpl
|
||||
.lifetime_params()
|
||||
.flat_map(|lp| lp.lifetime().map(|lt| Name::new_lifetime(<)))
|
||||
.collect(),
|
||||
None => Box::default(),
|
||||
};
|
||||
let path = for_type.ty().and_then(|ty| match ty {
|
||||
ast::Type::PathType(path_type) => lower_path_type(path_type),
|
||||
_ => None,
|
||||
});
|
||||
match path {
|
||||
Some(p) => TypeBound::ForLifetime(lt_refs, p),
|
||||
None => TypeBound::Error,
|
||||
}
|
||||
}
|
||||
ast::TypeBoundKind::Lifetime(lifetime) => {
|
||||
TypeBound::Lifetime(LifetimeRef::new(&lifetime))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_path(&self) -> Option<(&Path, &TraitBoundModifier)> {
|
||||
match self {
|
||||
TypeBound::Path(p, m) => Some((p, m)),
|
||||
TypeBound::ForLifetime(_, p) => Some((p, &TraitBoundModifier::None)),
|
||||
TypeBound::Lifetime(_) | TypeBound::Error => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ConstRef {
|
||||
Scalar(LiteralConstRef),
|
||||
Path(Name),
|
||||
Complex(AstId<ast::ConstArg>),
|
||||
}
|
||||
|
||||
impl ConstRef {
|
||||
pub(crate) fn from_const_arg(lower_ctx: &LowerCtx<'_>, arg: Option<ast::ConstArg>) -> Self {
|
||||
if let Some(arg) = arg {
|
||||
if let Some(expr) = arg.expr() {
|
||||
return Self::from_expr(expr, Some(lower_ctx.ast_id(&arg)));
|
||||
}
|
||||
}
|
||||
Self::Scalar(LiteralConstRef::Unknown)
|
||||
}
|
||||
|
||||
pub(crate) fn from_const_param(
|
||||
lower_ctx: &LowerCtx<'_>,
|
||||
param: &ast::ConstParam,
|
||||
) -> Option<Self> {
|
||||
param.default_val().map(|default| Self::from_const_arg(lower_ctx, Some(default)))
|
||||
}
|
||||
|
||||
pub fn display<'a>(&'a self, db: &'a dyn ExpandDatabase) -> impl fmt::Display + 'a {
|
||||
struct Display<'a>(&'a dyn ExpandDatabase, &'a ConstRef);
|
||||
impl fmt::Display for Display<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.1 {
|
||||
ConstRef::Scalar(s) => s.fmt(f),
|
||||
ConstRef::Path(n) => n.display(self.0).fmt(f),
|
||||
ConstRef::Complex(_) => f.write_str("{const}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Display(db, self)
|
||||
}
|
||||
|
||||
// We special case literals and single identifiers, to speed up things.
|
||||
fn from_expr(expr: ast::Expr, ast_id: Option<AstId<ast::ConstArg>>) -> Self {
|
||||
fn is_path_ident(p: &ast::PathExpr) -> bool {
|
||||
let Some(path) = p.path() else {
|
||||
return false;
|
||||
};
|
||||
if path.coloncolon_token().is_some() {
|
||||
return false;
|
||||
}
|
||||
if let Some(s) = path.segment() {
|
||||
if s.coloncolon_token().is_some() || s.generic_arg_list().is_some() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
match expr {
|
||||
ast::Expr::PathExpr(p) if is_path_ident(&p) => {
|
||||
match p.path().and_then(|it| it.segment()).and_then(|it| it.name_ref()) {
|
||||
Some(it) => Self::Path(it.as_name()),
|
||||
None => Self::Scalar(LiteralConstRef::Unknown),
|
||||
}
|
||||
}
|
||||
ast::Expr::Literal(literal) => Self::Scalar(match literal.kind() {
|
||||
ast::LiteralKind::IntNumber(num) => {
|
||||
num.value().map(LiteralConstRef::UInt).unwrap_or(LiteralConstRef::Unknown)
|
||||
}
|
||||
ast::LiteralKind::Char(c) => {
|
||||
c.value().map(LiteralConstRef::Char).unwrap_or(LiteralConstRef::Unknown)
|
||||
}
|
||||
ast::LiteralKind::Bool(f) => LiteralConstRef::Bool(f),
|
||||
_ => LiteralConstRef::Unknown,
|
||||
}),
|
||||
_ => {
|
||||
if let Some(ast_id) = ast_id {
|
||||
Self::Complex(ast_id)
|
||||
} else {
|
||||
Self::Scalar(LiteralConstRef::Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A literal constant value
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum LiteralConstRef {
|
||||
Int(i128),
|
||||
UInt(u128),
|
||||
Bool(bool),
|
||||
Char(char),
|
||||
|
||||
/// Case of an unknown value that rustc might know but we don't
|
||||
// FIXME: this is a hack to get around chalk not being able to represent unevaluatable
|
||||
// constants
|
||||
// https://github.com/rust-lang/rust-analyzer/pull/8813#issuecomment-840679177
|
||||
// https://rust-lang.zulipchat.com/#narrow/stream/144729-wg-traits/topic/Handling.20non.20evaluatable.20constants'.20equality/near/238386348
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl LiteralConstRef {
|
||||
pub fn builtin_type(&self) -> BuiltinType {
|
||||
match self {
|
||||
LiteralConstRef::UInt(_) | LiteralConstRef::Unknown => {
|
||||
BuiltinType::Uint(BuiltinUint::U128)
|
||||
}
|
||||
LiteralConstRef::Int(_) => BuiltinType::Int(BuiltinInt::I128),
|
||||
LiteralConstRef::Char(_) => BuiltinType::Char,
|
||||
LiteralConstRef::Bool(_) => BuiltinType::Bool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Literal> for LiteralConstRef {
|
||||
fn from(literal: Literal) -> Self {
|
||||
match literal {
|
||||
Literal::Char(c) => Self::Char(c),
|
||||
Literal::Bool(flag) => Self::Bool(flag),
|
||||
Literal::Int(num, _) => Self::Int(num),
|
||||
Literal::Uint(num, _) => Self::UInt(num),
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LiteralConstRef {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
LiteralConstRef::Int(num) => num.fmt(f),
|
||||
LiteralConstRef::UInt(num) => num.fmt(f),
|
||||
LiteralConstRef::Bool(flag) => flag.fmt(f),
|
||||
LiteralConstRef::Char(c) => write!(f, "'{c}'"),
|
||||
LiteralConstRef::Unknown => f.write_char('_'),
|
||||
}
|
||||
}
|
||||
}
|
||||
1035
src/tools/rust-analyzer/crates/hir-def/src/import_map.rs
Normal file
1035
src/tools/rust-analyzer/crates/hir-def/src/import_map.rs
Normal file
File diff suppressed because it is too large
Load diff
823
src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs
Normal file
823
src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs
Normal file
|
|
@ -0,0 +1,823 @@
|
|||
//! Describes items defined or visible (ie, imported) in a certain scope.
|
||||
//! This is shared between modules and blocks.
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use base_db::CrateId;
|
||||
use hir_expand::{attrs::AttrId, db::ExpandDatabase, name::Name, AstId, MacroCallId};
|
||||
use itertools::Itertools;
|
||||
use la_arena::Idx;
|
||||
use once_cell::sync::Lazy;
|
||||
use profile::Count;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use stdx::format_to;
|
||||
use syntax::ast;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
per_ns::PerNs,
|
||||
visibility::{Visibility, VisibilityExplicitness},
|
||||
AdtId, BuiltinType, ConstId, ExternCrateId, HasModule, ImplId, LocalModuleId, Lookup, MacroId,
|
||||
ModuleDefId, ModuleId, TraitId, UseId,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PerNsGlobImports {
|
||||
types: FxHashSet<(LocalModuleId, Name)>,
|
||||
values: FxHashSet<(LocalModuleId, Name)>,
|
||||
macros: FxHashSet<(LocalModuleId, Name)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ImportOrExternCrate {
|
||||
Import(ImportId),
|
||||
ExternCrate(ExternCrateId),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum ImportType {
|
||||
Import(ImportId),
|
||||
Glob(UseId),
|
||||
ExternCrate(ExternCrateId),
|
||||
}
|
||||
|
||||
impl ImportOrExternCrate {
|
||||
pub fn into_import(self) -> Option<ImportId> {
|
||||
match self {
|
||||
ImportOrExternCrate::Import(it) => Some(it),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ImportOrDef {
|
||||
Import(ImportId),
|
||||
ExternCrate(ExternCrateId),
|
||||
Def(ModuleDefId),
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub struct ImportId {
|
||||
pub import: UseId,
|
||||
pub idx: Idx<ast::UseTree>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct ItemScope {
|
||||
_c: Count<Self>,
|
||||
|
||||
/// Defs visible in this scope. This includes `declarations`, but also
|
||||
/// imports. The imports belong to this module and can be resolved by using them on
|
||||
/// the `use_imports_*` fields.
|
||||
types: FxHashMap<Name, (ModuleDefId, Visibility, Option<ImportOrExternCrate>)>,
|
||||
values: FxHashMap<Name, (ModuleDefId, Visibility, Option<ImportId>)>,
|
||||
macros: FxHashMap<Name, (MacroId, Visibility, Option<ImportId>)>,
|
||||
unresolved: FxHashSet<Name>,
|
||||
|
||||
/// The defs declared in this scope. Each def has a single scope where it is
|
||||
/// declared.
|
||||
declarations: Vec<ModuleDefId>,
|
||||
|
||||
impls: Vec<ImplId>,
|
||||
unnamed_consts: Vec<ConstId>,
|
||||
/// Traits imported via `use Trait as _;`.
|
||||
unnamed_trait_imports: FxHashMap<TraitId, (Visibility, Option<ImportId>)>,
|
||||
|
||||
// the resolutions of the imports of this scope
|
||||
use_imports_types: FxHashMap<ImportOrExternCrate, ImportOrDef>,
|
||||
use_imports_values: FxHashMap<ImportId, ImportOrDef>,
|
||||
use_imports_macros: FxHashMap<ImportId, ImportOrDef>,
|
||||
|
||||
use_decls: Vec<UseId>,
|
||||
extern_crate_decls: Vec<ExternCrateId>,
|
||||
/// Macros visible in current module in legacy textual scope
|
||||
///
|
||||
/// For macros invoked by an unqualified identifier like `bar!()`, `legacy_macros` will be searched in first.
|
||||
/// If it yields no result, then it turns to module scoped `macros`.
|
||||
/// It macros with name qualified with a path like `crate::foo::bar!()`, `legacy_macros` will be skipped,
|
||||
/// and only normal scoped `macros` will be searched in.
|
||||
///
|
||||
/// Note that this automatically inherit macros defined textually before the definition of module itself.
|
||||
///
|
||||
/// Module scoped macros will be inserted into `items` instead of here.
|
||||
// FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will
|
||||
// be all resolved to the last one defined if shadowing happens.
|
||||
legacy_macros: FxHashMap<Name, SmallVec<[MacroId; 1]>>,
|
||||
/// The attribute macro invocations in this scope.
|
||||
attr_macros: FxHashMap<AstId<ast::Item>, MacroCallId>,
|
||||
/// The macro invocations in this scope.
|
||||
macro_invocations: FxHashMap<AstId<ast::MacroCall>, MacroCallId>,
|
||||
/// The derive macro invocations in this scope, keyed by the owner item over the actual derive attributes
|
||||
/// paired with the derive macro invocations for the specific attribute.
|
||||
derive_macros: FxHashMap<AstId<ast::Adt>, SmallVec<[DeriveMacroInvocation; 1]>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct DeriveMacroInvocation {
|
||||
attr_id: AttrId,
|
||||
/// The `#[derive]` call
|
||||
attr_call_id: MacroCallId,
|
||||
derive_call_ids: SmallVec<[Option<MacroCallId>; 1]>,
|
||||
}
|
||||
|
||||
pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| {
|
||||
BuiltinType::ALL
|
||||
.iter()
|
||||
.map(|(name, ty)| (name.clone(), PerNs::types((*ty).into(), Visibility::Public, None)))
|
||||
.collect()
|
||||
});
|
||||
|
||||
/// Shadow mode for builtin type which can be shadowed by module.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum BuiltinShadowMode {
|
||||
/// Prefer user-defined modules (or other types) over builtins.
|
||||
Module,
|
||||
/// Prefer builtins over user-defined modules (but not other types).
|
||||
Other,
|
||||
}
|
||||
|
||||
/// Legacy macros can only be accessed through special methods like `get_legacy_macros`.
|
||||
/// Other methods will only resolve values, types and module scoped macros only.
|
||||
impl ItemScope {
|
||||
pub fn entries(&self) -> impl Iterator<Item = (&Name, PerNs)> + '_ {
|
||||
// FIXME: shadowing
|
||||
self.types
|
||||
.keys()
|
||||
.chain(self.values.keys())
|
||||
.chain(self.macros.keys())
|
||||
.chain(self.unresolved.iter())
|
||||
.sorted()
|
||||
.dedup()
|
||||
.map(move |name| (name, self.get(name)))
|
||||
}
|
||||
|
||||
pub fn imports(&self) -> impl Iterator<Item = ImportId> + '_ {
|
||||
self.use_imports_types
|
||||
.keys()
|
||||
.copied()
|
||||
.filter_map(ImportOrExternCrate::into_import)
|
||||
.chain(self.use_imports_values.keys().copied())
|
||||
.chain(self.use_imports_macros.keys().copied())
|
||||
.sorted()
|
||||
.dedup()
|
||||
}
|
||||
|
||||
pub fn fully_resolve_import(&self, db: &dyn DefDatabase, mut import: ImportId) -> PerNs {
|
||||
let mut res = PerNs::none();
|
||||
|
||||
let mut def_map;
|
||||
let mut scope = self;
|
||||
while let Some(&m) = scope.use_imports_macros.get(&import) {
|
||||
match m {
|
||||
ImportOrDef::Import(i) => {
|
||||
let module_id = i.import.lookup(db).container;
|
||||
def_map = module_id.def_map(db);
|
||||
scope = &def_map[module_id.local_id].scope;
|
||||
import = i;
|
||||
}
|
||||
ImportOrDef::Def(ModuleDefId::MacroId(def)) => {
|
||||
res.macros = Some((def, Visibility::Public, None));
|
||||
break;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
let mut scope = self;
|
||||
while let Some(&m) = scope.use_imports_types.get(&ImportOrExternCrate::Import(import)) {
|
||||
match m {
|
||||
ImportOrDef::Import(i) => {
|
||||
let module_id = i.import.lookup(db).container;
|
||||
def_map = module_id.def_map(db);
|
||||
scope = &def_map[module_id.local_id].scope;
|
||||
import = i;
|
||||
}
|
||||
ImportOrDef::Def(def) => {
|
||||
res.types = Some((def, Visibility::Public, None));
|
||||
break;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
let mut scope = self;
|
||||
while let Some(&m) = scope.use_imports_values.get(&import) {
|
||||
match m {
|
||||
ImportOrDef::Import(i) => {
|
||||
let module_id = i.import.lookup(db).container;
|
||||
def_map = module_id.def_map(db);
|
||||
scope = &def_map[module_id.local_id].scope;
|
||||
import = i;
|
||||
}
|
||||
ImportOrDef::Def(def) => {
|
||||
res.values = Some((def, Visibility::Public, None));
|
||||
break;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn declarations(&self) -> impl Iterator<Item = ModuleDefId> + '_ {
|
||||
self.declarations.iter().copied()
|
||||
}
|
||||
|
||||
pub fn extern_crate_decls(&self) -> impl ExactSizeIterator<Item = ExternCrateId> + '_ {
|
||||
self.extern_crate_decls.iter().copied()
|
||||
}
|
||||
|
||||
pub fn use_decls(&self) -> impl ExactSizeIterator<Item = UseId> + '_ {
|
||||
self.use_decls.iter().copied()
|
||||
}
|
||||
|
||||
pub fn impls(&self) -> impl ExactSizeIterator<Item = ImplId> + '_ {
|
||||
self.impls.iter().copied()
|
||||
}
|
||||
|
||||
pub(crate) fn modules_in_scope(&self) -> impl Iterator<Item = (ModuleId, Visibility)> + '_ {
|
||||
self.types.values().copied().filter_map(|(def, vis, _)| match def {
|
||||
ModuleDefId::ModuleId(module) => Some((module, vis)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unnamed_consts(&self) -> impl Iterator<Item = ConstId> + '_ {
|
||||
self.unnamed_consts.iter().copied()
|
||||
}
|
||||
|
||||
/// Iterate over all module scoped macros
|
||||
pub(crate) fn macros(&self) -> impl Iterator<Item = (&Name, MacroId)> + '_ {
|
||||
self.entries().filter_map(|(name, def)| def.take_macros().map(|macro_| (name, macro_)))
|
||||
}
|
||||
|
||||
/// Iterate over all legacy textual scoped macros visible at the end of the module
|
||||
pub fn legacy_macros(&self) -> impl Iterator<Item = (&Name, &[MacroId])> + '_ {
|
||||
self.legacy_macros.iter().map(|(name, def)| (name, &**def))
|
||||
}
|
||||
|
||||
/// Get a name from current module scope, legacy macros are not included
|
||||
pub(crate) fn get(&self, name: &Name) -> PerNs {
|
||||
PerNs {
|
||||
types: self.types.get(name).copied(),
|
||||
values: self.values.get(name).copied(),
|
||||
macros: self.macros.get(name).copied(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn type_(&self, name: &Name) -> Option<(ModuleDefId, Visibility)> {
|
||||
self.types.get(name).copied().map(|(a, b, _)| (a, b))
|
||||
}
|
||||
|
||||
/// XXX: this is O(N) rather than O(1), try to not introduce new usages.
|
||||
pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility, /*declared*/ bool)> {
|
||||
match item {
|
||||
ItemInNs::Macros(def) => self.macros.iter().find_map(|(name, &(other_def, vis, i))| {
|
||||
(other_def == def).then_some((name, vis, i.is_none()))
|
||||
}),
|
||||
ItemInNs::Types(def) => self.types.iter().find_map(|(name, &(other_def, vis, i))| {
|
||||
(other_def == def).then_some((name, vis, i.is_none()))
|
||||
}),
|
||||
ItemInNs::Values(def) => self.values.iter().find_map(|(name, &(other_def, vis, i))| {
|
||||
(other_def == def).then_some((name, vis, i.is_none()))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// XXX: this is O(N) rather than O(1), try to not introduce new usages.
|
||||
pub(crate) fn names_of<T>(
|
||||
&self,
|
||||
item: ItemInNs,
|
||||
mut cb: impl FnMut(&Name, Visibility, bool) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
match item {
|
||||
ItemInNs::Macros(def) => self
|
||||
.macros
|
||||
.iter()
|
||||
.filter_map(|(name, &(other_def, vis, i))| {
|
||||
(other_def == def).then_some((name, vis, i.is_none()))
|
||||
})
|
||||
.find_map(|(a, b, c)| cb(a, b, c)),
|
||||
ItemInNs::Types(def) => self
|
||||
.types
|
||||
.iter()
|
||||
.filter_map(|(name, &(other_def, vis, i))| {
|
||||
(other_def == def).then_some((name, vis, i.is_none()))
|
||||
})
|
||||
.find_map(|(a, b, c)| cb(a, b, c)),
|
||||
ItemInNs::Values(def) => self
|
||||
.values
|
||||
.iter()
|
||||
.filter_map(|(name, &(other_def, vis, i))| {
|
||||
(other_def == def).then_some((name, vis, i.is_none()))
|
||||
})
|
||||
.find_map(|(a, b, c)| cb(a, b, c)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn traits(&self) -> impl Iterator<Item = TraitId> + '_ {
|
||||
self.types
|
||||
.values()
|
||||
.filter_map(|&(def, _, _)| match def {
|
||||
ModuleDefId::TraitId(t) => Some(t),
|
||||
_ => None,
|
||||
})
|
||||
.chain(self.unnamed_trait_imports.keys().copied())
|
||||
}
|
||||
|
||||
pub(crate) fn resolutions(&self) -> impl Iterator<Item = (Option<Name>, PerNs)> + '_ {
|
||||
self.entries().map(|(name, res)| (Some(name.clone()), res)).chain(
|
||||
self.unnamed_trait_imports.iter().map(|(tr, (vis, i))| {
|
||||
(
|
||||
None,
|
||||
PerNs::types(
|
||||
ModuleDefId::TraitId(*tr),
|
||||
*vis,
|
||||
i.map(ImportOrExternCrate::Import),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn macro_invoc(&self, call: AstId<ast::MacroCall>) -> Option<MacroCallId> {
|
||||
self.macro_invocations.get(&call).copied()
|
||||
}
|
||||
|
||||
pub(crate) fn iter_macro_invoc(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (&AstId<ast::MacroCall>, &MacroCallId)> {
|
||||
self.macro_invocations.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemScope {
|
||||
pub(crate) fn declare(&mut self, def: ModuleDefId) {
|
||||
self.declarations.push(def)
|
||||
}
|
||||
|
||||
pub(crate) fn get_legacy_macro(&self, name: &Name) -> Option<&[MacroId]> {
|
||||
self.legacy_macros.get(name).map(|it| &**it)
|
||||
}
|
||||
|
||||
pub(crate) fn define_impl(&mut self, imp: ImplId) {
|
||||
self.impls.push(imp);
|
||||
}
|
||||
|
||||
pub(crate) fn define_extern_crate_decl(&mut self, extern_crate: ExternCrateId) {
|
||||
self.extern_crate_decls.push(extern_crate);
|
||||
}
|
||||
|
||||
pub(crate) fn define_unnamed_const(&mut self, konst: ConstId) {
|
||||
self.unnamed_consts.push(konst);
|
||||
}
|
||||
|
||||
pub(crate) fn define_legacy_macro(&mut self, name: Name, mac: MacroId) {
|
||||
self.legacy_macros.entry(name).or_default().push(mac);
|
||||
}
|
||||
|
||||
pub(crate) fn add_attr_macro_invoc(&mut self, item: AstId<ast::Item>, call: MacroCallId) {
|
||||
self.attr_macros.insert(item, call);
|
||||
}
|
||||
|
||||
pub(crate) fn add_macro_invoc(&mut self, call: AstId<ast::MacroCall>, call_id: MacroCallId) {
|
||||
self.macro_invocations.insert(call, call_id);
|
||||
}
|
||||
|
||||
pub(crate) fn attr_macro_invocs(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ {
|
||||
self.attr_macros.iter().map(|(k, v)| (*k, *v))
|
||||
}
|
||||
|
||||
pub(crate) fn set_derive_macro_invoc(
|
||||
&mut self,
|
||||
adt: AstId<ast::Adt>,
|
||||
call: MacroCallId,
|
||||
id: AttrId,
|
||||
idx: usize,
|
||||
) {
|
||||
if let Some(derives) = self.derive_macros.get_mut(&adt) {
|
||||
if let Some(DeriveMacroInvocation { derive_call_ids, .. }) =
|
||||
derives.iter_mut().find(|&&mut DeriveMacroInvocation { attr_id, .. }| id == attr_id)
|
||||
{
|
||||
derive_call_ids[idx] = Some(call);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// We are required to set this up front as derive invocation recording happens out of order
|
||||
/// due to the fixed pointer iteration loop being able to record some derives later than others
|
||||
/// independent of their indices.
|
||||
pub(crate) fn init_derive_attribute(
|
||||
&mut self,
|
||||
adt: AstId<ast::Adt>,
|
||||
attr_id: AttrId,
|
||||
attr_call_id: MacroCallId,
|
||||
len: usize,
|
||||
) {
|
||||
self.derive_macros.entry(adt).or_default().push(DeriveMacroInvocation {
|
||||
attr_id,
|
||||
attr_call_id,
|
||||
derive_call_ids: smallvec![None; len],
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn derive_macro_invocs(
|
||||
&self,
|
||||
) -> impl Iterator<
|
||||
Item = (
|
||||
AstId<ast::Adt>,
|
||||
impl Iterator<Item = (AttrId, MacroCallId, &[Option<MacroCallId>])>,
|
||||
),
|
||||
> + '_ {
|
||||
self.derive_macros.iter().map(|(k, v)| {
|
||||
(
|
||||
*k,
|
||||
v.iter().map(|DeriveMacroInvocation { attr_id, attr_call_id, derive_call_ids }| {
|
||||
(*attr_id, *attr_call_id, &**derive_call_ids)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn derive_macro_invoc(
|
||||
&self,
|
||||
ast_id: AstId<ast::Adt>,
|
||||
attr_id: AttrId,
|
||||
) -> Option<MacroCallId> {
|
||||
Some(self.derive_macros.get(&ast_id)?.iter().find(|it| it.attr_id == attr_id)?.attr_call_id)
|
||||
}
|
||||
|
||||
// FIXME: This is only used in collection, we should move the relevant parts of it out of ItemScope
|
||||
pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> {
|
||||
self.unnamed_trait_imports.get(&tr).copied().map(|(a, _)| a)
|
||||
}
|
||||
|
||||
pub(crate) fn push_unnamed_trait(&mut self, tr: TraitId, vis: Visibility) {
|
||||
// FIXME: import
|
||||
self.unnamed_trait_imports.insert(tr, (vis, None));
|
||||
}
|
||||
|
||||
pub(crate) fn push_res_with_import(
|
||||
&mut self,
|
||||
glob_imports: &mut PerNsGlobImports,
|
||||
lookup: (LocalModuleId, Name),
|
||||
def: PerNs,
|
||||
import: Option<ImportType>,
|
||||
) -> bool {
|
||||
let mut changed = false;
|
||||
|
||||
// FIXME: Document and simplify this
|
||||
|
||||
if let Some(mut fld) = def.types {
|
||||
let existing = self.types.entry(lookup.1.clone());
|
||||
match existing {
|
||||
Entry::Vacant(entry) => {
|
||||
match import {
|
||||
Some(ImportType::Glob(_)) => {
|
||||
glob_imports.types.insert(lookup.clone());
|
||||
}
|
||||
_ => _ = glob_imports.types.remove(&lookup),
|
||||
}
|
||||
let import = match import {
|
||||
Some(ImportType::ExternCrate(extern_crate)) => {
|
||||
Some(ImportOrExternCrate::ExternCrate(extern_crate))
|
||||
}
|
||||
Some(ImportType::Import(import)) => {
|
||||
Some(ImportOrExternCrate::Import(import))
|
||||
}
|
||||
None | Some(ImportType::Glob(_)) => None,
|
||||
};
|
||||
let prev = std::mem::replace(&mut fld.2, import);
|
||||
if let Some(import) = import {
|
||||
self.use_imports_types.insert(
|
||||
import,
|
||||
match prev {
|
||||
Some(ImportOrExternCrate::Import(import)) => {
|
||||
ImportOrDef::Import(import)
|
||||
}
|
||||
Some(ImportOrExternCrate::ExternCrate(import)) => {
|
||||
ImportOrDef::ExternCrate(import)
|
||||
}
|
||||
None => ImportOrDef::Def(fld.0),
|
||||
},
|
||||
);
|
||||
}
|
||||
entry.insert(fld);
|
||||
changed = true;
|
||||
}
|
||||
Entry::Occupied(mut entry) if !matches!(import, Some(ImportType::Glob(..))) => {
|
||||
if glob_imports.types.remove(&lookup) {
|
||||
let import = match import {
|
||||
Some(ImportType::ExternCrate(extern_crate)) => {
|
||||
Some(ImportOrExternCrate::ExternCrate(extern_crate))
|
||||
}
|
||||
Some(ImportType::Import(import)) => {
|
||||
Some(ImportOrExternCrate::Import(import))
|
||||
}
|
||||
None | Some(ImportType::Glob(_)) => None,
|
||||
};
|
||||
let prev = std::mem::replace(&mut fld.2, import);
|
||||
if let Some(import) = import {
|
||||
self.use_imports_types.insert(
|
||||
import,
|
||||
match prev {
|
||||
Some(ImportOrExternCrate::Import(import)) => {
|
||||
ImportOrDef::Import(import)
|
||||
}
|
||||
Some(ImportOrExternCrate::ExternCrate(import)) => {
|
||||
ImportOrDef::ExternCrate(import)
|
||||
}
|
||||
None => ImportOrDef::Def(fld.0),
|
||||
},
|
||||
);
|
||||
}
|
||||
cov_mark::hit!(import_shadowed);
|
||||
entry.insert(fld);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut fld) = def.values {
|
||||
let existing = self.values.entry(lookup.1.clone());
|
||||
match existing {
|
||||
Entry::Vacant(entry) => {
|
||||
match import {
|
||||
Some(ImportType::Glob(_)) => {
|
||||
glob_imports.values.insert(lookup.clone());
|
||||
}
|
||||
_ => _ = glob_imports.values.remove(&lookup),
|
||||
}
|
||||
let import = match import {
|
||||
Some(ImportType::Import(import)) => Some(import),
|
||||
_ => None,
|
||||
};
|
||||
let prev = std::mem::replace(&mut fld.2, import);
|
||||
if let Some(import) = import {
|
||||
self.use_imports_values.insert(
|
||||
import,
|
||||
match prev {
|
||||
Some(import) => ImportOrDef::Import(import),
|
||||
None => ImportOrDef::Def(fld.0),
|
||||
},
|
||||
);
|
||||
}
|
||||
entry.insert(fld);
|
||||
changed = true;
|
||||
}
|
||||
Entry::Occupied(mut entry) if !matches!(import, Some(ImportType::Glob(..))) => {
|
||||
if glob_imports.values.remove(&lookup) {
|
||||
cov_mark::hit!(import_shadowed);
|
||||
let import = match import {
|
||||
Some(ImportType::Import(import)) => Some(import),
|
||||
_ => None,
|
||||
};
|
||||
let prev = std::mem::replace(&mut fld.2, import);
|
||||
if let Some(import) = import {
|
||||
self.use_imports_values.insert(
|
||||
import,
|
||||
match prev {
|
||||
Some(import) => ImportOrDef::Import(import),
|
||||
None => ImportOrDef::Def(fld.0),
|
||||
},
|
||||
);
|
||||
}
|
||||
entry.insert(fld);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut fld) = def.macros {
|
||||
let existing = self.macros.entry(lookup.1.clone());
|
||||
match existing {
|
||||
Entry::Vacant(entry) => {
|
||||
match import {
|
||||
Some(ImportType::Glob(_)) => {
|
||||
glob_imports.macros.insert(lookup.clone());
|
||||
}
|
||||
_ => _ = glob_imports.macros.remove(&lookup),
|
||||
}
|
||||
let import = match import {
|
||||
Some(ImportType::Import(import)) => Some(import),
|
||||
_ => None,
|
||||
};
|
||||
let prev = std::mem::replace(&mut fld.2, import);
|
||||
if let Some(import) = import {
|
||||
self.use_imports_macros.insert(
|
||||
import,
|
||||
match prev {
|
||||
Some(import) => ImportOrDef::Import(import),
|
||||
None => ImportOrDef::Def(fld.0.into()),
|
||||
},
|
||||
);
|
||||
}
|
||||
entry.insert(fld);
|
||||
changed = true;
|
||||
}
|
||||
Entry::Occupied(mut entry) if !matches!(import, Some(ImportType::Glob(..))) => {
|
||||
if glob_imports.macros.remove(&lookup) {
|
||||
cov_mark::hit!(import_shadowed);
|
||||
let import = match import {
|
||||
Some(ImportType::Import(import)) => Some(import),
|
||||
_ => None,
|
||||
};
|
||||
let prev = std::mem::replace(&mut fld.2, import);
|
||||
if let Some(import) = import {
|
||||
self.use_imports_macros.insert(
|
||||
import,
|
||||
match prev {
|
||||
Some(import) => ImportOrDef::Import(import),
|
||||
None => ImportOrDef::Def(fld.0.into()),
|
||||
},
|
||||
);
|
||||
}
|
||||
entry.insert(fld);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if def.is_none() && self.unresolved.insert(lookup.1) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
/// Marks everything that is not a procedural macro as private to `this_module`.
|
||||
pub(crate) fn censor_non_proc_macros(&mut self, this_module: ModuleId) {
|
||||
self.types
|
||||
.values_mut()
|
||||
.map(|(_, vis, _)| vis)
|
||||
.chain(self.values.values_mut().map(|(_, vis, _)| vis))
|
||||
.chain(self.unnamed_trait_imports.values_mut().map(|(vis, _)| vis))
|
||||
.for_each(|vis| {
|
||||
*vis = Visibility::Module(this_module, VisibilityExplicitness::Implicit)
|
||||
});
|
||||
|
||||
for (mac, vis, import) in self.macros.values_mut() {
|
||||
if matches!(mac, MacroId::ProcMacroId(_) if import.is_none()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*vis = Visibility::Module(this_module, VisibilityExplicitness::Implicit);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dump(&self, db: &dyn ExpandDatabase, buf: &mut String) {
|
||||
let mut entries: Vec<_> = self.resolutions().collect();
|
||||
entries.sort_by_key(|(name, _)| name.clone());
|
||||
|
||||
for (name, def) in entries {
|
||||
format_to!(
|
||||
buf,
|
||||
"{}:",
|
||||
name.map_or("_".to_owned(), |name| name.display(db).to_string())
|
||||
);
|
||||
|
||||
if let Some((.., i)) = def.types {
|
||||
buf.push_str(" t");
|
||||
match i {
|
||||
Some(ImportOrExternCrate::Import(_)) => buf.push('i'),
|
||||
Some(ImportOrExternCrate::ExternCrate(_)) => buf.push('e'),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
if let Some((.., i)) = def.values {
|
||||
buf.push_str(" v");
|
||||
if i.is_some() {
|
||||
buf.push('i');
|
||||
}
|
||||
}
|
||||
if let Some((.., i)) = def.macros {
|
||||
buf.push_str(" m");
|
||||
if i.is_some() {
|
||||
buf.push('i');
|
||||
}
|
||||
}
|
||||
if def.is_none() {
|
||||
buf.push_str(" _");
|
||||
}
|
||||
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn shrink_to_fit(&mut self) {
|
||||
// Exhaustive match to require handling new fields.
|
||||
let Self {
|
||||
_c: _,
|
||||
types,
|
||||
values,
|
||||
macros,
|
||||
unresolved,
|
||||
declarations,
|
||||
impls,
|
||||
unnamed_consts,
|
||||
unnamed_trait_imports,
|
||||
legacy_macros,
|
||||
attr_macros,
|
||||
derive_macros,
|
||||
extern_crate_decls,
|
||||
use_decls,
|
||||
use_imports_values,
|
||||
use_imports_types,
|
||||
use_imports_macros,
|
||||
macro_invocations,
|
||||
} = self;
|
||||
types.shrink_to_fit();
|
||||
values.shrink_to_fit();
|
||||
macros.shrink_to_fit();
|
||||
use_imports_types.shrink_to_fit();
|
||||
use_imports_values.shrink_to_fit();
|
||||
use_imports_macros.shrink_to_fit();
|
||||
unresolved.shrink_to_fit();
|
||||
declarations.shrink_to_fit();
|
||||
impls.shrink_to_fit();
|
||||
unnamed_consts.shrink_to_fit();
|
||||
unnamed_trait_imports.shrink_to_fit();
|
||||
legacy_macros.shrink_to_fit();
|
||||
attr_macros.shrink_to_fit();
|
||||
derive_macros.shrink_to_fit();
|
||||
extern_crate_decls.shrink_to_fit();
|
||||
use_decls.shrink_to_fit();
|
||||
macro_invocations.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
impl PerNs {
|
||||
pub(crate) fn from_def(
|
||||
def: ModuleDefId,
|
||||
v: Visibility,
|
||||
has_constructor: bool,
|
||||
import: Option<ImportOrExternCrate>,
|
||||
) -> PerNs {
|
||||
match def {
|
||||
ModuleDefId::ModuleId(_) => PerNs::types(def, v, import),
|
||||
ModuleDefId::FunctionId(_) => {
|
||||
PerNs::values(def, v, import.and_then(ImportOrExternCrate::into_import))
|
||||
}
|
||||
ModuleDefId::AdtId(adt) => match adt {
|
||||
AdtId::UnionId(_) => PerNs::types(def, v, import),
|
||||
AdtId::EnumId(_) => PerNs::types(def, v, import),
|
||||
AdtId::StructId(_) => {
|
||||
if has_constructor {
|
||||
PerNs::both(def, def, v, import)
|
||||
} else {
|
||||
PerNs::types(def, v, import)
|
||||
}
|
||||
}
|
||||
},
|
||||
ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v, import),
|
||||
ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => {
|
||||
PerNs::values(def, v, import.and_then(ImportOrExternCrate::into_import))
|
||||
}
|
||||
ModuleDefId::TraitId(_) => PerNs::types(def, v, import),
|
||||
ModuleDefId::TraitAliasId(_) => PerNs::types(def, v, import),
|
||||
ModuleDefId::TypeAliasId(_) => PerNs::types(def, v, import),
|
||||
ModuleDefId::BuiltinType(_) => PerNs::types(def, v, import),
|
||||
ModuleDefId::MacroId(mac) => {
|
||||
PerNs::macros(mac, v, import.and_then(ImportOrExternCrate::into_import))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum ItemInNs {
|
||||
Types(ModuleDefId),
|
||||
Values(ModuleDefId),
|
||||
Macros(MacroId),
|
||||
}
|
||||
|
||||
impl ItemInNs {
|
||||
pub fn as_module_def_id(self) -> Option<ModuleDefId> {
|
||||
match self {
|
||||
ItemInNs::Types(id) | ItemInNs::Values(id) => Some(id),
|
||||
ItemInNs::Macros(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the crate defining this item (or `None` if `self` is built-in).
|
||||
pub fn krate(&self, db: &dyn DefDatabase) -> Option<CrateId> {
|
||||
match self {
|
||||
ItemInNs::Types(id) | ItemInNs::Values(id) => id.module(db).map(|m| m.krate),
|
||||
ItemInNs::Macros(id) => Some(id.module(db).krate),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn module(&self, db: &dyn DefDatabase) -> Option<ModuleId> {
|
||||
match self {
|
||||
ItemInNs::Types(id) | ItemInNs::Values(id) => id.module(db),
|
||||
ItemInNs::Macros(id) => Some(id.module(db)),
|
||||
}
|
||||
}
|
||||
}
|
||||
1057
src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs
Normal file
1057
src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs
Normal file
File diff suppressed because it is too large
Load diff
829
src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs
Normal file
829
src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs
Normal file
|
|
@ -0,0 +1,829 @@
|
|||
//! AST -> `ItemTree` lowering code.
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use hir_expand::{mod_path::path, name, name::AsName, span_map::SpanMapRef, HirFileId};
|
||||
use la_arena::Arena;
|
||||
use span::{AstIdMap, SyntaxContextId};
|
||||
use syntax::{
|
||||
ast::{self, HasModuleItem, HasName, HasTypeBounds, IsString},
|
||||
AstNode,
|
||||
};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
generics::{GenericParams, GenericParamsCollector, TypeParamData, TypeParamProvenance},
|
||||
item_tree::{
|
||||
AssocItem, AttrOwner, Const, Either, Enum, ExternBlock, ExternCrate, Field, FieldAstId,
|
||||
Fields, FileItemTreeId, FnFlags, Function, GenericArgs, Idx, IdxRange, Impl, ImportAlias,
|
||||
Interned, ItemTree, ItemTreeData, ItemTreeNode, Macro2, MacroCall, MacroRules, Mod,
|
||||
ModItem, ModKind, ModPath, Mutability, Name, Param, ParamAstId, Path, Range, RawAttrs,
|
||||
RawIdx, RawVisibilityId, Static, Struct, StructKind, Trait, TraitAlias, TypeAlias, Union,
|
||||
Use, UseTree, UseTreeKind, Variant,
|
||||
},
|
||||
path::AssociatedTypeBinding,
|
||||
type_ref::{LifetimeRef, TraitBoundModifier, TraitRef, TypeBound, TypeRef},
|
||||
visibility::RawVisibility,
|
||||
LocalLifetimeParamId, LocalTypeOrConstParamId,
|
||||
};
|
||||
|
||||
fn id<N: ItemTreeNode>(index: Idx<N>) -> FileItemTreeId<N> {
|
||||
FileItemTreeId(index)
|
||||
}
|
||||
|
||||
pub(super) struct Ctx<'a> {
|
||||
db: &'a dyn DefDatabase,
|
||||
tree: ItemTree,
|
||||
source_ast_id_map: Arc<AstIdMap>,
|
||||
body_ctx: crate::lower::LowerCtx<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Ctx<'a> {
|
||||
pub(super) fn new(db: &'a dyn DefDatabase, file: HirFileId) -> Self {
|
||||
Self {
|
||||
db,
|
||||
tree: ItemTree::default(),
|
||||
source_ast_id_map: db.ast_id_map(file),
|
||||
body_ctx: crate::lower::LowerCtx::new(db, file),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn span_map(&self) -> SpanMapRef<'_> {
|
||||
self.body_ctx.span_map()
|
||||
}
|
||||
|
||||
pub(super) fn lower_module_items(mut self, item_owner: &dyn HasModuleItem) -> ItemTree {
|
||||
self.tree.top_level =
|
||||
item_owner.items().flat_map(|item| self.lower_mod_item(&item)).collect();
|
||||
self.tree
|
||||
}
|
||||
|
||||
pub(super) fn lower_macro_stmts(mut self, stmts: ast::MacroStmts) -> ItemTree {
|
||||
self.tree.top_level = stmts
|
||||
.statements()
|
||||
.filter_map(|stmt| {
|
||||
match stmt {
|
||||
ast::Stmt::Item(item) => Some(item),
|
||||
// Macro calls can be both items and expressions. The syntax library always treats
|
||||
// them as expressions here, so we undo that.
|
||||
ast::Stmt::ExprStmt(es) => match es.expr()? {
|
||||
ast::Expr::MacroExpr(expr) => {
|
||||
cov_mark::hit!(macro_call_in_macro_stmts_is_added_to_item_tree);
|
||||
Some(expr.macro_call()?.into())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.flat_map(|item| self.lower_mod_item(&item))
|
||||
.collect();
|
||||
|
||||
if let Some(ast::Expr::MacroExpr(tail_macro)) = stmts.expr() {
|
||||
if let Some(call) = tail_macro.macro_call() {
|
||||
cov_mark::hit!(macro_stmt_with_trailing_macro_expr);
|
||||
if let Some(mod_item) = self.lower_mod_item(&call.into()) {
|
||||
self.tree.top_level.push(mod_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.tree
|
||||
}
|
||||
|
||||
pub(super) fn lower_block(mut self, block: &ast::BlockExpr) -> ItemTree {
|
||||
self.tree
|
||||
.attrs
|
||||
.insert(AttrOwner::TopLevel, RawAttrs::new(self.db.upcast(), block, self.span_map()));
|
||||
self.tree.top_level = block
|
||||
.statements()
|
||||
.filter_map(|stmt| match stmt {
|
||||
ast::Stmt::Item(item) => self.lower_mod_item(&item),
|
||||
// Macro calls can be both items and expressions. The syntax library always treats
|
||||
// them as expressions here, so we undo that.
|
||||
ast::Stmt::ExprStmt(es) => match es.expr()? {
|
||||
ast::Expr::MacroExpr(expr) => self.lower_mod_item(&expr.macro_call()?.into()),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
if let Some(ast::Expr::MacroExpr(expr)) = block.tail_expr() {
|
||||
if let Some(call) = expr.macro_call() {
|
||||
if let Some(mod_item) = self.lower_mod_item(&call.into()) {
|
||||
self.tree.top_level.push(mod_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.tree
|
||||
}
|
||||
|
||||
fn data(&mut self) -> &mut ItemTreeData {
|
||||
self.tree.data_mut()
|
||||
}
|
||||
|
||||
fn lower_mod_item(&mut self, item: &ast::Item) -> Option<ModItem> {
|
||||
let mod_item: ModItem = match item {
|
||||
ast::Item::Struct(ast) => self.lower_struct(ast)?.into(),
|
||||
ast::Item::Union(ast) => self.lower_union(ast)?.into(),
|
||||
ast::Item::Enum(ast) => self.lower_enum(ast)?.into(),
|
||||
ast::Item::Fn(ast) => self.lower_function(ast)?.into(),
|
||||
ast::Item::TypeAlias(ast) => self.lower_type_alias(ast)?.into(),
|
||||
ast::Item::Static(ast) => self.lower_static(ast)?.into(),
|
||||
ast::Item::Const(ast) => self.lower_const(ast).into(),
|
||||
ast::Item::Module(ast) => self.lower_module(ast)?.into(),
|
||||
ast::Item::Trait(ast) => self.lower_trait(ast)?.into(),
|
||||
ast::Item::TraitAlias(ast) => self.lower_trait_alias(ast)?.into(),
|
||||
ast::Item::Impl(ast) => self.lower_impl(ast)?.into(),
|
||||
ast::Item::Use(ast) => self.lower_use(ast)?.into(),
|
||||
ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast)?.into(),
|
||||
ast::Item::MacroCall(ast) => self.lower_macro_call(ast)?.into(),
|
||||
ast::Item::MacroRules(ast) => self.lower_macro_rules(ast)?.into(),
|
||||
ast::Item::MacroDef(ast) => self.lower_macro_def(ast)?.into(),
|
||||
ast::Item::ExternBlock(ast) => self.lower_extern_block(ast).into(),
|
||||
};
|
||||
let attrs = RawAttrs::new(self.db.upcast(), item, self.span_map());
|
||||
self.add_attrs(mod_item.into(), attrs);
|
||||
|
||||
Some(mod_item)
|
||||
}
|
||||
|
||||
fn add_attrs(&mut self, item: AttrOwner, attrs: RawAttrs) {
|
||||
match self.tree.attrs.entry(item) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
*entry.get_mut() = entry.get().merge(attrs);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_assoc_item(&mut self, item_node: &ast::AssocItem) -> Option<AssocItem> {
|
||||
let item: AssocItem = match item_node {
|
||||
ast::AssocItem::Fn(ast) => self.lower_function(ast).map(Into::into),
|
||||
ast::AssocItem::TypeAlias(ast) => self.lower_type_alias(ast).map(Into::into),
|
||||
ast::AssocItem::Const(ast) => Some(self.lower_const(ast).into()),
|
||||
ast::AssocItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
|
||||
}?;
|
||||
let attrs = RawAttrs::new(self.db.upcast(), item_node, self.span_map());
|
||||
self.add_attrs(
|
||||
match item {
|
||||
AssocItem::Function(it) => AttrOwner::ModItem(ModItem::Function(it)),
|
||||
AssocItem::TypeAlias(it) => AttrOwner::ModItem(ModItem::TypeAlias(it)),
|
||||
AssocItem::Const(it) => AttrOwner::ModItem(ModItem::Const(it)),
|
||||
AssocItem::MacroCall(it) => AttrOwner::ModItem(ModItem::MacroCall(it)),
|
||||
},
|
||||
attrs,
|
||||
);
|
||||
Some(item)
|
||||
}
|
||||
|
||||
fn lower_struct(&mut self, strukt: &ast::Struct) -> Option<FileItemTreeId<Struct>> {
|
||||
let visibility = self.lower_visibility(strukt);
|
||||
let name = strukt.name()?.as_name();
|
||||
let ast_id = self.source_ast_id_map.ast_id(strukt);
|
||||
let generic_params = self.lower_generic_params(HasImplicitSelf::No, strukt);
|
||||
let fields = self.lower_fields(&strukt.kind());
|
||||
let res = Struct { name, visibility, generic_params, fields, ast_id };
|
||||
Some(id(self.data().structs.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_fields(&mut self, strukt_kind: &ast::StructKind) -> Fields {
|
||||
match strukt_kind {
|
||||
ast::StructKind::Record(it) => {
|
||||
let range = self.lower_record_fields(it);
|
||||
Fields::Record(range)
|
||||
}
|
||||
ast::StructKind::Tuple(it) => {
|
||||
let range = self.lower_tuple_fields(it);
|
||||
Fields::Tuple(range)
|
||||
}
|
||||
ast::StructKind::Unit => Fields::Unit,
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_record_fields(&mut self, fields: &ast::RecordFieldList) -> IdxRange<Field> {
|
||||
let start = self.next_field_idx();
|
||||
for field in fields.fields() {
|
||||
if let Some(data) = self.lower_record_field(&field) {
|
||||
let idx = self.data().fields.alloc(data);
|
||||
self.add_attrs(
|
||||
idx.into(),
|
||||
RawAttrs::new(self.db.upcast(), &field, self.span_map()),
|
||||
);
|
||||
}
|
||||
}
|
||||
let end = self.next_field_idx();
|
||||
IdxRange::new(start..end)
|
||||
}
|
||||
|
||||
fn lower_record_field(&mut self, field: &ast::RecordField) -> Option<Field> {
|
||||
let name = field.name()?.as_name();
|
||||
let visibility = self.lower_visibility(field);
|
||||
let type_ref = self.lower_type_ref_opt(field.ty());
|
||||
let ast_id = FieldAstId::Record(self.source_ast_id_map.ast_id(field));
|
||||
let res = Field { name, type_ref, visibility, ast_id };
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn lower_tuple_fields(&mut self, fields: &ast::TupleFieldList) -> IdxRange<Field> {
|
||||
let start = self.next_field_idx();
|
||||
for (i, field) in fields.fields().enumerate() {
|
||||
let data = self.lower_tuple_field(i, &field);
|
||||
let idx = self.data().fields.alloc(data);
|
||||
self.add_attrs(idx.into(), RawAttrs::new(self.db.upcast(), &field, self.span_map()));
|
||||
}
|
||||
let end = self.next_field_idx();
|
||||
IdxRange::new(start..end)
|
||||
}
|
||||
|
||||
fn lower_tuple_field(&mut self, idx: usize, field: &ast::TupleField) -> Field {
|
||||
let name = Name::new_tuple_field(idx);
|
||||
let visibility = self.lower_visibility(field);
|
||||
let type_ref = self.lower_type_ref_opt(field.ty());
|
||||
let ast_id = FieldAstId::Tuple(self.source_ast_id_map.ast_id(field));
|
||||
Field { name, type_ref, visibility, ast_id }
|
||||
}
|
||||
|
||||
fn lower_union(&mut self, union: &ast::Union) -> Option<FileItemTreeId<Union>> {
|
||||
let visibility = self.lower_visibility(union);
|
||||
let name = union.name()?.as_name();
|
||||
let ast_id = self.source_ast_id_map.ast_id(union);
|
||||
let generic_params = self.lower_generic_params(HasImplicitSelf::No, union);
|
||||
let fields = match union.record_field_list() {
|
||||
Some(record_field_list) => self.lower_fields(&StructKind::Record(record_field_list)),
|
||||
None => Fields::Record(IdxRange::new(self.next_field_idx()..self.next_field_idx())),
|
||||
};
|
||||
let res = Union { name, visibility, generic_params, fields, ast_id };
|
||||
Some(id(self.data().unions.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_enum(&mut self, enum_: &ast::Enum) -> Option<FileItemTreeId<Enum>> {
|
||||
let visibility = self.lower_visibility(enum_);
|
||||
let name = enum_.name()?.as_name();
|
||||
let ast_id = self.source_ast_id_map.ast_id(enum_);
|
||||
let generic_params = self.lower_generic_params(HasImplicitSelf::No, enum_);
|
||||
let variants = match &enum_.variant_list() {
|
||||
Some(variant_list) => self.lower_variants(variant_list),
|
||||
None => {
|
||||
FileItemTreeId(self.next_variant_idx())..FileItemTreeId(self.next_variant_idx())
|
||||
}
|
||||
};
|
||||
let res = Enum { name, visibility, generic_params, variants, ast_id };
|
||||
Some(id(self.data().enums.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_variants(&mut self, variants: &ast::VariantList) -> Range<FileItemTreeId<Variant>> {
|
||||
let start = self.next_variant_idx();
|
||||
for variant in variants.variants() {
|
||||
if let Some(data) = self.lower_variant(&variant) {
|
||||
let idx = self.data().variants.alloc(data);
|
||||
self.add_attrs(
|
||||
id(idx).into(),
|
||||
RawAttrs::new(self.db.upcast(), &variant, self.span_map()),
|
||||
);
|
||||
}
|
||||
}
|
||||
let end = self.next_variant_idx();
|
||||
FileItemTreeId(start)..FileItemTreeId(end)
|
||||
}
|
||||
|
||||
fn lower_variant(&mut self, variant: &ast::Variant) -> Option<Variant> {
|
||||
let name = variant.name()?.as_name();
|
||||
let fields = self.lower_fields(&variant.kind());
|
||||
let ast_id = self.source_ast_id_map.ast_id(variant);
|
||||
let res = Variant { name, fields, ast_id };
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn lower_function(&mut self, func: &ast::Fn) -> Option<FileItemTreeId<Function>> {
|
||||
let visibility = self.lower_visibility(func);
|
||||
let name = func.name()?.as_name();
|
||||
|
||||
let mut has_self_param = false;
|
||||
let start_param = self.next_param_idx();
|
||||
if let Some(param_list) = func.param_list() {
|
||||
if let Some(self_param) = param_list.self_param() {
|
||||
let self_type = match self_param.ty() {
|
||||
Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
|
||||
None => {
|
||||
let self_type = TypeRef::Path(name![Self].into());
|
||||
match self_param.kind() {
|
||||
ast::SelfParamKind::Owned => self_type,
|
||||
ast::SelfParamKind::Ref => TypeRef::Reference(
|
||||
Box::new(self_type),
|
||||
self_param.lifetime().as_ref().map(LifetimeRef::new),
|
||||
Mutability::Shared,
|
||||
),
|
||||
ast::SelfParamKind::MutRef => TypeRef::Reference(
|
||||
Box::new(self_type),
|
||||
self_param.lifetime().as_ref().map(LifetimeRef::new),
|
||||
Mutability::Mut,
|
||||
),
|
||||
}
|
||||
}
|
||||
};
|
||||
let type_ref = Interned::new(self_type);
|
||||
let ast_id = self.source_ast_id_map.ast_id(&self_param);
|
||||
let idx = self.data().params.alloc(Param {
|
||||
type_ref: Some(type_ref),
|
||||
ast_id: ParamAstId::SelfParam(ast_id),
|
||||
});
|
||||
self.add_attrs(
|
||||
idx.into(),
|
||||
RawAttrs::new(self.db.upcast(), &self_param, self.span_map()),
|
||||
);
|
||||
has_self_param = true;
|
||||
}
|
||||
for param in param_list.params() {
|
||||
let ast_id = self.source_ast_id_map.ast_id(¶m);
|
||||
let idx = match param.dotdotdot_token() {
|
||||
Some(_) => self
|
||||
.data()
|
||||
.params
|
||||
.alloc(Param { type_ref: None, ast_id: ParamAstId::Param(ast_id) }),
|
||||
None => {
|
||||
let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty());
|
||||
let ty = Interned::new(type_ref);
|
||||
self.data()
|
||||
.params
|
||||
.alloc(Param { type_ref: Some(ty), ast_id: ParamAstId::Param(ast_id) })
|
||||
}
|
||||
};
|
||||
self.add_attrs(
|
||||
idx.into(),
|
||||
RawAttrs::new(self.db.upcast(), ¶m, self.span_map()),
|
||||
);
|
||||
}
|
||||
}
|
||||
let end_param = self.next_param_idx();
|
||||
let params = IdxRange::new(start_param..end_param);
|
||||
|
||||
let ret_type = match func.ret_type() {
|
||||
Some(rt) => match rt.ty() {
|
||||
Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
|
||||
None if rt.thin_arrow_token().is_some() => TypeRef::Error,
|
||||
None => TypeRef::unit(),
|
||||
},
|
||||
None => TypeRef::unit(),
|
||||
};
|
||||
|
||||
let ret_type = if func.async_token().is_some() {
|
||||
let future_impl = desugar_future_path(ret_type);
|
||||
let ty_bound = Interned::new(TypeBound::Path(future_impl, TraitBoundModifier::None));
|
||||
TypeRef::ImplTrait(vec![ty_bound])
|
||||
} else {
|
||||
ret_type
|
||||
};
|
||||
|
||||
let abi = func.abi().map(lower_abi);
|
||||
|
||||
let ast_id = self.source_ast_id_map.ast_id(func);
|
||||
|
||||
let mut flags = FnFlags::default();
|
||||
if func.body().is_some() {
|
||||
flags |= FnFlags::HAS_BODY;
|
||||
}
|
||||
if has_self_param {
|
||||
flags |= FnFlags::HAS_SELF_PARAM;
|
||||
}
|
||||
if func.default_token().is_some() {
|
||||
flags |= FnFlags::HAS_DEFAULT_KW;
|
||||
}
|
||||
if func.const_token().is_some() {
|
||||
flags |= FnFlags::HAS_CONST_KW;
|
||||
}
|
||||
if func.async_token().is_some() {
|
||||
flags |= FnFlags::HAS_ASYNC_KW;
|
||||
}
|
||||
if func.unsafe_token().is_some() {
|
||||
flags |= FnFlags::HAS_UNSAFE_KW;
|
||||
}
|
||||
|
||||
let res = Function {
|
||||
name,
|
||||
visibility,
|
||||
explicit_generic_params: self.lower_generic_params(HasImplicitSelf::No, func),
|
||||
abi,
|
||||
params,
|
||||
ret_type: Interned::new(ret_type),
|
||||
ast_id,
|
||||
flags,
|
||||
};
|
||||
|
||||
Some(id(self.data().functions.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_type_alias(
|
||||
&mut self,
|
||||
type_alias: &ast::TypeAlias,
|
||||
) -> Option<FileItemTreeId<TypeAlias>> {
|
||||
let name = type_alias.name()?.as_name();
|
||||
let type_ref = type_alias.ty().map(|it| self.lower_type_ref(&it));
|
||||
let visibility = self.lower_visibility(type_alias);
|
||||
let bounds = self.lower_type_bounds(type_alias);
|
||||
let ast_id = self.source_ast_id_map.ast_id(type_alias);
|
||||
let generic_params = self.lower_generic_params(HasImplicitSelf::No, type_alias);
|
||||
let res = TypeAlias { name, visibility, bounds, generic_params, type_ref, ast_id };
|
||||
Some(id(self.data().type_aliases.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_static(&mut self, static_: &ast::Static) -> Option<FileItemTreeId<Static>> {
|
||||
let name = static_.name()?.as_name();
|
||||
let type_ref = self.lower_type_ref_opt(static_.ty());
|
||||
let visibility = self.lower_visibility(static_);
|
||||
let mutable = static_.mut_token().is_some();
|
||||
let ast_id = self.source_ast_id_map.ast_id(static_);
|
||||
let res = Static { name, visibility, mutable, type_ref, ast_id };
|
||||
Some(id(self.data().statics.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_const(&mut self, konst: &ast::Const) -> FileItemTreeId<Const> {
|
||||
let name = konst.name().map(|it| it.as_name());
|
||||
let type_ref = self.lower_type_ref_opt(konst.ty());
|
||||
let visibility = self.lower_visibility(konst);
|
||||
let ast_id = self.source_ast_id_map.ast_id(konst);
|
||||
let res = Const { name, visibility, type_ref, ast_id, has_body: konst.body().is_some() };
|
||||
id(self.data().consts.alloc(res))
|
||||
}
|
||||
|
||||
fn lower_module(&mut self, module: &ast::Module) -> Option<FileItemTreeId<Mod>> {
|
||||
let name = module.name()?.as_name();
|
||||
let visibility = self.lower_visibility(module);
|
||||
let kind = if module.semicolon_token().is_some() {
|
||||
ModKind::Outline
|
||||
} else {
|
||||
ModKind::Inline {
|
||||
items: module
|
||||
.item_list()
|
||||
.map(|list| list.items().flat_map(|item| self.lower_mod_item(&item)).collect())
|
||||
.unwrap_or_else(|| {
|
||||
cov_mark::hit!(name_res_works_for_broken_modules);
|
||||
Box::new([]) as Box<[_]>
|
||||
}),
|
||||
}
|
||||
};
|
||||
let ast_id = self.source_ast_id_map.ast_id(module);
|
||||
let res = Mod { name, visibility, kind, ast_id };
|
||||
Some(id(self.data().mods.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_trait(&mut self, trait_def: &ast::Trait) -> Option<FileItemTreeId<Trait>> {
|
||||
let name = trait_def.name()?.as_name();
|
||||
let visibility = self.lower_visibility(trait_def);
|
||||
let ast_id = self.source_ast_id_map.ast_id(trait_def);
|
||||
let generic_params =
|
||||
self.lower_generic_params(HasImplicitSelf::Yes(trait_def.type_bound_list()), trait_def);
|
||||
let is_auto = trait_def.auto_token().is_some();
|
||||
let is_unsafe = trait_def.unsafe_token().is_some();
|
||||
|
||||
let items = trait_def
|
||||
.assoc_item_list()
|
||||
.into_iter()
|
||||
.flat_map(|list| list.assoc_items())
|
||||
.filter_map(|item_node| self.lower_assoc_item(&item_node))
|
||||
.collect();
|
||||
|
||||
let def = Trait { name, visibility, generic_params, is_auto, is_unsafe, items, ast_id };
|
||||
Some(id(self.data().traits.alloc(def)))
|
||||
}
|
||||
|
||||
fn lower_trait_alias(
|
||||
&mut self,
|
||||
trait_alias_def: &ast::TraitAlias,
|
||||
) -> Option<FileItemTreeId<TraitAlias>> {
|
||||
let name = trait_alias_def.name()?.as_name();
|
||||
let visibility = self.lower_visibility(trait_alias_def);
|
||||
let ast_id = self.source_ast_id_map.ast_id(trait_alias_def);
|
||||
let generic_params = self.lower_generic_params(
|
||||
HasImplicitSelf::Yes(trait_alias_def.type_bound_list()),
|
||||
trait_alias_def,
|
||||
);
|
||||
|
||||
let alias = TraitAlias { name, visibility, generic_params, ast_id };
|
||||
Some(id(self.data().trait_aliases.alloc(alias)))
|
||||
}
|
||||
|
||||
fn lower_impl(&mut self, impl_def: &ast::Impl) -> Option<FileItemTreeId<Impl>> {
|
||||
let ast_id = self.source_ast_id_map.ast_id(impl_def);
|
||||
// Note that trait impls don't get implicit `Self` unlike traits, because here they are a
|
||||
// type alias rather than a type parameter, so this is handled by the resolver.
|
||||
let generic_params = self.lower_generic_params(HasImplicitSelf::No, impl_def);
|
||||
// FIXME: If trait lowering fails, due to a non PathType for example, we treat this impl
|
||||
// as if it was an non-trait impl. Ideally we want to create a unique missing ref that only
|
||||
// equals itself.
|
||||
let target_trait = impl_def.trait_().and_then(|tr| self.lower_trait_ref(&tr));
|
||||
let self_ty = self.lower_type_ref(&impl_def.self_ty()?);
|
||||
let is_negative = impl_def.excl_token().is_some();
|
||||
let is_unsafe = impl_def.unsafe_token().is_some();
|
||||
|
||||
// We cannot use `assoc_items()` here as that does not include macro calls.
|
||||
let items = impl_def
|
||||
.assoc_item_list()
|
||||
.into_iter()
|
||||
.flat_map(|it| it.assoc_items())
|
||||
.filter_map(|item| self.lower_assoc_item(&item))
|
||||
.collect();
|
||||
let res =
|
||||
Impl { generic_params, target_trait, self_ty, is_negative, is_unsafe, items, ast_id };
|
||||
Some(id(self.data().impls.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Use>> {
|
||||
let visibility = self.lower_visibility(use_item);
|
||||
let ast_id = self.source_ast_id_map.ast_id(use_item);
|
||||
let (use_tree, _) = lower_use_tree(self.db, use_item.use_tree()?, &mut |range| {
|
||||
self.span_map().span_for_range(range).ctx
|
||||
})?;
|
||||
|
||||
let res = Use { visibility, ast_id, use_tree };
|
||||
Some(id(self.data().uses.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_extern_crate(
|
||||
&mut self,
|
||||
extern_crate: &ast::ExternCrate,
|
||||
) -> Option<FileItemTreeId<ExternCrate>> {
|
||||
let name = extern_crate.name_ref()?.as_name();
|
||||
let alias = extern_crate.rename().map(|a| {
|
||||
a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
|
||||
});
|
||||
let visibility = self.lower_visibility(extern_crate);
|
||||
let ast_id = self.source_ast_id_map.ast_id(extern_crate);
|
||||
|
||||
let res = ExternCrate { name, alias, visibility, ast_id };
|
||||
Some(id(self.data().extern_crates.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> {
|
||||
let span_map = self.span_map();
|
||||
let path = m.path()?;
|
||||
let range = path.syntax().text_range();
|
||||
let path = Interned::new(ModPath::from_src(self.db.upcast(), path, &mut |range| {
|
||||
span_map.span_for_range(range).ctx
|
||||
})?);
|
||||
let ast_id = self.source_ast_id_map.ast_id(m);
|
||||
let expand_to = hir_expand::ExpandTo::from_call_site(m);
|
||||
let res = MacroCall { path, ast_id, expand_to, ctxt: span_map.span_for_range(range).ctx };
|
||||
Some(id(self.data().macro_calls.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_macro_rules(&mut self, m: &ast::MacroRules) -> Option<FileItemTreeId<MacroRules>> {
|
||||
let name = m.name()?;
|
||||
let ast_id = self.source_ast_id_map.ast_id(m);
|
||||
|
||||
let res = MacroRules { name: name.as_name(), ast_id };
|
||||
Some(id(self.data().macro_rules.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_macro_def(&mut self, m: &ast::MacroDef) -> Option<FileItemTreeId<Macro2>> {
|
||||
let name = m.name()?;
|
||||
|
||||
let ast_id = self.source_ast_id_map.ast_id(m);
|
||||
let visibility = self.lower_visibility(m);
|
||||
|
||||
let res = Macro2 { name: name.as_name(), ast_id, visibility };
|
||||
Some(id(self.data().macro_defs.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_extern_block(&mut self, block: &ast::ExternBlock) -> FileItemTreeId<ExternBlock> {
|
||||
let ast_id = self.source_ast_id_map.ast_id(block);
|
||||
let abi = block.abi().map(lower_abi);
|
||||
let children: Box<[_]> = block.extern_item_list().map_or(Box::new([]), |list| {
|
||||
list.extern_items()
|
||||
.filter_map(|item| {
|
||||
// Note: All items in an `extern` block need to be lowered as if they're outside of one
|
||||
// (in other words, the knowledge that they're in an extern block must not be used).
|
||||
// This is because an extern block can contain macros whose ItemTree's top-level items
|
||||
// should be considered to be in an extern block too.
|
||||
let mod_item: ModItem = match &item {
|
||||
ast::ExternItem::Fn(ast) => self.lower_function(ast)?.into(),
|
||||
ast::ExternItem::Static(ast) => self.lower_static(ast)?.into(),
|
||||
ast::ExternItem::TypeAlias(ty) => self.lower_type_alias(ty)?.into(),
|
||||
ast::ExternItem::MacroCall(call) => self.lower_macro_call(call)?.into(),
|
||||
};
|
||||
let attrs = RawAttrs::new(self.db.upcast(), &item, self.span_map());
|
||||
self.add_attrs(mod_item.into(), attrs);
|
||||
Some(mod_item)
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
let res = ExternBlock { abi, ast_id, children };
|
||||
id(self.data().extern_blocks.alloc(res))
|
||||
}
|
||||
|
||||
fn lower_generic_params(
|
||||
&mut self,
|
||||
has_implicit_self: HasImplicitSelf,
|
||||
node: &dyn ast::HasGenericParams,
|
||||
) -> Interned<GenericParams> {
|
||||
let mut generics = GenericParamsCollector::default();
|
||||
|
||||
if let HasImplicitSelf::Yes(bounds) = has_implicit_self {
|
||||
// Traits and trait aliases get the Self type as an implicit first type parameter.
|
||||
generics.type_or_consts.alloc(
|
||||
TypeParamData {
|
||||
name: Some(name![Self]),
|
||||
default: None,
|
||||
provenance: TypeParamProvenance::TraitSelf,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
// add super traits as bounds on Self
|
||||
// i.e., `trait Foo: Bar` is equivalent to `trait Foo where Self: Bar`
|
||||
let self_param = TypeRef::Path(name![Self].into());
|
||||
generics.fill_bounds(&self.body_ctx, bounds, Either::Left(self_param));
|
||||
}
|
||||
|
||||
let add_param_attrs = |item: Either<LocalTypeOrConstParamId, LocalLifetimeParamId>,
|
||||
param| {
|
||||
let attrs = RawAttrs::new(self.db.upcast(), ¶m, self.body_ctx.span_map());
|
||||
// This is identical to the body of `Ctx::add_attrs()` but we can't call that here
|
||||
// because it requires `&mut self` and the call to `generics.fill()` below also
|
||||
// references `self`.
|
||||
match self.tree.attrs.entry(match item {
|
||||
Either::Right(id) => id.into(),
|
||||
Either::Left(id) => id.into(),
|
||||
}) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
*entry.get_mut() = entry.get().merge(attrs);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(attrs);
|
||||
}
|
||||
}
|
||||
};
|
||||
generics.fill(&self.body_ctx, node, add_param_attrs);
|
||||
|
||||
Interned::new(generics.finish())
|
||||
}
|
||||
|
||||
fn lower_type_bounds(&mut self, node: &dyn ast::HasTypeBounds) -> Box<[Interned<TypeBound>]> {
|
||||
match node.type_bound_list() {
|
||||
Some(bound_list) => bound_list
|
||||
.bounds()
|
||||
.map(|it| Interned::new(TypeBound::from_ast(&self.body_ctx, it)))
|
||||
.collect(),
|
||||
None => Box::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_visibility(&mut self, item: &dyn ast::HasVisibility) -> RawVisibilityId {
|
||||
let vis = RawVisibility::from_ast(self.db, item.visibility(), &mut |range| {
|
||||
self.span_map().span_for_range(range).ctx
|
||||
});
|
||||
self.data().vis.alloc(vis)
|
||||
}
|
||||
|
||||
fn lower_trait_ref(&mut self, trait_ref: &ast::Type) -> Option<Interned<TraitRef>> {
|
||||
let trait_ref = TraitRef::from_ast(&self.body_ctx, trait_ref.clone())?;
|
||||
Some(Interned::new(trait_ref))
|
||||
}
|
||||
|
||||
fn lower_type_ref(&mut self, type_ref: &ast::Type) -> Interned<TypeRef> {
|
||||
let tyref = TypeRef::from_ast(&self.body_ctx, type_ref.clone());
|
||||
Interned::new(tyref)
|
||||
}
|
||||
|
||||
fn lower_type_ref_opt(&mut self, type_ref: Option<ast::Type>) -> Interned<TypeRef> {
|
||||
match type_ref.map(|ty| self.lower_type_ref(&ty)) {
|
||||
Some(it) => it,
|
||||
None => Interned::new(TypeRef::Error),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_field_idx(&self) -> Idx<Field> {
|
||||
Idx::from_raw(RawIdx::from(
|
||||
self.tree.data.as_ref().map_or(0, |data| data.fields.len() as u32),
|
||||
))
|
||||
}
|
||||
fn next_variant_idx(&self) -> Idx<Variant> {
|
||||
Idx::from_raw(RawIdx::from(
|
||||
self.tree.data.as_ref().map_or(0, |data| data.variants.len() as u32),
|
||||
))
|
||||
}
|
||||
fn next_param_idx(&self) -> Idx<Param> {
|
||||
Idx::from_raw(RawIdx::from(
|
||||
self.tree.data.as_ref().map_or(0, |data| data.params.len() as u32),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn desugar_future_path(orig: TypeRef) -> Path {
|
||||
let path = path![core::future::Future];
|
||||
let mut generic_args: Vec<_> =
|
||||
std::iter::repeat(None).take(path.segments().len() - 1).collect();
|
||||
let binding = AssociatedTypeBinding {
|
||||
name: name![Output],
|
||||
args: None,
|
||||
type_ref: Some(orig),
|
||||
bounds: Box::default(),
|
||||
};
|
||||
generic_args.push(Some(Interned::new(GenericArgs {
|
||||
bindings: Box::new([binding]),
|
||||
..GenericArgs::empty()
|
||||
})));
|
||||
|
||||
Path::from_known_path(path, generic_args)
|
||||
}
|
||||
|
||||
enum HasImplicitSelf {
|
||||
/// Inner list is a type bound list for the implicit `Self`.
|
||||
Yes(Option<ast::TypeBoundList>),
|
||||
No,
|
||||
}
|
||||
|
||||
fn lower_abi(abi: ast::Abi) -> Interned<str> {
|
||||
match abi.abi_string() {
|
||||
Some(tok) => Interned::new_str(tok.text_without_quotes()),
|
||||
// `extern` default to be `extern "C"`.
|
||||
_ => Interned::new_str("C"),
|
||||
}
|
||||
}
|
||||
|
||||
struct UseTreeLowering<'a> {
|
||||
db: &'a dyn DefDatabase,
|
||||
mapping: Arena<ast::UseTree>,
|
||||
}
|
||||
|
||||
impl UseTreeLowering<'_> {
|
||||
fn lower_use_tree(
|
||||
&mut self,
|
||||
tree: ast::UseTree,
|
||||
span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
|
||||
) -> Option<UseTree> {
|
||||
if let Some(use_tree_list) = tree.use_tree_list() {
|
||||
let prefix = match tree.path() {
|
||||
// E.g. use something::{{{inner}}};
|
||||
None => None,
|
||||
// E.g. `use something::{inner}` (prefix is `None`, path is `something`)
|
||||
// or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`)
|
||||
Some(path) => {
|
||||
match ModPath::from_src(self.db.upcast(), path, span_for_range) {
|
||||
Some(it) => Some(it),
|
||||
None => return None, // FIXME: report errors somewhere
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let list = use_tree_list
|
||||
.use_trees()
|
||||
.filter_map(|tree| self.lower_use_tree(tree, span_for_range))
|
||||
.collect();
|
||||
|
||||
Some(
|
||||
self.use_tree(
|
||||
UseTreeKind::Prefixed { prefix: prefix.map(Interned::new), list },
|
||||
tree,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
let is_glob = tree.star_token().is_some();
|
||||
let path = match tree.path() {
|
||||
Some(path) => Some(ModPath::from_src(self.db.upcast(), path, span_for_range)?),
|
||||
None => None,
|
||||
};
|
||||
let alias = tree.rename().map(|a| {
|
||||
a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
|
||||
});
|
||||
if alias.is_some() && is_glob {
|
||||
return None;
|
||||
}
|
||||
|
||||
match (path, alias, is_glob) {
|
||||
(path, None, true) => {
|
||||
if path.is_none() {
|
||||
cov_mark::hit!(glob_enum_group);
|
||||
}
|
||||
Some(self.use_tree(UseTreeKind::Glob { path: path.map(Interned::new) }, tree))
|
||||
}
|
||||
// Globs can't be renamed
|
||||
(_, Some(_), true) | (None, None, false) => None,
|
||||
// `bla::{ as Name}` is invalid
|
||||
(None, Some(_), false) => None,
|
||||
(Some(path), alias, false) => Some(
|
||||
self.use_tree(UseTreeKind::Single { path: Interned::new(path), alias }, tree),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn use_tree(&mut self, kind: UseTreeKind, ast: ast::UseTree) -> UseTree {
|
||||
let index = self.mapping.alloc(ast);
|
||||
UseTree { index, kind }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lower_use_tree(
|
||||
db: &dyn DefDatabase,
|
||||
tree: ast::UseTree,
|
||||
span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
|
||||
) -> Option<(UseTree, Arena<ast::UseTree>)> {
|
||||
let mut lowering = UseTreeLowering { db, mapping: Arena::new() };
|
||||
let tree = lowering.lower_use_tree(tree, span_for_range)?;
|
||||
Some((tree, lowering.mapping))
|
||||
}
|
||||
647
src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs
Normal file
647
src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs
Normal file
|
|
@ -0,0 +1,647 @@
|
|||
//! `ItemTree` debug printer.
|
||||
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
use span::ErasedFileAstId;
|
||||
|
||||
use crate::{
|
||||
generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget},
|
||||
item_tree::{
|
||||
AttrOwner, Const, DefDatabase, Enum, ExternBlock, ExternCrate, Field, FieldAstId, Fields,
|
||||
FileItemTreeId, FnFlags, Function, GenericParams, Impl, Interned, ItemTree, Macro2,
|
||||
MacroCall, MacroRules, Mod, ModItem, ModKind, Param, ParamAstId, Path, RawAttrs,
|
||||
RawVisibilityId, Static, Struct, Trait, TraitAlias, TypeAlias, TypeBound, TypeRef, Union,
|
||||
Use, UseTree, UseTreeKind, Variant,
|
||||
},
|
||||
pretty::{print_path, print_type_bounds, print_type_ref},
|
||||
visibility::RawVisibility,
|
||||
};
|
||||
|
||||
pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree) -> String {
|
||||
let mut p = Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true };
|
||||
|
||||
if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) {
|
||||
p.print_attrs(attrs, true, "\n");
|
||||
}
|
||||
p.blank();
|
||||
|
||||
for item in tree.top_level_items() {
|
||||
p.print_mod_item(*item);
|
||||
}
|
||||
|
||||
let mut s = p.buf.trim_end_matches('\n').to_owned();
|
||||
s.push('\n');
|
||||
s
|
||||
}
|
||||
|
||||
macro_rules! w {
|
||||
($dst:expr, $($arg:tt)*) => {
|
||||
{ let _ = write!($dst, $($arg)*); }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! wln {
|
||||
($dst:expr) => {
|
||||
{ let _ = writeln!($dst); }
|
||||
};
|
||||
($dst:expr, $($arg:tt)*) => {
|
||||
{ let _ = writeln!($dst, $($arg)*); }
|
||||
};
|
||||
}
|
||||
|
||||
struct Printer<'a> {
|
||||
db: &'a dyn DefDatabase,
|
||||
tree: &'a ItemTree,
|
||||
buf: String,
|
||||
indent_level: usize,
|
||||
needs_indent: bool,
|
||||
}
|
||||
|
||||
impl Printer<'_> {
|
||||
fn indented(&mut self, f: impl FnOnce(&mut Self)) {
|
||||
self.indent_level += 1;
|
||||
wln!(self);
|
||||
f(self);
|
||||
self.indent_level -= 1;
|
||||
self.buf = self.buf.trim_end_matches('\n').to_owned();
|
||||
}
|
||||
|
||||
/// Ensures that a blank line is output before the next text.
|
||||
fn blank(&mut self) {
|
||||
let mut iter = self.buf.chars().rev().fuse();
|
||||
match (iter.next(), iter.next()) {
|
||||
(Some('\n'), Some('\n') | None) | (None, None) => {}
|
||||
(Some('\n'), Some(_)) => {
|
||||
self.buf.push('\n');
|
||||
}
|
||||
(Some(_), _) => {
|
||||
self.buf.push('\n');
|
||||
self.buf.push('\n');
|
||||
}
|
||||
(None, Some(_)) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn whitespace(&mut self) {
|
||||
match self.buf.chars().next_back() {
|
||||
None | Some('\n' | ' ') => {}
|
||||
_ => self.buf.push(' '),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_attrs(&mut self, attrs: &RawAttrs, inner: bool, separated_by: &str) {
|
||||
let inner = if inner { "!" } else { "" };
|
||||
for attr in &**attrs {
|
||||
w!(
|
||||
self,
|
||||
"#{}[{}{}]{}",
|
||||
inner,
|
||||
attr.path.display(self.db.upcast()),
|
||||
attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(),
|
||||
separated_by,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_attrs_of(&mut self, of: impl Into<AttrOwner>, separated_by: &str) {
|
||||
if let Some(attrs) = self.tree.attrs.get(&of.into()) {
|
||||
self.print_attrs(attrs, false, separated_by);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_visibility(&mut self, vis: RawVisibilityId) {
|
||||
match &self.tree[vis] {
|
||||
RawVisibility::Module(path, _expl) => {
|
||||
w!(self, "pub({}) ", path.display(self.db.upcast()))
|
||||
}
|
||||
RawVisibility::Public => w!(self, "pub "),
|
||||
};
|
||||
}
|
||||
|
||||
fn print_fields(&mut self, fields: &Fields) {
|
||||
match fields {
|
||||
Fields::Record(fields) => {
|
||||
self.whitespace();
|
||||
w!(self, "{{");
|
||||
self.indented(|this| {
|
||||
for field in fields.clone() {
|
||||
let Field { visibility, name, type_ref, ast_id } = &this.tree[field];
|
||||
this.print_ast_id(match ast_id {
|
||||
FieldAstId::Record(it) => it.erase(),
|
||||
FieldAstId::Tuple(it) => it.erase(),
|
||||
});
|
||||
this.print_attrs_of(field, "\n");
|
||||
this.print_visibility(*visibility);
|
||||
w!(this, "{}: ", name.display(self.db.upcast()));
|
||||
this.print_type_ref(type_ref);
|
||||
wln!(this, ",");
|
||||
}
|
||||
});
|
||||
w!(self, "}}");
|
||||
}
|
||||
Fields::Tuple(fields) => {
|
||||
w!(self, "(");
|
||||
self.indented(|this| {
|
||||
for field in fields.clone() {
|
||||
let Field { visibility, name, type_ref, ast_id } = &this.tree[field];
|
||||
this.print_ast_id(match ast_id {
|
||||
FieldAstId::Record(it) => it.erase(),
|
||||
FieldAstId::Tuple(it) => it.erase(),
|
||||
});
|
||||
this.print_attrs_of(field, "\n");
|
||||
this.print_visibility(*visibility);
|
||||
w!(this, "{}: ", name.display(self.db.upcast()));
|
||||
this.print_type_ref(type_ref);
|
||||
wln!(this, ",");
|
||||
}
|
||||
});
|
||||
w!(self, ")");
|
||||
}
|
||||
Fields::Unit => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_fields_and_where_clause(&mut self, fields: &Fields, params: &GenericParams) {
|
||||
match fields {
|
||||
Fields::Record(_) => {
|
||||
if self.print_where_clause(params) {
|
||||
wln!(self);
|
||||
}
|
||||
self.print_fields(fields);
|
||||
}
|
||||
Fields::Unit => {
|
||||
self.print_where_clause(params);
|
||||
self.print_fields(fields);
|
||||
}
|
||||
Fields::Tuple(_) => {
|
||||
self.print_fields(fields);
|
||||
self.print_where_clause(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_use_tree(&mut self, use_tree: &UseTree) {
|
||||
match &use_tree.kind {
|
||||
UseTreeKind::Single { path, alias } => {
|
||||
w!(self, "{}", path.display(self.db.upcast()));
|
||||
if let Some(alias) = alias {
|
||||
w!(self, " as {}", alias);
|
||||
}
|
||||
}
|
||||
UseTreeKind::Glob { path } => {
|
||||
if let Some(path) = path {
|
||||
w!(self, "{}::", path.display(self.db.upcast()));
|
||||
}
|
||||
w!(self, "*");
|
||||
}
|
||||
UseTreeKind::Prefixed { prefix, list } => {
|
||||
if let Some(prefix) = prefix {
|
||||
w!(self, "{}::", prefix.display(self.db.upcast()));
|
||||
}
|
||||
w!(self, "{{");
|
||||
for (i, tree) in list.iter().enumerate() {
|
||||
if i != 0 {
|
||||
w!(self, ", ");
|
||||
}
|
||||
self.print_use_tree(tree);
|
||||
}
|
||||
w!(self, "}}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_mod_item(&mut self, item: ModItem) {
|
||||
self.print_attrs_of(item, "\n");
|
||||
|
||||
match item {
|
||||
ModItem::Use(it) => {
|
||||
let Use { visibility, use_tree, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "use ");
|
||||
self.print_use_tree(use_tree);
|
||||
wln!(self, ";");
|
||||
}
|
||||
ModItem::ExternCrate(it) => {
|
||||
let ExternCrate { name, alias, visibility, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "extern crate {}", name.display(self.db.upcast()));
|
||||
if let Some(alias) = alias {
|
||||
w!(self, " as {}", alias);
|
||||
}
|
||||
wln!(self, ";");
|
||||
}
|
||||
ModItem::ExternBlock(it) => {
|
||||
let ExternBlock { abi, ast_id, children } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
w!(self, "extern ");
|
||||
if let Some(abi) = abi {
|
||||
w!(self, "\"{}\" ", abi);
|
||||
}
|
||||
w!(self, "{{");
|
||||
self.indented(|this| {
|
||||
for child in &**children {
|
||||
this.print_mod_item(*child);
|
||||
}
|
||||
});
|
||||
wln!(self, "}}");
|
||||
}
|
||||
ModItem::Function(it) => {
|
||||
let Function {
|
||||
name,
|
||||
visibility,
|
||||
explicit_generic_params,
|
||||
abi,
|
||||
params,
|
||||
ret_type,
|
||||
ast_id,
|
||||
flags,
|
||||
} = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
if flags.contains(FnFlags::HAS_DEFAULT_KW) {
|
||||
w!(self, "default ");
|
||||
}
|
||||
if flags.contains(FnFlags::HAS_CONST_KW) {
|
||||
w!(self, "const ");
|
||||
}
|
||||
if flags.contains(FnFlags::HAS_ASYNC_KW) {
|
||||
w!(self, "async ");
|
||||
}
|
||||
if flags.contains(FnFlags::HAS_UNSAFE_KW) {
|
||||
w!(self, "unsafe ");
|
||||
}
|
||||
if let Some(abi) = abi {
|
||||
w!(self, "extern \"{}\" ", abi);
|
||||
}
|
||||
w!(self, "fn {}", name.display(self.db.upcast()));
|
||||
self.print_generic_params(explicit_generic_params);
|
||||
w!(self, "(");
|
||||
if !params.is_empty() {
|
||||
self.indented(|this| {
|
||||
for param in params.clone() {
|
||||
this.print_attrs_of(param, "\n");
|
||||
let Param { type_ref, ast_id } = &this.tree[param];
|
||||
this.print_ast_id(match ast_id {
|
||||
ParamAstId::Param(it) => it.erase(),
|
||||
ParamAstId::SelfParam(it) => it.erase(),
|
||||
});
|
||||
match type_ref {
|
||||
Some(ty) => {
|
||||
if flags.contains(FnFlags::HAS_SELF_PARAM) {
|
||||
w!(this, "self: ");
|
||||
}
|
||||
this.print_type_ref(ty);
|
||||
wln!(this, ",");
|
||||
}
|
||||
None => {
|
||||
wln!(this, "...");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
w!(self, ") -> ");
|
||||
self.print_type_ref(ret_type);
|
||||
self.print_where_clause(explicit_generic_params);
|
||||
if flags.contains(FnFlags::HAS_BODY) {
|
||||
wln!(self, " {{ ... }}");
|
||||
} else {
|
||||
wln!(self, ";");
|
||||
}
|
||||
}
|
||||
ModItem::Struct(it) => {
|
||||
let Struct { visibility, name, fields, generic_params, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "struct {}", name.display(self.db.upcast()));
|
||||
self.print_generic_params(generic_params);
|
||||
self.print_fields_and_where_clause(fields, generic_params);
|
||||
if matches!(fields, Fields::Record(_)) {
|
||||
wln!(self);
|
||||
} else {
|
||||
wln!(self, ";");
|
||||
}
|
||||
}
|
||||
ModItem::Union(it) => {
|
||||
let Union { name, visibility, fields, generic_params, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "union {}", name.display(self.db.upcast()));
|
||||
self.print_generic_params(generic_params);
|
||||
self.print_fields_and_where_clause(fields, generic_params);
|
||||
if matches!(fields, Fields::Record(_)) {
|
||||
wln!(self);
|
||||
} else {
|
||||
wln!(self, ";");
|
||||
}
|
||||
}
|
||||
ModItem::Enum(it) => {
|
||||
let Enum { name, visibility, variants, generic_params, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "enum {}", name.display(self.db.upcast()));
|
||||
self.print_generic_params(generic_params);
|
||||
self.print_where_clause_and_opening_brace(generic_params);
|
||||
self.indented(|this| {
|
||||
for variant in FileItemTreeId::range_iter(variants.clone()) {
|
||||
let Variant { name, fields, ast_id } = &this.tree[variant];
|
||||
this.print_ast_id(ast_id.erase());
|
||||
this.print_attrs_of(variant, "\n");
|
||||
w!(this, "{}", name.display(self.db.upcast()));
|
||||
this.print_fields(fields);
|
||||
wln!(this, ",");
|
||||
}
|
||||
});
|
||||
wln!(self, "}}");
|
||||
}
|
||||
ModItem::Const(it) => {
|
||||
let Const { name, visibility, type_ref, ast_id, has_body: _ } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "const ");
|
||||
match name {
|
||||
Some(name) => w!(self, "{}", name.display(self.db.upcast())),
|
||||
None => w!(self, "_"),
|
||||
}
|
||||
w!(self, ": ");
|
||||
self.print_type_ref(type_ref);
|
||||
wln!(self, " = _;");
|
||||
}
|
||||
ModItem::Static(it) => {
|
||||
let Static { name, visibility, mutable, type_ref, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "static ");
|
||||
if *mutable {
|
||||
w!(self, "mut ");
|
||||
}
|
||||
w!(self, "{}: ", name.display(self.db.upcast()));
|
||||
self.print_type_ref(type_ref);
|
||||
w!(self, " = _;");
|
||||
wln!(self);
|
||||
}
|
||||
ModItem::Trait(it) => {
|
||||
let Trait { name, visibility, is_auto, is_unsafe, items, generic_params, ast_id } =
|
||||
&self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
if *is_unsafe {
|
||||
w!(self, "unsafe ");
|
||||
}
|
||||
if *is_auto {
|
||||
w!(self, "auto ");
|
||||
}
|
||||
w!(self, "trait {}", name.display(self.db.upcast()));
|
||||
self.print_generic_params(generic_params);
|
||||
self.print_where_clause_and_opening_brace(generic_params);
|
||||
self.indented(|this| {
|
||||
for item in &**items {
|
||||
this.print_mod_item((*item).into());
|
||||
}
|
||||
});
|
||||
wln!(self, "}}");
|
||||
}
|
||||
ModItem::TraitAlias(it) => {
|
||||
let TraitAlias { name, visibility, generic_params, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "trait {}", name.display(self.db.upcast()));
|
||||
self.print_generic_params(generic_params);
|
||||
w!(self, " = ");
|
||||
self.print_where_clause(generic_params);
|
||||
w!(self, ";");
|
||||
wln!(self);
|
||||
}
|
||||
ModItem::Impl(it) => {
|
||||
let Impl {
|
||||
target_trait,
|
||||
self_ty,
|
||||
is_negative,
|
||||
is_unsafe,
|
||||
items,
|
||||
generic_params,
|
||||
ast_id,
|
||||
} = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
if *is_unsafe {
|
||||
w!(self, "unsafe");
|
||||
}
|
||||
w!(self, "impl");
|
||||
self.print_generic_params(generic_params);
|
||||
w!(self, " ");
|
||||
if *is_negative {
|
||||
w!(self, "!");
|
||||
}
|
||||
if let Some(tr) = target_trait {
|
||||
self.print_path(&tr.path);
|
||||
w!(self, " for ");
|
||||
}
|
||||
self.print_type_ref(self_ty);
|
||||
self.print_where_clause_and_opening_brace(generic_params);
|
||||
self.indented(|this| {
|
||||
for item in &**items {
|
||||
this.print_mod_item((*item).into());
|
||||
}
|
||||
});
|
||||
wln!(self, "}}");
|
||||
}
|
||||
ModItem::TypeAlias(it) => {
|
||||
let TypeAlias { name, visibility, bounds, type_ref, generic_params, ast_id } =
|
||||
&self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "type {}", name.display(self.db.upcast()));
|
||||
self.print_generic_params(generic_params);
|
||||
if !bounds.is_empty() {
|
||||
w!(self, ": ");
|
||||
self.print_type_bounds(bounds);
|
||||
}
|
||||
if let Some(ty) = type_ref {
|
||||
w!(self, " = ");
|
||||
self.print_type_ref(ty);
|
||||
}
|
||||
self.print_where_clause(generic_params);
|
||||
w!(self, ";");
|
||||
wln!(self);
|
||||
}
|
||||
ModItem::Mod(it) => {
|
||||
let Mod { name, visibility, kind, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
w!(self, "mod {}", name.display(self.db.upcast()));
|
||||
match kind {
|
||||
ModKind::Inline { items } => {
|
||||
w!(self, " {{");
|
||||
self.indented(|this| {
|
||||
for item in &**items {
|
||||
this.print_mod_item(*item);
|
||||
}
|
||||
});
|
||||
wln!(self, "}}");
|
||||
}
|
||||
ModKind::Outline => {
|
||||
wln!(self, ";");
|
||||
}
|
||||
}
|
||||
}
|
||||
ModItem::MacroCall(it) => {
|
||||
let MacroCall { path, ast_id, expand_to, ctxt } = &self.tree[it];
|
||||
let _ = writeln!(
|
||||
self,
|
||||
"// AstId: {:?}, SyntaxContext: {}, ExpandTo: {:?}",
|
||||
ast_id.erase().into_raw(),
|
||||
ctxt,
|
||||
expand_to
|
||||
);
|
||||
wln!(self, "{}!(...);", path.display(self.db.upcast()));
|
||||
}
|
||||
ModItem::MacroRules(it) => {
|
||||
let MacroRules { name, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db.upcast()));
|
||||
}
|
||||
ModItem::Macro2(it) => {
|
||||
let Macro2 { name, visibility, ast_id } = &self.tree[it];
|
||||
self.print_ast_id(ast_id.erase());
|
||||
self.print_visibility(*visibility);
|
||||
wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast()));
|
||||
}
|
||||
}
|
||||
|
||||
self.blank();
|
||||
}
|
||||
|
||||
fn print_type_ref(&mut self, type_ref: &TypeRef) {
|
||||
print_type_ref(self.db, type_ref, self).unwrap();
|
||||
}
|
||||
|
||||
fn print_type_bounds(&mut self, bounds: &[Interned<TypeBound>]) {
|
||||
print_type_bounds(self.db, bounds, self).unwrap();
|
||||
}
|
||||
|
||||
fn print_path(&mut self, path: &Path) {
|
||||
print_path(self.db, path, self).unwrap();
|
||||
}
|
||||
|
||||
fn print_generic_params(&mut self, params: &GenericParams) {
|
||||
if params.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
w!(self, "<");
|
||||
let mut first = true;
|
||||
for (idx, lt) in params.lifetimes.iter() {
|
||||
if !first {
|
||||
w!(self, ", ");
|
||||
}
|
||||
first = false;
|
||||
self.print_attrs_of(idx, " ");
|
||||
w!(self, "{}", lt.name.display(self.db.upcast()));
|
||||
}
|
||||
for (idx, x) in params.type_or_consts.iter() {
|
||||
if !first {
|
||||
w!(self, ", ");
|
||||
}
|
||||
first = false;
|
||||
self.print_attrs_of(idx, " ");
|
||||
match x {
|
||||
TypeOrConstParamData::TypeParamData(ty) => match &ty.name {
|
||||
Some(name) => w!(self, "{}", name.display(self.db.upcast())),
|
||||
None => w!(self, "_anon_{}", idx.into_raw()),
|
||||
},
|
||||
TypeOrConstParamData::ConstParamData(konst) => {
|
||||
w!(self, "const {}: ", konst.name.display(self.db.upcast()));
|
||||
self.print_type_ref(&konst.ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
w!(self, ">");
|
||||
}
|
||||
|
||||
fn print_where_clause_and_opening_brace(&mut self, params: &GenericParams) {
|
||||
if self.print_where_clause(params) {
|
||||
w!(self, "\n{{");
|
||||
} else {
|
||||
self.whitespace();
|
||||
w!(self, "{{");
|
||||
}
|
||||
}
|
||||
|
||||
fn print_where_clause(&mut self, params: &GenericParams) -> bool {
|
||||
if params.where_predicates.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
w!(self, "\nwhere");
|
||||
self.indented(|this| {
|
||||
for (i, pred) in params.where_predicates.iter().enumerate() {
|
||||
if i != 0 {
|
||||
wln!(this, ",");
|
||||
}
|
||||
|
||||
let (target, bound) = match pred {
|
||||
WherePredicate::TypeBound { target, bound } => (target, bound),
|
||||
WherePredicate::Lifetime { target, bound } => {
|
||||
wln!(
|
||||
this,
|
||||
"{}: {},",
|
||||
target.name.display(self.db.upcast()),
|
||||
bound.name.display(self.db.upcast())
|
||||
);
|
||||
continue;
|
||||
}
|
||||
WherePredicate::ForLifetime { lifetimes, target, bound } => {
|
||||
w!(this, "for<");
|
||||
for (i, lt) in lifetimes.iter().enumerate() {
|
||||
if i != 0 {
|
||||
w!(this, ", ");
|
||||
}
|
||||
w!(this, "{}", lt.display(self.db.upcast()));
|
||||
}
|
||||
w!(this, "> ");
|
||||
(target, bound)
|
||||
}
|
||||
};
|
||||
|
||||
match target {
|
||||
WherePredicateTypeTarget::TypeRef(ty) => this.print_type_ref(ty),
|
||||
WherePredicateTypeTarget::TypeOrConstParam(id) => {
|
||||
match ¶ms.type_or_consts[*id].name() {
|
||||
Some(name) => w!(this, "{}", name.display(self.db.upcast())),
|
||||
None => w!(this, "_anon_{}", id.into_raw()),
|
||||
}
|
||||
}
|
||||
}
|
||||
w!(this, ": ");
|
||||
this.print_type_bounds(std::slice::from_ref(bound));
|
||||
}
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn print_ast_id(&mut self, ast_id: ErasedFileAstId) {
|
||||
wln!(self, "// AstId: {:?}", ast_id.into_raw());
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Printer<'_> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for line in s.split_inclusive('\n') {
|
||||
if self.needs_indent {
|
||||
match self.buf.chars().last() {
|
||||
Some('\n') | None => {}
|
||||
_ => self.buf.push('\n'),
|
||||
}
|
||||
self.buf.push_str(&" ".repeat(self.indent_level));
|
||||
self.needs_indent = false;
|
||||
}
|
||||
|
||||
self.buf.push_str(line);
|
||||
self.needs_indent = line.ends_with('\n');
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
449
src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs
Normal file
449
src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
use expect_test::{expect, Expect};
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
use crate::{db::DefDatabase, test_db::TestDB};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let (db, file_id) = TestDB::with_single_file(ra_fixture);
|
||||
let item_tree = db.file_item_tree(file_id.into());
|
||||
let pretty = item_tree.pretty_print(&db);
|
||||
expect.assert_eq(&pretty);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports() {
|
||||
check(
|
||||
r#"
|
||||
//! file comment
|
||||
#![no_std]
|
||||
//! another file comment
|
||||
|
||||
extern crate self as renamed;
|
||||
pub(super) extern crate bli;
|
||||
|
||||
pub use crate::path::{nested, items as renamed, Trait as _};
|
||||
use globs::*;
|
||||
|
||||
/// docs on import
|
||||
use crate::{A, B};
|
||||
|
||||
use a::{c, d::{e}};
|
||||
"#,
|
||||
expect![[r##"
|
||||
#![doc = " file comment"]
|
||||
#![no_std]
|
||||
#![doc = " another file comment"]
|
||||
|
||||
// AstId: 1
|
||||
pub(self) extern crate self as renamed;
|
||||
|
||||
// AstId: 2
|
||||
pub(super) extern crate bli;
|
||||
|
||||
// AstId: 3
|
||||
pub use crate::path::{nested, items as renamed, Trait as _};
|
||||
|
||||
// AstId: 4
|
||||
pub(self) use globs::*;
|
||||
|
||||
#[doc = " docs on import"]
|
||||
// AstId: 5
|
||||
pub(self) use crate::{A, B};
|
||||
|
||||
// AstId: 6
|
||||
pub(self) use a::{c, d::{e}};
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extern_blocks() {
|
||||
check(
|
||||
r#"
|
||||
#[on_extern_block]
|
||||
extern "C" {
|
||||
#[on_extern_type]
|
||||
type ExType;
|
||||
|
||||
#[on_extern_static]
|
||||
static EX_STATIC: u8;
|
||||
|
||||
#[on_extern_fn]
|
||||
fn ex_fn();
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[on_extern_block]
|
||||
// AstId: 1
|
||||
extern "C" {
|
||||
#[on_extern_type]
|
||||
// AstId: 2
|
||||
pub(self) type ExType;
|
||||
|
||||
#[on_extern_static]
|
||||
// AstId: 3
|
||||
pub(self) static EX_STATIC: u8 = _;
|
||||
|
||||
#[on_extern_fn]
|
||||
// AstId: 4
|
||||
pub(self) fn ex_fn() -> ();
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adts() {
|
||||
check(
|
||||
r#"
|
||||
struct Unit;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Struct {
|
||||
/// fld docs
|
||||
fld: (),
|
||||
}
|
||||
|
||||
struct Tuple(#[attr] u8);
|
||||
|
||||
union Ize {
|
||||
a: (),
|
||||
b: (),
|
||||
}
|
||||
|
||||
enum E {
|
||||
/// comment on Unit
|
||||
Unit,
|
||||
/// comment on Tuple
|
||||
Tuple(u8),
|
||||
Struct {
|
||||
/// comment on a: u8
|
||||
a: u8,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
// AstId: 1
|
||||
pub(self) struct Unit;
|
||||
|
||||
#[derive(Debug)]
|
||||
// AstId: 2
|
||||
pub(self) struct Struct {
|
||||
// AstId: 6
|
||||
#[doc = " fld docs"]
|
||||
pub(self) fld: (),
|
||||
}
|
||||
|
||||
// AstId: 3
|
||||
pub(self) struct Tuple(
|
||||
// AstId: 7
|
||||
#[attr]
|
||||
pub(self) 0: u8,
|
||||
);
|
||||
|
||||
// AstId: 4
|
||||
pub(self) union Ize {
|
||||
// AstId: 8
|
||||
pub(self) a: (),
|
||||
// AstId: 9
|
||||
pub(self) b: (),
|
||||
}
|
||||
|
||||
// AstId: 5
|
||||
pub(self) enum E {
|
||||
// AstId: 10
|
||||
#[doc = " comment on Unit"]
|
||||
Unit,
|
||||
// AstId: 11
|
||||
#[doc = " comment on Tuple"]
|
||||
Tuple(
|
||||
// AstId: 13
|
||||
pub(self) 0: u8,
|
||||
),
|
||||
// AstId: 12
|
||||
Struct {
|
||||
// AstId: 14
|
||||
#[doc = " comment on a: u8"]
|
||||
pub(self) a: u8,
|
||||
},
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn misc() {
|
||||
check(
|
||||
r#"
|
||||
pub static mut ST: () = ();
|
||||
|
||||
const _: Anon = ();
|
||||
|
||||
#[attr]
|
||||
fn f(#[attr] arg: u8, _: ()) {
|
||||
#![inner_attr_in_fn]
|
||||
}
|
||||
|
||||
trait Tr: SuperTrait + 'lifetime {
|
||||
type Assoc: AssocBound = Default;
|
||||
fn method(&self);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
// AstId: 1
|
||||
pub static mut ST: () = _;
|
||||
|
||||
// AstId: 2
|
||||
pub(self) const _: Anon = _;
|
||||
|
||||
#[attr]
|
||||
#[inner_attr_in_fn]
|
||||
// AstId: 3
|
||||
pub(self) fn f(
|
||||
#[attr]
|
||||
// AstId: 5
|
||||
u8,
|
||||
// AstId: 6
|
||||
(),
|
||||
) -> () { ... }
|
||||
|
||||
// AstId: 4
|
||||
pub(self) trait Tr<Self>
|
||||
where
|
||||
Self: SuperTrait,
|
||||
Self: 'lifetime
|
||||
{
|
||||
// AstId: 8
|
||||
pub(self) type Assoc: AssocBound = Default;
|
||||
|
||||
// AstId: 9
|
||||
pub(self) fn method(
|
||||
// AstId: 10
|
||||
self: &Self,
|
||||
) -> ();
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modules() {
|
||||
check(
|
||||
r#"
|
||||
/// outer
|
||||
mod inline {
|
||||
//! inner
|
||||
|
||||
use super::*;
|
||||
|
||||
fn fn_in_module() {}
|
||||
}
|
||||
|
||||
mod outline;
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[doc = " outer"]
|
||||
#[doc = " inner"]
|
||||
// AstId: 1
|
||||
pub(self) mod inline {
|
||||
// AstId: 3
|
||||
pub(self) use super::*;
|
||||
|
||||
// AstId: 4
|
||||
pub(self) fn fn_in_module() -> () { ... }
|
||||
}
|
||||
|
||||
// AstId: 2
|
||||
pub(self) mod outline;
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macros() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
() => {};
|
||||
}
|
||||
|
||||
pub macro m2() {}
|
||||
|
||||
m!();
|
||||
"#,
|
||||
expect![[r#"
|
||||
// AstId: 1
|
||||
macro_rules! m { ... }
|
||||
|
||||
// AstId: 2
|
||||
pub macro m2 { ... }
|
||||
|
||||
// AstId: 3, SyntaxContext: 0, ExpandTo: Items
|
||||
m!(...);
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mod_paths() {
|
||||
check(
|
||||
r#"
|
||||
struct S {
|
||||
a: self::Ty,
|
||||
b: super::SuperTy,
|
||||
c: super::super::SuperSuperTy,
|
||||
d: ::abs::Path,
|
||||
e: crate::Crate,
|
||||
f: plain::path::Ty,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
// AstId: 1
|
||||
pub(self) struct S {
|
||||
// AstId: 2
|
||||
pub(self) a: self::Ty,
|
||||
// AstId: 3
|
||||
pub(self) b: super::SuperTy,
|
||||
// AstId: 4
|
||||
pub(self) c: super::super::SuperSuperTy,
|
||||
// AstId: 5
|
||||
pub(self) d: ::abs::Path,
|
||||
// AstId: 6
|
||||
pub(self) e: crate::Crate,
|
||||
// AstId: 7
|
||||
pub(self) f: plain::path::Ty,
|
||||
}
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn types() {
|
||||
check(
|
||||
r#"
|
||||
struct S {
|
||||
a: Mixed<'a, T, Item=(), OtherItem=u8>,
|
||||
b: <Fully as Qualified>::Syntax,
|
||||
c: <TypeAnchored>::Path::<'a>,
|
||||
d: dyn for<'a> Trait<'a>,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
// AstId: 1
|
||||
pub(self) struct S {
|
||||
// AstId: 2
|
||||
pub(self) a: Mixed::<'a, T, Item = (), OtherItem = u8>,
|
||||
// AstId: 3
|
||||
pub(self) b: Qualified::<Self=Fully>::Syntax,
|
||||
// AstId: 4
|
||||
pub(self) c: <TypeAnchored>::Path::<'a>,
|
||||
// AstId: 5
|
||||
pub(self) d: dyn for<'a> Trait::<'a>,
|
||||
}
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generics() {
|
||||
check(
|
||||
r#"
|
||||
struct S<'a, 'b: 'a, T: Copy + 'a + 'b, const K: u8 = 0> {
|
||||
field: &'a &'b T,
|
||||
}
|
||||
|
||||
struct Tuple<T: Copy, U: ?Sized>(T, U);
|
||||
|
||||
impl<'a, 'b: 'a, T: Copy + 'a + 'b, const K: u8 = 0> S<'a, 'b, T, K> {
|
||||
fn f<G: 'a>(arg: impl Copy) -> impl Copy {}
|
||||
}
|
||||
|
||||
enum Enum<'a, T, const U: u8> {}
|
||||
union Union<'a, T, const U: u8> {}
|
||||
|
||||
trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
// AstId: 1
|
||||
pub(self) struct S<'a, 'b, T, const K: u8>
|
||||
where
|
||||
T: Copy,
|
||||
T: 'a,
|
||||
T: 'b
|
||||
{
|
||||
// AstId: 8
|
||||
pub(self) field: &'a &'b T,
|
||||
}
|
||||
|
||||
// AstId: 2
|
||||
pub(self) struct Tuple<T, U>(
|
||||
// AstId: 9
|
||||
pub(self) 0: T,
|
||||
// AstId: 10
|
||||
pub(self) 1: U,
|
||||
)
|
||||
where
|
||||
T: Copy,
|
||||
U: ?Sized;
|
||||
|
||||
// AstId: 3
|
||||
impl<'a, 'b, T, const K: u8> S::<'a, 'b, T, K>
|
||||
where
|
||||
T: Copy,
|
||||
T: 'a,
|
||||
T: 'b
|
||||
{
|
||||
// AstId: 12
|
||||
pub(self) fn f<G>(
|
||||
// AstId: 13
|
||||
impl Copy,
|
||||
) -> impl Copy
|
||||
where
|
||||
G: 'a { ... }
|
||||
}
|
||||
|
||||
// AstId: 4
|
||||
pub(self) enum Enum<'a, T, const U: u8> {
|
||||
}
|
||||
|
||||
// AstId: 5
|
||||
pub(self) union Union<'a, T, const U: u8> {
|
||||
}
|
||||
|
||||
// AstId: 6
|
||||
pub(self) trait Tr<'a, Self, T>
|
||||
where
|
||||
Self: Super,
|
||||
T: 'a,
|
||||
Self: for<'a> Tr::<'a, T>
|
||||
{
|
||||
}
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generics_with_attributes() {
|
||||
check(
|
||||
r#"
|
||||
struct S<#[cfg(never)] T>;
|
||||
"#,
|
||||
expect![[r#"
|
||||
// AstId: 1
|
||||
pub(self) struct S<#[cfg(never)] T>;
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pub_self() {
|
||||
check(
|
||||
r#"
|
||||
pub(self) struct S;
|
||||
"#,
|
||||
expect![[r#"
|
||||
// AstId: 1
|
||||
pub(self) struct S;
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
485
src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs
Normal file
485
src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
//! Collects lang items: items marked with `#[lang = "..."]` attribute.
|
||||
//!
|
||||
//! This attribute to tell the compiler about semi built-in std library
|
||||
//! features, such as Fn family of traits.
|
||||
use hir_expand::name::Name;
|
||||
use rustc_hash::FxHashMap;
|
||||
use syntax::SmolStr;
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase, path::Path, AdtId, AssocItemId, AttrDefId, CrateId, EnumId, EnumVariantId,
|
||||
FunctionId, ImplId, ModuleDefId, StaticId, StructId, TraitId, TypeAliasId, UnionId,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum LangItemTarget {
|
||||
EnumId(EnumId),
|
||||
Function(FunctionId),
|
||||
ImplDef(ImplId),
|
||||
Static(StaticId),
|
||||
Struct(StructId),
|
||||
Union(UnionId),
|
||||
TypeAlias(TypeAliasId),
|
||||
Trait(TraitId),
|
||||
EnumVariant(EnumVariantId),
|
||||
}
|
||||
|
||||
impl LangItemTarget {
|
||||
pub fn as_enum(self) -> Option<EnumId> {
|
||||
match self {
|
||||
LangItemTarget::EnumId(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_function(self) -> Option<FunctionId> {
|
||||
match self {
|
||||
LangItemTarget::Function(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_impl_def(self) -> Option<ImplId> {
|
||||
match self {
|
||||
LangItemTarget::ImplDef(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_static(self) -> Option<StaticId> {
|
||||
match self {
|
||||
LangItemTarget::Static(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_struct(self) -> Option<StructId> {
|
||||
match self {
|
||||
LangItemTarget::Struct(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_trait(self) -> Option<TraitId> {
|
||||
match self {
|
||||
LangItemTarget::Trait(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_enum_variant(self) -> Option<EnumVariantId> {
|
||||
match self {
|
||||
LangItemTarget::EnumVariant(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct LangItems {
|
||||
items: FxHashMap<LangItem, LangItemTarget>,
|
||||
}
|
||||
|
||||
impl LangItems {
|
||||
pub fn target(&self, item: LangItem) -> Option<LangItemTarget> {
|
||||
self.items.get(&item).copied()
|
||||
}
|
||||
|
||||
/// Salsa query. This will look for lang items in a specific crate.
|
||||
pub(crate) fn crate_lang_items_query(
|
||||
db: &dyn DefDatabase,
|
||||
krate: CrateId,
|
||||
) -> Option<Arc<LangItems>> {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "crate_lang_items_query").entered();
|
||||
|
||||
let mut lang_items = LangItems::default();
|
||||
|
||||
let crate_def_map = db.crate_def_map(krate);
|
||||
|
||||
for (_, module_data) in crate_def_map.modules() {
|
||||
for impl_def in module_data.scope.impls() {
|
||||
lang_items.collect_lang_item(db, impl_def, LangItemTarget::ImplDef);
|
||||
for assoc in db.impl_data(impl_def).items.iter().copied() {
|
||||
match assoc {
|
||||
AssocItemId::FunctionId(f) => {
|
||||
lang_items.collect_lang_item(db, f, LangItemTarget::Function)
|
||||
}
|
||||
AssocItemId::TypeAliasId(t) => {
|
||||
lang_items.collect_lang_item(db, t, LangItemTarget::TypeAlias)
|
||||
}
|
||||
AssocItemId::ConstId(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for def in module_data.scope.declarations() {
|
||||
match def {
|
||||
ModuleDefId::TraitId(trait_) => {
|
||||
lang_items.collect_lang_item(db, trait_, LangItemTarget::Trait);
|
||||
db.trait_data(trait_).items.iter().for_each(|&(_, assoc_id)| {
|
||||
if let AssocItemId::FunctionId(f) = assoc_id {
|
||||
lang_items.collect_lang_item(db, f, LangItemTarget::Function);
|
||||
}
|
||||
});
|
||||
}
|
||||
ModuleDefId::AdtId(AdtId::EnumId(e)) => {
|
||||
lang_items.collect_lang_item(db, e, LangItemTarget::EnumId);
|
||||
crate_def_map.enum_definitions[&e].iter().for_each(|&id| {
|
||||
lang_items.collect_lang_item(db, id, LangItemTarget::EnumVariant);
|
||||
});
|
||||
}
|
||||
ModuleDefId::AdtId(AdtId::StructId(s)) => {
|
||||
lang_items.collect_lang_item(db, s, LangItemTarget::Struct);
|
||||
}
|
||||
ModuleDefId::AdtId(AdtId::UnionId(u)) => {
|
||||
lang_items.collect_lang_item(db, u, LangItemTarget::Union);
|
||||
}
|
||||
ModuleDefId::FunctionId(f) => {
|
||||
lang_items.collect_lang_item(db, f, LangItemTarget::Function);
|
||||
}
|
||||
ModuleDefId::StaticId(s) => {
|
||||
lang_items.collect_lang_item(db, s, LangItemTarget::Static);
|
||||
}
|
||||
ModuleDefId::TypeAliasId(t) => {
|
||||
lang_items.collect_lang_item(db, t, LangItemTarget::TypeAlias);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if lang_items.items.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Arc::new(lang_items))
|
||||
}
|
||||
}
|
||||
|
||||
/// Salsa query. Look for a lang item, starting from the specified crate and recursively
|
||||
/// traversing its dependencies.
|
||||
pub(crate) fn lang_item_query(
|
||||
db: &dyn DefDatabase,
|
||||
start_crate: CrateId,
|
||||
item: LangItem,
|
||||
) -> Option<LangItemTarget> {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "lang_item_query").entered();
|
||||
if let Some(target) =
|
||||
db.crate_lang_items(start_crate).and_then(|it| it.items.get(&item).copied())
|
||||
{
|
||||
return Some(target);
|
||||
}
|
||||
db.crate_graph()[start_crate]
|
||||
.dependencies
|
||||
.iter()
|
||||
.find_map(|dep| db.lang_item(dep.crate_id, item))
|
||||
}
|
||||
|
||||
fn collect_lang_item<T>(
|
||||
&mut self,
|
||||
db: &dyn DefDatabase,
|
||||
item: T,
|
||||
constructor: fn(T) -> LangItemTarget,
|
||||
) where
|
||||
T: Into<AttrDefId> + Copy,
|
||||
{
|
||||
let _p = tracing::span!(tracing::Level::INFO, "collect_lang_item").entered();
|
||||
if let Some(lang_item) = lang_attr(db, item.into()) {
|
||||
self.items.entry(lang_item).or_insert_with(|| constructor(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lang_attr(db: &dyn DefDatabase, item: AttrDefId) -> Option<LangItem> {
|
||||
let attrs = db.attrs(item);
|
||||
attrs.by_key("lang").string_value().and_then(LangItem::from_str)
|
||||
}
|
||||
|
||||
pub(crate) fn notable_traits_in_deps(
|
||||
db: &dyn DefDatabase,
|
||||
krate: CrateId,
|
||||
) -> Arc<[Arc<[TraitId]>]> {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "notable_traits_in_deps", ?krate).entered();
|
||||
let crate_graph = db.crate_graph();
|
||||
|
||||
Arc::from_iter(
|
||||
crate_graph.transitive_deps(krate).filter_map(|krate| db.crate_notable_traits(krate)),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn crate_notable_traits(db: &dyn DefDatabase, krate: CrateId) -> Option<Arc<[TraitId]>> {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "crate_notable_traits", ?krate).entered();
|
||||
|
||||
let mut traits = Vec::new();
|
||||
|
||||
let crate_def_map = db.crate_def_map(krate);
|
||||
|
||||
for (_, module_data) in crate_def_map.modules() {
|
||||
for def in module_data.scope.declarations() {
|
||||
if let ModuleDefId::TraitId(trait_) = def {
|
||||
if db.attrs(trait_.into()).has_doc_notable_trait() {
|
||||
traits.push(trait_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if traits.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(traits.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum GenericRequirement {
|
||||
None,
|
||||
Minimum(usize),
|
||||
Exact(usize),
|
||||
}
|
||||
|
||||
macro_rules! language_item_table {
|
||||
(
|
||||
$( $(#[$attr:meta])* $variant:ident, $module:ident :: $name:ident, $method:ident, $target:expr, $generics:expr; )*
|
||||
) => {
|
||||
|
||||
/// A representation of all the valid language items in Rust.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum LangItem {
|
||||
$(
|
||||
#[doc = concat!("The `", stringify!($name), "` lang item.")]
|
||||
$(#[$attr])*
|
||||
$variant,
|
||||
)*
|
||||
}
|
||||
|
||||
impl LangItem {
|
||||
pub fn name(self) -> SmolStr {
|
||||
match self {
|
||||
$( LangItem::$variant => SmolStr::new(stringify!($name)), )*
|
||||
}
|
||||
}
|
||||
|
||||
/// Opposite of [`LangItem::name`]
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(name: &str) -> Option<Self> {
|
||||
match name {
|
||||
$( stringify!($name) => Some(LangItem::$variant), )*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LangItem {
|
||||
/// Opposite of [`LangItem::name`]
|
||||
pub fn from_name(name: &hir_expand::name::Name) -> Option<Self> {
|
||||
Self::from_str(name.as_str()?)
|
||||
}
|
||||
|
||||
pub fn path(&self, db: &dyn DefDatabase, start_crate: CrateId) -> Option<Path> {
|
||||
let t = db.lang_item(start_crate, *self)?;
|
||||
Some(Path::LangItem(t, None))
|
||||
}
|
||||
|
||||
pub fn ty_rel_path(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
start_crate: CrateId,
|
||||
seg: Name,
|
||||
) -> Option<Path> {
|
||||
let t = db.lang_item(start_crate, *self)?;
|
||||
Some(Path::LangItem(t, Some(seg)))
|
||||
}
|
||||
}
|
||||
|
||||
language_item_table! {
|
||||
// Variant name, Name, Getter method name, Target Generic requirements;
|
||||
Sized, sym::sized, sized_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
Unsize, sym::unsize, unsize_trait, Target::Trait, GenericRequirement::Minimum(1);
|
||||
/// Trait injected by `#[derive(PartialEq)]`, (i.e. "Partial EQ").
|
||||
StructuralPeq, sym::structural_peq, structural_peq_trait, Target::Trait, GenericRequirement::None;
|
||||
/// Trait injected by `#[derive(Eq)]`, (i.e. "Total EQ"; no, I will not apologize).
|
||||
StructuralTeq, sym::structural_teq, structural_teq_trait, Target::Trait, GenericRequirement::None;
|
||||
Copy, sym::copy, copy_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
Clone, sym::clone, clone_trait, Target::Trait, GenericRequirement::None;
|
||||
Sync, sym::sync, sync_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
DiscriminantKind, sym::discriminant_kind, discriminant_kind_trait, Target::Trait, GenericRequirement::None;
|
||||
/// The associated item of the [`DiscriminantKind`] trait.
|
||||
Discriminant, sym::discriminant_type, discriminant_type, Target::AssocTy, GenericRequirement::None;
|
||||
|
||||
PointeeTrait, sym::pointee_trait, pointee_trait, Target::Trait, GenericRequirement::None;
|
||||
Metadata, sym::metadata_type, metadata_type, Target::AssocTy, GenericRequirement::None;
|
||||
DynMetadata, sym::dyn_metadata, dyn_metadata, Target::Struct, GenericRequirement::None;
|
||||
|
||||
Freeze, sym::freeze, freeze_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
|
||||
FnPtrTrait, sym::fn_ptr_trait, fn_ptr_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
FnPtrAddr, sym::fn_ptr_addr, fn_ptr_addr, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
|
||||
|
||||
Drop, sym::drop, drop_trait, Target::Trait, GenericRequirement::None;
|
||||
Destruct, sym::destruct, destruct_trait, Target::Trait, GenericRequirement::None;
|
||||
|
||||
CoerceUnsized, sym::coerce_unsized, coerce_unsized_trait, Target::Trait, GenericRequirement::Minimum(1);
|
||||
DispatchFromDyn, sym::dispatch_from_dyn, dispatch_from_dyn_trait, Target::Trait, GenericRequirement::Minimum(1);
|
||||
|
||||
// language items relating to transmutability
|
||||
TransmuteOpts, sym::transmute_opts, transmute_opts, Target::Struct, GenericRequirement::Exact(0);
|
||||
TransmuteTrait, sym::transmute_trait, transmute_trait, Target::Trait, GenericRequirement::Exact(3);
|
||||
|
||||
Add, sym::add, add_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
Sub, sym::sub, sub_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
Mul, sym::mul, mul_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
Div, sym::div, div_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
Rem, sym::rem, rem_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
Neg, sym::neg, neg_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
Not, sym::not, not_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
BitXor, sym::bitxor, bitxor_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
BitAnd, sym::bitand, bitand_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
BitOr, sym::bitor, bitor_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
Shl, sym::shl, shl_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
Shr, sym::shr, shr_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
AddAssign, sym::add_assign, add_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
SubAssign, sym::sub_assign, sub_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
MulAssign, sym::mul_assign, mul_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
DivAssign, sym::div_assign, div_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
RemAssign, sym::rem_assign, rem_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
BitXorAssign, sym::bitxor_assign, bitxor_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
BitAndAssign, sym::bitand_assign, bitand_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
BitOrAssign, sym::bitor_assign, bitor_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
ShlAssign, sym::shl_assign, shl_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
ShrAssign, sym::shr_assign, shr_assign_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
Index, sym::index, index_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
IndexMut, sym::index_mut, index_mut_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
|
||||
UnsafeCell, sym::unsafe_cell, unsafe_cell_type, Target::Struct, GenericRequirement::None;
|
||||
VaList, sym::va_list, va_list, Target::Struct, GenericRequirement::None;
|
||||
|
||||
Deref, sym::deref, deref_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
DerefMut, sym::deref_mut, deref_mut_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
DerefTarget, sym::deref_target, deref_target, Target::AssocTy, GenericRequirement::None;
|
||||
Receiver, sym::receiver, receiver_trait, Target::Trait, GenericRequirement::None;
|
||||
|
||||
Fn, kw::fn, fn_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
FnMut, sym::fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
FnOnce, sym::fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
|
||||
FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None;
|
||||
|
||||
Future, sym::future_trait, future_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
CoroutineState, sym::coroutine_state, coroutine_state, Target::Enum, GenericRequirement::None;
|
||||
Coroutine, sym::coroutine, coroutine_trait, Target::Trait, GenericRequirement::Minimum(1);
|
||||
Unpin, sym::unpin, unpin_trait, Target::Trait, GenericRequirement::None;
|
||||
Pin, sym::pin, pin_type, Target::Struct, GenericRequirement::None;
|
||||
|
||||
PartialEq, sym::eq, eq_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
CVoid, sym::c_void, c_void, Target::Enum, GenericRequirement::None;
|
||||
|
||||
// A number of panic-related lang items. The `panic` item corresponds to divide-by-zero and
|
||||
// various panic cases with `match`. The `panic_bounds_check` item is for indexing arrays.
|
||||
//
|
||||
// The `begin_unwind` lang item has a predefined symbol name and is sort of a "weak lang item"
|
||||
// in the sense that a crate is not required to have it defined to use it, but a final product
|
||||
// is required to define it somewhere. Additionally, there are restrictions on crates that use
|
||||
// a weak lang item, but do not have it defined.
|
||||
Panic, sym::panic, panic_fn, Target::Fn, GenericRequirement::Exact(0);
|
||||
PanicNounwind, sym::panic_nounwind, panic_nounwind, Target::Fn, GenericRequirement::Exact(0);
|
||||
PanicFmt, sym::panic_fmt, panic_fmt, Target::Fn, GenericRequirement::None;
|
||||
PanicDisplay, sym::panic_display, panic_display, Target::Fn, GenericRequirement::None;
|
||||
ConstPanicFmt, sym::const_panic_fmt, const_panic_fmt, Target::Fn, GenericRequirement::None;
|
||||
PanicBoundsCheck, sym::panic_bounds_check, panic_bounds_check_fn, Target::Fn, GenericRequirement::Exact(0);
|
||||
PanicMisalignedPointerDereference, sym::panic_misaligned_pointer_dereference, panic_misaligned_pointer_dereference_fn, Target::Fn, GenericRequirement::Exact(0);
|
||||
PanicInfo, sym::panic_info, panic_info, Target::Struct, GenericRequirement::None;
|
||||
PanicLocation, sym::panic_location, panic_location, Target::Struct, GenericRequirement::None;
|
||||
PanicImpl, sym::panic_impl, panic_impl, Target::Fn, GenericRequirement::None;
|
||||
PanicCannotUnwind, sym::panic_cannot_unwind, panic_cannot_unwind, Target::Fn, GenericRequirement::Exact(0);
|
||||
/// libstd panic entry point. Necessary for const eval to be able to catch it
|
||||
BeginPanic, sym::begin_panic, begin_panic_fn, Target::Fn, GenericRequirement::None;
|
||||
|
||||
// Lang items needed for `format_args!()`.
|
||||
FormatAlignment, sym::format_alignment, format_alignment, Target::Enum, GenericRequirement::None;
|
||||
FormatArgument, sym::format_argument, format_argument, Target::Struct, GenericRequirement::None;
|
||||
FormatArguments, sym::format_arguments, format_arguments, Target::Struct, GenericRequirement::None;
|
||||
FormatCount, sym::format_count, format_count, Target::Enum, GenericRequirement::None;
|
||||
FormatPlaceholder, sym::format_placeholder, format_placeholder, Target::Struct, GenericRequirement::None;
|
||||
FormatUnsafeArg, sym::format_unsafe_arg, format_unsafe_arg, Target::Struct, GenericRequirement::None;
|
||||
|
||||
ExchangeMalloc, sym::exchange_malloc, exchange_malloc_fn, Target::Fn, GenericRequirement::None;
|
||||
BoxFree, sym::box_free, box_free_fn, Target::Fn, GenericRequirement::Minimum(1);
|
||||
DropInPlace, sym::drop_in_place, drop_in_place_fn, Target::Fn, GenericRequirement::Minimum(1);
|
||||
AllocLayout, sym::alloc_layout, alloc_layout, Target::Struct, GenericRequirement::None;
|
||||
|
||||
Start, sym::start, start_fn, Target::Fn, GenericRequirement::Exact(1);
|
||||
|
||||
EhPersonality, sym::eh_personality, eh_personality, Target::Fn, GenericRequirement::None;
|
||||
EhCatchTypeinfo, sym::eh_catch_typeinfo, eh_catch_typeinfo, Target::Static, GenericRequirement::None;
|
||||
|
||||
OwnedBox, sym::owned_box, owned_box, Target::Struct, GenericRequirement::Minimum(1);
|
||||
|
||||
PhantomData, sym::phantom_data, phantom_data, Target::Struct, GenericRequirement::Exact(1);
|
||||
|
||||
ManuallyDrop, sym::manually_drop, manually_drop, Target::Struct, GenericRequirement::None;
|
||||
|
||||
MaybeUninit, sym::maybe_uninit, maybe_uninit, Target::Union, GenericRequirement::None;
|
||||
|
||||
/// Align offset for stride != 1; must not panic.
|
||||
AlignOffset, sym::align_offset, align_offset_fn, Target::Fn, GenericRequirement::None;
|
||||
|
||||
Termination, sym::termination, termination, Target::Trait, GenericRequirement::None;
|
||||
|
||||
Try, sym::Try, try_trait, Target::Trait, GenericRequirement::None;
|
||||
|
||||
Tuple, sym::tuple_trait, tuple_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
|
||||
SliceLen, sym::slice_len_fn, slice_len_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None;
|
||||
|
||||
// Language items from AST lowering
|
||||
TryTraitFromResidual, sym::from_residual, from_residual_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
|
||||
TryTraitFromOutput, sym::from_output, from_output_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
|
||||
TryTraitBranch, sym::branch, branch_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
|
||||
TryTraitFromYeet, sym::from_yeet, from_yeet_fn, Target::Fn, GenericRequirement::None;
|
||||
|
||||
PointerLike, sym::pointer_like, pointer_like, Target::Trait, GenericRequirement::Exact(0);
|
||||
|
||||
ConstParamTy, sym::const_param_ty, const_param_ty_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||
|
||||
Poll, sym::Poll, poll, Target::Enum, GenericRequirement::None;
|
||||
PollReady, sym::Ready, poll_ready_variant, Target::Variant, GenericRequirement::None;
|
||||
PollPending, sym::Pending, poll_pending_variant, Target::Variant, GenericRequirement::None;
|
||||
|
||||
// FIXME(swatinem): the following lang items are used for async lowering and
|
||||
// should become obsolete eventually.
|
||||
ResumeTy, sym::ResumeTy, resume_ty, Target::Struct, GenericRequirement::None;
|
||||
GetContext, sym::get_context, get_context_fn, Target::Fn, GenericRequirement::None;
|
||||
|
||||
Context, sym::Context, context, Target::Struct, GenericRequirement::None;
|
||||
FuturePoll, sym::poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
|
||||
|
||||
Option, sym::Option, option_type, Target::Enum, GenericRequirement::None;
|
||||
OptionSome, sym::Some, option_some_variant, Target::Variant, GenericRequirement::None;
|
||||
OptionNone, sym::None, option_none_variant, Target::Variant, GenericRequirement::None;
|
||||
|
||||
ResultOk, sym::Ok, result_ok_variant, Target::Variant, GenericRequirement::None;
|
||||
ResultErr, sym::Err, result_err_variant, Target::Variant, GenericRequirement::None;
|
||||
|
||||
ControlFlowContinue, sym::Continue, cf_continue_variant, Target::Variant, GenericRequirement::None;
|
||||
ControlFlowBreak, sym::Break, cf_break_variant, Target::Variant, GenericRequirement::None;
|
||||
|
||||
IntoFutureIntoFuture, sym::into_future, into_future_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
|
||||
IntoIterIntoIter, sym::into_iter, into_iter_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
|
||||
IteratorNext, sym::next, next_fn, Target::Method(MethodKind::Trait { body: false}), GenericRequirement::None;
|
||||
|
||||
PinNewUnchecked, sym::new_unchecked, new_unchecked_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None;
|
||||
|
||||
RangeFrom, sym::RangeFrom, range_from_struct, Target::Struct, GenericRequirement::None;
|
||||
RangeFull, sym::RangeFull, range_full_struct, Target::Struct, GenericRequirement::None;
|
||||
RangeInclusiveStruct, sym::RangeInclusive, range_inclusive_struct, Target::Struct, GenericRequirement::None;
|
||||
RangeInclusiveNew, sym::range_inclusive_new, range_inclusive_new_method, Target::Method(MethodKind::Inherent), GenericRequirement::None;
|
||||
Range, sym::Range, range_struct, Target::Struct, GenericRequirement::None;
|
||||
RangeToInclusive, sym::RangeToInclusive, range_to_inclusive_struct, Target::Struct, GenericRequirement::None;
|
||||
RangeTo, sym::RangeTo, range_to_struct, Target::Struct, GenericRequirement::None;
|
||||
|
||||
String, sym::String, string, Target::Struct, GenericRequirement::None;
|
||||
CStr, sym::CStr, c_str, Target::Struct, GenericRequirement::None;
|
||||
}
|
||||
1459
src/tools/rust-analyzer/crates/hir-def/src/lib.rs
Normal file
1459
src/tools/rust-analyzer/crates/hir-def/src/lib.rs
Normal file
File diff suppressed because it is too large
Load diff
48
src/tools/rust-analyzer/crates/hir-def/src/lower.rs
Normal file
48
src/tools/rust-analyzer/crates/hir-def/src/lower.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
//! Context for lowering paths.
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use hir_expand::{
|
||||
span_map::{SpanMap, SpanMapRef},
|
||||
AstId, HirFileId, InFile,
|
||||
};
|
||||
use span::{AstIdMap, AstIdNode};
|
||||
use syntax::ast;
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{db::DefDatabase, path::Path};
|
||||
|
||||
pub struct LowerCtx<'a> {
|
||||
pub db: &'a dyn DefDatabase,
|
||||
file_id: HirFileId,
|
||||
span_map: OnceCell<SpanMap>,
|
||||
ast_id_map: OnceCell<Arc<AstIdMap>>,
|
||||
}
|
||||
|
||||
impl<'a> LowerCtx<'a> {
|
||||
pub fn new(db: &'a dyn DefDatabase, file_id: HirFileId) -> Self {
|
||||
LowerCtx { db, file_id, span_map: OnceCell::new(), ast_id_map: OnceCell::new() }
|
||||
}
|
||||
|
||||
pub fn with_span_map_cell(
|
||||
db: &'a dyn DefDatabase,
|
||||
file_id: HirFileId,
|
||||
span_map: OnceCell<SpanMap>,
|
||||
) -> Self {
|
||||
LowerCtx { db, file_id, span_map, ast_id_map: OnceCell::new() }
|
||||
}
|
||||
|
||||
pub(crate) fn span_map(&self) -> SpanMapRef<'_> {
|
||||
self.span_map.get_or_init(|| self.db.span_map(self.file_id)).as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn lower_path(&self, ast: ast::Path) -> Option<Path> {
|
||||
Path::from_src(self, ast)
|
||||
}
|
||||
|
||||
pub(crate) fn ast_id<N: AstIdNode>(&self, item: &N) -> AstId<N> {
|
||||
InFile::new(
|
||||
self.file_id,
|
||||
self.ast_id_map.get_or_init(|| self.db.ast_id_map(self.file_id)).ast_id(item),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,656 @@
|
|||
//! Tests for `builtin_derive_macro.rs` from `hir_expand`.
|
||||
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::macro_expansion_tests::check;
|
||||
|
||||
#[test]
|
||||
fn test_copy_expand_simple() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, copy
|
||||
#[derive(Copy)]
|
||||
struct Foo;
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[derive(Copy)]
|
||||
struct Foo;
|
||||
|
||||
impl < > $crate::marker::Copy for Foo< > where {}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_expand_in_core() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:core
|
||||
#[rustc_builtin_macro]
|
||||
macro derive {}
|
||||
#[rustc_builtin_macro]
|
||||
macro Copy {}
|
||||
#[derive(Copy)]
|
||||
struct Foo;
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro derive {}
|
||||
#[rustc_builtin_macro]
|
||||
macro Copy {}
|
||||
#[derive(Copy)]
|
||||
struct Foo;
|
||||
|
||||
impl < > $crate::marker::Copy for Foo< > where {}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_expand_with_type_params() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, copy
|
||||
#[derive(Copy)]
|
||||
struct Foo<A, B>;
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[derive(Copy)]
|
||||
struct Foo<A, B>;
|
||||
|
||||
impl <A: $crate::marker::Copy, B: $crate::marker::Copy, > $crate::marker::Copy for Foo<A, B, > where {}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_expand_with_lifetimes() {
|
||||
// We currently just ignore lifetimes
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, copy
|
||||
#[derive(Copy)]
|
||||
struct Foo<A, B, 'a, 'b>;
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[derive(Copy)]
|
||||
struct Foo<A, B, 'a, 'b>;
|
||||
|
||||
impl <A: $crate::marker::Copy, B: $crate::marker::Copy, > $crate::marker::Copy for Foo<A, B, > where {}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clone_expand() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, clone
|
||||
#[derive(Clone)]
|
||||
enum Command<A, B> {
|
||||
Move { x: A, y: B },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[derive(Clone)]
|
||||
enum Command<A, B> {
|
||||
Move { x: A, y: B },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
|
||||
impl <A: $crate::clone::Clone, B: $crate::clone::Clone, > $crate::clone::Clone for Command<A, B, > where {
|
||||
fn clone(&self ) -> Self {
|
||||
match self {
|
||||
Command::Move {
|
||||
x: x, y: y,
|
||||
}
|
||||
=>Command::Move {
|
||||
x: x.clone(), y: y.clone(),
|
||||
}
|
||||
, Command::Do(f0, )=>Command::Do(f0.clone(), ), Command::Jump=>Command::Jump,
|
||||
}
|
||||
}
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clone_expand_with_associated_types() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, clone
|
||||
trait Trait {
|
||||
type InWc;
|
||||
type InFieldQualified;
|
||||
type InFieldShorthand;
|
||||
type InGenericArg;
|
||||
}
|
||||
trait Marker {}
|
||||
struct Vec<T>(T);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Foo<T: Trait>
|
||||
where
|
||||
<T as Trait>::InWc: Marker,
|
||||
{
|
||||
qualified: <T as Trait>::InFieldQualified,
|
||||
shorthand: T::InFieldShorthand,
|
||||
generic: Vec<T::InGenericArg>,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
trait Trait {
|
||||
type InWc;
|
||||
type InFieldQualified;
|
||||
type InFieldShorthand;
|
||||
type InGenericArg;
|
||||
}
|
||||
trait Marker {}
|
||||
struct Vec<T>(T);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Foo<T: Trait>
|
||||
where
|
||||
<T as Trait>::InWc: Marker,
|
||||
{
|
||||
qualified: <T as Trait>::InFieldQualified,
|
||||
shorthand: T::InFieldShorthand,
|
||||
generic: Vec<T::InGenericArg>,
|
||||
}
|
||||
|
||||
impl <T: $crate::clone::Clone, > $crate::clone::Clone for Foo<T, > where <T as Trait>::InWc: Marker, T: Trait, T::InFieldShorthand: $crate::clone::Clone, T::InGenericArg: $crate::clone::Clone, {
|
||||
fn clone(&self ) -> Self {
|
||||
match self {
|
||||
Foo {
|
||||
qualified: qualified, shorthand: shorthand, generic: generic,
|
||||
}
|
||||
=>Foo {
|
||||
qualified: qualified.clone(), shorthand: shorthand.clone(), generic: generic.clone(),
|
||||
}
|
||||
,
|
||||
}
|
||||
}
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clone_expand_with_const_generics() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, clone
|
||||
#[derive(Clone)]
|
||||
struct Foo<const X: usize, T>(u32);
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[derive(Clone)]
|
||||
struct Foo<const X: usize, T>(u32);
|
||||
|
||||
impl <const X: usize, T: $crate::clone::Clone, > $crate::clone::Clone for Foo<X, T, > where {
|
||||
fn clone(&self ) -> Self {
|
||||
match self {
|
||||
Foo(f0, )=>Foo(f0.clone(), ),
|
||||
}
|
||||
}
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_expand() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, default
|
||||
#[derive(Default)]
|
||||
struct Foo {
|
||||
field1: i32,
|
||||
field2: (),
|
||||
}
|
||||
#[derive(Default)]
|
||||
enum Bar {
|
||||
Foo(u8),
|
||||
#[default]
|
||||
Bar,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[derive(Default)]
|
||||
struct Foo {
|
||||
field1: i32,
|
||||
field2: (),
|
||||
}
|
||||
#[derive(Default)]
|
||||
enum Bar {
|
||||
Foo(u8),
|
||||
#[default]
|
||||
Bar,
|
||||
}
|
||||
|
||||
impl < > $crate::default::Default for Foo< > where {
|
||||
fn default() -> Self {
|
||||
Foo {
|
||||
field1: $crate::default::Default::default(), field2: $crate::default::Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl < > $crate::default::Default for Bar< > where {
|
||||
fn default() -> Self {
|
||||
Bar::Bar
|
||||
}
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_eq_expand() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, eq
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
|
||||
impl < > $crate::cmp::PartialEq for Command< > where {
|
||||
fn eq(&self , other: &Self ) -> bool {
|
||||
match (self , other) {
|
||||
(Command::Move {
|
||||
x: x_self, y: y_self,
|
||||
}
|
||||
, Command::Move {
|
||||
x: x_other, y: y_other,
|
||||
}
|
||||
)=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false
|
||||
}
|
||||
}
|
||||
}
|
||||
impl < > $crate::cmp::Eq for Command< > where {}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_eq_expand_with_derive_const() {
|
||||
// FIXME: actually expand with const
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, eq
|
||||
#[derive_const(PartialEq, Eq)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[derive_const(PartialEq, Eq)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
|
||||
impl < > $crate::cmp::PartialEq for Command< > where {
|
||||
fn eq(&self , other: &Self ) -> bool {
|
||||
match (self , other) {
|
||||
(Command::Move {
|
||||
x: x_self, y: y_self,
|
||||
}
|
||||
, Command::Move {
|
||||
x: x_other, y: y_other,
|
||||
}
|
||||
)=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false
|
||||
}
|
||||
}
|
||||
}
|
||||
impl < > $crate::cmp::Eq for Command< > where {}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_ord_expand() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, ord
|
||||
#[derive(PartialOrd, Ord)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[derive(PartialOrd, Ord)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
|
||||
impl < > $crate::cmp::PartialOrd for Command< > where {
|
||||
fn partial_cmp(&self , other: &Self ) -> $crate::option::Option::Option<$crate::cmp::Ordering> {
|
||||
match $crate::intrinsics::discriminant_value(self ).partial_cmp(&$crate::intrinsics::discriminant_value(other)) {
|
||||
$crate::option::Option::Some($crate::cmp::Ordering::Equal)=> {
|
||||
match (self , other) {
|
||||
(Command::Move {
|
||||
x: x_self, y: y_self,
|
||||
}
|
||||
, Command::Move {
|
||||
x: x_other, y: y_other,
|
||||
}
|
||||
)=>match x_self.partial_cmp(&x_other) {
|
||||
$crate::option::Option::Some($crate::cmp::Ordering::Equal)=> {
|
||||
match y_self.partial_cmp(&y_other) {
|
||||
$crate::option::Option::Some($crate::cmp::Ordering::Equal)=> {
|
||||
$crate::option::Option::Some($crate::cmp::Ordering::Equal)
|
||||
}
|
||||
c=>return c,
|
||||
}
|
||||
}
|
||||
c=>return c,
|
||||
}
|
||||
, (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.partial_cmp(&f0_other) {
|
||||
$crate::option::Option::Some($crate::cmp::Ordering::Equal)=> {
|
||||
$crate::option::Option::Some($crate::cmp::Ordering::Equal)
|
||||
}
|
||||
c=>return c,
|
||||
}
|
||||
, (Command::Jump, Command::Jump)=>$crate::option::Option::Some($crate::cmp::Ordering::Equal), _unused=>$crate::option::Option::Some($crate::cmp::Ordering::Equal)
|
||||
}
|
||||
}
|
||||
c=>return c,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl < > $crate::cmp::Ord for Command< > where {
|
||||
fn cmp(&self , other: &Self ) -> $crate::cmp::Ordering {
|
||||
match $crate::intrinsics::discriminant_value(self ).cmp(&$crate::intrinsics::discriminant_value(other)) {
|
||||
$crate::cmp::Ordering::Equal=> {
|
||||
match (self , other) {
|
||||
(Command::Move {
|
||||
x: x_self, y: y_self,
|
||||
}
|
||||
, Command::Move {
|
||||
x: x_other, y: y_other,
|
||||
}
|
||||
)=>match x_self.cmp(&x_other) {
|
||||
$crate::cmp::Ordering::Equal=> {
|
||||
match y_self.cmp(&y_other) {
|
||||
$crate::cmp::Ordering::Equal=> {
|
||||
$crate::cmp::Ordering::Equal
|
||||
}
|
||||
c=>return c,
|
||||
}
|
||||
}
|
||||
c=>return c,
|
||||
}
|
||||
, (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.cmp(&f0_other) {
|
||||
$crate::cmp::Ordering::Equal=> {
|
||||
$crate::cmp::Ordering::Equal
|
||||
}
|
||||
c=>return c,
|
||||
}
|
||||
, (Command::Jump, Command::Jump)=>$crate::cmp::Ordering::Equal, _unused=>$crate::cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
c=>return c,
|
||||
}
|
||||
}
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_expand() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, hash
|
||||
use core::hash::Hash;
|
||||
|
||||
#[derive(Hash)]
|
||||
struct Foo {
|
||||
x: i32,
|
||||
y: u64,
|
||||
z: (i32, u64),
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
use core::hash::Hash;
|
||||
|
||||
#[derive(Hash)]
|
||||
struct Foo {
|
||||
x: i32,
|
||||
y: u64,
|
||||
z: (i32, u64),
|
||||
}
|
||||
|
||||
impl < > $crate::hash::Hash for Foo< > where {
|
||||
fn hash<H: $crate::hash::Hasher>(&self , ra_expand_state: &mut H) {
|
||||
match self {
|
||||
Foo {
|
||||
x: x, y: y, z: z,
|
||||
}
|
||||
=> {
|
||||
x.hash(ra_expand_state);
|
||||
y.hash(ra_expand_state);
|
||||
z.hash(ra_expand_state);
|
||||
}
|
||||
,
|
||||
}
|
||||
}
|
||||
}"#]],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, hash
|
||||
use core::hash::Hash;
|
||||
|
||||
#[derive(Hash)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
use core::hash::Hash;
|
||||
|
||||
#[derive(Hash)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
|
||||
impl < > $crate::hash::Hash for Command< > where {
|
||||
fn hash<H: $crate::hash::Hasher>(&self , ra_expand_state: &mut H) {
|
||||
$crate::mem::discriminant(self ).hash(ra_expand_state);
|
||||
match self {
|
||||
Command::Move {
|
||||
x: x, y: y,
|
||||
}
|
||||
=> {
|
||||
x.hash(ra_expand_state);
|
||||
y.hash(ra_expand_state);
|
||||
}
|
||||
, Command::Do(f0, )=> {
|
||||
f0.hash(ra_expand_state);
|
||||
}
|
||||
, Command::Jump=> {}
|
||||
,
|
||||
}
|
||||
}
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_expand() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, fmt
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Command {
|
||||
Move { x: i32, y: i32 },
|
||||
Do(&'static str),
|
||||
Jump,
|
||||
}
|
||||
|
||||
impl < > $crate::fmt::Debug for Command< > where {
|
||||
fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result {
|
||||
match self {
|
||||
Command::Move {
|
||||
x: x, y: y,
|
||||
}
|
||||
=>f.debug_struct("Move").field("x", &x).field("y", &y).finish(), Command::Do(f0, )=>f.debug_tuple("Do").field(&f0).finish(), Command::Jump=>f.write_str("Jump"),
|
||||
}
|
||||
}
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_debug_expand_with_cfg() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, fmt
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HideAndShow {
|
||||
#[cfg(never)]
|
||||
always_hide: u32,
|
||||
#[cfg(not(never))]
|
||||
always_show: u32,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
enum HideAndShowEnum {
|
||||
#[cfg(never)]
|
||||
AlwaysHide,
|
||||
#[cfg(not(never))]
|
||||
AlwaysShow{
|
||||
#[cfg(never)]
|
||||
always_hide: u32,
|
||||
#[cfg(not(never))]
|
||||
always_show: u32,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HideAndShow {
|
||||
#[cfg(never)]
|
||||
always_hide: u32,
|
||||
#[cfg(not(never))]
|
||||
always_show: u32,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
enum HideAndShowEnum {
|
||||
#[cfg(never)]
|
||||
AlwaysHide,
|
||||
#[cfg(not(never))]
|
||||
AlwaysShow{
|
||||
#[cfg(never)]
|
||||
always_hide: u32,
|
||||
#[cfg(not(never))]
|
||||
always_show: u32,
|
||||
}
|
||||
}
|
||||
|
||||
impl < > $crate::fmt::Debug for HideAndShow< > where {
|
||||
fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result {
|
||||
match self {
|
||||
HideAndShow {
|
||||
always_show: always_show,
|
||||
}
|
||||
=>f.debug_struct("HideAndShow").field("always_show", &always_show).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl < > $crate::fmt::Debug for HideAndShowEnum< > where {
|
||||
fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result {
|
||||
match self {
|
||||
HideAndShowEnum::AlwaysShow {
|
||||
always_show: always_show,
|
||||
}
|
||||
=>f.debug_struct("AlwaysShow").field("always_show", &always_show).finish(),
|
||||
}
|
||||
}
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_default_expand_with_cfg() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: derive, default
|
||||
#[derive(Default)]
|
||||
struct Foo {
|
||||
field1: i32,
|
||||
#[cfg(never)]
|
||||
field2: (),
|
||||
#[cfg(feature = "never")]
|
||||
field3: (),
|
||||
#[cfg(not(feature = "never"))]
|
||||
field4: (),
|
||||
}
|
||||
#[derive(Default)]
|
||||
enum Bar {
|
||||
Foo,
|
||||
#[cfg_attr(not(never), default)]
|
||||
Bar,
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[derive(Default)]
|
||||
struct Foo {
|
||||
field1: i32,
|
||||
#[cfg(never)]
|
||||
field2: (),
|
||||
#[cfg(feature = "never")]
|
||||
field3: (),
|
||||
#[cfg(not(feature = "never"))]
|
||||
field4: (),
|
||||
}
|
||||
#[derive(Default)]
|
||||
enum Bar {
|
||||
Foo,
|
||||
#[cfg_attr(not(never), default)]
|
||||
Bar,
|
||||
}
|
||||
|
||||
impl < > $crate::default::Default for Foo< > where {
|
||||
fn default() -> Self {
|
||||
Foo {
|
||||
field1: $crate::default::Default::default(), field4: $crate::default::Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl < > $crate::default::Default for Bar< > where {
|
||||
fn default() -> Self {
|
||||
Bar::Bar
|
||||
}
|
||||
}"##]],
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,534 @@
|
|||
//! Tests for `builtin_fn_macro.rs` from `hir_expand`.
|
||||
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::macro_expansion_tests::check;
|
||||
|
||||
#[test]
|
||||
fn test_column_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! column {() => {}}
|
||||
|
||||
fn main() { column!(); }
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! column {() => {}}
|
||||
|
||||
fn main() { 0u32; }
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asm_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! asm {() => {}}
|
||||
|
||||
fn main() {
|
||||
let i: u64 = 3;
|
||||
let o: u64;
|
||||
unsafe {
|
||||
asm!(
|
||||
"mov {0}, {1}",
|
||||
"add {0}, 5",
|
||||
out(reg) o,
|
||||
in(reg) i,
|
||||
);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! asm {() => {}}
|
||||
|
||||
fn main() {
|
||||
let i: u64 = 3;
|
||||
let o: u64;
|
||||
unsafe {
|
||||
builtin #asm ( {
|
||||
$crate::format_args!("mov {0}, {1}");
|
||||
$crate::format_args!("add {0}, 5");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! line {() => {}}
|
||||
|
||||
fn main() { line!() }
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! line {() => {}}
|
||||
|
||||
fn main() { 0u32 }
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stringify_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! stringify {() => {}}
|
||||
|
||||
fn main() {
|
||||
stringify!(
|
||||
a
|
||||
b
|
||||
c
|
||||
);
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! stringify {() => {}}
|
||||
|
||||
fn main() {
|
||||
"a b c";
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_env_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! env {() => {}}
|
||||
|
||||
fn main() { env!("TEST_ENV_VAR"); }
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! env {() => {}}
|
||||
|
||||
fn main() { "UNRESOLVED_ENV_VAR"; }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_option_env_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! option_env {() => {}}
|
||||
|
||||
fn main() { option_env!("TEST_ENV_VAR"); }
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! option_env {() => {}}
|
||||
|
||||
fn main() { $crate::option::Option::None:: < &str>; }
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! file {() => {}}
|
||||
|
||||
fn main() { file!(); }
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! file {() => {}}
|
||||
|
||||
fn main() { ""; }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assert_expand() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: assert
|
||||
fn main() {
|
||||
assert!(true, "{} {:?}", arg1(a, b, c), arg2);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn main() {
|
||||
{
|
||||
if !(true ) {
|
||||
$crate::panic::panic_2021!("{} {:?}", arg1(a, b, c), arg2);
|
||||
}
|
||||
};
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
// FIXME: This is the wrong expansion, see FIXME on `builtin_fn_macro::use_panic_2021`
|
||||
#[test]
|
||||
fn test_assert_expand_2015() {
|
||||
check(
|
||||
r#"
|
||||
//- minicore: assert
|
||||
//- /main.rs edition:2015
|
||||
fn main() {
|
||||
assert!(true, "{} {:?}", arg1(a, b, c), arg2);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn main() {
|
||||
{
|
||||
if !(true ) {
|
||||
$crate::panic::panic_2021!("{} {:?}", arg1(a, b, c), arg2);
|
||||
}
|
||||
};
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_error_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! compile_error {
|
||||
($msg:expr) => ({ /* compiler built-in */ });
|
||||
($msg:expr,) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
// This expands to nothing (since it's in item position), but emits an error.
|
||||
compile_error!("error, with an escaped quote: \"");
|
||||
compile_error!(r"this is a raw string");
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! compile_error {
|
||||
($msg:expr) => ({ /* compiler built-in */ });
|
||||
($msg:expr,) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
/* error: error, with an escaped quote: " */
|
||||
/* error: this is a raw string */
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_args_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
format_args!("{} {:?}", arg1(a, b, c), arg2);
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
builtin #format_args ("{} {:?}", arg1(a, b, c), arg2);
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_15002() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
format_args!(x = 2);
|
||||
format_args!/*+errors*/(x =);
|
||||
format_args!/*+errors*/(x =, x = 2);
|
||||
format_args!/*+errors*/("{}", x =);
|
||||
format_args!/*+errors*/(=, "{}", x =);
|
||||
format_args!(x = 2, "{}", 5);
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
builtin #format_args (x = 2);
|
||||
/* parse error: expected expression */
|
||||
builtin #format_args (x = );
|
||||
/* parse error: expected expression */
|
||||
/* parse error: expected R_PAREN */
|
||||
/* parse error: expected expression, item or let statement */
|
||||
builtin #format_args (x = , x = 2);
|
||||
/* parse error: expected expression */
|
||||
builtin #format_args ("{}", x = );
|
||||
/* parse error: expected expression */
|
||||
/* parse error: expected expression */
|
||||
builtin #format_args ( = , "{}", x = );
|
||||
builtin #format_args (x = 2, "{}", 5);
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_args_expand_with_comma_exprs() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
format_args!("{} {:?}", a::<A,B>(), b);
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
builtin #format_args ("{} {:?}", a::<A, B>(), b);
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_args_expand_with_raw_strings() {
|
||||
check(
|
||||
r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
format_args!(
|
||||
r#"{},mismatch,"{}","{}""#,
|
||||
location_csv_pat(db, &analysis, vfs, &sm, pat_id),
|
||||
mismatch.expected.display(db),
|
||||
mismatch.actual.display(db)
|
||||
);
|
||||
}
|
||||
"##,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
builtin #format_args (r#"{},mismatch,"{}","{}""#, location_csv_pat(db, &analysis, vfs, &sm, pat_id), mismatch.expected.display(db), mismatch.actual.display(db));
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_args_expand_eager() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat {}
|
||||
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
format_args!(concat!("xxx{}y", "{:?}zzz"), 2, b);
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat {}
|
||||
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
builtin #format_args (concat!("xxx{}y", "{:?}zzz"), 2, b);
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_args_expand_with_broken_member_access() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ =
|
||||
format_args!/*+errors*/("{} {:?}", a.);
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! format_args {
|
||||
($fmt:expr) => ({ /* compiler built-in */ });
|
||||
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ =
|
||||
/* parse error: expected field name or number */
|
||||
builtin #format_args ("{} {:?}", a.);
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include_bytes_expand() {
|
||||
check(
|
||||
r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! include_bytes {
|
||||
($file:expr) => {{ /* compiler built-in */ }};
|
||||
($file:expr,) => {{ /* compiler built-in */ }};
|
||||
}
|
||||
|
||||
fn main() { include_bytes("foo"); }
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! include_bytes {
|
||||
($file:expr) => {{ /* compiler built-in */ }};
|
||||
($file:expr,) => {{ /* compiler built-in */ }};
|
||||
}
|
||||
|
||||
fn main() { include_bytes("foo"); }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concat_expand() {
|
||||
check(
|
||||
r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat {}
|
||||
|
||||
fn main() { concat!("fo", "o", 0, r#"bar"#, "\n", false, '"', '\0'); }
|
||||
"##,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat {}
|
||||
|
||||
fn main() { "foo0bar\nfalse\"\u{0}"; }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concat_bytes_expand() {
|
||||
check(
|
||||
r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat_bytes {}
|
||||
|
||||
fn main() { concat_bytes!(b'A', b"BC", [68, b'E', 70]); }
|
||||
"##,
|
||||
expect![[r#"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat_bytes {}
|
||||
|
||||
fn main() { [b'A', 66, 67, 68, b'E', 70]; }
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concat_with_captured_expr() {
|
||||
check(
|
||||
r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat {}
|
||||
|
||||
macro_rules! surprise {
|
||||
() => { "s" };
|
||||
}
|
||||
|
||||
fn main() { concat!(surprise!()); }
|
||||
"##,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat {}
|
||||
|
||||
macro_rules! surprise {
|
||||
() => { "s" };
|
||||
}
|
||||
|
||||
fn main() { "s"; }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concat_idents_expand() {
|
||||
check(
|
||||
r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat_idents {}
|
||||
|
||||
fn main() { concat_idents!(foo, bar); }
|
||||
"##,
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! concat_idents {}
|
||||
|
||||
fn main() { foobar; }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,186 @@
|
|||
//! Test that `$var:expr` captures function correctly.
|
||||
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::macro_expansion_tests::check;
|
||||
|
||||
#[test]
|
||||
fn unary_minus_is_a_literal() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($x:literal) => (literal!();); ($x:tt) => (not_a_literal!();); }
|
||||
m!(92);
|
||||
m!(-92);
|
||||
m!(-9.2);
|
||||
m!(--92);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m { ($x:literal) => (literal!();); ($x:tt) => (not_a_literal!();); }
|
||||
literal!();
|
||||
literal!();
|
||||
literal!();
|
||||
/* error: leftover tokens */not_a_literal!();
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_bad_literal() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($i:literal) => {}; }
|
||||
m!(&k");
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m { ($i:literal) => {}; }
|
||||
/* error: expected literal */"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_comments() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m{ ($fmt:expr) => (); }
|
||||
m!(/**/);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m{ ($fmt:expr) => (); }
|
||||
/* error: expected Expr */
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asi() {
|
||||
// Thanks, Christopher!
|
||||
//
|
||||
// https://internals.rust-lang.org/t/understanding-decisions-behind-semicolons/15181/29
|
||||
check(
|
||||
r#"
|
||||
macro_rules! asi { ($($stmt:stmt)*) => ($($stmt)*); }
|
||||
|
||||
fn main() {
|
||||
asi! {
|
||||
let a = 2
|
||||
let b = 5
|
||||
drop(b-a)
|
||||
println!("{}", a+b)
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! asi { ($($stmt:stmt)*) => ($($stmt)*); }
|
||||
|
||||
fn main() {
|
||||
let a = 2 let b = 5 drop(b-a)println!("{}", a+b)
|
||||
}
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stmt_boundaries() {
|
||||
// FIXME: this actually works OK under rustc.
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
($($s:stmt)*) => (stringify!($($s |)*);)
|
||||
}
|
||||
m!(;;92;let x = 92; loop {};);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
($($s:stmt)*) => (stringify!($($s |)*);)
|
||||
}
|
||||
stringify!(;
|
||||
| ;
|
||||
|92| ;
|
||||
|let x = 92| ;
|
||||
|loop {}
|
||||
| ;
|
||||
|);
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_patterns() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
($($p:pat)*) => (stringify!($($p |)*);)
|
||||
}
|
||||
m!(.. .. ..);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
($($p:pat)*) => (stringify!($($p |)*);)
|
||||
}
|
||||
stringify!(.. | .. | .. |);
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_vis() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($($i:ident)? $vis:vis) => () }
|
||||
m!(x pub);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m { ($($i:ident)? $vis:vis) => () }
|
||||
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
// For this test and the one below, see rust-lang/rust#86730.
|
||||
#[test]
|
||||
fn expr_dont_match_let_expr() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo {
|
||||
($e:expr) => { $e }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
foo!(let a = 3);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! foo {
|
||||
($e:expr) => { $e }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
/* error: no rule matches input tokens */missing;
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_dont_match_inline_const() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo {
|
||||
($e:expr) => { $e }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
foo!(const { 3 });
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! foo {
|
||||
($e:expr) => { $e }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
/* error: no rule matches input tokens */missing;
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
//! Test for the syntax of macros themselves.
|
||||
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::macro_expansion_tests::check;
|
||||
|
||||
#[test]
|
||||
fn well_formed_macro_rules() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
($i:ident) => ();
|
||||
($(x),*) => ();
|
||||
($(x)_*) => ();
|
||||
($(x)i*) => ();
|
||||
($($i:ident)*) => ($_);
|
||||
($($true:ident)*) => ($true);
|
||||
($($false:ident)*) => ($false);
|
||||
(double_dollar) => ($$);
|
||||
($) => (m!($););
|
||||
($($t:tt)*) => ($( ${ignore($t)} ${index()} )-*);
|
||||
}
|
||||
m!($);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
($i:ident) => ();
|
||||
($(x),*) => ();
|
||||
($(x)_*) => ();
|
||||
($(x)i*) => ();
|
||||
($($i:ident)*) => ($_);
|
||||
($($true:ident)*) => ($true);
|
||||
($($false:ident)*) => ($false);
|
||||
(double_dollar) => ($$);
|
||||
($) => (m!($););
|
||||
($($t:tt)*) => ($( ${ignore($t)} ${index()} )-*);
|
||||
}
|
||||
m!($);
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_macro_rules() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! i1 { invalid }
|
||||
i1!();
|
||||
|
||||
macro_rules! e1 { $i:ident => () }
|
||||
e1!();
|
||||
macro_rules! e2 { ($i:ident) () }
|
||||
e2!();
|
||||
macro_rules! e3 { ($(i:ident)_) => () }
|
||||
e3!();
|
||||
|
||||
macro_rules! f1 { ($i) => ($i) }
|
||||
f1!();
|
||||
macro_rules! f2 { ($i:) => ($i) }
|
||||
f2!();
|
||||
macro_rules! f3 { ($i:_) => () }
|
||||
f3!();
|
||||
|
||||
macro_rules! m1 { ($$i) => () }
|
||||
m1!();
|
||||
macro_rules! m2 { () => ( ${invalid()} ) }
|
||||
m2!();
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! i1 { invalid }
|
||||
/* error: macro definition has parse errors */
|
||||
|
||||
macro_rules! e1 { $i:ident => () }
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! e2 { ($i:ident) () }
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! e3 { ($(i:ident)_) => () }
|
||||
/* error: macro definition has parse errors */
|
||||
|
||||
macro_rules! f1 { ($i) => ($i) }
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! f2 { ($i:) => ($i) }
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! f3 { ($i:_) => () }
|
||||
/* error: macro definition has parse errors */
|
||||
|
||||
macro_rules! m1 { ($$i) => () }
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! m2 { () => ( ${invalid()} ) }
|
||||
/* error: macro definition has parse errors */
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rustc_issue_57597() {
|
||||
// <https://github.com/rust-lang/rust/blob/master/tests/ui/issues/issue-57597.rs>
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m0 { ($($($i:ident)?)+) => {}; }
|
||||
macro_rules! m1 { ($($($i:ident)?)*) => {}; }
|
||||
macro_rules! m2 { ($($($i:ident)?)?) => {}; }
|
||||
macro_rules! m3 { ($($($($i:ident)?)?)?) => {}; }
|
||||
macro_rules! m4 { ($($($($i:ident)*)?)?) => {}; }
|
||||
macro_rules! m5 { ($($($($i:ident)?)*)?) => {}; }
|
||||
macro_rules! m6 { ($($($($i:ident)?)?)*) => {}; }
|
||||
macro_rules! m7 { ($($($($i:ident)*)*)?) => {}; }
|
||||
macro_rules! m8 { ($($($($i:ident)?)*)*) => {}; }
|
||||
macro_rules! m9 { ($($($($i:ident)?)*)+) => {}; }
|
||||
macro_rules! mA { ($($($($i:ident)+)?)*) => {}; }
|
||||
macro_rules! mB { ($($($($i:ident)+)*)?) => {}; }
|
||||
|
||||
m0!();
|
||||
m1!();
|
||||
m2!();
|
||||
m3!();
|
||||
m4!();
|
||||
m5!();
|
||||
m6!();
|
||||
m7!();
|
||||
m8!();
|
||||
m9!();
|
||||
mA!();
|
||||
mB!();
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m0 { ($($($i:ident)?)+) => {}; }
|
||||
macro_rules! m1 { ($($($i:ident)?)*) => {}; }
|
||||
macro_rules! m2 { ($($($i:ident)?)?) => {}; }
|
||||
macro_rules! m3 { ($($($($i:ident)?)?)?) => {}; }
|
||||
macro_rules! m4 { ($($($($i:ident)*)?)?) => {}; }
|
||||
macro_rules! m5 { ($($($($i:ident)?)*)?) => {}; }
|
||||
macro_rules! m6 { ($($($($i:ident)?)?)*) => {}; }
|
||||
macro_rules! m7 { ($($($($i:ident)*)*)?) => {}; }
|
||||
macro_rules! m8 { ($($($($i:ident)?)*)*) => {}; }
|
||||
macro_rules! m9 { ($($($($i:ident)?)*)+) => {}; }
|
||||
macro_rules! mA { ($($($($i:ident)+)?)*) => {}; }
|
||||
macro_rules! mB { ($($($($i:ident)+)*)?) => {}; }
|
||||
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: macro definition has parse errors */
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
//! Tests for RFC 3086 metavariable expressions.
|
||||
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::macro_expansion_tests::check;
|
||||
|
||||
#[test]
|
||||
fn test_dollar_dollar() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! register_struct { ($Struct:ident) => {
|
||||
macro_rules! register_methods { ($$($method:ident),*) => {
|
||||
macro_rules! implement_methods { ($$$$($$val:expr),*) => {
|
||||
struct $Struct;
|
||||
impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
register_struct!(Foo);
|
||||
register_methods!(alpha, beta);
|
||||
implement_methods!(1, 2, 3);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! register_struct { ($Struct:ident) => {
|
||||
macro_rules! register_methods { ($$($method:ident),*) => {
|
||||
macro_rules! implement_methods { ($$$$($$val:expr),*) => {
|
||||
struct $Struct;
|
||||
impl $Struct { $$(fn $method() -> &'static [u32] { &[$$$$($$$$val),*] })*}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
macro_rules !register_methods {
|
||||
($($method: ident), *) = > {
|
||||
macro_rules!implement_methods {
|
||||
($$($val: expr), *) = > {
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
$(fn $method()-> &'static[u32] {
|
||||
&[$$($$val), *]
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
macro_rules !implement_methods {
|
||||
($($val: expr), *) = > {
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn alpha()-> &'static[u32] {
|
||||
&[$($val), *]
|
||||
}
|
||||
fn beta()-> &'static[u32] {
|
||||
&[$($val), *]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn alpha() -> &'static[u32] {
|
||||
&[1, 2, 3]
|
||||
}
|
||||
fn beta() -> &'static[u32] {
|
||||
&[1, 2, 3]
|
||||
}
|
||||
}
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_metavar_exprs() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
( $( $t:tt )* ) => ( $( ${ignore($t)} -${index()} )-* );
|
||||
}
|
||||
const _: i32 = m!(a b c);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
( $( $t:tt )* ) => ( $( ${ignore($t)} -${index()} )-* );
|
||||
}
|
||||
const _: i32 = -0--1--2;
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_basic() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
($($t:ident),*) => {
|
||||
${count($t)}
|
||||
}
|
||||
}
|
||||
|
||||
fn test() {
|
||||
m!();
|
||||
m!(a);
|
||||
m!(a, a);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
($($t:ident),*) => {
|
||||
${count($t)}
|
||||
}
|
||||
}
|
||||
|
||||
fn test() {
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_with_depth() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo {
|
||||
($( $( $($t:ident)* ),* );*) => {
|
||||
$(
|
||||
{
|
||||
let depth_none = ${count($t)};
|
||||
let depth_zero = ${count($t, 0)};
|
||||
let depth_one = ${count($t, 1)};
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
fn bar() {
|
||||
foo!(
|
||||
a a a, a, a a;
|
||||
a a a
|
||||
)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! foo {
|
||||
($( $( $($t:ident)* ),* );*) => {
|
||||
$(
|
||||
{
|
||||
let depth_none = ${count($t)};
|
||||
let depth_zero = ${count($t, 0)};
|
||||
let depth_one = ${count($t, 1)};
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
fn bar() {
|
||||
{
|
||||
let depth_none = 3;
|
||||
let depth_zero = 3;
|
||||
let depth_one = 6;
|
||||
} {
|
||||
let depth_none = 1;
|
||||
let depth_zero = 1;
|
||||
let depth_one = 3;
|
||||
}
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_depth_out_of_bounds() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo {
|
||||
($($t:ident)*) => { ${count($t, 1)} };
|
||||
($( $( $l:literal )* );*) => { $(${count($l, 1)};)* }
|
||||
}
|
||||
macro_rules! bar {
|
||||
($($t:ident)*) => { ${count($t, 1024)} };
|
||||
($( $( $l:literal )* );*) => { $(${count($l, 8192)};)* }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
foo!(a b);
|
||||
foo!(1 2; 3);
|
||||
bar!(a b);
|
||||
bar!(1 2; 3);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! foo {
|
||||
($($t:ident)*) => { ${count($t, 1)} };
|
||||
($( $( $l:literal )* );*) => { $(${count($l, 1)};)* }
|
||||
}
|
||||
macro_rules! bar {
|
||||
($($t:ident)*) => { ${count($t, 1024)} };
|
||||
($( $( $l:literal )* );*) => { $(${count($l, 8192)};)* }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
2;
|
||||
2;
|
||||
1;;
|
||||
2;
|
||||
2;
|
||||
1;;
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn misplaced_count() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo {
|
||||
($($t:ident)*) => { $(${count($t)})* };
|
||||
($l:literal) => { ${count($l)} }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
foo!(a b c);
|
||||
foo!(1);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! foo {
|
||||
($($t:ident)*) => { $(${count($t)})* };
|
||||
($l:literal) => { ${count($l)} }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
1 1 1;
|
||||
1;
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_count() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! too_many_args {
|
||||
($($t:ident)*) => { ${count($t, 1, leftover)} }
|
||||
}
|
||||
macro_rules! depth_suffixed {
|
||||
($($t:ident)*) => { ${count($t, 0usize)} }
|
||||
}
|
||||
macro_rules! depth_too_large {
|
||||
($($t:ident)*) => { ${count($t, 18446744073709551616)} }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
too_many_args!();
|
||||
depth_suffixed!();
|
||||
depth_too_large!();
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! too_many_args {
|
||||
($($t:ident)*) => { ${count($t, 1, leftover)} }
|
||||
}
|
||||
macro_rules! depth_suffixed {
|
||||
($($t:ident)*) => { ${count($t, 0usize)} }
|
||||
}
|
||||
macro_rules! depth_too_large {
|
||||
($($t:ident)*) => { ${count($t, 18446744073709551616)} }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
/* error: macro definition has parse errors */;
|
||||
/* error: macro definition has parse errors */;
|
||||
/* error: macro definition has parse errors */;
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_interaction_with_empty_binding() {
|
||||
// FIXME: Should this error? rustc currently accepts it.
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
($($t:ident),*) => {
|
||||
${count($t, 100)}
|
||||
}
|
||||
}
|
||||
|
||||
fn test() {
|
||||
m!();
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
($($t:ident),*) => {
|
||||
${count($t, 100)}
|
||||
}
|
||||
}
|
||||
|
||||
fn test() {
|
||||
0;
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,200 @@
|
|||
//! Unlike rustc, rust-analyzer's syntax tree are not "made of" token trees.
|
||||
//! Rather, token trees are an explicit bridge between the parser and
|
||||
//! (procedural or declarative) macros.
|
||||
//!
|
||||
//! This module tests tt <-> syntax tree conversion specifically. In particular,
|
||||
//! it, among other things, check that we convert `tt` to the right kind of
|
||||
//! syntax node depending on the macro call-site.
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::macro_expansion_tests::check;
|
||||
|
||||
#[test]
|
||||
fn round_trips_compound_tokens() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
() => { type qual: ::T = qual::T; }
|
||||
}
|
||||
m!();
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
() => { type qual: ::T = qual::T; }
|
||||
}
|
||||
type qual: ::T = qual::T;
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trips_literals() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
() => {
|
||||
let _ = 'c';
|
||||
let _ = 1000;
|
||||
let _ = 12E+99_f64;
|
||||
let _ = "rust1";
|
||||
let _ = -92;
|
||||
}
|
||||
}
|
||||
fn f() {
|
||||
m!()
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
() => {
|
||||
let _ = 'c';
|
||||
let _ = 1000;
|
||||
let _ = 12E+99_f64;
|
||||
let _ = "rust1";
|
||||
let _ = -92;
|
||||
}
|
||||
}
|
||||
fn f() {
|
||||
let _ = 'c';
|
||||
let _ = 1000;
|
||||
let _ = 12E+99_f64;
|
||||
let _ = "rust1";
|
||||
let _ = -92;
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_lifetime() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
($($t:tt)*) => { $($t)*}
|
||||
}
|
||||
m!(static bar: &'static str = "hello";);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
($($t:tt)*) => { $($t)*}
|
||||
}
|
||||
static bar: &'static str = "hello";
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broken_parenthesis_sequence() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m1 { ($x:ident) => { ($x } }
|
||||
macro_rules! m2 { ($x:ident) => {} }
|
||||
|
||||
m1!();
|
||||
m2!(x
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m1 { ($x:ident) => { ($x } }
|
||||
macro_rules! m2 { ($x:ident) => {} }
|
||||
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: expected ident */
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expansion_does_not_parse_as_expression() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! stmts {
|
||||
() => { fn foo() {} }
|
||||
}
|
||||
|
||||
fn f() { let _ = stmts!/*+errors*/(); }
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! stmts {
|
||||
() => { fn foo() {} }
|
||||
}
|
||||
|
||||
fn f() { let _ = /* parse error: expected expression */
|
||||
fn foo() {}; }
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broken_pat() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m1 { () => (Some(x) left overs) }
|
||||
macro_rules! m2 { () => ($) }
|
||||
|
||||
fn main() {
|
||||
let m1!() = ();
|
||||
let m2!/*+errors*/() = ();
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m1 { () => (Some(x) left overs) }
|
||||
macro_rules! m2 { () => ($) }
|
||||
|
||||
fn main() {
|
||||
let Some(x)left overs = ();
|
||||
let /* parse error: expected pattern */
|
||||
$ = ();
|
||||
}
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_literal_in_tt() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! constant {
|
||||
($( $ret:expr; )*) => {};
|
||||
}
|
||||
macro_rules! float_const_impl {
|
||||
() => ( constant!(0.3; 3.3;); );
|
||||
}
|
||||
float_const_impl! {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! constant {
|
||||
($( $ret:expr; )*) => {};
|
||||
}
|
||||
macro_rules! float_const_impl {
|
||||
() => ( constant!(0.3; 3.3;); );
|
||||
}
|
||||
constant!(0.3;
|
||||
3.3;
|
||||
);
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_literal_in_output() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! constant {
|
||||
($e:expr ;) => {$e};
|
||||
}
|
||||
|
||||
const _: () = constant!(0.0;);
|
||||
const _: () = constant!(0.;);
|
||||
const _: () = constant!(0e0;);
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! constant {
|
||||
($e:expr ;) => {$e};
|
||||
}
|
||||
|
||||
const _: () = 0.0;
|
||||
const _: () = 0.;
|
||||
const _: () = 0e0;
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
//! This module contains tests for macro expansion. Effectively, it covers `tt`,
|
||||
//! `mbe`, `proc_macro_api` and `hir_expand` crates. This might seem like a
|
||||
//! wrong architecture at the first glance, but is intentional.
|
||||
//!
|
||||
//! Physically, macro expansion process is intertwined with name resolution. You
|
||||
//! can not expand *just* the syntax. So, to be able to write integration tests
|
||||
//! of the "expand this code please" form, we have to do it after name
|
||||
//! resolution. That is, in this crate. We *could* fake some dependencies and
|
||||
//! write unit-tests (in fact, we used to do that), but that makes tests brittle
|
||||
//! and harder to understand.
|
||||
|
||||
mod builtin_derive_macro;
|
||||
mod builtin_fn_macro;
|
||||
mod mbe;
|
||||
mod proc_macros;
|
||||
|
||||
use std::{iter, ops::Range, sync};
|
||||
|
||||
use base_db::SourceDatabase;
|
||||
use expect_test::Expect;
|
||||
use hir_expand::{
|
||||
db::ExpandDatabase,
|
||||
proc_macro::{ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind},
|
||||
span_map::SpanMapRef,
|
||||
InFile, MacroFileId, MacroFileIdExt,
|
||||
};
|
||||
use span::Span;
|
||||
use stdx::{format_to, format_to_acc};
|
||||
use syntax::{
|
||||
ast::{self, edit::IndentLevel},
|
||||
AstNode,
|
||||
SyntaxKind::{COMMENT, EOF, IDENT, LIFETIME_IDENT},
|
||||
SyntaxNode, T,
|
||||
};
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
nameres::{DefMap, MacroSubNs, ModuleSource},
|
||||
resolver::HasResolver,
|
||||
src::HasSource,
|
||||
test_db::TestDB,
|
||||
tt::Subtree,
|
||||
AdtId, AsMacroCall, Lookup, ModuleDefId,
|
||||
};
|
||||
|
||||
#[track_caller]
|
||||
fn check(ra_fixture: &str, mut expect: Expect) {
|
||||
let extra_proc_macros = vec![(
|
||||
r#"
|
||||
#[proc_macro_attribute]
|
||||
pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
item
|
||||
}
|
||||
"#
|
||||
.into(),
|
||||
ProcMacro {
|
||||
name: "identity_when_valid".into(),
|
||||
kind: ProcMacroKind::Attr,
|
||||
expander: sync::Arc::new(IdentityWhenValidProcMacroExpander),
|
||||
disabled: false,
|
||||
},
|
||||
)];
|
||||
let db = TestDB::with_files_extra_proc_macros(ra_fixture, extra_proc_macros);
|
||||
let krate = db.crate_graph().iter().next().unwrap();
|
||||
let def_map = db.crate_def_map(krate);
|
||||
let local_id = DefMap::ROOT;
|
||||
let module = def_map.module_id(local_id);
|
||||
let resolver = module.resolver(&db);
|
||||
let source = def_map[local_id].definition_source(&db);
|
||||
let source_file = match source.value {
|
||||
ModuleSource::SourceFile(it) => it,
|
||||
ModuleSource::Module(_) | ModuleSource::BlockExpr(_) => panic!(),
|
||||
};
|
||||
|
||||
// What we want to do is to replace all macros (fn-like, derive, attr) with
|
||||
// their expansions. Turns out, we don't actually store enough information
|
||||
// to do this precisely though! Specifically, if a macro expands to nothing,
|
||||
// it leaves zero traces in def-map, so we can't get its expansion after the
|
||||
// fact.
|
||||
//
|
||||
// This is the usual
|
||||
// <https://github.com/rust-lang/rust-analyzer/issues/3407>
|
||||
// resolve/record tension!
|
||||
//
|
||||
// So here we try to do a resolve, which is necessary a heuristic. For macro
|
||||
// calls, we use `as_call_id_with_errors`. For derives, we look at the impls
|
||||
// in the module and assume that, if impls's source is a different
|
||||
// `HirFileId`, than it came from macro expansion.
|
||||
|
||||
let mut text_edits = Vec::new();
|
||||
let mut expansions = Vec::new();
|
||||
|
||||
for macro_call in source_file.syntax().descendants().filter_map(ast::MacroCall::cast) {
|
||||
let macro_call = InFile::new(source.file_id, ¯o_call);
|
||||
let res = macro_call
|
||||
.as_call_id_with_errors(&db, krate, |path| {
|
||||
resolver
|
||||
.resolve_path_as_macro(&db, &path, Some(MacroSubNs::Bang))
|
||||
.map(|(it, _)| db.macro_def(it))
|
||||
})
|
||||
.unwrap();
|
||||
let macro_call_id = res.value.unwrap();
|
||||
let macro_file = MacroFileId { macro_call_id };
|
||||
let mut expansion_result = db.parse_macro_expansion(macro_file);
|
||||
expansion_result.err = expansion_result.err.or(res.err);
|
||||
expansions.push((macro_call.value.clone(), expansion_result));
|
||||
}
|
||||
|
||||
for (call, exp) in expansions.into_iter().rev() {
|
||||
let mut tree = false;
|
||||
let mut expect_errors = false;
|
||||
let mut show_spans = false;
|
||||
let mut show_ctxt = false;
|
||||
for comment in call.syntax().children_with_tokens().filter(|it| it.kind() == COMMENT) {
|
||||
tree |= comment.to_string().contains("+tree");
|
||||
expect_errors |= comment.to_string().contains("+errors");
|
||||
show_spans |= comment.to_string().contains("+spans");
|
||||
show_ctxt |= comment.to_string().contains("+syntaxctxt");
|
||||
}
|
||||
|
||||
let mut expn_text = String::new();
|
||||
if let Some(err) = exp.err {
|
||||
format_to!(expn_text, "/* error: {} */", err);
|
||||
}
|
||||
let (parse, token_map) = exp.value;
|
||||
if expect_errors {
|
||||
assert!(!parse.errors().is_empty(), "no parse errors in expansion");
|
||||
for e in parse.errors() {
|
||||
format_to!(expn_text, "/* parse error: {} */\n", e);
|
||||
}
|
||||
} else {
|
||||
assert!(
|
||||
parse.errors().is_empty(),
|
||||
"parse errors in expansion: \n{:#?}\n```\n{}\n```",
|
||||
parse.errors(),
|
||||
parse.syntax_node(),
|
||||
);
|
||||
}
|
||||
let pp = pretty_print_macro_expansion(
|
||||
parse.syntax_node(),
|
||||
SpanMapRef::ExpansionSpanMap(&token_map),
|
||||
show_spans,
|
||||
show_ctxt,
|
||||
);
|
||||
let indent = IndentLevel::from_node(call.syntax());
|
||||
let pp = reindent(indent, pp);
|
||||
format_to!(expn_text, "{}", pp);
|
||||
|
||||
if tree {
|
||||
let tree = format!("{:#?}", parse.syntax_node())
|
||||
.split_inclusive('\n')
|
||||
.fold(String::new(), |mut acc, line| format_to_acc!(acc, "// {line}"));
|
||||
format_to!(expn_text, "\n{}", tree)
|
||||
}
|
||||
let range = call.syntax().text_range();
|
||||
let range: Range<usize> = range.into();
|
||||
text_edits.push((range, expn_text));
|
||||
}
|
||||
|
||||
text_edits.sort_by_key(|(range, _)| range.start);
|
||||
text_edits.reverse();
|
||||
let mut expanded_text = source_file.to_string();
|
||||
for (range, text) in text_edits {
|
||||
expanded_text.replace_range(range, &text);
|
||||
}
|
||||
|
||||
for decl_id in def_map[local_id].scope.declarations() {
|
||||
// FIXME: I'm sure there's already better way to do this
|
||||
let src = match decl_id {
|
||||
ModuleDefId::AdtId(AdtId::StructId(struct_id)) => {
|
||||
Some(struct_id.lookup(&db).source(&db).syntax().cloned())
|
||||
}
|
||||
ModuleDefId::FunctionId(function_id) => {
|
||||
Some(function_id.lookup(&db).source(&db).syntax().cloned())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(src) = src {
|
||||
if let Some(file_id) = src.file_id.macro_file() {
|
||||
if file_id.is_attr_macro(&db) || file_id.is_custom_derive(&db) {
|
||||
let call = file_id.call_node(&db);
|
||||
let mut show_spans = false;
|
||||
let mut show_ctxt = false;
|
||||
for comment in
|
||||
call.value.children_with_tokens().filter(|it| it.kind() == COMMENT)
|
||||
{
|
||||
show_spans |= comment.to_string().contains("+spans");
|
||||
show_ctxt |= comment.to_string().contains("+syntaxctxt");
|
||||
}
|
||||
let pp = pretty_print_macro_expansion(
|
||||
src.value,
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
show_spans,
|
||||
show_ctxt,
|
||||
);
|
||||
format_to!(expanded_text, "\n{}", pp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for impl_id in def_map[local_id].scope.impls() {
|
||||
let src = impl_id.lookup(&db).source(&db);
|
||||
if let Some(macro_file) = src.file_id.macro_file() {
|
||||
if macro_file.is_builtin_derive(&db) {
|
||||
let pp = pretty_print_macro_expansion(
|
||||
src.value.syntax().clone(),
|
||||
db.span_map(macro_file.into()).as_ref(),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
format_to!(expanded_text, "\n{}", pp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect.indent(false);
|
||||
expect.assert_eq(&expanded_text);
|
||||
}
|
||||
|
||||
fn reindent(indent: IndentLevel, pp: String) -> String {
|
||||
if !pp.contains('\n') {
|
||||
return pp;
|
||||
}
|
||||
let mut lines = pp.split_inclusive('\n');
|
||||
let mut res = lines.next().unwrap().to_owned();
|
||||
for line in lines {
|
||||
if line.trim().is_empty() {
|
||||
res.push_str(line)
|
||||
} else {
|
||||
format_to!(res, "{}{}", indent, line)
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn pretty_print_macro_expansion(
|
||||
expn: SyntaxNode,
|
||||
map: SpanMapRef<'_>,
|
||||
show_spans: bool,
|
||||
show_ctxt: bool,
|
||||
) -> String {
|
||||
let mut res = String::new();
|
||||
let mut prev_kind = EOF;
|
||||
let mut indent_level = 0;
|
||||
for token in iter::successors(expn.first_token(), |t| t.next_token()) {
|
||||
let curr_kind = token.kind();
|
||||
let space = match (prev_kind, curr_kind) {
|
||||
_ if prev_kind.is_trivia() || curr_kind.is_trivia() => "",
|
||||
_ if prev_kind.is_literal() && !curr_kind.is_punct() => " ",
|
||||
(T!['{'], T!['}']) => "",
|
||||
(T![=], _) | (_, T![=]) => " ",
|
||||
(_, T!['{']) => " ",
|
||||
(T![;] | T!['{'] | T!['}'], _) => "\n",
|
||||
(_, T!['}']) => "\n",
|
||||
(IDENT | LIFETIME_IDENT, IDENT | LIFETIME_IDENT) => " ",
|
||||
_ if prev_kind.is_keyword() && curr_kind.is_keyword() => " ",
|
||||
(IDENT, _) if curr_kind.is_keyword() => " ",
|
||||
(_, IDENT) if prev_kind.is_keyword() => " ",
|
||||
(T![>], IDENT) => " ",
|
||||
(T![>], _) if curr_kind.is_keyword() => " ",
|
||||
(T![->], _) | (_, T![->]) => " ",
|
||||
(T![&&], _) | (_, T![&&]) => " ",
|
||||
(T![,], _) => " ",
|
||||
(T![:], IDENT | T!['(']) => " ",
|
||||
(T![:], _) if curr_kind.is_keyword() => " ",
|
||||
(T![fn], T!['(']) => "",
|
||||
(T![']'], _) if curr_kind.is_keyword() => " ",
|
||||
(T![']'], T![#]) => "\n",
|
||||
(T![Self], T![::]) => "",
|
||||
_ if prev_kind.is_keyword() => " ",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
match prev_kind {
|
||||
T!['{'] => indent_level += 1,
|
||||
T!['}'] => indent_level -= 1,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
res.push_str(space);
|
||||
if space == "\n" {
|
||||
let level = if curr_kind == T!['}'] { indent_level - 1 } else { indent_level };
|
||||
res.push_str(&" ".repeat(level));
|
||||
}
|
||||
prev_kind = curr_kind;
|
||||
format_to!(res, "{}", token);
|
||||
if show_spans || show_ctxt {
|
||||
let span = map.span_for_range(token.text_range());
|
||||
format_to!(res, "#");
|
||||
if show_spans {
|
||||
format_to!(res, "{span}",);
|
||||
} else if show_ctxt {
|
||||
format_to!(res, "\\{}", span.ctx);
|
||||
}
|
||||
format_to!(res, "#");
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
// Identity mapping, but only works when the input is syntactically valid. This
|
||||
// simulates common proc macros that unnecessarily parse their input and return
|
||||
// compile errors.
|
||||
#[derive(Debug)]
|
||||
struct IdentityWhenValidProcMacroExpander;
|
||||
impl ProcMacroExpander for IdentityWhenValidProcMacroExpander {
|
||||
fn expand(
|
||||
&self,
|
||||
subtree: &Subtree,
|
||||
_: Option<&Subtree>,
|
||||
_: &base_db::Env,
|
||||
_: Span,
|
||||
_: Span,
|
||||
_: Span,
|
||||
) -> Result<Subtree, ProcMacroExpansionError> {
|
||||
let (parse, _) = ::mbe::token_tree_to_syntax_node(
|
||||
subtree,
|
||||
::mbe::TopEntryPoint::MacroItems,
|
||||
span::Edition::CURRENT,
|
||||
);
|
||||
if parse.errors().is_empty() {
|
||||
Ok(subtree.clone())
|
||||
} else {
|
||||
panic!("got invalid macro input: {:?}", parse.errors());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
//! Tests for user-defined procedural macros.
|
||||
//!
|
||||
//! Note `//- proc_macros: identity` fixture metas in tests -- we don't use real
|
||||
//! proc-macros here, as that would be slow. Instead, we use several hard-coded
|
||||
//! in-memory macros.
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::macro_expansion_tests::check;
|
||||
|
||||
#[test]
|
||||
fn attribute_macro_attr_censoring() {
|
||||
cov_mark::check!(attribute_macro_attr_censoring);
|
||||
check(
|
||||
r#"
|
||||
//- proc_macros: identity
|
||||
#[attr1] #[proc_macros::identity] #[attr2]
|
||||
struct S;
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[attr1] #[proc_macros::identity] #[attr2]
|
||||
struct S;
|
||||
|
||||
#[attr1]
|
||||
#[attr2] struct S;"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_censoring() {
|
||||
cov_mark::check!(derive_censoring);
|
||||
check(
|
||||
r#"
|
||||
//- proc_macros: derive_identity
|
||||
//- minicore:derive
|
||||
#[attr1]
|
||||
#[derive(Foo)]
|
||||
#[derive(proc_macros::DeriveIdentity)]
|
||||
#[derive(Bar)]
|
||||
#[attr2]
|
||||
struct S;
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[attr1]
|
||||
#[derive(Foo)]
|
||||
#[derive(proc_macros::DeriveIdentity)]
|
||||
#[derive(Bar)]
|
||||
#[attr2]
|
||||
struct S;
|
||||
|
||||
#[attr1]
|
||||
#[derive(Bar)]
|
||||
#[attr2] struct S;"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_macro_syntax_completion_1() {
|
||||
// this is just the case where the input is actually valid
|
||||
check(
|
||||
r#"
|
||||
//- proc_macros: identity_when_valid
|
||||
#[proc_macros::identity_when_valid]
|
||||
fn foo() { bar.baz(); blub }
|
||||
"#,
|
||||
expect![[r##"
|
||||
#[proc_macros::identity_when_valid]
|
||||
fn foo() { bar.baz(); blub }
|
||||
|
||||
fn foo() {
|
||||
bar.baz();
|
||||
blub
|
||||
}"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_macro_syntax_completion_2() {
|
||||
// common case of dot completion while typing
|
||||
check(
|
||||
r#"
|
||||
//- proc_macros: identity_when_valid
|
||||
#[proc_macros::identity_when_valid]
|
||||
fn foo() { bar.; blub }
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[proc_macros::identity_when_valid]
|
||||
fn foo() { bar.; blub }
|
||||
|
||||
fn foo() {
|
||||
bar. ;
|
||||
blub
|
||||
}"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_rules_in_attr() {
|
||||
// Regression test for https://github.com/rust-lang/rust-analyzer/issues/12211
|
||||
check(
|
||||
r#"
|
||||
//- proc_macros: identity
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => {
|
||||
$($t)*
|
||||
};
|
||||
}
|
||||
id! {
|
||||
#[proc_macros::identity]
|
||||
impl Foo for WrapBj {
|
||||
async fn foo(&self) {
|
||||
self.id().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => {
|
||||
$($t)*
|
||||
};
|
||||
}
|
||||
#[proc_macros::identity] impl Foo for WrapBj {
|
||||
async fn foo(&self ) {
|
||||
self .id().await ;
|
||||
}
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_parsing_panic() {
|
||||
// Regression test for https://github.com/rust-lang/rust-analyzer/issues/12211
|
||||
check(
|
||||
r#"
|
||||
//- proc_macros: identity
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => {
|
||||
$($t)*
|
||||
};
|
||||
}
|
||||
id! {
|
||||
#[proc_macros::identity]
|
||||
impl Foo for WrapBj {
|
||||
async fn foo(&self) {
|
||||
self.0. id().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => {
|
||||
$($t)*
|
||||
};
|
||||
}
|
||||
#[proc_macros::identity] impl Foo for WrapBj {
|
||||
async fn foo(&self ) {
|
||||
self .0.id().await ;
|
||||
}
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_attribute_mapping() {
|
||||
check(
|
||||
r#"
|
||||
//- proc_macros: identity
|
||||
//+spans+syntaxctxt
|
||||
#[proc_macros::identity]
|
||||
fn foo(&self) {
|
||||
self.0. 1;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
//+spans+syntaxctxt
|
||||
#[proc_macros::identity]
|
||||
fn foo(&self) {
|
||||
self.0. 1;
|
||||
}
|
||||
|
||||
fn#0:1@45..47#0# foo#0:1@48..51#0#(#0:1@51..52#0#�:1@52..53#0#self#0:1@53..57#0# )#0:1@57..58#0# {#0:1@59..60#0#
|
||||
self#0:1@65..69#0# .#0:1@69..70#0#0#0:1@70..71#0#.#0:1@71..72#0#1#0:1@73..74#0#;#0:1@74..75#0#
|
||||
}#0:1@76..77#0#"#]],
|
||||
);
|
||||
}
|
||||
766
src/tools/rust-analyzer/crates/hir-def/src/nameres.rs
Normal file
766
src/tools/rust-analyzer/crates/hir-def/src/nameres.rs
Normal file
|
|
@ -0,0 +1,766 @@
|
|||
//! This module implements import-resolution/macro expansion algorithm.
|
||||
//!
|
||||
//! The result of this module is `DefMap`: a data structure which contains:
|
||||
//!
|
||||
//! * a tree of modules for the crate
|
||||
//! * for each module, a set of items visible in the module (directly declared
|
||||
//! or imported)
|
||||
//!
|
||||
//! Note that `DefMap` contains fully macro expanded code.
|
||||
//!
|
||||
//! Computing `DefMap` can be partitioned into several logically
|
||||
//! independent "phases". The phases are mutually recursive though, there's no
|
||||
//! strict ordering.
|
||||
//!
|
||||
//! ## Collecting RawItems
|
||||
//!
|
||||
//! This happens in the `raw` module, which parses a single source file into a
|
||||
//! set of top-level items. Nested imports are desugared to flat imports in this
|
||||
//! phase. Macro calls are represented as a triple of (Path, Option<Name>,
|
||||
//! TokenTree).
|
||||
//!
|
||||
//! ## Collecting Modules
|
||||
//!
|
||||
//! This happens in the `collector` module. In this phase, we recursively walk
|
||||
//! tree of modules, collect raw items from submodules, populate module scopes
|
||||
//! with defined items (so, we assign item ids in this phase) and record the set
|
||||
//! of unresolved imports and macros.
|
||||
//!
|
||||
//! While we walk tree of modules, we also record macro_rules definitions and
|
||||
//! expand calls to macro_rules defined macros.
|
||||
//!
|
||||
//! ## Resolving Imports
|
||||
//!
|
||||
//! We maintain a list of currently unresolved imports. On every iteration, we
|
||||
//! try to resolve some imports from this list. If the import is resolved, we
|
||||
//! record it, by adding an item to current module scope and, if necessary, by
|
||||
//! recursively populating glob imports.
|
||||
//!
|
||||
//! ## Resolving Macros
|
||||
//!
|
||||
//! macro_rules from the same crate use a global mutable namespace. We expand
|
||||
//! them immediately, when we collect modules.
|
||||
//!
|
||||
//! Macros from other crates (including proc-macros) can be used with
|
||||
//! `foo::bar!` syntax. We handle them similarly to imports. There's a list of
|
||||
//! unexpanded macros. On every iteration, we try to resolve each macro call
|
||||
//! path and, upon success, we run macro expansion and "collect module" phase on
|
||||
//! the result
|
||||
|
||||
pub mod attr_resolution;
|
||||
mod collector;
|
||||
pub mod diagnostics;
|
||||
mod mod_resolution;
|
||||
mod path_resolution;
|
||||
pub mod proc_macro;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use base_db::{CrateId, FileId};
|
||||
use hir_expand::{
|
||||
name::Name, proc_macro::ProcMacroKind, ErasedAstId, HirFileId, InFile, MacroCallId, MacroDefId,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use la_arena::Arena;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use span::{Edition, FileAstId, ROOT_ERASED_FILE_AST_ID};
|
||||
use stdx::format_to;
|
||||
use syntax::{ast, SmolStr};
|
||||
use triomphe::Arc;
|
||||
use tt::TextRange;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
item_scope::{BuiltinShadowMode, ItemScope},
|
||||
item_tree::{ItemTreeId, Mod, TreeId},
|
||||
nameres::{diagnostics::DefDiagnostic, path_resolution::ResolveMode},
|
||||
path::ModPath,
|
||||
per_ns::PerNs,
|
||||
visibility::{Visibility, VisibilityExplicitness},
|
||||
AstId, BlockId, BlockLoc, CrateRootModuleId, EnumId, EnumVariantId, ExternCrateId, FunctionId,
|
||||
LocalModuleId, Lookup, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId,
|
||||
};
|
||||
|
||||
/// Contains the results of (early) name resolution.
|
||||
///
|
||||
/// A `DefMap` stores the module tree and the definitions that are in scope in every module after
|
||||
/// item-level macros have been expanded.
|
||||
///
|
||||
/// Every crate has a primary `DefMap` whose root is the crate's main file (`main.rs`/`lib.rs`),
|
||||
/// computed by the `crate_def_map` query. Additionally, every block expression introduces the
|
||||
/// opportunity to write arbitrary item and module hierarchies, and thus gets its own `DefMap` that
|
||||
/// is computed by the `block_def_map` query.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct DefMap {
|
||||
/// When this is a block def map, this will hold the block id of the block and module that
|
||||
/// contains this block.
|
||||
block: Option<BlockInfo>,
|
||||
/// The modules and their data declared in this crate.
|
||||
pub modules: Arena<ModuleData>,
|
||||
krate: CrateId,
|
||||
/// The prelude module for this crate. This either comes from an import
|
||||
/// marked with the `prelude_import` attribute, or (in the normal case) from
|
||||
/// a dependency (`std` or `core`).
|
||||
/// The prelude is empty for non-block DefMaps (unless `#[prelude_import]` was used,
|
||||
/// but that attribute is nightly and when used in a block, it affects resolution globally
|
||||
/// so we aren't handling this correctly anyways).
|
||||
prelude: Option<(ModuleId, Option<UseId>)>,
|
||||
/// `macro_use` prelude that contains macros from `#[macro_use]`'d external crates. Note that
|
||||
/// this contains all kinds of macro, not just `macro_rules!` macro.
|
||||
/// ExternCrateId being None implies it being imported from the general prelude import.
|
||||
macro_use_prelude: FxHashMap<Name, (MacroId, Option<ExternCrateId>)>,
|
||||
pub(crate) enum_definitions: FxHashMap<EnumId, Box<[EnumVariantId]>>,
|
||||
|
||||
/// Tracks which custom derives are in scope for an item, to allow resolution of derive helper
|
||||
/// attributes.
|
||||
derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<(Name, MacroId, MacroCallId)>>,
|
||||
|
||||
/// The diagnostics that need to be emitted for this crate.
|
||||
diagnostics: Vec<DefDiagnostic>,
|
||||
|
||||
/// The crate data that is shared between a crate's def map and all its block def maps.
|
||||
data: Arc<DefMapCrateData>,
|
||||
}
|
||||
|
||||
/// Data that belongs to a crate which is shared between a crate's def map and all its block def maps.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
struct DefMapCrateData {
|
||||
/// The extern prelude which contains all root modules of external crates that are in scope.
|
||||
extern_prelude: FxHashMap<Name, (CrateRootModuleId, Option<ExternCrateId>)>,
|
||||
|
||||
/// Side table for resolving derive helpers.
|
||||
exported_derives: FxHashMap<MacroDefId, Box<[Name]>>,
|
||||
fn_proc_macro_mapping: FxHashMap<FunctionId, ProcMacroId>,
|
||||
/// The error that occurred when failing to load the proc-macro dll.
|
||||
proc_macro_loading_error: Option<Box<str>>,
|
||||
|
||||
/// Custom attributes registered with `#![register_attr]`.
|
||||
registered_attrs: Vec<SmolStr>,
|
||||
/// Custom tool modules registered with `#![register_tool]`.
|
||||
registered_tools: Vec<SmolStr>,
|
||||
/// Unstable features of Rust enabled with `#![feature(A, B)]`.
|
||||
unstable_features: FxHashSet<SmolStr>,
|
||||
/// #[rustc_coherence_is_core]
|
||||
rustc_coherence_is_core: bool,
|
||||
no_core: bool,
|
||||
no_std: bool,
|
||||
|
||||
edition: Edition,
|
||||
recursion_limit: Option<u32>,
|
||||
}
|
||||
|
||||
impl DefMapCrateData {
|
||||
fn new(edition: Edition) -> Self {
|
||||
Self {
|
||||
extern_prelude: FxHashMap::default(),
|
||||
exported_derives: FxHashMap::default(),
|
||||
fn_proc_macro_mapping: FxHashMap::default(),
|
||||
proc_macro_loading_error: None,
|
||||
registered_attrs: Vec::new(),
|
||||
registered_tools: Vec::new(),
|
||||
unstable_features: FxHashSet::default(),
|
||||
rustc_coherence_is_core: false,
|
||||
no_core: false,
|
||||
no_std: false,
|
||||
edition,
|
||||
recursion_limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn shrink_to_fit(&mut self) {
|
||||
let Self {
|
||||
extern_prelude,
|
||||
exported_derives,
|
||||
fn_proc_macro_mapping,
|
||||
registered_attrs,
|
||||
registered_tools,
|
||||
unstable_features,
|
||||
proc_macro_loading_error: _,
|
||||
rustc_coherence_is_core: _,
|
||||
no_core: _,
|
||||
no_std: _,
|
||||
edition: _,
|
||||
recursion_limit: _,
|
||||
} = self;
|
||||
extern_prelude.shrink_to_fit();
|
||||
exported_derives.shrink_to_fit();
|
||||
fn_proc_macro_mapping.shrink_to_fit();
|
||||
registered_attrs.shrink_to_fit();
|
||||
registered_tools.shrink_to_fit();
|
||||
unstable_features.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
/// For `DefMap`s computed for a block expression, this stores its location in the parent map.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
struct BlockInfo {
|
||||
/// The `BlockId` this `DefMap` was created from.
|
||||
block: BlockId,
|
||||
/// The containing module.
|
||||
parent: BlockRelativeModuleId,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
struct BlockRelativeModuleId {
|
||||
block: Option<BlockId>,
|
||||
local_id: LocalModuleId,
|
||||
}
|
||||
|
||||
impl BlockRelativeModuleId {
|
||||
fn def_map(self, db: &dyn DefDatabase, krate: CrateId) -> Arc<DefMap> {
|
||||
self.into_module(krate).def_map(db)
|
||||
}
|
||||
|
||||
fn into_module(self, krate: CrateId) -> ModuleId {
|
||||
ModuleId { krate, block: self.block, local_id: self.local_id }
|
||||
}
|
||||
|
||||
fn is_block_module(self) -> bool {
|
||||
self.block.is_some() && self.local_id == DefMap::ROOT
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<LocalModuleId> for DefMap {
|
||||
type Output = ModuleData;
|
||||
fn index(&self, id: LocalModuleId) -> &ModuleData {
|
||||
&self.modules[id]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum ModuleOrigin {
|
||||
CrateRoot {
|
||||
definition: FileId,
|
||||
},
|
||||
/// Note that non-inline modules, by definition, live inside non-macro file.
|
||||
File {
|
||||
is_mod_rs: bool,
|
||||
declaration: FileAstId<ast::Module>,
|
||||
declaration_tree_id: ItemTreeId<Mod>,
|
||||
definition: FileId,
|
||||
},
|
||||
Inline {
|
||||
definition_tree_id: ItemTreeId<Mod>,
|
||||
definition: FileAstId<ast::Module>,
|
||||
},
|
||||
/// Pseudo-module introduced by a block scope (contains only inner items).
|
||||
BlockExpr {
|
||||
id: BlockId,
|
||||
block: AstId<ast::BlockExpr>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ModuleOrigin {
|
||||
pub fn declaration(&self) -> Option<AstId<ast::Module>> {
|
||||
match self {
|
||||
&ModuleOrigin::File { declaration, declaration_tree_id, .. } => {
|
||||
Some(AstId::new(declaration_tree_id.file_id(), declaration))
|
||||
}
|
||||
&ModuleOrigin::Inline { definition, definition_tree_id } => {
|
||||
Some(AstId::new(definition_tree_id.file_id(), definition))
|
||||
}
|
||||
ModuleOrigin::CrateRoot { .. } | ModuleOrigin::BlockExpr { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_id(&self) -> Option<FileId> {
|
||||
match self {
|
||||
ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => {
|
||||
Some(*definition)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_inline(&self) -> bool {
|
||||
match self {
|
||||
ModuleOrigin::Inline { .. } | ModuleOrigin::BlockExpr { .. } => true,
|
||||
ModuleOrigin::CrateRoot { .. } | ModuleOrigin::File { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a node which defines this module.
|
||||
/// That is, a file or a `mod foo {}` with items.
|
||||
fn definition_source(&self, db: &dyn DefDatabase) -> InFile<ModuleSource> {
|
||||
match self {
|
||||
&ModuleOrigin::File { definition, .. } | &ModuleOrigin::CrateRoot { definition } => {
|
||||
let sf = db.parse(definition).tree();
|
||||
InFile::new(definition.into(), ModuleSource::SourceFile(sf))
|
||||
}
|
||||
&ModuleOrigin::Inline { definition, definition_tree_id } => InFile::new(
|
||||
definition_tree_id.file_id(),
|
||||
ModuleSource::Module(
|
||||
AstId::new(definition_tree_id.file_id(), definition).to_node(db.upcast()),
|
||||
),
|
||||
),
|
||||
ModuleOrigin::BlockExpr { block, .. } => {
|
||||
InFile::new(block.file_id, ModuleSource::BlockExpr(block.to_node(db.upcast())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ModuleData {
|
||||
/// Where does this module come from?
|
||||
pub origin: ModuleOrigin,
|
||||
/// Declared visibility of this module.
|
||||
pub visibility: Visibility,
|
||||
/// Parent module in the same `DefMap`.
|
||||
///
|
||||
/// [`None`] for block modules because they are always its `DefMap`'s root.
|
||||
pub parent: Option<LocalModuleId>,
|
||||
pub children: FxHashMap<Name, LocalModuleId>,
|
||||
pub scope: ItemScope,
|
||||
}
|
||||
|
||||
impl DefMap {
|
||||
/// The module id of a crate or block root.
|
||||
pub const ROOT: LocalModuleId = LocalModuleId::from_raw(la_arena::RawIdx::from_u32(0));
|
||||
|
||||
pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, crate_id: CrateId) -> Arc<DefMap> {
|
||||
let crate_graph = db.crate_graph();
|
||||
let krate = &crate_graph[crate_id];
|
||||
let name = krate.display_name.as_deref().unwrap_or_default();
|
||||
let _p = tracing::span!(tracing::Level::INFO, "crate_def_map_query", ?name).entered();
|
||||
|
||||
let module_data = ModuleData::new(
|
||||
ModuleOrigin::CrateRoot { definition: krate.root_file_id },
|
||||
Visibility::Public,
|
||||
);
|
||||
|
||||
let def_map = DefMap::empty(
|
||||
crate_id,
|
||||
Arc::new(DefMapCrateData::new(krate.edition)),
|
||||
module_data,
|
||||
None,
|
||||
);
|
||||
let def_map =
|
||||
collector::collect_defs(db, def_map, TreeId::new(krate.root_file_id.into(), None));
|
||||
|
||||
Arc::new(def_map)
|
||||
}
|
||||
|
||||
pub(crate) fn block_def_map_query(db: &dyn DefDatabase, block_id: BlockId) -> Arc<DefMap> {
|
||||
let BlockLoc { ast_id, module } = block_id.lookup(db);
|
||||
|
||||
let visibility = Visibility::Module(
|
||||
ModuleId { krate: module.krate, local_id: Self::ROOT, block: module.block },
|
||||
VisibilityExplicitness::Implicit,
|
||||
);
|
||||
let module_data =
|
||||
ModuleData::new(ModuleOrigin::BlockExpr { block: ast_id, id: block_id }, visibility);
|
||||
|
||||
let parent_map = module.def_map(db);
|
||||
let def_map = DefMap::empty(
|
||||
module.krate,
|
||||
parent_map.data.clone(),
|
||||
module_data,
|
||||
Some(BlockInfo {
|
||||
block: block_id,
|
||||
parent: BlockRelativeModuleId { block: module.block, local_id: module.local_id },
|
||||
}),
|
||||
);
|
||||
|
||||
let def_map =
|
||||
collector::collect_defs(db, def_map, TreeId::new(ast_id.file_id, Some(block_id)));
|
||||
Arc::new(def_map)
|
||||
}
|
||||
|
||||
fn empty(
|
||||
krate: CrateId,
|
||||
crate_data: Arc<DefMapCrateData>,
|
||||
module_data: ModuleData,
|
||||
block: Option<BlockInfo>,
|
||||
) -> DefMap {
|
||||
let mut modules: Arena<ModuleData> = Arena::default();
|
||||
let root = modules.alloc(module_data);
|
||||
assert_eq!(root, Self::ROOT);
|
||||
|
||||
DefMap {
|
||||
block,
|
||||
modules,
|
||||
krate,
|
||||
prelude: None,
|
||||
macro_use_prelude: FxHashMap::default(),
|
||||
derive_helpers_in_scope: FxHashMap::default(),
|
||||
diagnostics: Vec::new(),
|
||||
enum_definitions: FxHashMap::default(),
|
||||
data: crate_data,
|
||||
}
|
||||
}
|
||||
fn shrink_to_fit(&mut self) {
|
||||
// Exhaustive match to require handling new fields.
|
||||
let Self {
|
||||
macro_use_prelude,
|
||||
diagnostics,
|
||||
modules,
|
||||
derive_helpers_in_scope,
|
||||
block: _,
|
||||
krate: _,
|
||||
prelude: _,
|
||||
data: _,
|
||||
enum_definitions,
|
||||
} = self;
|
||||
|
||||
macro_use_prelude.shrink_to_fit();
|
||||
diagnostics.shrink_to_fit();
|
||||
modules.shrink_to_fit();
|
||||
derive_helpers_in_scope.shrink_to_fit();
|
||||
enum_definitions.shrink_to_fit();
|
||||
for (_, module) in modules.iter_mut() {
|
||||
module.children.shrink_to_fit();
|
||||
module.scope.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefMap {
|
||||
pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator<Item = LocalModuleId> + '_ {
|
||||
self.modules
|
||||
.iter()
|
||||
.filter(move |(_id, data)| data.origin.file_id() == Some(file_id))
|
||||
.map(|(id, _data)| id)
|
||||
}
|
||||
|
||||
pub fn modules(&self) -> impl Iterator<Item = (LocalModuleId, &ModuleData)> + '_ {
|
||||
self.modules.iter()
|
||||
}
|
||||
|
||||
pub fn derive_helpers_in_scope(
|
||||
&self,
|
||||
id: AstId<ast::Adt>,
|
||||
) -> Option<&[(Name, MacroId, MacroCallId)]> {
|
||||
self.derive_helpers_in_scope.get(&id.map(|it| it.upcast())).map(Deref::deref)
|
||||
}
|
||||
|
||||
pub fn registered_tools(&self) -> &[SmolStr] {
|
||||
&self.data.registered_tools
|
||||
}
|
||||
|
||||
pub fn registered_attrs(&self) -> &[SmolStr] {
|
||||
&self.data.registered_attrs
|
||||
}
|
||||
|
||||
pub fn is_unstable_feature_enabled(&self, feature: &str) -> bool {
|
||||
self.data.unstable_features.contains(feature)
|
||||
}
|
||||
|
||||
pub fn is_rustc_coherence_is_core(&self) -> bool {
|
||||
self.data.rustc_coherence_is_core
|
||||
}
|
||||
|
||||
pub fn is_no_std(&self) -> bool {
|
||||
self.data.no_std || self.data.no_core
|
||||
}
|
||||
|
||||
pub fn fn_as_proc_macro(&self, id: FunctionId) -> Option<ProcMacroId> {
|
||||
self.data.fn_proc_macro_mapping.get(&id).copied()
|
||||
}
|
||||
|
||||
pub fn proc_macro_loading_error(&self) -> Option<&str> {
|
||||
self.data.proc_macro_loading_error.as_deref()
|
||||
}
|
||||
|
||||
pub fn krate(&self) -> CrateId {
|
||||
self.krate
|
||||
}
|
||||
|
||||
pub fn module_id(&self, local_id: LocalModuleId) -> ModuleId {
|
||||
let block = self.block.map(|b| b.block);
|
||||
ModuleId { krate: self.krate, local_id, block }
|
||||
}
|
||||
|
||||
pub fn crate_root(&self) -> CrateRootModuleId {
|
||||
CrateRootModuleId { krate: self.krate }
|
||||
}
|
||||
|
||||
/// This is the same as [`Self::crate_root`] for crate def maps, but for block def maps, it
|
||||
/// returns the root block module.
|
||||
pub fn root_module_id(&self) -> ModuleId {
|
||||
self.module_id(Self::ROOT)
|
||||
}
|
||||
|
||||
/// If this `DefMap` is for a block expression, returns the module containing the block (which
|
||||
/// might again be a block, or a module inside a block).
|
||||
pub fn parent(&self) -> Option<ModuleId> {
|
||||
let BlockRelativeModuleId { block, local_id } = self.block?.parent;
|
||||
Some(ModuleId { krate: self.krate, block, local_id })
|
||||
}
|
||||
|
||||
/// Returns the module containing `local_mod`, either the parent `mod`, or the module (or block) containing
|
||||
/// the block, if `self` corresponds to a block expression.
|
||||
pub fn containing_module(&self, local_mod: LocalModuleId) -> Option<ModuleId> {
|
||||
match self[local_mod].parent {
|
||||
Some(parent) => Some(self.module_id(parent)),
|
||||
None => {
|
||||
self.block.map(
|
||||
|BlockInfo { parent: BlockRelativeModuleId { block, local_id }, .. }| {
|
||||
ModuleId { krate: self.krate, block, local_id }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the def map's diagnostics.
|
||||
pub fn diagnostics(&self) -> &[DefDiagnostic] {
|
||||
self.diagnostics.as_slice()
|
||||
}
|
||||
|
||||
pub fn recursion_limit(&self) -> u32 {
|
||||
// 128 is the default in rustc
|
||||
self.data.recursion_limit.unwrap_or(128)
|
||||
}
|
||||
|
||||
// FIXME: this can use some more human-readable format (ideally, an IR
|
||||
// even), as this should be a great debugging aid.
|
||||
pub fn dump(&self, db: &dyn DefDatabase) -> String {
|
||||
let mut buf = String::new();
|
||||
let mut arc;
|
||||
let mut current_map = self;
|
||||
while let Some(block) = current_map.block {
|
||||
go(&mut buf, db, current_map, "block scope", Self::ROOT);
|
||||
buf.push('\n');
|
||||
arc = block.parent.def_map(db, self.krate);
|
||||
current_map = &arc;
|
||||
}
|
||||
go(&mut buf, db, current_map, "crate", Self::ROOT);
|
||||
return buf;
|
||||
|
||||
fn go(
|
||||
buf: &mut String,
|
||||
db: &dyn DefDatabase,
|
||||
map: &DefMap,
|
||||
path: &str,
|
||||
module: LocalModuleId,
|
||||
) {
|
||||
format_to!(buf, "{}\n", path);
|
||||
|
||||
map.modules[module].scope.dump(db.upcast(), buf);
|
||||
|
||||
for (name, child) in
|
||||
map.modules[module].children.iter().sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
|
||||
{
|
||||
let path = format!("{path}::{}", name.display(db.upcast()));
|
||||
buf.push('\n');
|
||||
go(buf, db, map, &path, *child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump_block_scopes(&self, db: &dyn DefDatabase) -> String {
|
||||
let mut buf = String::new();
|
||||
let mut arc;
|
||||
let mut current_map = self;
|
||||
while let Some(block) = current_map.block {
|
||||
format_to!(buf, "{:?} in {:?}\n", block.block, block.parent);
|
||||
arc = block.parent.def_map(db, self.krate);
|
||||
current_map = &arc;
|
||||
}
|
||||
|
||||
format_to!(buf, "crate scope\n");
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
impl DefMap {
|
||||
pub(crate) fn block_id(&self) -> Option<BlockId> {
|
||||
self.block.map(|block| block.block)
|
||||
}
|
||||
|
||||
pub(crate) fn prelude(&self) -> Option<(ModuleId, Option<UseId>)> {
|
||||
self.prelude
|
||||
}
|
||||
|
||||
pub(crate) fn extern_prelude(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (&Name, (CrateRootModuleId, Option<ExternCrateId>))> + '_ {
|
||||
self.data.extern_prelude.iter().map(|(name, &def)| (name, def))
|
||||
}
|
||||
|
||||
pub(crate) fn macro_use_prelude(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (&Name, (MacroId, Option<ExternCrateId>))> + '_ {
|
||||
self.macro_use_prelude.iter().map(|(name, &def)| (name, def))
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_path(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
original_module: LocalModuleId,
|
||||
path: &ModPath,
|
||||
shadow: BuiltinShadowMode,
|
||||
expected_macro_subns: Option<MacroSubNs>,
|
||||
) -> (PerNs, Option<usize>) {
|
||||
let res = self.resolve_path_fp_with_macro(
|
||||
db,
|
||||
ResolveMode::Other,
|
||||
original_module,
|
||||
path,
|
||||
shadow,
|
||||
expected_macro_subns,
|
||||
);
|
||||
(res.resolved_def, res.segment_index)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_path_locally(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
original_module: LocalModuleId,
|
||||
path: &ModPath,
|
||||
shadow: BuiltinShadowMode,
|
||||
) -> (PerNs, Option<usize>) {
|
||||
let res = self.resolve_path_fp_with_macro_single(
|
||||
db,
|
||||
ResolveMode::Other,
|
||||
original_module,
|
||||
path,
|
||||
shadow,
|
||||
None, // Currently this function isn't used for macro resolution.
|
||||
);
|
||||
(res.resolved_def, res.segment_index)
|
||||
}
|
||||
|
||||
/// Ascends the `DefMap` hierarchy and calls `f` with every `DefMap` and containing module.
|
||||
///
|
||||
/// If `f` returns `Some(val)`, iteration is stopped and `Some(val)` is returned. If `f` returns
|
||||
/// `None`, iteration continues.
|
||||
pub(crate) fn with_ancestor_maps<T>(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
local_mod: LocalModuleId,
|
||||
f: &mut dyn FnMut(&DefMap, LocalModuleId) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
if let Some(it) = f(self, local_mod) {
|
||||
return Some(it);
|
||||
}
|
||||
let mut block = self.block;
|
||||
while let Some(block_info) = block {
|
||||
let parent = block_info.parent.def_map(db, self.krate);
|
||||
if let Some(it) = f(&parent, block_info.parent.local_id) {
|
||||
return Some(it);
|
||||
}
|
||||
block = parent.block;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleData {
|
||||
pub(crate) fn new(origin: ModuleOrigin, visibility: Visibility) -> Self {
|
||||
ModuleData {
|
||||
origin,
|
||||
visibility,
|
||||
parent: None,
|
||||
children: FxHashMap::default(),
|
||||
scope: ItemScope::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a node which defines this module. That is, a file or a `mod foo {}` with items.
|
||||
pub fn definition_source(&self, db: &dyn DefDatabase) -> InFile<ModuleSource> {
|
||||
self.origin.definition_source(db)
|
||||
}
|
||||
|
||||
/// Same as [`definition_source`] but only returns the file id to prevent parsing the ASt.
|
||||
pub fn definition_source_file_id(&self) -> HirFileId {
|
||||
match self.origin {
|
||||
ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => {
|
||||
definition.into()
|
||||
}
|
||||
ModuleOrigin::Inline { definition_tree_id, .. } => definition_tree_id.file_id(),
|
||||
ModuleOrigin::BlockExpr { block, .. } => block.file_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn definition_source_range(&self, db: &dyn DefDatabase) -> InFile<TextRange> {
|
||||
match &self.origin {
|
||||
&ModuleOrigin::File { definition, .. } | &ModuleOrigin::CrateRoot { definition } => {
|
||||
InFile::new(
|
||||
definition.into(),
|
||||
ErasedAstId::new(definition.into(), ROOT_ERASED_FILE_AST_ID)
|
||||
.to_range(db.upcast()),
|
||||
)
|
||||
}
|
||||
&ModuleOrigin::Inline { definition, definition_tree_id } => InFile::new(
|
||||
definition_tree_id.file_id(),
|
||||
AstId::new(definition_tree_id.file_id(), definition).to_range(db.upcast()),
|
||||
),
|
||||
ModuleOrigin::BlockExpr { block, .. } => {
|
||||
InFile::new(block.file_id, block.to_range(db.upcast()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`.
|
||||
/// `None` for the crate root or block.
|
||||
pub fn declaration_source(&self, db: &dyn DefDatabase) -> Option<InFile<ast::Module>> {
|
||||
let decl = self.origin.declaration()?;
|
||||
let value = decl.to_node(db.upcast());
|
||||
Some(InFile { file_id: decl.file_id, value })
|
||||
}
|
||||
|
||||
/// Returns the range which declares this module, either a `mod foo;` or a `mod foo {}`.
|
||||
/// `None` for the crate root or block.
|
||||
pub fn declaration_source_range(&self, db: &dyn DefDatabase) -> Option<InFile<TextRange>> {
|
||||
let decl = self.origin.declaration()?;
|
||||
Some(InFile { file_id: decl.file_id, value: decl.to_range(db.upcast()) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ModuleSource {
|
||||
SourceFile(ast::SourceFile),
|
||||
Module(ast::Module),
|
||||
BlockExpr(ast::BlockExpr),
|
||||
}
|
||||
|
||||
/// See `sub_namespace_match()`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MacroSubNs {
|
||||
/// Function-like macros, suffixed with `!`.
|
||||
Bang,
|
||||
/// Macros inside attributes, i.e. attribute macros and derive macros.
|
||||
Attr,
|
||||
}
|
||||
|
||||
impl MacroSubNs {
|
||||
fn from_id(db: &dyn DefDatabase, macro_id: MacroId) -> Self {
|
||||
let expander = match macro_id {
|
||||
MacroId::Macro2Id(it) => it.lookup(db).expander,
|
||||
MacroId::MacroRulesId(it) => it.lookup(db).expander,
|
||||
MacroId::ProcMacroId(it) => {
|
||||
return match it.lookup(db).kind {
|
||||
ProcMacroKind::CustomDerive | ProcMacroKind::Attr => Self::Attr,
|
||||
ProcMacroKind::Bang => Self::Bang,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Eager macros aren't *guaranteed* to be bang macros, but they *are* all bang macros currently.
|
||||
match expander {
|
||||
MacroExpander::Declarative
|
||||
| MacroExpander::BuiltIn(_)
|
||||
| MacroExpander::BuiltInEager(_) => Self::Bang,
|
||||
MacroExpander::BuiltInAttr(_) | MacroExpander::BuiltInDerive(_) => Self::Attr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Quoted from [rustc]:
|
||||
/// Macro namespace is separated into two sub-namespaces, one for bang macros and
|
||||
/// one for attribute-like macros (attributes, derives).
|
||||
/// We ignore resolutions from one sub-namespace when searching names in scope for another.
|
||||
///
|
||||
/// [rustc]: https://github.com/rust-lang/rust/blob/1.69.0/compiler/rustc_resolve/src/macros.rs#L75
|
||||
fn sub_namespace_match(candidate: Option<MacroSubNs>, expected: Option<MacroSubNs>) -> bool {
|
||||
match (candidate, expected) {
|
||||
(Some(candidate), Some(expected)) => candidate == expected,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
//! Post-nameres attribute resolution.
|
||||
|
||||
use base_db::CrateId;
|
||||
use hir_expand::{
|
||||
attrs::{Attr, AttrId, AttrInput},
|
||||
MacroCallId, MacroCallKind, MacroDefId,
|
||||
};
|
||||
use span::SyntaxContextId;
|
||||
use syntax::{ast, SmolStr};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
attr::builtin::{find_builtin_attr_idx, TOOL_MODULES},
|
||||
db::DefDatabase,
|
||||
item_scope::BuiltinShadowMode,
|
||||
nameres::path_resolution::ResolveMode,
|
||||
path::{self, ModPath, PathKind},
|
||||
AstIdWithPath, LocalModuleId, MacroId, UnresolvedMacro,
|
||||
};
|
||||
|
||||
use super::{DefMap, MacroSubNs};
|
||||
|
||||
pub enum ResolvedAttr {
|
||||
/// Attribute resolved to an attribute macro.
|
||||
Macro(MacroCallId),
|
||||
/// Attribute resolved to something else that does not require expansion.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl DefMap {
|
||||
pub(crate) fn resolve_attr_macro(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
original_module: LocalModuleId,
|
||||
ast_id: AstIdWithPath<ast::Item>,
|
||||
attr: &Attr,
|
||||
) -> Result<ResolvedAttr, UnresolvedMacro> {
|
||||
// NB: does not currently work for derive helpers as they aren't recorded in the `DefMap`
|
||||
|
||||
if self.is_builtin_or_registered_attr(&ast_id.path) {
|
||||
return Ok(ResolvedAttr::Other);
|
||||
}
|
||||
|
||||
let resolved_res = self.resolve_path_fp_with_macro(
|
||||
db,
|
||||
ResolveMode::Other,
|
||||
original_module,
|
||||
&ast_id.path,
|
||||
BuiltinShadowMode::Module,
|
||||
Some(MacroSubNs::Attr),
|
||||
);
|
||||
let def = match resolved_res.resolved_def.take_macros() {
|
||||
Some(def) => {
|
||||
// `MacroSubNs` is just a hint, so the path may still resolve to a custom derive
|
||||
// macro, or even function-like macro when the path is qualified.
|
||||
if def.is_attribute(db) {
|
||||
def
|
||||
} else {
|
||||
return Ok(ResolvedAttr::Other);
|
||||
}
|
||||
}
|
||||
None => return Err(UnresolvedMacro { path: ast_id.path }),
|
||||
};
|
||||
|
||||
Ok(ResolvedAttr::Macro(attr_macro_as_call_id(
|
||||
db,
|
||||
&ast_id,
|
||||
attr,
|
||||
self.krate,
|
||||
db.macro_def(def),
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn is_builtin_or_registered_attr(&self, path: &ModPath) -> bool {
|
||||
if path.kind != PathKind::Plain {
|
||||
return false;
|
||||
}
|
||||
|
||||
let segments = path.segments();
|
||||
|
||||
if let Some(name) = segments.first() {
|
||||
let name = name.to_smol_str();
|
||||
let pred = |n: &_| *n == name;
|
||||
|
||||
let registered = self.data.registered_tools.iter().map(SmolStr::as_str);
|
||||
let is_tool = TOOL_MODULES.iter().copied().chain(registered).any(pred);
|
||||
// FIXME: tool modules can be shadowed by actual modules
|
||||
if is_tool {
|
||||
return true;
|
||||
}
|
||||
|
||||
if segments.len() == 1 {
|
||||
let mut registered = self.data.registered_attrs.iter().map(SmolStr::as_str);
|
||||
let is_inert = find_builtin_attr_idx(&name).is_some() || registered.any(pred);
|
||||
return is_inert;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn attr_macro_as_call_id(
|
||||
db: &dyn DefDatabase,
|
||||
item_attr: &AstIdWithPath<ast::Item>,
|
||||
macro_attr: &Attr,
|
||||
krate: CrateId,
|
||||
def: MacroDefId,
|
||||
) -> MacroCallId {
|
||||
let arg = match macro_attr.input.as_deref() {
|
||||
Some(AttrInput::TokenTree(tt)) => {
|
||||
let mut tt = tt.as_ref().clone();
|
||||
tt.delimiter.kind = tt::DelimiterKind::Invisible;
|
||||
Some(tt)
|
||||
}
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
def.make_call(
|
||||
db.upcast(),
|
||||
krate,
|
||||
MacroCallKind::Attr {
|
||||
ast_id: item_attr.ast_id,
|
||||
attr_args: arg.map(Arc::new),
|
||||
invoc_attr_index: macro_attr.id,
|
||||
},
|
||||
macro_attr.ctxt,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn derive_macro_as_call_id(
|
||||
db: &dyn DefDatabase,
|
||||
item_attr: &AstIdWithPath<ast::Adt>,
|
||||
derive_attr_index: AttrId,
|
||||
derive_pos: u32,
|
||||
call_site: SyntaxContextId,
|
||||
krate: CrateId,
|
||||
resolver: impl Fn(path::ModPath) -> Option<(MacroId, MacroDefId)>,
|
||||
derive_macro_id: MacroCallId,
|
||||
) -> Result<(MacroId, MacroDefId, MacroCallId), UnresolvedMacro> {
|
||||
let (macro_id, def_id) = resolver(item_attr.path.clone())
|
||||
.filter(|(_, def_id)| def_id.is_derive())
|
||||
.ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
|
||||
let call_id = def_id.make_call(
|
||||
db.upcast(),
|
||||
krate,
|
||||
MacroCallKind::Derive {
|
||||
ast_id: item_attr.ast_id,
|
||||
derive_index: derive_pos,
|
||||
derive_attr_index,
|
||||
derive_macro_id,
|
||||
},
|
||||
call_site,
|
||||
);
|
||||
Ok((macro_id, def_id, call_id))
|
||||
}
|
||||
2528
src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs
Normal file
2528
src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,165 @@
|
|||
//! Diagnostics emitted during DefMap construction.
|
||||
|
||||
use std::ops::Not;
|
||||
|
||||
use base_db::CrateId;
|
||||
use cfg::{CfgExpr, CfgOptions};
|
||||
use hir_expand::{attrs::AttrId, ErasedAstId, MacroCallKind};
|
||||
use la_arena::Idx;
|
||||
use syntax::{ast, SyntaxError};
|
||||
|
||||
use crate::{
|
||||
item_tree::{self, ItemTreeId},
|
||||
nameres::LocalModuleId,
|
||||
path::ModPath,
|
||||
AstId,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum DefDiagnosticKind {
|
||||
UnresolvedModule { ast: AstId<ast::Module>, candidates: Box<[String]> },
|
||||
UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
|
||||
UnresolvedImport { id: ItemTreeId<item_tree::Use>, index: Idx<ast::UseTree> },
|
||||
UnconfiguredCode { ast: ErasedAstId, cfg: CfgExpr, opts: CfgOptions },
|
||||
UnresolvedProcMacro { ast: MacroCallKind, krate: CrateId },
|
||||
UnresolvedMacroCall { ast: MacroCallKind, path: ModPath },
|
||||
MacroError { ast: MacroCallKind, message: String },
|
||||
MacroExpansionParseError { ast: MacroCallKind, errors: Box<[SyntaxError]> },
|
||||
UnimplementedBuiltinMacro { ast: AstId<ast::Macro> },
|
||||
InvalidDeriveTarget { ast: AstId<ast::Item>, id: usize },
|
||||
MalformedDerive { ast: AstId<ast::Adt>, id: usize },
|
||||
MacroDefError { ast: AstId<ast::Macro>, message: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct DefDiagnostics(Option<triomphe::Arc<Box<[DefDiagnostic]>>>);
|
||||
|
||||
impl DefDiagnostics {
|
||||
pub fn new(diagnostics: Vec<DefDiagnostic>) -> Self {
|
||||
Self(
|
||||
diagnostics
|
||||
.is_empty()
|
||||
.not()
|
||||
.then(|| triomphe::Arc::new(diagnostics.into_boxed_slice())),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &DefDiagnostic> {
|
||||
self.0.as_ref().into_iter().flat_map(|it| &***it)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct DefDiagnostic {
|
||||
pub in_module: LocalModuleId,
|
||||
pub kind: DefDiagnosticKind,
|
||||
}
|
||||
|
||||
impl DefDiagnostic {
|
||||
pub(super) fn unresolved_module(
|
||||
container: LocalModuleId,
|
||||
declaration: AstId<ast::Module>,
|
||||
candidates: Box<[String]>,
|
||||
) -> Self {
|
||||
Self {
|
||||
in_module: container,
|
||||
kind: DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates },
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn unresolved_extern_crate(
|
||||
container: LocalModuleId,
|
||||
declaration: AstId<ast::ExternCrate>,
|
||||
) -> Self {
|
||||
Self {
|
||||
in_module: container,
|
||||
kind: DefDiagnosticKind::UnresolvedExternCrate { ast: declaration },
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn unresolved_import(
|
||||
container: LocalModuleId,
|
||||
id: ItemTreeId<item_tree::Use>,
|
||||
index: Idx<ast::UseTree>,
|
||||
) -> Self {
|
||||
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } }
|
||||
}
|
||||
|
||||
pub fn unconfigured_code(
|
||||
container: LocalModuleId,
|
||||
ast: ErasedAstId,
|
||||
cfg: CfgExpr,
|
||||
opts: CfgOptions,
|
||||
) -> Self {
|
||||
Self { in_module: container, kind: DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
|
||||
}
|
||||
|
||||
// FIXME: Whats the difference between this and unresolved_macro_call
|
||||
// FIXME: This is used for a lot of things, unresolved proc macros, disabled proc macros, etc
|
||||
// yet the diagnostic handler in ide-diagnostics has to figure out what happened because this
|
||||
// struct loses all that information!
|
||||
pub(crate) fn unresolved_proc_macro(
|
||||
container: LocalModuleId,
|
||||
ast: MacroCallKind,
|
||||
krate: CrateId,
|
||||
) -> Self {
|
||||
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast, krate } }
|
||||
}
|
||||
|
||||
pub(crate) fn macro_error(
|
||||
container: LocalModuleId,
|
||||
ast: MacroCallKind,
|
||||
message: String,
|
||||
) -> Self {
|
||||
Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, message } }
|
||||
}
|
||||
|
||||
pub(crate) fn macro_expansion_parse_error(
|
||||
container: LocalModuleId,
|
||||
ast: MacroCallKind,
|
||||
errors: Box<[SyntaxError]>,
|
||||
) -> Self {
|
||||
Self {
|
||||
in_module: container,
|
||||
kind: DefDiagnosticKind::MacroExpansionParseError { ast, errors },
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Whats the difference between this and unresolved_proc_macro
|
||||
pub(crate) fn unresolved_macro_call(
|
||||
container: LocalModuleId,
|
||||
ast: MacroCallKind,
|
||||
path: ModPath,
|
||||
) -> Self {
|
||||
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedMacroCall { ast, path } }
|
||||
}
|
||||
|
||||
pub(super) fn unimplemented_builtin_macro(
|
||||
container: LocalModuleId,
|
||||
ast: AstId<ast::Macro>,
|
||||
) -> Self {
|
||||
Self { in_module: container, kind: DefDiagnosticKind::UnimplementedBuiltinMacro { ast } }
|
||||
}
|
||||
|
||||
pub(super) fn invalid_derive_target(
|
||||
container: LocalModuleId,
|
||||
ast: AstId<ast::Item>,
|
||||
id: AttrId,
|
||||
) -> Self {
|
||||
Self {
|
||||
in_module: container,
|
||||
kind: DefDiagnosticKind::InvalidDeriveTarget { ast, id: id.ast_index() },
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn malformed_derive(
|
||||
container: LocalModuleId,
|
||||
ast: AstId<ast::Adt>,
|
||||
id: AttrId,
|
||||
) -> Self {
|
||||
Self {
|
||||
in_module: container,
|
||||
kind: DefDiagnosticKind::MalformedDerive { ast, id: id.ast_index() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
//! This module resolves `mod foo;` declaration to file.
|
||||
use arrayvec::ArrayVec;
|
||||
use base_db::{AnchoredPath, FileId};
|
||||
use hir_expand::{name::Name, HirFileIdExt, MacroFileIdExt};
|
||||
use limit::Limit;
|
||||
|
||||
use crate::{db::DefDatabase, HirFileId};
|
||||
|
||||
static MOD_DEPTH_LIMIT: Limit = Limit::new(32);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct ModDir {
|
||||
/// `` for `mod.rs`, `lib.rs`
|
||||
/// `foo/` for `foo.rs`
|
||||
/// `foo/bar/` for `mod bar { mod x; }` nested in `foo.rs`
|
||||
/// Invariant: path.is_empty() || path.ends_with('/')
|
||||
dir_path: DirPath,
|
||||
/// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/`
|
||||
root_non_dir_owner: bool,
|
||||
depth: u32,
|
||||
}
|
||||
|
||||
impl ModDir {
|
||||
pub(super) fn root() -> ModDir {
|
||||
ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false, depth: 0 }
|
||||
}
|
||||
|
||||
pub(super) fn descend_into_definition(
|
||||
&self,
|
||||
name: &Name,
|
||||
attr_path: Option<&str>,
|
||||
) -> Option<ModDir> {
|
||||
let path = match attr_path {
|
||||
None => {
|
||||
let mut path = self.dir_path.clone();
|
||||
path.push(&name.unescaped().to_smol_str());
|
||||
path
|
||||
}
|
||||
Some(attr_path) => {
|
||||
let mut path = self.dir_path.join_attr(attr_path, self.root_non_dir_owner);
|
||||
if !(path.is_empty() || path.ends_with('/')) {
|
||||
path.push('/')
|
||||
}
|
||||
DirPath::new(path)
|
||||
}
|
||||
};
|
||||
self.child(path, false)
|
||||
}
|
||||
|
||||
fn child(&self, dir_path: DirPath, root_non_dir_owner: bool) -> Option<ModDir> {
|
||||
let depth = self.depth + 1;
|
||||
if MOD_DEPTH_LIMIT.check(depth as usize).is_err() {
|
||||
tracing::error!("MOD_DEPTH_LIMIT exceeded");
|
||||
cov_mark::hit!(circular_mods);
|
||||
return None;
|
||||
}
|
||||
Some(ModDir { dir_path, root_non_dir_owner, depth })
|
||||
}
|
||||
|
||||
pub(super) fn resolve_declaration(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
file_id: HirFileId,
|
||||
name: &Name,
|
||||
attr_path: Option<&str>,
|
||||
) -> Result<(FileId, bool, ModDir), Box<[String]>> {
|
||||
let name = name.unescaped();
|
||||
|
||||
let mut candidate_files = ArrayVec::<_, 2>::new();
|
||||
match attr_path {
|
||||
Some(attr_path) => {
|
||||
candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner))
|
||||
}
|
||||
None if file_id.macro_file().map_or(false, |it| it.is_include_macro(db.upcast())) => {
|
||||
candidate_files.push(format!("{}.rs", name.display(db.upcast())));
|
||||
candidate_files.push(format!("{}/mod.rs", name.display(db.upcast())));
|
||||
}
|
||||
None => {
|
||||
candidate_files.push(format!(
|
||||
"{}{}.rs",
|
||||
self.dir_path.0,
|
||||
name.display(db.upcast())
|
||||
));
|
||||
candidate_files.push(format!(
|
||||
"{}{}/mod.rs",
|
||||
self.dir_path.0,
|
||||
name.display(db.upcast())
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let orig_file_id = file_id.original_file_respecting_includes(db.upcast());
|
||||
for candidate in candidate_files.iter() {
|
||||
let path = AnchoredPath { anchor: orig_file_id, path: candidate.as_str() };
|
||||
if let Some(file_id) = db.resolve_path(path) {
|
||||
let is_mod_rs = candidate.ends_with("/mod.rs");
|
||||
|
||||
let root_dir_owner = is_mod_rs || attr_path.is_some();
|
||||
let dir_path = if root_dir_owner {
|
||||
DirPath::empty()
|
||||
} else {
|
||||
DirPath::new(format!("{}/", name.display(db.upcast())))
|
||||
};
|
||||
if let Some(mod_dir) = self.child(dir_path, !root_dir_owner) {
|
||||
return Ok((file_id, is_mod_rs, mod_dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(candidate_files.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct DirPath(String);
|
||||
|
||||
impl DirPath {
|
||||
fn assert_invariant(&self) {
|
||||
assert!(self.0.is_empty() || self.0.ends_with('/'));
|
||||
}
|
||||
fn new(repr: String) -> DirPath {
|
||||
let res = DirPath(repr);
|
||||
res.assert_invariant();
|
||||
res
|
||||
}
|
||||
fn empty() -> DirPath {
|
||||
DirPath::new(String::new())
|
||||
}
|
||||
fn push(&mut self, name: &str) {
|
||||
self.0.push_str(name);
|
||||
self.0.push('/');
|
||||
self.assert_invariant();
|
||||
}
|
||||
fn parent(&self) -> Option<&str> {
|
||||
if self.0.is_empty() {
|
||||
return None;
|
||||
};
|
||||
let idx =
|
||||
self.0[..self.0.len() - '/'.len_utf8()].rfind('/').map_or(0, |it| it + '/'.len_utf8());
|
||||
Some(&self.0[..idx])
|
||||
}
|
||||
/// So this is the case which doesn't really work I think if we try to be
|
||||
/// 100% platform agnostic:
|
||||
///
|
||||
/// ```
|
||||
/// mod a {
|
||||
/// #[path="C://sad/face"]
|
||||
/// mod b { mod c; }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Here, we need to join logical dir path to a string path from an
|
||||
/// attribute. Ideally, we should somehow losslessly communicate the whole
|
||||
/// construction to `FileLoader`.
|
||||
fn join_attr(&self, mut attr: &str, relative_to_parent: bool) -> String {
|
||||
let base = if relative_to_parent { self.parent().unwrap() } else { &self.0 };
|
||||
|
||||
if attr.starts_with("./") {
|
||||
attr = &attr["./".len()..];
|
||||
}
|
||||
let tmp;
|
||||
let attr = if attr.contains('\\') {
|
||||
tmp = attr.replace('\\', "/");
|
||||
&tmp
|
||||
} else {
|
||||
attr
|
||||
};
|
||||
let res = format!("{base}{attr}");
|
||||
res
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,576 @@
|
|||
//! This modules implements a function to resolve a path `foo::bar::baz` to a
|
||||
//! def, which is used within the name resolution.
|
||||
//!
|
||||
//! When name resolution is finished, the result of resolving a path is either
|
||||
//! `Some(def)` or `None`. However, when we are in process of resolving imports
|
||||
//! or macros, there's a third possibility:
|
||||
//!
|
||||
//! I can't resolve this path right now, but I might be resolve this path
|
||||
//! later, when more macros are expanded.
|
||||
//!
|
||||
//! `ReachedFixedPoint` signals about this.
|
||||
|
||||
use hir_expand::{name::Name, Lookup};
|
||||
use span::Edition;
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
item_scope::{ImportOrExternCrate, BUILTIN_SCOPE},
|
||||
item_tree::Fields,
|
||||
nameres::{sub_namespace_match, BlockInfo, BuiltinShadowMode, DefMap, MacroSubNs},
|
||||
path::{ModPath, PathKind},
|
||||
per_ns::PerNs,
|
||||
visibility::{RawVisibility, Visibility},
|
||||
AdtId, LocalModuleId, ModuleDefId,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum ResolveMode {
|
||||
Import,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum ReachedFixedPoint {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ResolvePathResult {
|
||||
pub(super) resolved_def: PerNs,
|
||||
pub(super) segment_index: Option<usize>,
|
||||
pub(super) reached_fixedpoint: ReachedFixedPoint,
|
||||
pub(super) from_differing_crate: bool,
|
||||
}
|
||||
|
||||
impl ResolvePathResult {
|
||||
fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult {
|
||||
ResolvePathResult::new(PerNs::none(), reached_fixedpoint, None, false)
|
||||
}
|
||||
|
||||
fn new(
|
||||
resolved_def: PerNs,
|
||||
reached_fixedpoint: ReachedFixedPoint,
|
||||
segment_index: Option<usize>,
|
||||
from_differing_crate: bool,
|
||||
) -> ResolvePathResult {
|
||||
ResolvePathResult { resolved_def, segment_index, reached_fixedpoint, from_differing_crate }
|
||||
}
|
||||
}
|
||||
|
||||
impl PerNs {
|
||||
pub(super) fn filter_macro(
|
||||
mut self,
|
||||
db: &dyn DefDatabase,
|
||||
expected: Option<MacroSubNs>,
|
||||
) -> Self {
|
||||
self.macros = self.macros.filter(|&(id, _, _)| {
|
||||
let this = MacroSubNs::from_id(db, id);
|
||||
sub_namespace_match(Some(this), expected)
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DefMap {
|
||||
pub(crate) fn resolve_visibility(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
// module to import to
|
||||
original_module: LocalModuleId,
|
||||
// pub(path)
|
||||
// ^^^^ this
|
||||
visibility: &RawVisibility,
|
||||
within_impl: bool,
|
||||
) -> Option<Visibility> {
|
||||
let mut vis = match visibility {
|
||||
RawVisibility::Module(path, explicitness) => {
|
||||
let (result, remaining) =
|
||||
self.resolve_path(db, original_module, path, BuiltinShadowMode::Module, None);
|
||||
if remaining.is_some() {
|
||||
return None;
|
||||
}
|
||||
let types = result.take_types()?;
|
||||
match types {
|
||||
ModuleDefId::ModuleId(m) => Visibility::Module(m, *explicitness),
|
||||
// error: visibility needs to refer to module
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
RawVisibility::Public => Visibility::Public,
|
||||
};
|
||||
|
||||
// In block expressions, `self` normally refers to the containing non-block module, and
|
||||
// `super` to its parent (etc.). However, visibilities must only refer to a module in the
|
||||
// DefMap they're written in, so we restrict them when that happens.
|
||||
if let Visibility::Module(m, mv) = vis {
|
||||
// ...unless we're resolving visibility for an associated item in an impl.
|
||||
if self.block_id() != m.block && !within_impl {
|
||||
cov_mark::hit!(adjust_vis_in_block_def_map);
|
||||
vis = Visibility::Module(self.module_id(Self::ROOT), mv);
|
||||
tracing::debug!("visibility {:?} points outside DefMap, adjusting to {:?}", m, vis);
|
||||
}
|
||||
}
|
||||
|
||||
Some(vis)
|
||||
}
|
||||
|
||||
// Returns Yes if we are sure that additions to `ItemMap` wouldn't change
|
||||
// the result.
|
||||
pub(super) fn resolve_path_fp_with_macro(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
mode: ResolveMode,
|
||||
// module to import to
|
||||
mut original_module: LocalModuleId,
|
||||
path: &ModPath,
|
||||
shadow: BuiltinShadowMode,
|
||||
// Pass `MacroSubNs` if we know we're resolving macro names and which kind of macro we're
|
||||
// resolving them to. Pass `None` otherwise, e.g. when we're resolving import paths.
|
||||
expected_macro_subns: Option<MacroSubNs>,
|
||||
) -> ResolvePathResult {
|
||||
let mut result = self.resolve_path_fp_with_macro_single(
|
||||
db,
|
||||
mode,
|
||||
original_module,
|
||||
path,
|
||||
shadow,
|
||||
expected_macro_subns,
|
||||
);
|
||||
|
||||
if self.block.is_none() {
|
||||
// If we're in the root `DefMap`, we can resolve the path directly.
|
||||
return result;
|
||||
}
|
||||
|
||||
let mut arc;
|
||||
let mut current_map = self;
|
||||
loop {
|
||||
let new = current_map.resolve_path_fp_with_macro_single(
|
||||
db,
|
||||
mode,
|
||||
original_module,
|
||||
path,
|
||||
shadow,
|
||||
expected_macro_subns,
|
||||
);
|
||||
|
||||
// Merge `new` into `result`.
|
||||
result.resolved_def = result.resolved_def.or(new.resolved_def);
|
||||
if result.reached_fixedpoint == ReachedFixedPoint::No {
|
||||
result.reached_fixedpoint = new.reached_fixedpoint;
|
||||
}
|
||||
result.from_differing_crate |= new.from_differing_crate;
|
||||
result.segment_index = match (result.segment_index, new.segment_index) {
|
||||
(Some(idx), None) => Some(idx),
|
||||
(Some(old), Some(new)) => Some(old.max(new)),
|
||||
(None, new) => new,
|
||||
};
|
||||
|
||||
match current_map.block {
|
||||
Some(block) if original_module == Self::ROOT => {
|
||||
// Block modules "inherit" names from its parent module.
|
||||
original_module = block.parent.local_id;
|
||||
arc = block.parent.def_map(db, current_map.krate);
|
||||
current_map = &arc;
|
||||
}
|
||||
// Proper (non-block) modules, including those in block `DefMap`s, don't.
|
||||
_ => return result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn resolve_path_fp_with_macro_single(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
mode: ResolveMode,
|
||||
original_module: LocalModuleId,
|
||||
path: &ModPath,
|
||||
shadow: BuiltinShadowMode,
|
||||
expected_macro_subns: Option<MacroSubNs>,
|
||||
) -> ResolvePathResult {
|
||||
let mut segments = path.segments().iter().enumerate();
|
||||
let mut curr_per_ns = match path.kind {
|
||||
PathKind::DollarCrate(krate) => {
|
||||
if krate == self.krate {
|
||||
cov_mark::hit!(macro_dollar_crate_self);
|
||||
PerNs::types(self.crate_root().into(), Visibility::Public, None)
|
||||
} else {
|
||||
let def_map = db.crate_def_map(krate);
|
||||
let module = def_map.module_id(Self::ROOT);
|
||||
cov_mark::hit!(macro_dollar_crate_other);
|
||||
PerNs::types(module.into(), Visibility::Public, None)
|
||||
}
|
||||
}
|
||||
PathKind::Crate => PerNs::types(self.crate_root().into(), Visibility::Public, None),
|
||||
// plain import or absolute path in 2015: crate-relative with
|
||||
// fallback to extern prelude (with the simplification in
|
||||
// rust-lang/rust#57745)
|
||||
// FIXME there must be a nicer way to write this condition
|
||||
PathKind::Plain | PathKind::Abs
|
||||
if self.data.edition == Edition::Edition2015
|
||||
&& (path.kind == PathKind::Abs || mode == ResolveMode::Import) =>
|
||||
{
|
||||
let (_, segment) = match segments.next() {
|
||||
Some((idx, segment)) => (idx, segment),
|
||||
None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
|
||||
};
|
||||
tracing::debug!("resolving {:?} in crate root (+ extern prelude)", segment);
|
||||
self.resolve_name_in_crate_root_or_extern_prelude(db, segment)
|
||||
}
|
||||
PathKind::Plain => {
|
||||
let (_, segment) = match segments.next() {
|
||||
Some((idx, segment)) => (idx, segment),
|
||||
None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
|
||||
};
|
||||
// The first segment may be a builtin type. If the path has more
|
||||
// than one segment, we first try resolving it as a module
|
||||
// anyway.
|
||||
// FIXME: If the next segment doesn't resolve in the module and
|
||||
// BuiltinShadowMode wasn't Module, then we need to try
|
||||
// resolving it as a builtin.
|
||||
let prefer_module =
|
||||
if path.segments().len() == 1 { shadow } else { BuiltinShadowMode::Module };
|
||||
|
||||
tracing::debug!("resolving {:?} in module", segment);
|
||||
self.resolve_name_in_module(
|
||||
db,
|
||||
original_module,
|
||||
segment,
|
||||
prefer_module,
|
||||
expected_macro_subns,
|
||||
)
|
||||
}
|
||||
PathKind::Super(lvl) => {
|
||||
let mut local_id = original_module;
|
||||
let mut ext;
|
||||
let mut def_map = self;
|
||||
|
||||
// Adjust `local_id` to `self`, i.e. the nearest non-block module.
|
||||
if def_map.module_id(local_id).is_block_module() {
|
||||
(ext, local_id) = adjust_to_nearest_non_block_module(db, def_map, local_id);
|
||||
def_map = &ext;
|
||||
}
|
||||
|
||||
// Go up the module tree but skip block modules as `super` always refers to the
|
||||
// nearest non-block module.
|
||||
for _ in 0..lvl {
|
||||
// Loop invariant: at the beginning of each loop, `local_id` must refer to a
|
||||
// non-block module.
|
||||
if let Some(parent) = def_map.modules[local_id].parent {
|
||||
local_id = parent;
|
||||
if def_map.module_id(local_id).is_block_module() {
|
||||
(ext, local_id) =
|
||||
adjust_to_nearest_non_block_module(db, def_map, local_id);
|
||||
def_map = &ext;
|
||||
}
|
||||
} else {
|
||||
stdx::always!(def_map.block.is_none());
|
||||
tracing::debug!("super path in root module");
|
||||
return ResolvePathResult::empty(ReachedFixedPoint::Yes);
|
||||
}
|
||||
}
|
||||
|
||||
let module = def_map.module_id(local_id);
|
||||
stdx::never!(module.is_block_module());
|
||||
|
||||
if self.block != def_map.block {
|
||||
// If we have a different `DefMap` from `self` (the original `DefMap` we started
|
||||
// with), resolve the remaining path segments in that `DefMap`.
|
||||
let path =
|
||||
ModPath::from_segments(PathKind::Super(0), path.segments().iter().cloned());
|
||||
return def_map.resolve_path_fp_with_macro(
|
||||
db,
|
||||
mode,
|
||||
local_id,
|
||||
&path,
|
||||
shadow,
|
||||
expected_macro_subns,
|
||||
);
|
||||
}
|
||||
|
||||
PerNs::types(module.into(), Visibility::Public, None)
|
||||
}
|
||||
PathKind::Abs => {
|
||||
// 2018-style absolute path -- only extern prelude
|
||||
let segment = match segments.next() {
|
||||
Some((_, segment)) => segment,
|
||||
None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
|
||||
};
|
||||
if let Some(&(def, extern_crate)) = self.data.extern_prelude.get(segment) {
|
||||
tracing::debug!("absolute path {:?} resolved to crate {:?}", path, def);
|
||||
PerNs::types(
|
||||
def.into(),
|
||||
Visibility::Public,
|
||||
extern_crate.map(ImportOrExternCrate::ExternCrate),
|
||||
)
|
||||
} else {
|
||||
return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (i, segment) in segments {
|
||||
let (curr, vis, imp) = match curr_per_ns.take_types_full() {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
// we still have path segments left, but the path so far
|
||||
// didn't resolve in the types namespace => no resolution
|
||||
// (don't break here because `curr_per_ns` might contain
|
||||
// something in the value namespace, and it would be wrong
|
||||
// to return that)
|
||||
return ResolvePathResult::empty(ReachedFixedPoint::No);
|
||||
}
|
||||
};
|
||||
// resolve segment in curr
|
||||
|
||||
curr_per_ns = match curr {
|
||||
ModuleDefId::ModuleId(module) => {
|
||||
if module.krate != self.krate {
|
||||
let path = ModPath::from_segments(
|
||||
PathKind::Super(0),
|
||||
path.segments()[i..].iter().cloned(),
|
||||
);
|
||||
tracing::debug!("resolving {:?} in other crate", path);
|
||||
let defp_map = module.def_map(db);
|
||||
// Macro sub-namespaces only matter when resolving single-segment paths
|
||||
// because `macro_use` and other preludes should be taken into account. At
|
||||
// this point, we know we're resolving a multi-segment path so macro kind
|
||||
// expectation is discarded.
|
||||
let (def, s) =
|
||||
defp_map.resolve_path(db, module.local_id, &path, shadow, None);
|
||||
return ResolvePathResult::new(
|
||||
def,
|
||||
ReachedFixedPoint::Yes,
|
||||
s.map(|s| s + i),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
let def_map;
|
||||
let module_data = if module.block == self.block_id() {
|
||||
&self[module.local_id]
|
||||
} else {
|
||||
def_map = module.def_map(db);
|
||||
&def_map[module.local_id]
|
||||
};
|
||||
|
||||
// Since it is a qualified path here, it should not contains legacy macros
|
||||
module_data.scope.get(segment)
|
||||
}
|
||||
ModuleDefId::AdtId(AdtId::EnumId(e)) => {
|
||||
// enum variant
|
||||
cov_mark::hit!(can_import_enum_variant);
|
||||
let def_map;
|
||||
|
||||
let loc = e.lookup(db);
|
||||
let tree = loc.id.item_tree(db);
|
||||
let current_def_map =
|
||||
self.krate == loc.container.krate && self.block_id() == loc.container.block;
|
||||
let res = if current_def_map {
|
||||
&self.enum_definitions[&e]
|
||||
} else {
|
||||
def_map = loc.container.def_map(db);
|
||||
&def_map.enum_definitions[&e]
|
||||
}
|
||||
.iter()
|
||||
.find_map(|&variant| {
|
||||
let variant_data = &tree[variant.lookup(db).id.value];
|
||||
(variant_data.name == *segment).then(|| match variant_data.fields {
|
||||
Fields::Record(_) => {
|
||||
PerNs::types(variant.into(), Visibility::Public, None)
|
||||
}
|
||||
Fields::Tuple(_) | Fields::Unit => PerNs::both(
|
||||
variant.into(),
|
||||
variant.into(),
|
||||
Visibility::Public,
|
||||
None,
|
||||
),
|
||||
})
|
||||
});
|
||||
match res {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
return ResolvePathResult::new(
|
||||
PerNs::types(e.into(), vis, imp),
|
||||
ReachedFixedPoint::Yes,
|
||||
Some(i),
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
s => {
|
||||
// could be an inherent method call in UFCS form
|
||||
// (`Struct::method`), or some other kind of associated item
|
||||
tracing::debug!(
|
||||
"path segment {:?} resolved to non-module {:?}, but is not last",
|
||||
segment,
|
||||
curr,
|
||||
);
|
||||
|
||||
return ResolvePathResult::new(
|
||||
PerNs::types(s, vis, imp),
|
||||
ReachedFixedPoint::Yes,
|
||||
Some(i),
|
||||
false,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
curr_per_ns = curr_per_ns
|
||||
.filter_visibility(|vis| vis.is_visible_from_def_map(db, self, original_module));
|
||||
}
|
||||
|
||||
ResolvePathResult::new(curr_per_ns, ReachedFixedPoint::Yes, None, false)
|
||||
}
|
||||
|
||||
fn resolve_name_in_module(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
module: LocalModuleId,
|
||||
name: &Name,
|
||||
shadow: BuiltinShadowMode,
|
||||
expected_macro_subns: Option<MacroSubNs>,
|
||||
) -> PerNs {
|
||||
// Resolve in:
|
||||
// - legacy scope of macro
|
||||
// - current module / scope
|
||||
// - extern prelude / macro_use prelude
|
||||
// - std prelude
|
||||
let from_legacy_macro = self[module]
|
||||
.scope
|
||||
.get_legacy_macro(name)
|
||||
// FIXME: shadowing
|
||||
.and_then(|it| it.last())
|
||||
.copied()
|
||||
.filter(|&id| {
|
||||
sub_namespace_match(Some(MacroSubNs::from_id(db, id)), expected_macro_subns)
|
||||
})
|
||||
.map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public, None));
|
||||
let from_scope = self[module].scope.get(name).filter_macro(db, expected_macro_subns);
|
||||
let from_builtin = match self.block {
|
||||
Some(_) => {
|
||||
// Only resolve to builtins in the root `DefMap`.
|
||||
PerNs::none()
|
||||
}
|
||||
None => BUILTIN_SCOPE.get(name).copied().unwrap_or_else(PerNs::none),
|
||||
};
|
||||
let from_scope_or_builtin = match shadow {
|
||||
BuiltinShadowMode::Module => from_scope.or(from_builtin),
|
||||
BuiltinShadowMode::Other => match from_scope.take_types() {
|
||||
Some(ModuleDefId::ModuleId(_)) => from_builtin.or(from_scope),
|
||||
Some(_) | None => from_scope.or(from_builtin),
|
||||
},
|
||||
};
|
||||
|
||||
let extern_prelude = || {
|
||||
if self.block.is_some() {
|
||||
// Don't resolve extern prelude in block `DefMap`s, defer it to the crate def map so
|
||||
// that blocks can properly shadow them
|
||||
return PerNs::none();
|
||||
}
|
||||
self.data.extern_prelude.get(name).map_or(PerNs::none(), |&(it, extern_crate)| {
|
||||
PerNs::types(
|
||||
it.into(),
|
||||
Visibility::Public,
|
||||
extern_crate.map(ImportOrExternCrate::ExternCrate),
|
||||
)
|
||||
})
|
||||
};
|
||||
let macro_use_prelude = || {
|
||||
self.macro_use_prelude.get(name).map_or(PerNs::none(), |&(it, _extern_crate)| {
|
||||
PerNs::macros(
|
||||
it,
|
||||
Visibility::Public,
|
||||
// FIXME?
|
||||
None, // extern_crate.map(ImportOrExternCrate::ExternCrate),
|
||||
)
|
||||
})
|
||||
};
|
||||
let prelude = || self.resolve_in_prelude(db, name);
|
||||
|
||||
from_legacy_macro
|
||||
.or(from_scope_or_builtin)
|
||||
.or_else(extern_prelude)
|
||||
.or_else(macro_use_prelude)
|
||||
.or_else(prelude)
|
||||
}
|
||||
|
||||
fn resolve_name_in_crate_root_or_extern_prelude(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
name: &Name,
|
||||
) -> PerNs {
|
||||
let from_crate_root = match self.block {
|
||||
Some(_) => {
|
||||
let def_map = self.crate_root().def_map(db);
|
||||
def_map[Self::ROOT].scope.get(name)
|
||||
}
|
||||
None => self[Self::ROOT].scope.get(name),
|
||||
};
|
||||
let from_extern_prelude = || {
|
||||
if self.block.is_some() {
|
||||
// Don't resolve extern prelude in block `DefMap`s.
|
||||
return PerNs::none();
|
||||
}
|
||||
self.data.extern_prelude.get(name).copied().map_or(
|
||||
PerNs::none(),
|
||||
|(it, extern_crate)| {
|
||||
PerNs::types(
|
||||
it.into(),
|
||||
Visibility::Public,
|
||||
extern_crate.map(ImportOrExternCrate::ExternCrate),
|
||||
)
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
from_crate_root.or_else(from_extern_prelude)
|
||||
}
|
||||
|
||||
fn resolve_in_prelude(&self, db: &dyn DefDatabase, name: &Name) -> PerNs {
|
||||
if let Some((prelude, _use)) = self.prelude {
|
||||
let keep;
|
||||
let def_map = if prelude.krate == self.krate {
|
||||
self
|
||||
} else {
|
||||
// Extend lifetime
|
||||
keep = prelude.def_map(db);
|
||||
&keep
|
||||
};
|
||||
def_map[prelude.local_id].scope.get(name)
|
||||
} else {
|
||||
PerNs::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a block module, returns its nearest non-block module and the `DefMap` it belongs to.
|
||||
fn adjust_to_nearest_non_block_module(
|
||||
db: &dyn DefDatabase,
|
||||
def_map: &DefMap,
|
||||
mut local_id: LocalModuleId,
|
||||
) -> (Arc<DefMap>, LocalModuleId) {
|
||||
// INVARIANT: `local_id` in `def_map` must be a block module.
|
||||
stdx::always!(def_map.module_id(local_id).is_block_module());
|
||||
|
||||
let mut ext;
|
||||
// This needs to be a local variable due to our mighty lifetime.
|
||||
let mut def_map = def_map;
|
||||
loop {
|
||||
let BlockInfo { parent, .. } = def_map.block.expect("block module without parent module");
|
||||
|
||||
ext = parent.def_map(db, def_map.krate);
|
||||
def_map = &ext;
|
||||
local_id = parent.local_id;
|
||||
if !parent.is_block_module() {
|
||||
return (ext, local_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
//! Nameres-specific procedural macro data and helpers.
|
||||
|
||||
use hir_expand::name::{AsName, Name};
|
||||
|
||||
use crate::attr::Attrs;
|
||||
use crate::tt::{Leaf, TokenTree};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ProcMacroDef {
|
||||
pub name: Name,
|
||||
pub kind: ProcMacroKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ProcMacroKind {
|
||||
Derive { helpers: Box<[Name]> },
|
||||
Bang,
|
||||
Attr,
|
||||
}
|
||||
|
||||
impl ProcMacroKind {
|
||||
pub(super) fn to_basedb_kind(&self) -> hir_expand::proc_macro::ProcMacroKind {
|
||||
match self {
|
||||
ProcMacroKind::Derive { .. } => hir_expand::proc_macro::ProcMacroKind::CustomDerive,
|
||||
ProcMacroKind::Bang => hir_expand::proc_macro::ProcMacroKind::Bang,
|
||||
ProcMacroKind::Attr => hir_expand::proc_macro::ProcMacroKind::Attr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Attrs {
|
||||
#[rustfmt::skip]
|
||||
pub fn parse_proc_macro_decl(&self, func_name: &Name) -> Option<ProcMacroDef> {
|
||||
if self.is_proc_macro() {
|
||||
Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::Bang })
|
||||
} else if self.is_proc_macro_attribute() {
|
||||
Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::Attr })
|
||||
} else if self.by_key("proc_macro_derive").exists() {
|
||||
let derive = self.by_key("proc_macro_derive").tt_values().next()?;
|
||||
let def = parse_macro_name_and_helper_attrs(&derive.token_trees)
|
||||
.map(|(name, helpers)| ProcMacroDef { name, kind: ProcMacroKind::Derive { helpers } });
|
||||
|
||||
if def.is_none() {
|
||||
tracing::trace!("malformed `#[proc_macro_derive]`: {}", derive);
|
||||
}
|
||||
|
||||
def
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This fn is intended for `#[proc_macro_derive(..)]` and `#[rustc_builtin_macro(..)]`, which have
|
||||
// the same structure.
|
||||
#[rustfmt::skip]
|
||||
pub(crate) fn parse_macro_name_and_helper_attrs(tt: &[TokenTree]) -> Option<(Name, Box<[Name]>)> {
|
||||
match tt {
|
||||
// `#[proc_macro_derive(Trait)]`
|
||||
// `#[rustc_builtin_macro(Trait)]`
|
||||
[TokenTree::Leaf(Leaf::Ident(trait_name))] => Some((trait_name.as_name(), Box::new([]))),
|
||||
|
||||
// `#[proc_macro_derive(Trait, attributes(helper1, helper2, ...))]`
|
||||
// `#[rustc_builtin_macro(Trait, attributes(helper1, helper2, ...))]`
|
||||
[
|
||||
TokenTree::Leaf(Leaf::Ident(trait_name)),
|
||||
TokenTree::Leaf(Leaf::Punct(comma)),
|
||||
TokenTree::Leaf(Leaf::Ident(attributes)),
|
||||
TokenTree::Subtree(helpers)
|
||||
] if comma.char == ',' && attributes.text == "attributes" =>
|
||||
{
|
||||
let helpers = helpers
|
||||
.token_trees
|
||||
.iter()
|
||||
.filter(
|
||||
|tt| !matches!(tt, TokenTree::Leaf(Leaf::Punct(comma)) if comma.char == ','),
|
||||
)
|
||||
.map(|tt| match tt {
|
||||
TokenTree::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Option<Box<[_]>>>()?;
|
||||
|
||||
Some((trait_name.as_name(), helpers))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue