Use josh for subtree syncs

This commit is contained in:
Laurențiu Nicola 2024-04-07 11:41:39 +03:00
parent 55d9a533b3
commit 8f2138102f
1987 changed files with 494678 additions and 0 deletions

View 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 }

View 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

View 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

View 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

View 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
```

View 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.

View file

@ -0,0 +1,8 @@
---
name: Feature Request
about: Create a feature request for rust-analyzer.
title: ''
labels: 'C-feature'
assignees: ''
---

View file

@ -0,0 +1,8 @@
---
name: Support Question
about: A question regarding functionality of rust-analyzer.
title: ''
labels: 'C-support'
assignees: ''
---

View file

@ -0,0 +1,8 @@
FROM node:slim
COPY . /action
WORKDIR /action
RUN npm install --production
ENTRYPOINT ["node", "/action/main.js"]

View 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.

View 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'

View 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);
});

View 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"
}
}

View 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
}
]
}
]
}

View 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))

View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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"

View 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": []
}

View 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"
}
]
}

View 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"
}
]
}

View 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

File diff suppressed because it is too large Load diff

View 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"

View 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.

View 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.

View file

@ -0,0 +1 @@
See the [Privacy](https://rust-analyzer.github.io/manual.html#privacy) section of the user manual.

View 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.

View 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

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View 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>"}
]

View 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

View 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
}
}

View 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,)]
);
}
}

View 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)
}
}

View 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

View 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(),
})
}
}
}

View 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,
}
}

View 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(())
}
}

View 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);
}
}

View 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

View 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}"
)))
}
}
}

View 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),
}

View 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)? })
}
}

View 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

View 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)
}

View 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",
),
];

View 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()),
);
}

View 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();
}
}

File diff suppressed because it is too large Load diff

View 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(|(&param, 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()));
}
}

View 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,
);
}
}

View 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))
}

View 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
"#]],
)
}

View 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",
})
}
}

View 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),
}
}

View 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(|&param| 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()
}

View 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(),
}
}

View 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,
}
}
}
}

View 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) }
}
}

View 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())
}
}

View 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(&macro_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,
}

File diff suppressed because it is too large Load diff

View 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(&lt));
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(&lt))
})
.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
}
})
}
}

View 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),
}
}
}

View 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),
}
}
}

View 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(&lt));
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(&lt)))
.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('_'),
}
}
}

File diff suppressed because it is too large Load diff

View 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)),
}
}
}

File diff suppressed because it is too large Load diff

View 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(&param);
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(), &param, 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(), &param, 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))
}

View 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 &params.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(())
}
}

View 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;
"#]],
)
}

View 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;
}

File diff suppressed because it is too large Load diff

View 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),
)
}
}

View file

@ -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
}
}"##]],
);
}

View file

@ -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

View file

@ -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;
}
"#]],
);
}

View file

@ -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 */
"#]],
);
}

View file

@ -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;
}
"#]],
);
}

View file

@ -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;
"#]],
);
}

View file

@ -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, &macro_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());
}
}
}

View file

@ -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#&#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#"#]],
);
}

View 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,
}
}

View file

@ -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))
}

File diff suppressed because it is too large Load diff

View file

@ -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() },
}
}
}

View file

@ -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
}
}

View file

@ -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);
}
}
}

View file

@ -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