mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-15 01:17:27 +00:00
Merge branch 'master' into sync-from-rust
This commit is contained in:
commit
fb31610ae8
242 changed files with 8794 additions and 2547 deletions
33
.github/rust.json
vendored
Normal file
33
.github/rust.json
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "rustfmt",
|
||||
"severity": "warning",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(Diff in (.+)) at line (\\d+):$",
|
||||
"message": 1,
|
||||
"file": 2,
|
||||
"line": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "clippy",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(?:\\x1b\\[[\\d;]+m)*(warning|warn|error)(?:\\x1b\\[[\\d;]+m)*(\\[(.*)\\])?(?:\\x1b\\[[\\d;]+m)*:(?:\\x1b\\[[\\d;]+m)* ([^\\x1b]*)(?:\\x1b\\[[\\d;]+m)*$",
|
||||
"severity": 1,
|
||||
"message": 4,
|
||||
"code": 3
|
||||
},
|
||||
{
|
||||
"regexp": "^(?:\\x1b\\[[\\d;]+m)*\\s*(?:\\x1b\\[[\\d;]+m)*\\s*--> (?:\\x1b\\[[\\d;]+m)*(.*):(\\d*):(\\d*)(?:\\x1b\\[[\\d;]+m)*$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
2
.github/workflows/autopublish.yaml
vendored
2
.github/workflows/autopublish.yaml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
25
.github/workflows/ci.yaml
vendored
25
.github/workflows/ci.yaml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
typescript: ${{ steps.filter.outputs.typescript }}
|
||||
proc_macros: ${{ steps.filter.outputs.proc_macros }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@1441771bbfdd59dcd748680ee64ebd8faab1a242
|
||||
id: filter
|
||||
with:
|
||||
|
@ -56,7 +56,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
|
@ -65,6 +65,10 @@ jobs:
|
|||
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@988c164c3d0e93c4dbab36aaf5bbeb77425b2894
|
||||
|
@ -107,6 +111,10 @@ jobs:
|
|||
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'
|
||||
|
@ -121,7 +129,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: |
|
||||
|
@ -153,13 +161,13 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
if: needs.changes.outputs.typescript == 'true'
|
||||
|
||||
- name: Install Nodejs
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
if: needs.changes.outputs.typescript == 'true'
|
||||
|
||||
- name: Install xvfb
|
||||
|
@ -218,6 +226,11 @@ jobs:
|
|||
- 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
|
||||
|
||||
|
|
2
.github/workflows/fuzz.yml
vendored
2
.github/workflows/fuzz.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 1
|
||||
|
|
25
.github/workflows/metrics.yaml
vendored
25
.github/workflows/metrics.yaml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
rustup component add rustfmt rust-src
|
||||
rustup default stable
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
|
@ -36,10 +36,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore cargo cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
|
@ -52,7 +52,7 @@ jobs:
|
|||
run: cargo xtask metrics build
|
||||
|
||||
- name: Cache target
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: target/
|
||||
key: ${{ runner.os }}-target-${{ github.sha }}
|
||||
|
@ -67,16 +67,16 @@ jobs:
|
|||
other_metrics:
|
||||
strategy:
|
||||
matrix:
|
||||
names: [self, rustc_tests, ripgrep-13.0.0, webrender-2022, diesel-1.4.8, hyper-0.14.18]
|
||||
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@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore cargo cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
|
@ -86,7 +86,7 @@ jobs:
|
|||
key: ${{ runner.os }}-cargo-${{ github.sha }}
|
||||
|
||||
- name: Restore target cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: target/
|
||||
key: ${{ runner.os }}-target-${{ github.sha }}
|
||||
|
@ -106,7 +106,7 @@ jobs:
|
|||
needs: [build_metrics, other_metrics]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download build metrics
|
||||
uses: actions/download-artifact@v3
|
||||
|
@ -118,11 +118,6 @@ jobs:
|
|||
with:
|
||||
name: self-${{ github.sha }}
|
||||
|
||||
- name: Download rustc_tests metrics
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: rustc_tests-${{ github.sha }}
|
||||
|
||||
- name: Download ripgrep-13.0.0 metrics
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
|
@ -151,7 +146,7 @@ jobs:
|
|||
chmod 700 ~/.ssh
|
||||
|
||||
git clone --depth 1 git@github.com:rust-analyzer/metrics.git
|
||||
jq -s ".[0] * .[1] * .[2] * .[3] * .[4] * .[5] * .[6]" build.json self.json rustc_tests.json ripgrep-13.0.0.json webrender-2022.json diesel-1.4.8.json hyper-0.14.18.json -c >> metrics/metrics.json
|
||||
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 📈
|
||||
|
|
2
.github/workflows/publish-libs.yaml
vendored
2
.github/workflows/publish-libs.yaml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
8
.github/workflows/release.yaml
vendored
8
.github/workflows/release.yaml
vendored
|
@ -154,7 +154,7 @@ jobs:
|
|||
run: apk add --no-cache git clang lld musl-dev nodejs npm
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: ${{ env.FETCH_DEPTH }}
|
||||
|
||||
|
@ -188,9 +188,9 @@ jobs:
|
|||
needs: ["dist", "dist-x86_64-unknown-linux-musl"]
|
||||
steps:
|
||||
- name: Install Nodejs
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- run: echo "TAG=$(date --iso -u)" >> $GITHUB_ENV
|
||||
if: github.ref == 'refs/heads/release'
|
||||
|
@ -199,7 +199,7 @@ jobs:
|
|||
- run: 'echo "TAG: $TAG"'
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: ${{ env.FETCH_DEPTH }}
|
||||
|
||||
|
|
2
.github/workflows/rustdoc.yaml
vendored
2
.github/workflows/rustdoc.yaml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup update --no-self-update stable
|
||||
|
|
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -1329,6 +1329,7 @@ dependencies = [
|
|||
"paths",
|
||||
"proc-macro-api",
|
||||
"proc-macro-test",
|
||||
"ra-ap-rustc_lexer",
|
||||
"span",
|
||||
"stdx",
|
||||
"tt",
|
||||
|
@ -1470,12 +1471,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ra-ap-rustc_index"
|
||||
version = "0.36.0"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a41dee58608b1fc93779ea365edaa70ac9927e3335ae914b675be0fa063cd7"
|
||||
checksum = "df5a0ba0d08af366cf235dbe8eb7226cced7a4fe502c98aa434ccf416defd746"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"ra-ap-rustc_index_macros 0.36.0",
|
||||
"ra-ap-rustc_index_macros 0.37.0",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
|
@ -1493,9 +1494,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ra-ap-rustc_index_macros"
|
||||
version = "0.36.0"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbfe98def54c4337a2f7d8233850bd5d5349972b185fe8a0db2b979164b30ed8"
|
||||
checksum = "1971ebf9a701e0e68387c264a32517dcb4861ad3a4862f2e2803c1121ade20d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1525,11 +1526,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ra-ap-rustc_pattern_analysis"
|
||||
version = "0.36.0"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5529bffec7530b4a3425640bfdfd9b95d87c4c620f740266c0de6572561aab4"
|
||||
checksum = "2c3c0e7ca9c5bdc66e3b590688e237a22ac47a48e4eac7f46b05b2abbfaf0abd"
|
||||
dependencies = [
|
||||
"ra-ap-rustc_index 0.36.0",
|
||||
"ra-ap-rustc_index 0.37.0",
|
||||
"rustc-hash",
|
||||
"rustc_apfloat",
|
||||
"smallvec",
|
||||
|
@ -1708,6 +1709,7 @@ dependencies = [
|
|||
"dissimilar",
|
||||
"expect-test",
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"linked-hash-map",
|
||||
"lock_api",
|
||||
"oorandom",
|
||||
|
|
|
@ -84,7 +84,7 @@ ra-ap-rustc_lexer = { version = "0.35.0", default-features = false }
|
|||
ra-ap-rustc_parse_format = { version = "0.35.0", default-features = false }
|
||||
ra-ap-rustc_index = { version = "0.35.0", default-features = false }
|
||||
ra-ap-rustc_abi = { version = "0.35.0", default-features = false }
|
||||
ra-ap-rustc_pattern_analysis = { version = "0.36.0", default-features = false }
|
||||
ra-ap-rustc_pattern_analysis = { version = "0.37.0", default-features = false }
|
||||
|
||||
# local crates that aren't published to crates.io. These should not have versions.
|
||||
sourcegen = { path = "./crates/sourcegen" }
|
||||
|
|
|
@ -11,7 +11,6 @@ use std::{fmt, mem, ops, str::FromStr};
|
|||
use cfg::CfgOptions;
|
||||
use la_arena::{Arena, Idx, RawIdx};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use semver::Version;
|
||||
use syntax::SmolStr;
|
||||
use triomphe::Arc;
|
||||
use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath};
|
||||
|
@ -243,6 +242,7 @@ impl CrateDisplayName {
|
|||
CrateDisplayName { crate_name, canonical_name }
|
||||
}
|
||||
}
|
||||
|
||||
pub type TargetLayoutLoadResult = Result<Arc<str>, Arc<str>>;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
|
@ -291,71 +291,6 @@ pub struct CrateData {
|
|||
pub dependencies: Vec<Dependency>,
|
||||
pub origin: CrateOrigin,
|
||||
pub is_proc_macro: bool,
|
||||
// FIXME: These things should not be per crate! These are more per workspace crate graph level
|
||||
// things. This info does need to be somewhat present though as to prevent deduplication from
|
||||
// happening across different workspaces with different layouts.
|
||||
pub target_layout: TargetLayoutLoadResult,
|
||||
pub toolchain: Option<Version>,
|
||||
}
|
||||
|
||||
impl CrateData {
|
||||
/// Check if [`other`] is almost equal to [`self`] ignoring `CrateOrigin` value.
|
||||
pub fn eq_ignoring_origin_and_deps(&self, other: &CrateData, ignore_dev_deps: bool) -> bool {
|
||||
// This method has some obscure bits. These are mostly there to be compliant with
|
||||
// some patches. References to the patches are given.
|
||||
if self.root_file_id != other.root_file_id {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.display_name != other.display_name {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.is_proc_macro != other.is_proc_macro {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.edition != other.edition {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.version != other.version {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut opts = self.cfg_options.difference(&other.cfg_options);
|
||||
if let Some(it) = opts.next() {
|
||||
// Don't care if rust_analyzer CfgAtom is the only cfg in the difference set of self's and other's cfgs.
|
||||
// https://github.com/rust-lang/rust-analyzer/blob/0840038f02daec6ba3238f05d8caa037d28701a0/crates/project-model/src/workspace.rs#L894
|
||||
if it.to_string() != "rust_analyzer" {
|
||||
return false;
|
||||
}
|
||||
|
||||
if opts.next().is_some() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if self.env != other.env {
|
||||
return false;
|
||||
}
|
||||
|
||||
let slf_deps = self.dependencies.iter();
|
||||
let other_deps = other.dependencies.iter();
|
||||
|
||||
if ignore_dev_deps {
|
||||
return slf_deps
|
||||
.clone()
|
||||
.filter(|it| it.kind != DependencyKind::Dev)
|
||||
.eq(other_deps.clone().filter(|it| it.kind != DependencyKind::Dev));
|
||||
}
|
||||
|
||||
slf_deps.eq(other_deps)
|
||||
}
|
||||
|
||||
pub fn channel(&self) -> Option<ReleaseChannel> {
|
||||
self.toolchain.as_ref().and_then(|v| ReleaseChannel::from_str(&v.pre))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -398,32 +333,22 @@ pub enum DependencyKind {
|
|||
pub struct Dependency {
|
||||
pub crate_id: CrateId,
|
||||
pub name: CrateName,
|
||||
kind: DependencyKind,
|
||||
prelude: bool,
|
||||
}
|
||||
|
||||
impl Dependency {
|
||||
pub fn new(name: CrateName, crate_id: CrateId, kind: DependencyKind) -> Self {
|
||||
Self { name, crate_id, prelude: true, kind }
|
||||
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,
|
||||
kind: DependencyKind,
|
||||
) -> Self {
|
||||
Self { name, crate_id, prelude, kind }
|
||||
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
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> DependencyKind {
|
||||
self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl CrateGraph {
|
||||
|
@ -438,8 +363,6 @@ impl CrateGraph {
|
|||
env: Env,
|
||||
is_proc_macro: bool,
|
||||
origin: CrateOrigin,
|
||||
target_layout: Result<Arc<str>, Arc<str>>,
|
||||
toolchain: Option<Version>,
|
||||
) -> CrateId {
|
||||
let data = CrateData {
|
||||
root_file_id,
|
||||
|
@ -451,9 +374,7 @@ impl CrateGraph {
|
|||
env,
|
||||
dependencies: Vec::new(),
|
||||
origin,
|
||||
target_layout,
|
||||
is_proc_macro,
|
||||
toolchain,
|
||||
};
|
||||
self.arena.alloc(data)
|
||||
}
|
||||
|
@ -523,6 +444,10 @@ impl CrateGraph {
|
|||
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)
|
||||
}
|
||||
|
@ -623,13 +548,17 @@ impl CrateGraph {
|
|||
///
|
||||
/// 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.
|
||||
/// 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,
|
||||
on_finished: impl FnOnce(&FxHashMap<CrateId, CrateId>),
|
||||
) {
|
||||
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 {
|
||||
|
@ -637,51 +566,21 @@ impl CrateGraph {
|
|||
|
||||
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().find_map(|(id, data)| {
|
||||
match (&data.origin, &crate_data.origin) {
|
||||
(a, b) if a == b => {
|
||||
if data.eq_ignoring_origin_and_deps(crate_data, false) {
|
||||
return Some((id, false));
|
||||
}
|
||||
}
|
||||
(a @ CrateOrigin::Local { .. }, CrateOrigin::Library { .. })
|
||||
| (a @ CrateOrigin::Library { .. }, CrateOrigin::Local { .. }) => {
|
||||
// If the origins differ, check if the two crates are equal without
|
||||
// considering the dev dependencies, if they are, they most likely are in
|
||||
// different loaded workspaces which may cause issues. We keep the local
|
||||
// version and discard the library one as the local version may have
|
||||
// dev-dependencies that we want to keep resolving. See #15656 for more
|
||||
// information.
|
||||
if data.eq_ignoring_origin_and_deps(crate_data, true) {
|
||||
return Some((id, !a.is_local()));
|
||||
}
|
||||
}
|
||||
(_, _) => return None,
|
||||
}
|
||||
let res = self
|
||||
.arena
|
||||
.iter_mut()
|
||||
.take(m)
|
||||
.find_map(|(id, data)| merge((id, data), (topo, &crate_data)).then_some(id));
|
||||
|
||||
None
|
||||
});
|
||||
|
||||
if let Some((res, should_update_lib_to_local)) = res {
|
||||
id_map.insert(topo, res);
|
||||
if should_update_lib_to_local {
|
||||
assert!(self.arena[res].origin.is_lib());
|
||||
assert!(crate_data.origin.is_local());
|
||||
self.arena[res].origin = crate_data.origin.clone();
|
||||
|
||||
// Move local's dev dependencies into the newly-local-formerly-lib crate.
|
||||
self.arena[res].dependencies = crate_data.dependencies.clone();
|
||||
}
|
||||
} else {
|
||||
let id = self.arena.alloc(crate_data.clone());
|
||||
id_map.insert(topo, 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();
|
||||
|
||||
on_finished(&id_map);
|
||||
id_map
|
||||
}
|
||||
|
||||
fn find_path(
|
||||
|
@ -719,11 +618,9 @@ impl CrateGraph {
|
|||
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,
|
||||
DependencyKind::Normal,
|
||||
));
|
||||
self.arena[std]
|
||||
.dependencies
|
||||
.push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if));
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
|
@ -871,7 +768,7 @@ impl fmt::Display for CyclicDependenciesError {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{CrateOrigin, DependencyKind};
|
||||
use crate::CrateOrigin;
|
||||
|
||||
use super::{CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId};
|
||||
|
||||
|
@ -888,8 +785,6 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId::from_raw(2u32),
|
||||
|
@ -901,8 +796,6 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
let crate3 = graph.add_crate_root(
|
||||
FileId::from_raw(3u32),
|
||||
|
@ -914,26 +807,15 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(
|
||||
crate1,
|
||||
Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
|
||||
)
|
||||
.add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2,))
|
||||
.is_ok());
|
||||
assert!(graph
|
||||
.add_dep(
|
||||
crate2,
|
||||
Dependency::new(CrateName::new("crate3").unwrap(), crate3, DependencyKind::Normal)
|
||||
)
|
||||
.add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3,))
|
||||
.is_ok());
|
||||
assert!(graph
|
||||
.add_dep(
|
||||
crate3,
|
||||
Dependency::new(CrateName::new("crate1").unwrap(), crate1, DependencyKind::Normal)
|
||||
)
|
||||
.add_dep(crate3, Dependency::new(CrateName::new("crate1").unwrap(), crate1,))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
|
@ -950,8 +832,6 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId::from_raw(2u32),
|
||||
|
@ -963,20 +843,12 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(
|
||||
crate1,
|
||||
Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
|
||||
)
|
||||
.add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2,))
|
||||
.is_ok());
|
||||
assert!(graph
|
||||
.add_dep(
|
||||
crate2,
|
||||
Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
|
||||
)
|
||||
.add_dep(crate2, Dependency::new(CrateName::new("crate2").unwrap(), crate2,))
|
||||
.is_err());
|
||||
}
|
||||
|
||||
|
@ -993,8 +865,6 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId::from_raw(2u32),
|
||||
|
@ -1006,8 +876,6 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
let crate3 = graph.add_crate_root(
|
||||
FileId::from_raw(3u32),
|
||||
|
@ -1019,20 +887,12 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(
|
||||
crate1,
|
||||
Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
|
||||
)
|
||||
.add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2,))
|
||||
.is_ok());
|
||||
assert!(graph
|
||||
.add_dep(
|
||||
crate2,
|
||||
Dependency::new(CrateName::new("crate3").unwrap(), crate3, DependencyKind::Normal)
|
||||
)
|
||||
.add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3,))
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
|
@ -1049,8 +909,6 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId::from_raw(2u32),
|
||||
|
@ -1062,26 +920,16 @@ mod tests {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("".into()),
|
||||
None,
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(
|
||||
crate1,
|
||||
Dependency::new(
|
||||
CrateName::normalize_dashes("crate-name-with-dashes"),
|
||||
crate2,
|
||||
DependencyKind::Normal
|
||||
)
|
||||
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,
|
||||
DependencyKind::Normal
|
||||
)]
|
||||
vec![Dependency::new(CrateName::new("crate_name_with_dashes").unwrap(), crate2,)]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,20 @@ pub trait SourceDatabase: FileLoader + std::fmt::Debug {
|
|||
/// 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> {
|
||||
|
|
|
@ -14,7 +14,7 @@ use std::{
|
|||
|
||||
use command_group::{CommandGroup, GroupChild};
|
||||
use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
|
||||
use paths::AbsPathBuf;
|
||||
use paths::{AbsPath, AbsPathBuf};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
use stdx::process::streaming_output;
|
||||
|
@ -23,6 +23,7 @@ pub use cargo_metadata::diagnostic::{
|
|||
Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
|
||||
DiagnosticSpanMacroExpansion,
|
||||
};
|
||||
use toolchain::Tool;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum InvocationStrategy {
|
||||
|
@ -89,9 +90,10 @@ impl FlycheckHandle {
|
|||
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, workspace_root);
|
||||
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())
|
||||
|
@ -101,13 +103,15 @@ impl FlycheckHandle {
|
|||
}
|
||||
|
||||
/// Schedule a re-start of the cargo check worker to do a workspace wide check.
|
||||
pub fn restart_workspace(&self) {
|
||||
self.sender.send(StateChange::Restart(None)).unwrap();
|
||||
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(Some(package))).unwrap();
|
||||
self.sender
|
||||
.send(StateChange::Restart { package: Some(package), saved_file: None })
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Stop this cargo check worker.
|
||||
|
@ -158,7 +162,7 @@ pub enum Progress {
|
|||
}
|
||||
|
||||
enum StateChange {
|
||||
Restart(Option<String>),
|
||||
Restart { package: Option<String>, saved_file: Option<AbsPathBuf> },
|
||||
Cancel,
|
||||
}
|
||||
|
||||
|
@ -171,6 +175,7 @@ struct FlycheckActor {
|
|||
/// 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
|
||||
|
@ -184,15 +189,25 @@ enum Event {
|
|||
CheckEvent(Option<CargoMessage>),
|
||||
}
|
||||
|
||||
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, root: workspace_root, command_handle: None }
|
||||
FlycheckActor {
|
||||
id,
|
||||
sender,
|
||||
config,
|
||||
sysroot_root,
|
||||
root: workspace_root,
|
||||
command_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn report_progress(&self, progress: Progress) {
|
||||
|
@ -218,7 +233,7 @@ impl FlycheckActor {
|
|||
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
|
||||
self.cancel_check_process();
|
||||
}
|
||||
Event::RequestStateChange(StateChange::Restart(package)) => {
|
||||
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)) {
|
||||
|
@ -228,7 +243,11 @@ impl FlycheckActor {
|
|||
}
|
||||
}
|
||||
|
||||
let command = self.check_command(package.as_deref());
|
||||
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");
|
||||
|
@ -302,7 +321,14 @@ impl FlycheckActor {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_command(&self, package: Option<&str>) -> Command {
|
||||
/// 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,
|
||||
|
@ -316,7 +342,10 @@ impl FlycheckActor {
|
|||
ansi_color_output,
|
||||
target_dir,
|
||||
} => {
|
||||
let mut cmd = Command::new(toolchain::cargo());
|
||||
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);
|
||||
|
||||
|
@ -355,7 +384,7 @@ impl FlycheckActor {
|
|||
cmd.arg("--target-dir").arg(target_dir);
|
||||
}
|
||||
cmd.envs(extra_env);
|
||||
(cmd, extra_args)
|
||||
(cmd, extra_args.clone())
|
||||
}
|
||||
FlycheckConfig::CustomCommand {
|
||||
command,
|
||||
|
@ -384,12 +413,34 @@ impl FlycheckActor {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
cmd
|
||||
Some(cmd)
|
||||
}
|
||||
|
||||
fn send(&self, check_task: Message) {
|
||||
|
|
|
@ -377,27 +377,39 @@ impl AttrsWithOwner {
|
|||
AttrDefId::GenericParamId(it) => match it {
|
||||
GenericParamId::ConstParamId(it) => {
|
||||
let src = it.parent().child_source(db);
|
||||
RawAttrs::from_attrs_owner(
|
||||
// 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(&src.value[it.local_id()]),
|
||||
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);
|
||||
RawAttrs::from_attrs_owner(
|
||||
// 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(&src.value[it.local_id()]),
|
||||
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);
|
||||
RawAttrs::from_attrs_owner(
|
||||
// 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(&src.value[it.local_id]),
|
||||
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),
|
||||
|
|
|
@ -650,7 +650,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
rustc_attr!(TEST, rustc_regions, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(
|
||||
TEST, rustc_error, Normal,
|
||||
template!(Word, List: "span_delayed_bug_from_inside_query"), WarnFollowingWordOnly
|
||||
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),
|
||||
|
|
|
@ -416,6 +416,11 @@ impl ExprCollector<'_> {
|
|||
let expr = e.expr().map(|e| self.collect_expr(e));
|
||||
self.alloc_expr(Expr::Return { expr }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::BecomeExpr(e) => {
|
||||
let expr =
|
||||
e.expr().map(|e| self.collect_expr(e)).unwrap_or_else(|| self.missing_expr());
|
||||
self.alloc_expr(Expr::Become { expr }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::YieldExpr(e) => {
|
||||
self.is_lowering_coroutine = true;
|
||||
let expr = e.expr().map(|e| self.collect_expr(e));
|
||||
|
@ -1000,10 +1005,6 @@ impl ExprCollector<'_> {
|
|||
krate: *krate,
|
||||
});
|
||||
}
|
||||
Some(ExpandError::RecursionOverflowPoisoned) => {
|
||||
// Recursion limit has been reached in the macro expansion tree, but not in
|
||||
// this very macro call. Don't add diagnostics to avoid duplication.
|
||||
}
|
||||
Some(err) => {
|
||||
self.source_map.diagnostics.push(BodyDiagnostic::MacroError {
|
||||
node: InFile::new(outer_file, syntax_ptr),
|
||||
|
@ -1112,7 +1113,7 @@ impl ExprCollector<'_> {
|
|||
statements.push(Statement::Expr { expr, has_semi });
|
||||
}
|
||||
}
|
||||
ast::Stmt::Item(_item) => (),
|
||||
ast::Stmt::Item(_item) => statements.push(Statement::Item),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ use itertools::Itertools;
|
|||
|
||||
use crate::{
|
||||
hir::{
|
||||
Array, BindingAnnotation, BindingId, CaptureBy, ClosureKind, Literal, LiteralOrConst,
|
||||
Movability, Statement,
|
||||
Array, BindingAnnotation, CaptureBy, ClosureKind, Literal, LiteralOrConst, Movability,
|
||||
Statement,
|
||||
},
|
||||
pretty::{print_generic_args, print_path, print_type_ref},
|
||||
type_ref::TypeRef,
|
||||
|
@ -261,6 +261,11 @@ impl Printer<'_> {
|
|||
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 {
|
||||
|
@ -623,6 +628,7 @@ impl Printer<'_> {
|
|||
}
|
||||
wln!(self);
|
||||
}
|
||||
Statement::Item => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -197,6 +197,7 @@ fn compute_block_scopes(
|
|||
Statement::Expr { expr, .. } => {
|
||||
compute_expr_scopes(*expr, body, scopes, scope);
|
||||
}
|
||||
Statement::Item => (),
|
||||
}
|
||||
}
|
||||
if let Some(expr) = tail {
|
||||
|
|
|
@ -634,7 +634,6 @@ impl<'a> AssocItemCollector<'a> {
|
|||
attr,
|
||||
) {
|
||||
Ok(ResolvedAttr::Macro(call_id)) => {
|
||||
self.attr_calls.push((ast_id, call_id));
|
||||
// If proc attribute macro expansion is disabled, skip expanding it here
|
||||
if !self.db.expand_proc_attr_macros() {
|
||||
continue 'attrs;
|
||||
|
@ -647,10 +646,21 @@ impl<'a> AssocItemCollector<'a> {
|
|||
// 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());
|
||||
|
|
|
@ -40,7 +40,7 @@ pub struct StructData {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct StructFlags: u8 {
|
||||
const NO_FLAGS = 0;
|
||||
/// Indicates whether the struct is `PhantomData`.
|
||||
|
|
|
@ -140,13 +140,11 @@ impl Expander {
|
|||
// 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::only_err(ExpandError::RecursionOverflowPoisoned);
|
||||
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::other(
|
||||
"reached recursion limit during macro expansion",
|
||||
));
|
||||
return ExpandResult::only_err(ExpandError::RecursionOverflow);
|
||||
}
|
||||
|
||||
let ExpandResult { value, err } = op(self);
|
||||
|
|
|
@ -447,18 +447,25 @@ fn select_best_path(
|
|||
}
|
||||
const STD_CRATES: [Name; 3] = [known::std, known::core, known::alloc];
|
||||
|
||||
let choose = |new_path: (ModPath, _), old_path: (ModPath, _)| {
|
||||
let new_has_prelude = new_path.0.segments().iter().any(|seg| seg == &known::prelude);
|
||||
let old_has_prelude = old_path.0.segments().iter().any(|seg| seg == &known::prelude);
|
||||
let choose = |new: (ModPath, _), old: (ModPath, _)| {
|
||||
let (new_path, _) = &new;
|
||||
let (old_path, _) = &old;
|
||||
let new_has_prelude = new_path.segments().iter().any(|seg| seg == &known::prelude);
|
||||
let old_has_prelude = old_path.segments().iter().any(|seg| seg == &known::prelude);
|
||||
match (new_has_prelude, old_has_prelude, prefer_prelude) {
|
||||
(true, false, true) | (false, true, false) => new_path,
|
||||
(true, false, false) | (false, true, true) => old_path,
|
||||
// no prelude difference in the paths, so pick the smaller one
|
||||
(true, false, true) | (false, true, false) => new,
|
||||
(true, false, false) | (false, true, true) => old,
|
||||
// no prelude difference in the paths, so pick the shorter one
|
||||
(true, true, _) | (false, false, _) => {
|
||||
if new_path.0.len() < old_path.0.len() {
|
||||
new_path
|
||||
let new_path_is_shorter = new_path
|
||||
.len()
|
||||
.cmp(&old_path.len())
|
||||
.then_with(|| new_path.textual_len().cmp(&old_path.textual_len()))
|
||||
.is_lt();
|
||||
if new_path_is_shorter {
|
||||
new
|
||||
} else {
|
||||
old_path
|
||||
old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -469,8 +476,8 @@ fn select_best_path(
|
|||
let rank = match prefer_no_std {
|
||||
false => |name: &Name| match name {
|
||||
name if name == &known::core => 0,
|
||||
name if name == &known::alloc => 0,
|
||||
name if name == &known::std => 1,
|
||||
name if name == &known::alloc => 1,
|
||||
name if name == &known::std => 2,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
true => |name: &Name| match name {
|
||||
|
@ -1539,4 +1546,38 @@ pub mod foo {
|
|||
"krate::prelude::Foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn respect_segment_length() {
|
||||
check_found_path(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:petgraph
|
||||
$0
|
||||
//- /petgraph.rs crate:petgraph
|
||||
pub mod graph {
|
||||
pub use crate::graph_impl::{
|
||||
NodeIndex
|
||||
};
|
||||
}
|
||||
|
||||
mod graph_impl {
|
||||
pub struct NodeIndex<Ix>(Ix);
|
||||
}
|
||||
|
||||
pub mod stable_graph {
|
||||
#[doc(no_inline)]
|
||||
pub use crate::graph::{NodeIndex};
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
#[doc(no_inline)]
|
||||
pub use crate::graph::{NodeIndex};
|
||||
}
|
||||
"#,
|
||||
"petgraph::graph::NodeIndex",
|
||||
"petgraph::graph::NodeIndex",
|
||||
"petgraph::graph::NodeIndex",
|
||||
"petgraph::graph::NodeIndex",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,6 +182,7 @@ pub enum Expr {
|
|||
tail: Option<ExprId>,
|
||||
},
|
||||
Const(ConstBlockId),
|
||||
// FIXME: Fold this into Block with an unsafe flag?
|
||||
Unsafe {
|
||||
id: Option<BlockId>,
|
||||
statements: Box<[Statement]>,
|
||||
|
@ -216,6 +217,9 @@ pub enum Expr {
|
|||
Return {
|
||||
expr: Option<ExprId>,
|
||||
},
|
||||
Become {
|
||||
expr: ExprId,
|
||||
},
|
||||
Yield {
|
||||
expr: Option<ExprId>,
|
||||
},
|
||||
|
@ -349,6 +353,9 @@ pub enum Statement {
|
|||
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 {
|
||||
|
@ -382,6 +389,7 @@ impl Expr {
|
|||
}
|
||||
}
|
||||
Statement::Expr { expr: expression, .. } => f(*expression),
|
||||
Statement::Item => (),
|
||||
}
|
||||
}
|
||||
if let &Some(expr) = tail {
|
||||
|
@ -410,6 +418,7 @@ impl Expr {
|
|||
f(expr);
|
||||
}
|
||||
}
|
||||
Expr::Become { expr } => f(*expr),
|
||||
Expr::RecordLit { fields, spread, .. } => {
|
||||
for field in fields.iter() {
|
||||
f(field.expr);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::{fmt, hash::BuildHasherDefault};
|
||||
|
||||
use base_db::CrateId;
|
||||
use fst::{self, raw::IndexedValue, Automaton, Streamer};
|
||||
use fst::{raw::IndexedValue, Automaton, Streamer};
|
||||
use hir_expand::name::Name;
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
|
@ -477,7 +477,7 @@ mod tests {
|
|||
use expect_test::{expect, Expect};
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
use crate::{db::DefDatabase, test_db::TestDB, ItemContainerId, Lookup};
|
||||
use crate::{test_db::TestDB, ItemContainerId, Lookup};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
|
@ -44,13 +44,13 @@ use std::{
|
|||
ops::{Index, Range},
|
||||
};
|
||||
|
||||
use ast::{AstNode, HasName, StructKind};
|
||||
use ast::{AstNode, StructKind};
|
||||
use base_db::CrateId;
|
||||
use either::Either;
|
||||
use hir_expand::{
|
||||
ast_id_map::{AstIdNode, FileAstId},
|
||||
attrs::RawAttrs,
|
||||
name::{name, AsName, Name},
|
||||
name::Name,
|
||||
ExpandTo, HirFileId, InFile,
|
||||
};
|
||||
use intern::Interned;
|
||||
|
@ -67,7 +67,7 @@ use crate::{
|
|||
attr::Attrs,
|
||||
db::DefDatabase,
|
||||
generics::{GenericParams, LifetimeParamData, TypeOrConstParamData},
|
||||
path::{path, AssociatedTypeBinding, GenericArgs, ImportAlias, ModPath, Path, PathKind},
|
||||
path::{GenericArgs, ImportAlias, ModPath, Path, PathKind},
|
||||
type_ref::{Mutability, TraitRef, TypeBound, TypeRef},
|
||||
visibility::{RawVisibility, VisibilityExplicitness},
|
||||
BlockId, Lookup,
|
||||
|
|
|
@ -2,17 +2,33 @@
|
|||
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use hir_expand::{ast_id_map::AstIdMap, span_map::SpanMapRef, HirFileId};
|
||||
use syntax::ast::{self, HasModuleItem, HasTypeBounds, IsString};
|
||||
use hir_expand::{
|
||||
ast_id_map::AstIdMap, mod_path::path, name, name::AsName, span_map::SpanMapRef, HirFileId,
|
||||
};
|
||||
use la_arena::Arena;
|
||||
use syntax::{
|
||||
ast::{self, HasModuleItem, HasName, HasTypeBounds, IsString},
|
||||
AstNode,
|
||||
};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
generics::{GenericParams, GenericParamsCollector, TypeParamData, TypeParamProvenance},
|
||||
type_ref::{LifetimeRef, TraitBoundModifier, TraitRef},
|
||||
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,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn id<N: ItemTreeNode>(index: Idx<N>) -> FileItemTreeId<N> {
|
||||
FileItemTreeId(index)
|
||||
}
|
||||
|
|
|
@ -6,12 +6,17 @@ 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,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
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 };
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ m!(&k");
|
|||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m { ($i:literal) => {}; }
|
||||
/* error: invalid token tree */"#]],
|
||||
/* error: mismatched delimiters */"#]],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -68,26 +68,26 @@ m2!();
|
|||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! i1 { invalid }
|
||||
/* error: invalid macro definition: expected subtree */
|
||||
/* error: macro definition has parse errors */
|
||||
|
||||
macro_rules! e1 { $i:ident => () }
|
||||
/* error: invalid macro definition: expected subtree */
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! e2 { ($i:ident) () }
|
||||
/* error: invalid macro definition: expected `=` */
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! e3 { ($(i:ident)_) => () }
|
||||
/* error: invalid macro definition: invalid repeat */
|
||||
/* error: macro definition has parse errors */
|
||||
|
||||
macro_rules! f1 { ($i) => ($i) }
|
||||
/* error: invalid macro definition: missing fragment specifier */
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! f2 { ($i:) => ($i) }
|
||||
/* error: invalid macro definition: missing fragment specifier */
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! f3 { ($i:_) => () }
|
||||
/* error: invalid macro definition: missing fragment specifier */
|
||||
/* error: macro definition has parse errors */
|
||||
|
||||
macro_rules! m1 { ($$i) => () }
|
||||
/* error: invalid macro definition: `$$` is not allowed on the pattern side */
|
||||
/* error: macro definition has parse errors */
|
||||
macro_rules! m2 { () => ( ${invalid()} ) }
|
||||
/* error: invalid macro definition: invalid metavariable expression */
|
||||
/* error: macro definition has parse errors */
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
@ -137,18 +137,18 @@ macro_rules! m9 { ($($($($i:ident)?)*)+) => {}; }
|
|||
macro_rules! mA { ($($($($i:ident)+)?)*) => {}; }
|
||||
macro_rules! mB { ($($($($i:ident)+)*)?) => {}; }
|
||||
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* error: invalid macro definition: empty token tree in repetition */
|
||||
/* 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 */
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -275,9 +275,9 @@ macro_rules! depth_too_large {
|
|||
}
|
||||
|
||||
fn test() {
|
||||
/* error: invalid macro definition: invalid metavariable expression */;
|
||||
/* error: invalid macro definition: invalid metavariable expression */;
|
||||
/* error: invalid macro definition: invalid metavariable expression */;
|
||||
/* error: macro definition has parse errors */;
|
||||
/* error: macro definition has parse errors */;
|
||||
/* error: macro definition has parse errors */;
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
|
|
|
@ -1090,3 +1090,57 @@ fn main() {
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_16529() {
|
||||
check(
|
||||
r#"
|
||||
mod any {
|
||||
#[macro_export]
|
||||
macro_rules! nameable {
|
||||
{
|
||||
struct $name:ident[$a:lifetime]
|
||||
} => {
|
||||
$crate::any::nameable! {
|
||||
struct $name[$a]
|
||||
a
|
||||
}
|
||||
};
|
||||
{
|
||||
struct $name:ident[$a:lifetime]
|
||||
a
|
||||
} => {};
|
||||
}
|
||||
pub use nameable;
|
||||
|
||||
nameable! {
|
||||
Name['a]
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
mod any {
|
||||
#[macro_export]
|
||||
macro_rules! nameable {
|
||||
{
|
||||
struct $name:ident[$a:lifetime]
|
||||
} => {
|
||||
$crate::any::nameable! {
|
||||
struct $name[$a]
|
||||
a
|
||||
}
|
||||
};
|
||||
{
|
||||
struct $name:ident[$a:lifetime]
|
||||
a
|
||||
} => {};
|
||||
}
|
||||
pub use nameable;
|
||||
|
||||
/* error: unexpected token in input */$crate::any::nameable! {
|
||||
struct $name[$a]a
|
||||
}
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -97,8 +97,8 @@ m2!(x
|
|||
macro_rules! m1 { ($x:ident) => { ($x } }
|
||||
macro_rules! m2 { ($x:ident) => {} }
|
||||
|
||||
/* error: invalid macro definition: expected subtree */
|
||||
/* error: invalid token tree */
|
||||
/* error: macro definition has parse errors */
|
||||
/* error: mismatched delimiters */
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream
|
|||
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);
|
||||
|
|
|
@ -57,7 +57,7 @@ pub mod proc_macro;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::{cmp::Ord, ops::Deref};
|
||||
use std::ops::Deref;
|
||||
|
||||
use base_db::{CrateId, Edition, FileId};
|
||||
use hir_expand::{
|
||||
|
|
|
@ -11,7 +11,7 @@ use either::Either;
|
|||
use hir_expand::{
|
||||
ast_id_map::FileAstId,
|
||||
attrs::{Attr, AttrId},
|
||||
builtin_attr_macro::find_builtin_attr,
|
||||
builtin_attr_macro::{find_builtin_attr, BuiltinAttrExpander},
|
||||
builtin_derive_macro::find_builtin_derive,
|
||||
builtin_fn_macro::find_builtin_macro,
|
||||
name::{name, AsName, Name},
|
||||
|
@ -98,9 +98,13 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, def_map: DefMap, tree_id: TreeI
|
|||
};
|
||||
(
|
||||
name.as_name(),
|
||||
CustomProcMacroExpander::new(hir_expand::proc_macro::ProcMacroId(
|
||||
idx as u32,
|
||||
)),
|
||||
if it.disabled {
|
||||
CustomProcMacroExpander::disabled()
|
||||
} else {
|
||||
CustomProcMacroExpander::new(
|
||||
hir_expand::proc_macro::ProcMacroId::new(idx as u32),
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
|
@ -604,9 +608,6 @@ impl DefCollector<'_> {
|
|||
id: ItemTreeId<item_tree::Function>,
|
||||
fn_id: FunctionId,
|
||||
) {
|
||||
if self.def_map.block.is_some() {
|
||||
return;
|
||||
}
|
||||
let kind = def.kind.to_basedb_kind();
|
||||
let (expander, kind) =
|
||||
match self.proc_macros.as_ref().map(|it| it.iter().find(|(n, _)| n == &def.name)) {
|
||||
|
@ -1120,9 +1121,16 @@ impl DefCollector<'_> {
|
|||
let mut push_resolved = |directive: &MacroDirective, call_id| {
|
||||
resolved.push((directive.module_id, directive.depth, directive.container, call_id));
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum Resolved {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
let mut res = ReachedFixedPoint::Yes;
|
||||
// Retain unresolved macros after this round of resolution.
|
||||
macros.retain(|directive| {
|
||||
let mut retain = |directive: &MacroDirective| {
|
||||
let subns = match &directive.kind {
|
||||
MacroDirectiveKind::FnLike { .. } => MacroSubNs::Bang,
|
||||
MacroDirectiveKind::Attr { .. } | MacroDirectiveKind::Derive { .. } => {
|
||||
|
@ -1156,10 +1164,11 @@ impl DefCollector<'_> {
|
|||
self.def_map.modules[directive.module_id]
|
||||
.scope
|
||||
.add_macro_invoc(ast_id.ast_id, call_id);
|
||||
|
||||
push_resolved(directive, call_id);
|
||||
|
||||
res = ReachedFixedPoint::No;
|
||||
return false;
|
||||
return Resolved::Yes;
|
||||
}
|
||||
}
|
||||
MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos, call_site } => {
|
||||
|
@ -1198,7 +1207,7 @@ impl DefCollector<'_> {
|
|||
|
||||
push_resolved(directive, call_id);
|
||||
res = ReachedFixedPoint::No;
|
||||
return false;
|
||||
return Resolved::Yes;
|
||||
}
|
||||
}
|
||||
MacroDirectiveKind::Attr { ast_id: file_ast_id, mod_item, attr, tree } => {
|
||||
|
@ -1221,7 +1230,7 @@ impl DefCollector<'_> {
|
|||
}
|
||||
.collect(&[*mod_item], directive.container);
|
||||
res = ReachedFixedPoint::No;
|
||||
false
|
||||
Resolved::Yes
|
||||
};
|
||||
|
||||
if let Some(ident) = path.as_ident() {
|
||||
|
@ -1237,13 +1246,18 @@ impl DefCollector<'_> {
|
|||
|
||||
let def = match resolver_def_id(path.clone()) {
|
||||
Some(def) if def.is_attribute() => def,
|
||||
_ => return true,
|
||||
_ => return Resolved::No,
|
||||
};
|
||||
if matches!(
|
||||
def,
|
||||
MacroDefId { kind: MacroDefKind::BuiltInAttr(expander, _),.. }
|
||||
if expander.is_derive()
|
||||
) {
|
||||
|
||||
if let MacroDefId {
|
||||
kind:
|
||||
MacroDefKind::BuiltInAttr(
|
||||
BuiltinAttrExpander::Derive | BuiltinAttrExpander::DeriveConst,
|
||||
_,
|
||||
),
|
||||
..
|
||||
} = def
|
||||
{
|
||||
// Resolved to `#[derive]`, we don't actually expand this attribute like
|
||||
// normal (as that would just be an identity expansion with extra output)
|
||||
// Instead we treat derive attributes special and apply them separately.
|
||||
|
@ -1316,16 +1330,6 @@ impl DefCollector<'_> {
|
|||
let call_id =
|
||||
attr_macro_as_call_id(self.db, file_ast_id, attr, self.def_map.krate, def);
|
||||
|
||||
// If proc attribute macro expansion is disabled, skip expanding it here
|
||||
if !self.db.expand_proc_attr_macros() {
|
||||
self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
|
||||
directive.module_id,
|
||||
self.db.lookup_intern_macro_call(call_id).kind,
|
||||
def.krate,
|
||||
));
|
||||
return recollect_without(self);
|
||||
}
|
||||
|
||||
// Skip #[test]/#[bench] expansion, which would merely result in more memory usage
|
||||
// due to duplicating functions into macro expansions
|
||||
if matches!(
|
||||
|
@ -1337,17 +1341,29 @@ impl DefCollector<'_> {
|
|||
}
|
||||
|
||||
if let MacroDefKind::ProcMacro(exp, ..) = def.kind {
|
||||
if exp.is_dummy() {
|
||||
// If there's no expander for the proc macro (e.g.
|
||||
// because proc macros are disabled, or building the
|
||||
// proc macro crate failed), report this and skip
|
||||
// expansion like we would if it was disabled
|
||||
// If proc attribute macro expansion is disabled, skip expanding it here
|
||||
if !self.db.expand_proc_attr_macros() {
|
||||
self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
|
||||
directive.module_id,
|
||||
self.db.lookup_intern_macro_call(call_id).kind,
|
||||
def.krate,
|
||||
));
|
||||
return recollect_without(self);
|
||||
}
|
||||
|
||||
// If there's no expander for the proc macro (e.g.
|
||||
// because proc macros are disabled, or building the
|
||||
// proc macro crate failed), report this and skip
|
||||
// expansion like we would if it was disabled
|
||||
if exp.is_dummy() {
|
||||
self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
|
||||
directive.module_id,
|
||||
self.db.lookup_intern_macro_call(call_id).kind,
|
||||
def.krate,
|
||||
));
|
||||
return recollect_without(self);
|
||||
}
|
||||
if exp.is_disabled() {
|
||||
return recollect_without(self);
|
||||
}
|
||||
}
|
||||
|
@ -1358,12 +1374,13 @@ impl DefCollector<'_> {
|
|||
|
||||
push_resolved(directive, call_id);
|
||||
res = ReachedFixedPoint::No;
|
||||
return false;
|
||||
return Resolved::Yes;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
Resolved::No
|
||||
};
|
||||
macros.retain(|it| retain(it) == Resolved::No);
|
||||
// Attribute resolution can add unresolved macro invocations, so concatenate the lists.
|
||||
macros.extend(mem::take(&mut self.unresolved_macros));
|
||||
self.unresolved_macros = macros;
|
||||
|
@ -1673,7 +1690,11 @@ impl ModCollector<'_, '_> {
|
|||
FunctionLoc { container, id: ItemTreeId::new(self.tree_id, id) }.intern(db);
|
||||
|
||||
let vis = resolve_vis(def_map, &self.item_tree[it.visibility]);
|
||||
if self.def_collector.is_proc_macro && self.module_id == DefMap::ROOT {
|
||||
|
||||
if self.def_collector.def_map.block.is_none()
|
||||
&& self.def_collector.is_proc_macro
|
||||
&& self.module_id == DefMap::ROOT
|
||||
{
|
||||
if let Some(proc_macro) = attrs.parse_proc_macro_decl(&it.name) {
|
||||
self.def_collector.export_proc_macro(
|
||||
proc_macro,
|
||||
|
@ -2333,7 +2354,7 @@ impl ModCollector<'_, '_> {
|
|||
resolved_res.resolved_def.take_macros().map(|it| db.macro_def(it))
|
||||
},
|
||||
) {
|
||||
// FIXME: if there were errors, this mightve been in the eager expansion from an
|
||||
// FIXME: if there were errors, this might've been in the eager expansion from an
|
||||
// unresolved macro, so we need to push this into late macro resolution. see fixme above
|
||||
if res.err.is_none() {
|
||||
// Legacy macros need to be expanded immediately, so that any macros they produce
|
||||
|
@ -2425,7 +2446,7 @@ mod tests {
|
|||
use base_db::SourceDatabase;
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
use crate::{db::DefDatabase, test_db::TestDB};
|
||||
use crate::test_db::TestDB;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
|
@ -103,6 +103,9 @@ impl DefDiagnostic {
|
|||
}
|
||||
|
||||
// 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,
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use expect_test::expect;
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::nameres::tests::check;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -446,7 +446,7 @@ fn compile_error_expand(
|
|||
) -> ExpandResult<tt::Subtree> {
|
||||
let err = match &*tt.token_trees {
|
||||
[tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) {
|
||||
Some(unquoted) => ExpandError::other(unquoted),
|
||||
Some(unquoted) => ExpandError::other(unquoted.into_boxed_str()),
|
||||
None => ExpandError::other("`compile_error!` argument must be a string"),
|
||||
},
|
||||
_ => ExpandError::other("`compile_error!` argument must be a string"),
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
//! Defines a unit of change that can applied to the database to get the next
|
||||
//! state. Changes are transactional.
|
||||
use base_db::{salsa::Durability, CrateGraph, FileChange, SourceDatabaseExt, SourceRoot};
|
||||
use base_db::{
|
||||
salsa::Durability, CrateGraph, CrateId, FileChange, SourceDatabaseExt, SourceRoot,
|
||||
TargetLayoutLoadResult, Version,
|
||||
};
|
||||
use la_arena::RawIdx;
|
||||
use span::FileId;
|
||||
use triomphe::Arc;
|
||||
|
||||
|
@ -10,6 +14,8 @@ use crate::{db::ExpandDatabase, proc_macro::ProcMacros};
|
|||
pub struct Change {
|
||||
pub source_change: FileChange,
|
||||
pub proc_macros: Option<ProcMacros>,
|
||||
pub toolchains: Option<Vec<Option<Version>>>,
|
||||
pub target_data_layouts: Option<Vec<TargetLayoutLoadResult>>,
|
||||
}
|
||||
|
||||
impl Change {
|
||||
|
@ -22,6 +28,24 @@ impl Change {
|
|||
if let Some(proc_macros) = self.proc_macros {
|
||||
db.set_proc_macros_with_durability(Arc::new(proc_macros), Durability::HIGH);
|
||||
}
|
||||
if let Some(target_data_layouts) = self.target_data_layouts {
|
||||
for (id, val) in target_data_layouts.into_iter().enumerate() {
|
||||
db.set_data_layout_with_durability(
|
||||
CrateId::from_raw(RawIdx::from(id as u32)),
|
||||
val,
|
||||
Durability::HIGH,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(toolchains) = self.toolchains {
|
||||
for (id, val) in toolchains.into_iter().enumerate() {
|
||||
db.set_toolchain_with_durability(
|
||||
CrateId::from_raw(RawIdx::from(id as u32)),
|
||||
val,
|
||||
Durability::HIGH,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_file(&mut self, file_id: FileId, new_text: Option<Arc<str>>) {
|
||||
|
@ -36,6 +60,14 @@ impl Change {
|
|||
self.proc_macros = Some(proc_macros);
|
||||
}
|
||||
|
||||
pub fn set_toolchains(&mut self, toolchains: Vec<Option<Version>>) {
|
||||
self.toolchains = Some(toolchains);
|
||||
}
|
||||
|
||||
pub fn set_target_data_layouts(&mut self, target_data_layouts: Vec<TargetLayoutLoadResult>) {
|
||||
self.target_data_layouts = Some(target_data_layouts);
|
||||
}
|
||||
|
||||
pub fn set_roots(&mut self, roots: Vec<SourceRoot>) {
|
||||
self.source_change.set_roots(roots)
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ pub trait ExpandDatabase: SourceDatabase {
|
|||
fn macro_arg(
|
||||
&self,
|
||||
id: MacroCallId,
|
||||
) -> ValueResult<Option<(Arc<tt::Subtree>, SyntaxFixupUndoInfo)>, Arc<Box<[SyntaxError]>>>;
|
||||
) -> ValueResult<(Arc<tt::Subtree>, SyntaxFixupUndoInfo), Arc<Box<[SyntaxError]>>>;
|
||||
/// Fetches the expander for this macro.
|
||||
#[salsa::transparent]
|
||||
#[salsa::invoke(TokenExpander::macro_expander)]
|
||||
|
@ -326,58 +326,77 @@ fn macro_arg(
|
|||
db: &dyn ExpandDatabase,
|
||||
id: MacroCallId,
|
||||
// FIXME: consider the following by putting fixup info into eager call info args
|
||||
// ) -> ValueResult<Option<Arc<(tt::Subtree, SyntaxFixupUndoInfo)>>, Arc<Box<[SyntaxError]>>> {
|
||||
) -> ValueResult<Option<(Arc<tt::Subtree>, SyntaxFixupUndoInfo)>, Arc<Box<[SyntaxError]>>> {
|
||||
let mismatched_delimiters = |arg: &SyntaxNode| {
|
||||
let first = arg.first_child_or_token().map_or(T![.], |it| it.kind());
|
||||
let last = arg.last_child_or_token().map_or(T![.], |it| it.kind());
|
||||
let well_formed_tt =
|
||||
matches!((first, last), (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']));
|
||||
if !well_formed_tt {
|
||||
// Don't expand malformed (unbalanced) macro invocations. This is
|
||||
// less than ideal, but trying to expand unbalanced macro calls
|
||||
// sometimes produces pathological, deeply nested code which breaks
|
||||
// all kinds of things.
|
||||
//
|
||||
// Some day, we'll have explicit recursion counters for all
|
||||
// recursive things, at which point this code might be removed.
|
||||
cov_mark::hit!(issue9358_bad_macro_stack_overflow);
|
||||
Some(Arc::new(Box::new([SyntaxError::new(
|
||||
"unbalanced token tree".to_owned(),
|
||||
arg.text_range(),
|
||||
)]) as Box<[_]>))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
// ) -> ValueResult<Arc<(tt::Subtree, SyntaxFixupUndoInfo)>, Arc<Box<[SyntaxError]>>> {
|
||||
) -> ValueResult<(Arc<tt::Subtree>, SyntaxFixupUndoInfo), Arc<Box<[SyntaxError]>>> {
|
||||
let loc = db.lookup_intern_macro_call(id);
|
||||
if let Some(EagerCallInfo { arg, .. }) = matches!(loc.def.kind, MacroDefKind::BuiltInEager(..))
|
||||
.then(|| loc.eager.as_deref())
|
||||
.flatten()
|
||||
{
|
||||
ValueResult::ok(Some((arg.clone(), SyntaxFixupUndoInfo::NONE)))
|
||||
ValueResult::ok((arg.clone(), SyntaxFixupUndoInfo::NONE))
|
||||
} else {
|
||||
let (parse, map) = parse_with_map(db, loc.kind.file_id());
|
||||
let root = parse.syntax_node();
|
||||
|
||||
let syntax = match loc.kind {
|
||||
MacroCallKind::FnLike { ast_id, .. } => {
|
||||
let dummy_tt = |kind| {
|
||||
(
|
||||
Arc::new(tt::Subtree {
|
||||
delimiter: tt::Delimiter {
|
||||
open: loc.call_site,
|
||||
close: loc.call_site,
|
||||
kind,
|
||||
},
|
||||
token_trees: Box::default(),
|
||||
}),
|
||||
SyntaxFixupUndoInfo::default(),
|
||||
)
|
||||
};
|
||||
|
||||
let node = &ast_id.to_ptr(db).to_node(&root);
|
||||
let offset = node.syntax().text_range().start();
|
||||
match node.token_tree() {
|
||||
Some(tt) => {
|
||||
let tt = tt.syntax();
|
||||
if let Some(e) = mismatched_delimiters(tt) {
|
||||
return ValueResult::only_err(e);
|
||||
}
|
||||
tt.clone()
|
||||
}
|
||||
None => {
|
||||
return ValueResult::only_err(Arc::new(Box::new([
|
||||
SyntaxError::new_at_offset("missing token tree".to_owned(), offset),
|
||||
])));
|
||||
}
|
||||
let Some(tt) = node.token_tree() else {
|
||||
return ValueResult::new(
|
||||
dummy_tt(tt::DelimiterKind::Invisible),
|
||||
Arc::new(Box::new([SyntaxError::new_at_offset(
|
||||
"missing token tree".to_owned(),
|
||||
offset,
|
||||
)])),
|
||||
);
|
||||
};
|
||||
let first = tt.left_delimiter_token().map(|it| it.kind()).unwrap_or(T!['(']);
|
||||
let last = tt.right_delimiter_token().map(|it| it.kind()).unwrap_or(T![.]);
|
||||
|
||||
let mismatched_delimiters = !matches!(
|
||||
(first, last),
|
||||
(T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}'])
|
||||
);
|
||||
if mismatched_delimiters {
|
||||
// Don't expand malformed (unbalanced) macro invocations. This is
|
||||
// less than ideal, but trying to expand unbalanced macro calls
|
||||
// sometimes produces pathological, deeply nested code which breaks
|
||||
// all kinds of things.
|
||||
//
|
||||
// So instead, we'll return an empty subtree here
|
||||
cov_mark::hit!(issue9358_bad_macro_stack_overflow);
|
||||
|
||||
let kind = match first {
|
||||
_ if loc.def.is_proc_macro() => tt::DelimiterKind::Invisible,
|
||||
T!['('] => tt::DelimiterKind::Parenthesis,
|
||||
T!['['] => tt::DelimiterKind::Bracket,
|
||||
T!['{'] => tt::DelimiterKind::Brace,
|
||||
_ => tt::DelimiterKind::Invisible,
|
||||
};
|
||||
return ValueResult::new(
|
||||
dummy_tt(kind),
|
||||
Arc::new(Box::new([SyntaxError::new_at_offset(
|
||||
"mismatched delimiters".to_owned(),
|
||||
offset,
|
||||
)])),
|
||||
);
|
||||
}
|
||||
tt.syntax().clone()
|
||||
}
|
||||
MacroCallKind::Derive { ast_id, .. } => {
|
||||
ast_id.to_ptr(db).to_node(&root).syntax().clone()
|
||||
|
@ -427,15 +446,15 @@ fn macro_arg(
|
|||
|
||||
if matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) {
|
||||
match parse.errors() {
|
||||
[] => ValueResult::ok(Some((Arc::new(tt), undo_info))),
|
||||
[] => ValueResult::ok((Arc::new(tt), undo_info)),
|
||||
errors => ValueResult::new(
|
||||
Some((Arc::new(tt), undo_info)),
|
||||
(Arc::new(tt), undo_info),
|
||||
// Box::<[_]>::from(res.errors()), not stable yet
|
||||
Arc::new(errors.to_vec().into_boxed_slice()),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
ValueResult::ok(Some((Arc::new(tt), undo_info)))
|
||||
ValueResult::ok((Arc::new(tt), undo_info))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -519,21 +538,20 @@ fn macro_expand(
|
|||
expander.expand(db, macro_call_id, &node, map.as_ref())
|
||||
}
|
||||
_ => {
|
||||
let ValueResult { value, err } = db.macro_arg(macro_call_id);
|
||||
let Some((macro_arg, undo_info)) = value else {
|
||||
return ExpandResult {
|
||||
value: CowArc::Owned(tt::Subtree {
|
||||
delimiter: tt::Delimiter::invisible_spanned(loc.call_site),
|
||||
token_trees: Box::new([]),
|
||||
}),
|
||||
// FIXME: We should make sure to enforce an invariant that invalid macro
|
||||
// calls do not reach this call path!
|
||||
err: Some(ExpandError::other("invalid token tree")),
|
||||
};
|
||||
let ValueResult { value: (macro_arg, undo_info), err } = db.macro_arg(macro_call_id);
|
||||
let format_parse_err = |err: Arc<Box<[SyntaxError]>>| {
|
||||
let mut buf = String::new();
|
||||
for err in &**err {
|
||||
use std::fmt::Write;
|
||||
_ = write!(buf, "{}, ", err);
|
||||
}
|
||||
buf.pop();
|
||||
buf.pop();
|
||||
ExpandError::other(buf)
|
||||
};
|
||||
|
||||
let arg = &*macro_arg;
|
||||
match loc.def.kind {
|
||||
let res = match loc.def.kind {
|
||||
MacroDefKind::Declarative(id) => {
|
||||
db.decl_macro_expander(loc.def.krate, id).expand(db, arg.clone(), macro_call_id)
|
||||
}
|
||||
|
@ -549,16 +567,7 @@ fn macro_expand(
|
|||
MacroDefKind::BuiltInEager(..) if loc.eager.is_none() => {
|
||||
return ExpandResult {
|
||||
value: CowArc::Arc(macro_arg.clone()),
|
||||
err: err.map(|err| {
|
||||
let mut buf = String::new();
|
||||
for err in &**err {
|
||||
use std::fmt::Write;
|
||||
_ = write!(buf, "{}, ", err);
|
||||
}
|
||||
buf.pop();
|
||||
buf.pop();
|
||||
ExpandError::other(buf)
|
||||
}),
|
||||
err: err.map(format_parse_err),
|
||||
};
|
||||
}
|
||||
MacroDefKind::BuiltInEager(it, _) => {
|
||||
|
@ -570,6 +579,11 @@ fn macro_expand(
|
|||
res
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
ExpandResult {
|
||||
value: res.value,
|
||||
// if the arg had parse errors, show them instead of the expansion errors
|
||||
err: err.map(format_parse_err).or(res.err),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -597,17 +611,7 @@ fn macro_expand(
|
|||
|
||||
fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<Arc<tt::Subtree>> {
|
||||
let loc = db.lookup_intern_macro_call(id);
|
||||
let Some((macro_arg, undo_info)) = db.macro_arg(id).value else {
|
||||
return ExpandResult {
|
||||
value: Arc::new(tt::Subtree {
|
||||
delimiter: tt::Delimiter::invisible_spanned(loc.call_site),
|
||||
token_trees: Box::new([]),
|
||||
}),
|
||||
// FIXME: We should make sure to enforce an invariant that invalid macro
|
||||
// calls do not reach this call path!
|
||||
err: Some(ExpandError::other("invalid token tree")),
|
||||
};
|
||||
};
|
||||
let (macro_arg, undo_info) = db.macro_arg(id).value;
|
||||
|
||||
let expander = match loc.def.kind {
|
||||
MacroDefKind::ProcMacro(expander, ..) => expander,
|
||||
|
|
|
@ -31,7 +31,7 @@ impl DeclarativeMacroExpander {
|
|||
call_id: MacroCallId,
|
||||
) -> ExpandResult<tt::Subtree> {
|
||||
let loc = db.lookup_intern_macro_call(call_id);
|
||||
let toolchain = &db.crate_graph()[loc.def.krate].toolchain;
|
||||
let toolchain = db.toolchain(loc.def.krate);
|
||||
let new_meta_vars = toolchain.as_ref().map_or(false, |version| {
|
||||
REQUIREMENT.get_or_init(|| VersionReq::parse(">=1.76").unwrap()).matches(
|
||||
&base_db::Version {
|
||||
|
@ -44,9 +44,9 @@ impl DeclarativeMacroExpander {
|
|||
)
|
||||
});
|
||||
match self.mac.err() {
|
||||
Some(e) => ExpandResult::new(
|
||||
Some(_) => ExpandResult::new(
|
||||
tt::Subtree::empty(tt::DelimSpan { open: loc.call_site, close: loc.call_site }),
|
||||
ExpandError::other(format!("invalid macro definition: {e}")),
|
||||
ExpandError::MacroDefinition,
|
||||
),
|
||||
None => self
|
||||
.mac
|
||||
|
@ -67,7 +67,7 @@ impl DeclarativeMacroExpander {
|
|||
krate: CrateId,
|
||||
call_site: Span,
|
||||
) -> ExpandResult<tt::Subtree> {
|
||||
let toolchain = &db.crate_graph()[krate].toolchain;
|
||||
let toolchain = db.toolchain(krate);
|
||||
let new_meta_vars = toolchain.as_ref().map_or(false, |version| {
|
||||
REQUIREMENT.get_or_init(|| VersionReq::parse(">=1.76").unwrap()).matches(
|
||||
&base_db::Version {
|
||||
|
@ -80,9 +80,9 @@ impl DeclarativeMacroExpander {
|
|||
)
|
||||
});
|
||||
match self.mac.err() {
|
||||
Some(e) => ExpandResult::new(
|
||||
Some(_) => ExpandResult::new(
|
||||
tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
|
||||
ExpandError::other(format!("invalid macro definition: {e}")),
|
||||
ExpandError::MacroDefinition,
|
||||
),
|
||||
None => self.mac.expand(&tt, |_| (), new_meta_vars, call_site).map_err(Into::into),
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ impl DeclarativeMacroExpander {
|
|||
_ => None,
|
||||
}
|
||||
};
|
||||
let toolchain = crate_data.toolchain.as_ref();
|
||||
let toolchain = db.toolchain(def_crate);
|
||||
let new_meta_vars = toolchain.as_ref().map_or(false, |version| {
|
||||
REQUIREMENT.get_or_init(|| VersionReq::parse(">=1.76").unwrap()).matches(
|
||||
&base_db::Version {
|
||||
|
|
|
@ -44,7 +44,6 @@ use crate::{
|
|||
builtin_derive_macro::BuiltinDeriveExpander,
|
||||
builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander},
|
||||
db::{ExpandDatabase, TokenExpander},
|
||||
fixup::SyntaxFixupUndoInfo,
|
||||
hygiene::SyntaxContextData,
|
||||
mod_path::ModPath,
|
||||
proc_macro::{CustomProcMacroExpander, ProcMacroKind},
|
||||
|
@ -129,8 +128,11 @@ pub type ExpandResult<T> = ValueResult<T, ExpandError>;
|
|||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub enum ExpandError {
|
||||
UnresolvedProcMacro(CrateId),
|
||||
/// The macro expansion is disabled.
|
||||
MacroDisabled,
|
||||
MacroDefinition,
|
||||
Mbe(mbe::ExpandError),
|
||||
RecursionOverflowPoisoned,
|
||||
RecursionOverflow,
|
||||
Other(Box<Box<str>>),
|
||||
ProcMacroPanic(Box<Box<str>>),
|
||||
}
|
||||
|
@ -152,14 +154,14 @@ impl fmt::Display for ExpandError {
|
|||
match self {
|
||||
ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"),
|
||||
ExpandError::Mbe(it) => it.fmt(f),
|
||||
ExpandError::RecursionOverflowPoisoned => {
|
||||
f.write_str("overflow expanding the original macro")
|
||||
}
|
||||
ExpandError::RecursionOverflow => f.write_str("overflow expanding the original macro"),
|
||||
ExpandError::ProcMacroPanic(it) => {
|
||||
f.write_str("proc-macro panicked: ")?;
|
||||
f.write_str(it)
|
||||
}
|
||||
ExpandError::Other(it) => f.write_str(it),
|
||||
ExpandError::MacroDisabled => f.write_str("macro disabled"),
|
||||
ExpandError::MacroDefinition => f.write_str("macro definition has parse errors"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -225,8 +227,8 @@ pub enum MacroCallKind {
|
|||
},
|
||||
Attr {
|
||||
ast_id: AstId<ast::Item>,
|
||||
// FIXME: This is being interned, subtrees can vary quickly differ just slightly causing
|
||||
// leakage problems here
|
||||
// FIXME: This shouldn't be here, we can derive this from `invoc_attr_index`
|
||||
// but we need to fix the `cfg_attr` handling first.
|
||||
attr_args: Option<Arc<tt::Subtree>>,
|
||||
/// Syntactical index of the invoking `#[attribute]`.
|
||||
///
|
||||
|
@ -758,15 +760,7 @@ impl ExpansionInfo {
|
|||
let (parse, exp_map) = db.parse_macro_expansion(macro_file).value;
|
||||
let expanded = InMacroFile { file_id: macro_file, value: parse.syntax_node() };
|
||||
|
||||
let (macro_arg, _) = db.macro_arg(macro_file.macro_call_id).value.unwrap_or_else(|| {
|
||||
(
|
||||
Arc::new(tt::Subtree {
|
||||
delimiter: tt::Delimiter::invisible_spanned(loc.call_site),
|
||||
token_trees: Box::new([]),
|
||||
}),
|
||||
SyntaxFixupUndoInfo::NONE,
|
||||
)
|
||||
});
|
||||
let (macro_arg, _) = db.macro_arg(macro_file.macro_call_id).value;
|
||||
|
||||
let def = loc.def.ast_id().left().and_then(|id| {
|
||||
let def_tt = match id.to_node(db) {
|
||||
|
|
|
@ -94,6 +94,21 @@ impl ModPath {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn textual_len(&self) -> usize {
|
||||
let base = match self.kind {
|
||||
PathKind::Plain => 0,
|
||||
PathKind::Super(0) => "self".len(),
|
||||
PathKind::Super(i) => "super".len() * i as usize,
|
||||
PathKind::Crate => "crate".len(),
|
||||
PathKind::Abs => 0,
|
||||
PathKind::DollarCrate(_) => "$crate".len(),
|
||||
};
|
||||
self.segments()
|
||||
.iter()
|
||||
.map(|segment| segment.as_str().map_or(0, str::len))
|
||||
.fold(base, core::ops::Add::add)
|
||||
}
|
||||
|
||||
pub fn is_ident(&self) -> bool {
|
||||
self.as_ident().is_some()
|
||||
}
|
||||
|
|
|
@ -12,7 +12,13 @@ use syntax::SmolStr;
|
|||
use crate::{db::ExpandDatabase, tt, ExpandError, ExpandResult};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ProcMacroId(pub u32);
|
||||
pub struct ProcMacroId(u32);
|
||||
|
||||
impl ProcMacroId {
|
||||
pub fn new(u32: u32) -> Self {
|
||||
ProcMacroId(u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
||||
pub enum ProcMacroKind {
|
||||
|
@ -49,6 +55,7 @@ pub struct ProcMacro {
|
|||
pub name: SmolStr,
|
||||
pub kind: ProcMacroKind,
|
||||
pub expander: sync::Arc<dyn ProcMacroExpander>,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
|
@ -56,20 +63,35 @@ pub struct CustomProcMacroExpander {
|
|||
proc_macro_id: ProcMacroId,
|
||||
}
|
||||
|
||||
const DUMMY_ID: u32 = !0;
|
||||
|
||||
impl CustomProcMacroExpander {
|
||||
const DUMMY_ID: u32 = !0;
|
||||
const DISABLED_ID: u32 = !1;
|
||||
|
||||
pub fn new(proc_macro_id: ProcMacroId) -> Self {
|
||||
assert_ne!(proc_macro_id.0, DUMMY_ID);
|
||||
assert_ne!(proc_macro_id.0, Self::DUMMY_ID);
|
||||
assert_ne!(proc_macro_id.0, Self::DISABLED_ID);
|
||||
Self { proc_macro_id }
|
||||
}
|
||||
|
||||
pub fn dummy() -> Self {
|
||||
Self { proc_macro_id: ProcMacroId(DUMMY_ID) }
|
||||
/// A dummy expander that always errors. This is used for proc-macros that are missing, usually
|
||||
/// due to them not being built yet.
|
||||
pub const fn dummy() -> Self {
|
||||
Self { proc_macro_id: ProcMacroId(Self::DUMMY_ID) }
|
||||
}
|
||||
|
||||
pub fn is_dummy(&self) -> bool {
|
||||
self.proc_macro_id.0 == DUMMY_ID
|
||||
/// The macro was not yet resolved.
|
||||
pub const fn is_dummy(&self) -> bool {
|
||||
self.proc_macro_id.0 == Self::DUMMY_ID
|
||||
}
|
||||
|
||||
/// A dummy expander that always errors. This expander is used for macros that have been disabled.
|
||||
pub const fn disabled() -> Self {
|
||||
Self { proc_macro_id: ProcMacroId(Self::DISABLED_ID) }
|
||||
}
|
||||
|
||||
/// The macro is explicitly disabled and cannot be expanded.
|
||||
pub const fn is_disabled(&self) -> bool {
|
||||
self.proc_macro_id.0 == Self::DISABLED_ID
|
||||
}
|
||||
|
||||
pub fn expand(
|
||||
|
@ -84,10 +106,14 @@ impl CustomProcMacroExpander {
|
|||
mixed_site: Span,
|
||||
) -> ExpandResult<tt::Subtree> {
|
||||
match self.proc_macro_id {
|
||||
ProcMacroId(DUMMY_ID) => ExpandResult::new(
|
||||
ProcMacroId(Self::DUMMY_ID) => ExpandResult::new(
|
||||
tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
|
||||
ExpandError::UnresolvedProcMacro(def_crate),
|
||||
),
|
||||
ProcMacroId(Self::DISABLED_ID) => ExpandResult::new(
|
||||
tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
|
||||
ExpandError::MacroDisabled,
|
||||
),
|
||||
ProcMacroId(id) => {
|
||||
let proc_macros = db.proc_macros();
|
||||
let proc_macros = match proc_macros.get(&def_crate) {
|
||||
|
@ -110,7 +136,7 @@ impl CustomProcMacroExpander {
|
|||
);
|
||||
return ExpandResult::new(
|
||||
tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
|
||||
ExpandError::other("Internal error"),
|
||||
ExpandError::other("Internal error: proc-macro index out of bounds"),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! The implementation of `RustIrDatabase` for Chalk, which provides information
|
||||
//! about the code that Chalk needs.
|
||||
use core::ops;
|
||||
use std::{iter, sync::Arc};
|
||||
use std::{iter, ops::ControlFlow, sync::Arc};
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
|
@ -10,9 +10,10 @@ use chalk_solve::rust_ir::{self, OpaqueTyDatumBound, WellKnownTrait};
|
|||
|
||||
use base_db::CrateId;
|
||||
use hir_def::{
|
||||
data::adt::StructFlags,
|
||||
hir::Movability,
|
||||
lang_item::{LangItem, LangItemTarget},
|
||||
AssocItemId, BlockId, GenericDefId, HasModule, ItemContainerId, Lookup, TypeAliasId,
|
||||
AssocItemId, BlockId, GenericDefId, HasModule, ItemContainerId, Lookup, TypeAliasId, VariantId,
|
||||
};
|
||||
use hir_expand::name::name;
|
||||
|
||||
|
@ -33,7 +34,7 @@ use crate::{
|
|||
|
||||
pub(crate) type AssociatedTyDatum = chalk_solve::rust_ir::AssociatedTyDatum<Interner>;
|
||||
pub(crate) type TraitDatum = chalk_solve::rust_ir::TraitDatum<Interner>;
|
||||
pub(crate) type StructDatum = chalk_solve::rust_ir::AdtDatum<Interner>;
|
||||
pub(crate) type AdtDatum = chalk_solve::rust_ir::AdtDatum<Interner>;
|
||||
pub(crate) type ImplDatum = chalk_solve::rust_ir::ImplDatum<Interner>;
|
||||
pub(crate) type OpaqueTyDatum = chalk_solve::rust_ir::OpaqueTyDatum<Interner>;
|
||||
|
||||
|
@ -53,8 +54,8 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
|
|||
fn trait_datum(&self, trait_id: TraitId) -> Arc<TraitDatum> {
|
||||
self.db.trait_datum(self.krate, trait_id)
|
||||
}
|
||||
fn adt_datum(&self, struct_id: AdtId) -> Arc<StructDatum> {
|
||||
self.db.struct_datum(self.krate, struct_id)
|
||||
fn adt_datum(&self, struct_id: AdtId) -> Arc<AdtDatum> {
|
||||
self.db.adt_datum(self.krate, struct_id)
|
||||
}
|
||||
fn adt_repr(&self, _struct_id: AdtId) -> Arc<rust_ir::AdtRepr<Interner>> {
|
||||
// FIXME: keep track of these
|
||||
|
@ -136,81 +137,92 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
|
|||
_ => self_ty_fp.as_ref().map(std::slice::from_ref).unwrap_or(&[]),
|
||||
};
|
||||
|
||||
let trait_module = trait_.module(self.db.upcast());
|
||||
let type_module = match self_ty_fp {
|
||||
Some(TyFingerprint::Adt(adt_id)) => Some(adt_id.module(self.db.upcast())),
|
||||
Some(TyFingerprint::ForeignType(type_id)) => {
|
||||
Some(from_foreign_def_id(type_id).module(self.db.upcast()))
|
||||
}
|
||||
Some(TyFingerprint::Dyn(trait_id)) => Some(trait_id.module(self.db.upcast())),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut def_blocks =
|
||||
[trait_module.containing_block(), type_module.and_then(|it| it.containing_block())];
|
||||
|
||||
// Note: Since we're using impls_for_trait, only impls where the trait
|
||||
// can be resolved should ever reach Chalk. impl_datum relies on that
|
||||
// and will panic if the trait can't be resolved.
|
||||
let in_deps = self.db.trait_impls_in_deps(self.krate);
|
||||
let in_self = self.db.trait_impls_in_crate(self.krate);
|
||||
|
||||
let block_impls = iter::successors(self.block, |&block_id| {
|
||||
cov_mark::hit!(block_local_impls);
|
||||
self.db.block_def_map(block_id).parent().and_then(|module| module.containing_block())
|
||||
})
|
||||
.inspect(|&block_id| {
|
||||
// make sure we don't search the same block twice
|
||||
def_blocks.iter_mut().for_each(|block| {
|
||||
if *block == Some(block_id) {
|
||||
*block = None;
|
||||
}
|
||||
});
|
||||
})
|
||||
.filter_map(|block_id| self.db.trait_impls_in_block(block_id));
|
||||
|
||||
let id_to_chalk = |id: hir_def::ImplId| id.to_chalk(self.db);
|
||||
|
||||
let mut result = vec![];
|
||||
match fps {
|
||||
[] => {
|
||||
if fps.is_empty() {
|
||||
debug!("Unrestricted search for {:?} impls...", trait_);
|
||||
let mut f = |impls: &TraitImpls| {
|
||||
self.for_trait_impls(trait_, self_ty_fp, |impls| {
|
||||
result.extend(impls.for_trait(trait_).map(id_to_chalk));
|
||||
};
|
||||
f(&in_self);
|
||||
in_deps.iter().map(ops::Deref::deref).for_each(&mut f);
|
||||
block_impls.for_each(|it| f(&it));
|
||||
def_blocks
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|it| self.db.trait_impls_in_block(it))
|
||||
.for_each(|it| f(&it));
|
||||
}
|
||||
fps => {
|
||||
let mut f =
|
||||
|impls: &TraitImpls| {
|
||||
result.extend(fps.iter().flat_map(|fp| {
|
||||
ControlFlow::Continue(())
|
||||
})
|
||||
} else {
|
||||
self.for_trait_impls(trait_, self_ty_fp, |impls| {
|
||||
result.extend(
|
||||
fps.iter().flat_map(move |fp| {
|
||||
impls.for_trait_and_self_ty(trait_, *fp).map(id_to_chalk)
|
||||
}));
|
||||
}),
|
||||
);
|
||||
ControlFlow::Continue(())
|
||||
})
|
||||
};
|
||||
f(&in_self);
|
||||
in_deps.iter().map(ops::Deref::deref).for_each(&mut f);
|
||||
block_impls.for_each(|it| f(&it));
|
||||
def_blocks
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|it| self.db.trait_impls_in_block(it))
|
||||
.for_each(|it| f(&it));
|
||||
}
|
||||
}
|
||||
|
||||
debug!("impls_for_trait returned {} impls", result.len());
|
||||
result
|
||||
}
|
||||
|
||||
fn impl_provided_for(&self, auto_trait_id: TraitId, kind: &chalk_ir::TyKind<Interner>) -> bool {
|
||||
debug!("impl_provided_for {:?}, {:?}", auto_trait_id, kind);
|
||||
false // FIXME
|
||||
|
||||
let trait_id = from_chalk_trait_id(auto_trait_id);
|
||||
let self_ty = kind.clone().intern(Interner);
|
||||
// We cannot filter impls by `TyFingerprint` for the following types:
|
||||
let self_ty_fp = match kind {
|
||||
// because we need to find any impl whose Self type is a ref with the same mutability
|
||||
// (we don't care about the inner type).
|
||||
TyKind::Ref(..) => None,
|
||||
// because we need to find any impl whose Self type is a tuple with the same arity.
|
||||
TyKind::Tuple(..) => None,
|
||||
_ => TyFingerprint::for_trait_impl(&self_ty),
|
||||
};
|
||||
|
||||
let check_kind = |impl_id| {
|
||||
let impl_self_ty = self.db.impl_self_ty(impl_id);
|
||||
// NOTE(skip_binders): it's safe to skip binders here as we don't check substitutions.
|
||||
let impl_self_kind = impl_self_ty.skip_binders().kind(Interner);
|
||||
|
||||
match (kind, impl_self_kind) {
|
||||
(TyKind::Adt(id_a, _), TyKind::Adt(id_b, _)) => id_a == id_b,
|
||||
(TyKind::AssociatedType(id_a, _), TyKind::AssociatedType(id_b, _)) => id_a == id_b,
|
||||
(TyKind::Scalar(scalar_a), TyKind::Scalar(scalar_b)) => scalar_a == scalar_b,
|
||||
(TyKind::Error, TyKind::Error)
|
||||
| (TyKind::Str, TyKind::Str)
|
||||
| (TyKind::Slice(_), TyKind::Slice(_))
|
||||
| (TyKind::Never, TyKind::Never)
|
||||
| (TyKind::Array(_, _), TyKind::Array(_, _)) => true,
|
||||
(TyKind::Tuple(arity_a, _), TyKind::Tuple(arity_b, _)) => arity_a == arity_b,
|
||||
(TyKind::OpaqueType(id_a, _), TyKind::OpaqueType(id_b, _)) => id_a == id_b,
|
||||
(TyKind::FnDef(id_a, _), TyKind::FnDef(id_b, _)) => id_a == id_b,
|
||||
(TyKind::Ref(id_a, _, _), TyKind::Ref(id_b, _, _))
|
||||
| (TyKind::Raw(id_a, _), TyKind::Raw(id_b, _)) => id_a == id_b,
|
||||
(TyKind::Closure(id_a, _), TyKind::Closure(id_b, _)) => id_a == id_b,
|
||||
(TyKind::Coroutine(id_a, _), TyKind::Coroutine(id_b, _))
|
||||
| (TyKind::CoroutineWitness(id_a, _), TyKind::CoroutineWitness(id_b, _)) => {
|
||||
id_a == id_b
|
||||
}
|
||||
(TyKind::Foreign(id_a), TyKind::Foreign(id_b)) => id_a == id_b,
|
||||
(_, _) => false,
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(fp) = self_ty_fp {
|
||||
self.for_trait_impls(trait_id, self_ty_fp, |impls| {
|
||||
match impls.for_trait_and_self_ty(trait_id, fp).any(check_kind) {
|
||||
true => ControlFlow::Break(()),
|
||||
false => ControlFlow::Continue(()),
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.for_trait_impls(trait_id, self_ty_fp, |impls| {
|
||||
match impls.for_trait(trait_id).any(check_kind) {
|
||||
true => ControlFlow::Break(()),
|
||||
false => ControlFlow::Continue(()),
|
||||
}
|
||||
})
|
||||
}
|
||||
.is_break()
|
||||
}
|
||||
|
||||
fn associated_ty_value(&self, id: AssociatedTyValueId) -> Arc<AssociatedTyValue> {
|
||||
self.db.associated_ty_value(self.krate, id)
|
||||
}
|
||||
|
@ -489,6 +501,59 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> ChalkContext<'a> {
|
||||
fn for_trait_impls(
|
||||
&self,
|
||||
trait_id: hir_def::TraitId,
|
||||
self_ty_fp: Option<TyFingerprint>,
|
||||
mut f: impl FnMut(&TraitImpls) -> ControlFlow<()>,
|
||||
) -> ControlFlow<()> {
|
||||
// Note: Since we're using `impls_for_trait` and `impl_provided_for`,
|
||||
// only impls where the trait can be resolved should ever reach Chalk.
|
||||
// `impl_datum` relies on that and will panic if the trait can't be resolved.
|
||||
let in_deps = self.db.trait_impls_in_deps(self.krate);
|
||||
let in_self = self.db.trait_impls_in_crate(self.krate);
|
||||
let trait_module = trait_id.module(self.db.upcast());
|
||||
let type_module = match self_ty_fp {
|
||||
Some(TyFingerprint::Adt(adt_id)) => Some(adt_id.module(self.db.upcast())),
|
||||
Some(TyFingerprint::ForeignType(type_id)) => {
|
||||
Some(from_foreign_def_id(type_id).module(self.db.upcast()))
|
||||
}
|
||||
Some(TyFingerprint::Dyn(trait_id)) => Some(trait_id.module(self.db.upcast())),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut def_blocks =
|
||||
[trait_module.containing_block(), type_module.and_then(|it| it.containing_block())];
|
||||
|
||||
let block_impls = iter::successors(self.block, |&block_id| {
|
||||
cov_mark::hit!(block_local_impls);
|
||||
self.db.block_def_map(block_id).parent().and_then(|module| module.containing_block())
|
||||
})
|
||||
.inspect(|&block_id| {
|
||||
// make sure we don't search the same block twice
|
||||
def_blocks.iter_mut().for_each(|block| {
|
||||
if *block == Some(block_id) {
|
||||
*block = None;
|
||||
}
|
||||
});
|
||||
})
|
||||
.filter_map(|block_id| self.db.trait_impls_in_block(block_id));
|
||||
f(&in_self)?;
|
||||
for it in in_deps.iter().map(ops::Deref::deref) {
|
||||
f(it)?;
|
||||
}
|
||||
for it in block_impls {
|
||||
f(&it)?;
|
||||
}
|
||||
for it in def_blocks.into_iter().flatten().filter_map(|it| self.db.trait_impls_in_block(it))
|
||||
{
|
||||
f(&it)?;
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
impl chalk_ir::UnificationDatabase<Interner> for &dyn HirDatabase {
|
||||
fn fn_def_variance(
|
||||
&self,
|
||||
|
@ -590,7 +655,7 @@ pub(crate) fn trait_datum_query(
|
|||
coinductive: false, // only relevant for Chalk testing
|
||||
// FIXME: set these flags correctly
|
||||
marker: false,
|
||||
fundamental: false,
|
||||
fundamental: trait_data.fundamental,
|
||||
};
|
||||
let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars);
|
||||
let associated_ty_ids = trait_data.associated_types().map(to_assoc_type_id).collect();
|
||||
|
@ -649,35 +714,75 @@ fn lang_item_from_well_known_trait(trait_: WellKnownTrait) -> LangItem {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn struct_datum_query(
|
||||
pub(crate) fn adt_datum_query(
|
||||
db: &dyn HirDatabase,
|
||||
krate: CrateId,
|
||||
struct_id: AdtId,
|
||||
) -> Arc<StructDatum> {
|
||||
debug!("struct_datum {:?}", struct_id);
|
||||
let chalk_ir::AdtId(adt_id) = struct_id;
|
||||
chalk_ir::AdtId(adt_id): AdtId,
|
||||
) -> Arc<AdtDatum> {
|
||||
debug!("adt_datum {:?}", adt_id);
|
||||
let generic_params = generics(db.upcast(), adt_id.into());
|
||||
let upstream = adt_id.module(db.upcast()).krate() != krate;
|
||||
let where_clauses = {
|
||||
let generic_params = generics(db.upcast(), adt_id.into());
|
||||
let bound_vars = generic_params.bound_vars_subst(db, DebruijnIndex::INNERMOST);
|
||||
convert_where_clauses(db, adt_id.into(), &bound_vars)
|
||||
let bound_vars_subst = generic_params.bound_vars_subst(db, DebruijnIndex::INNERMOST);
|
||||
let where_clauses = convert_where_clauses(db, adt_id.into(), &bound_vars_subst);
|
||||
|
||||
let (fundamental, phantom_data) = match adt_id {
|
||||
hir_def::AdtId::StructId(s) => {
|
||||
let flags = db.struct_data(s).flags;
|
||||
(
|
||||
flags.contains(StructFlags::IS_FUNDAMENTAL),
|
||||
flags.contains(StructFlags::IS_PHANTOM_DATA),
|
||||
)
|
||||
}
|
||||
// FIXME set fundamental flags correctly
|
||||
hir_def::AdtId::UnionId(_) => (false, false),
|
||||
hir_def::AdtId::EnumId(_) => (false, false),
|
||||
};
|
||||
let flags = rust_ir::AdtFlags {
|
||||
upstream,
|
||||
// FIXME set fundamental and phantom_data flags correctly
|
||||
fundamental: false,
|
||||
phantom_data: false,
|
||||
upstream: adt_id.module(db.upcast()).krate() != krate,
|
||||
fundamental,
|
||||
phantom_data,
|
||||
};
|
||||
// FIXME provide enum variants properly (for auto traits)
|
||||
let variant = rust_ir::AdtVariantDatum {
|
||||
fields: Vec::new(), // FIXME add fields (only relevant for auto traits),
|
||||
|
||||
#[cfg(FALSE)]
|
||||
// this slows down rust-analyzer by quite a bit unfortunately, so enabling this is currently not worth it
|
||||
let variant_id_to_fields = |id: VariantId| {
|
||||
let variant_data = &id.variant_data(db.upcast());
|
||||
let fields = if variant_data.fields().is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
let field_types = db.field_types(id);
|
||||
variant_data
|
||||
.fields()
|
||||
.iter()
|
||||
.map(|(idx, _)| field_types[idx].clone().substitute(Interner, &bound_vars_subst))
|
||||
.filter(|it| !it.contains_unknown())
|
||||
.collect()
|
||||
};
|
||||
let struct_datum_bound = rust_ir::AdtDatumBound { variants: vec![variant], where_clauses };
|
||||
let struct_datum = StructDatum {
|
||||
// FIXME set ADT kind
|
||||
kind: rust_ir::AdtKind::Struct,
|
||||
id: struct_id,
|
||||
rust_ir::AdtVariantDatum { fields }
|
||||
};
|
||||
let variant_id_to_fields = |_: VariantId| rust_ir::AdtVariantDatum { fields: vec![] };
|
||||
|
||||
let (kind, variants) = match adt_id {
|
||||
hir_def::AdtId::StructId(id) => {
|
||||
(rust_ir::AdtKind::Struct, vec![variant_id_to_fields(id.into())])
|
||||
}
|
||||
hir_def::AdtId::EnumId(id) => {
|
||||
let variants = db
|
||||
.enum_data(id)
|
||||
.variants
|
||||
.iter()
|
||||
.map(|&(variant_id, _)| variant_id_to_fields(variant_id.into()))
|
||||
.collect();
|
||||
(rust_ir::AdtKind::Enum, variants)
|
||||
}
|
||||
hir_def::AdtId::UnionId(id) => {
|
||||
(rust_ir::AdtKind::Union, vec![variant_id_to_fields(id.into())])
|
||||
}
|
||||
};
|
||||
|
||||
let struct_datum_bound = rust_ir::AdtDatumBound { variants, where_clauses };
|
||||
let struct_datum = AdtDatum {
|
||||
kind,
|
||||
id: chalk_ir::AdtId(adt_id),
|
||||
binders: make_binders(db, &generic_params, struct_datum_bound),
|
||||
flags,
|
||||
};
|
||||
|
|
|
@ -90,7 +90,7 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
#[salsa::cycle(crate::lower::ty_recover)]
|
||||
fn ty(&self, def: TyDefId) -> Binders<Ty>;
|
||||
|
||||
/// Returns the type of the value of the given constant, or `None` if the the `ValueTyDefId` is
|
||||
/// Returns the type of the value of the given constant, or `None` if the `ValueTyDefId` is
|
||||
/// a `StructId` or `EnumVariantId` with a record constructor.
|
||||
#[salsa::invoke(crate::lower::value_ty_query)]
|
||||
fn value_ty(&self, def: ValueTyDefId) -> Option<Binders<Ty>>;
|
||||
|
@ -220,12 +220,12 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
trait_id: chalk_db::TraitId,
|
||||
) -> sync::Arc<chalk_db::TraitDatum>;
|
||||
|
||||
#[salsa::invoke(chalk_db::struct_datum_query)]
|
||||
fn struct_datum(
|
||||
#[salsa::invoke(chalk_db::adt_datum_query)]
|
||||
fn adt_datum(
|
||||
&self,
|
||||
krate: CrateId,
|
||||
struct_id: chalk_db::AdtId,
|
||||
) -> sync::Arc<chalk_db::StructDatum>;
|
||||
) -> sync::Arc<chalk_db::AdtDatum>;
|
||||
|
||||
#[salsa::invoke(chalk_db::impl_datum_query)]
|
||||
fn impl_datum(
|
||||
|
|
|
@ -12,6 +12,8 @@ use hir_expand::name;
|
|||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustc_pattern_analysis::usefulness::{compute_match_usefulness, ValidityConstraint};
|
||||
use syntax::{ast, AstNode};
|
||||
use tracing::debug;
|
||||
use triomphe::Arc;
|
||||
use typed_arena::Arena;
|
||||
|
||||
|
@ -44,6 +46,10 @@ pub enum BodyValidationDiagnostic {
|
|||
match_expr: ExprId,
|
||||
uncovered_patterns: String,
|
||||
},
|
||||
NonExhaustiveLet {
|
||||
pat: PatId,
|
||||
uncovered_patterns: String,
|
||||
},
|
||||
RemoveTrailingReturn {
|
||||
return_expr: ExprId,
|
||||
},
|
||||
|
@ -57,7 +63,8 @@ impl BodyValidationDiagnostic {
|
|||
let _p =
|
||||
tracing::span!(tracing::Level::INFO, "BodyValidationDiagnostic::collect").entered();
|
||||
let infer = db.infer(owner);
|
||||
let mut validator = ExprValidator::new(owner, infer);
|
||||
let body = db.body(owner);
|
||||
let mut validator = ExprValidator { owner, body, infer, diagnostics: Vec::new() };
|
||||
validator.validate_body(db);
|
||||
validator.diagnostics
|
||||
}
|
||||
|
@ -65,18 +72,16 @@ impl BodyValidationDiagnostic {
|
|||
|
||||
struct ExprValidator {
|
||||
owner: DefWithBodyId,
|
||||
body: Arc<Body>,
|
||||
infer: Arc<InferenceResult>,
|
||||
pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
|
||||
diagnostics: Vec<BodyValidationDiagnostic>,
|
||||
}
|
||||
|
||||
impl ExprValidator {
|
||||
fn new(owner: DefWithBodyId, infer: Arc<InferenceResult>) -> ExprValidator {
|
||||
ExprValidator { owner, infer, diagnostics: Vec::new() }
|
||||
}
|
||||
|
||||
fn validate_body(&mut self, db: &dyn HirDatabase) {
|
||||
let body = db.body(self.owner);
|
||||
let mut filter_map_next_checker = None;
|
||||
// we'll pass &mut self while iterating over body.exprs, so they need to be disjoint
|
||||
let body = Arc::clone(&self.body);
|
||||
|
||||
if matches!(self.owner, DefWithBodyId::FunctionId(_)) {
|
||||
self.check_for_trailing_return(body.body_expr, &body);
|
||||
|
@ -104,7 +109,10 @@ impl ExprValidator {
|
|||
self.check_for_trailing_return(*body_expr, &body);
|
||||
}
|
||||
Expr::If { .. } => {
|
||||
self.check_for_unnecessary_else(id, expr, &body);
|
||||
self.check_for_unnecessary_else(id, expr, db);
|
||||
}
|
||||
Expr::Block { .. } => {
|
||||
self.validate_block(db, expr);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -162,16 +170,14 @@ impl ExprValidator {
|
|||
arms: &[MatchArm],
|
||||
db: &dyn HirDatabase,
|
||||
) {
|
||||
let body = db.body(self.owner);
|
||||
|
||||
let scrut_ty = &self.infer[scrutinee_expr];
|
||||
if scrut_ty.is_unknown() {
|
||||
return;
|
||||
}
|
||||
|
||||
let pattern_arena = Arena::new();
|
||||
let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db, &pattern_arena);
|
||||
let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db);
|
||||
|
||||
let pattern_arena = Arena::new();
|
||||
let mut m_arms = Vec::with_capacity(arms.len());
|
||||
let mut has_lowering_errors = false;
|
||||
for arm in arms {
|
||||
|
@ -191,13 +197,14 @@ impl ExprValidator {
|
|||
.as_reference()
|
||||
.map(|(match_expr_ty, ..)| match_expr_ty == pat_ty)
|
||||
.unwrap_or(false))
|
||||
&& types_of_subpatterns_do_match(arm.pat, &body, &self.infer)
|
||||
&& types_of_subpatterns_do_match(arm.pat, &self.body, &self.infer)
|
||||
{
|
||||
// If we had a NotUsefulMatchArm diagnostic, we could
|
||||
// check the usefulness of each pattern as we added it
|
||||
// to the matrix here.
|
||||
let pat = self.lower_pattern(&cx, arm.pat, db, &mut has_lowering_errors);
|
||||
let m_arm = pat_analysis::MatchArm {
|
||||
pat: self.lower_pattern(&cx, arm.pat, db, &body, &mut has_lowering_errors),
|
||||
pat: pattern_arena.alloc(pat),
|
||||
has_guard: arm.guard.is_some(),
|
||||
arm_data: (),
|
||||
};
|
||||
|
@ -223,7 +230,7 @@ impl ExprValidator {
|
|||
ValidityConstraint::ValidOnly,
|
||||
) {
|
||||
Ok(report) => report,
|
||||
Err(void) => match void {},
|
||||
Err(()) => return,
|
||||
};
|
||||
|
||||
// FIXME Report unreachable arms
|
||||
|
@ -233,22 +240,65 @@ impl ExprValidator {
|
|||
if !witnesses.is_empty() {
|
||||
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
|
||||
match_expr,
|
||||
uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, arms),
|
||||
uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, m_arms.is_empty()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_block(&mut self, db: &dyn HirDatabase, expr: &Expr) {
|
||||
let Expr::Block { statements, .. } = expr else { return };
|
||||
let pattern_arena = Arena::new();
|
||||
let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db);
|
||||
for stmt in &**statements {
|
||||
let &Statement::Let { pat, initializer, else_branch: None, .. } = stmt else {
|
||||
continue;
|
||||
};
|
||||
let Some(initializer) = initializer else { continue };
|
||||
let ty = &self.infer[initializer];
|
||||
|
||||
let mut have_errors = false;
|
||||
let deconstructed_pat = self.lower_pattern(&cx, pat, db, &mut have_errors);
|
||||
let match_arm = rustc_pattern_analysis::MatchArm {
|
||||
pat: pattern_arena.alloc(deconstructed_pat),
|
||||
has_guard: false,
|
||||
arm_data: (),
|
||||
};
|
||||
if have_errors {
|
||||
continue;
|
||||
}
|
||||
|
||||
let report = match compute_match_usefulness(
|
||||
&cx,
|
||||
&[match_arm],
|
||||
ty.clone(),
|
||||
ValidityConstraint::ValidOnly,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
debug!(?e, "match usefulness error");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let witnesses = report.non_exhaustiveness_witnesses;
|
||||
if !witnesses.is_empty() {
|
||||
self.diagnostics.push(BodyValidationDiagnostic::NonExhaustiveLet {
|
||||
pat,
|
||||
uncovered_patterns: missing_match_arms(&cx, ty, witnesses, false),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_pattern<'p>(
|
||||
&self,
|
||||
cx: &MatchCheckCtx<'p>,
|
||||
pat: PatId,
|
||||
db: &dyn HirDatabase,
|
||||
body: &Body,
|
||||
have_errors: &mut bool,
|
||||
) -> &'p DeconstructedPat<'p> {
|
||||
let mut patcx = match_check::PatCtxt::new(db, &self.infer, body);
|
||||
) -> DeconstructedPat<'p> {
|
||||
let mut patcx = match_check::PatCtxt::new(db, &self.infer, &self.body);
|
||||
let pattern = patcx.lower_pattern(pat);
|
||||
let pattern = cx.pattern_arena.alloc(cx.lower_pat(&pattern));
|
||||
let pattern = cx.lower_pat(&pattern);
|
||||
if !patcx.errors.is_empty() {
|
||||
*have_errors = true;
|
||||
}
|
||||
|
@ -287,12 +337,12 @@ impl ExprValidator {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_for_unnecessary_else(&mut self, id: ExprId, expr: &Expr, body: &Body) {
|
||||
fn check_for_unnecessary_else(&mut self, id: ExprId, expr: &Expr, db: &dyn HirDatabase) {
|
||||
if let Expr::If { condition: _, then_branch, else_branch } = expr {
|
||||
if else_branch.is_none() {
|
||||
return;
|
||||
}
|
||||
if let Expr::Block { statements, tail, .. } = &body.exprs[*then_branch] {
|
||||
if let Expr::Block { statements, tail, .. } = &self.body.exprs[*then_branch] {
|
||||
let last_then_expr = tail.or_else(|| match statements.last()? {
|
||||
Statement::Expr { expr, .. } => Some(*expr),
|
||||
_ => None,
|
||||
|
@ -300,6 +350,36 @@ impl ExprValidator {
|
|||
if let Some(last_then_expr) = last_then_expr {
|
||||
let last_then_expr_ty = &self.infer[last_then_expr];
|
||||
if last_then_expr_ty.is_never() {
|
||||
// Only look at sources if the then branch diverges and we have an else branch.
|
||||
let (_, source_map) = db.body_with_source_map(self.owner);
|
||||
let Ok(source_ptr) = source_map.expr_syntax(id) else {
|
||||
return;
|
||||
};
|
||||
let root = source_ptr.file_syntax(db.upcast());
|
||||
let ast::Expr::IfExpr(if_expr) = source_ptr.value.to_node(&root) else {
|
||||
return;
|
||||
};
|
||||
let mut top_if_expr = if_expr;
|
||||
loop {
|
||||
let parent = top_if_expr.syntax().parent();
|
||||
let has_parent_expr_stmt_or_stmt_list =
|
||||
parent.as_ref().map_or(false, |node| {
|
||||
ast::ExprStmt::can_cast(node.kind())
|
||||
| ast::StmtList::can_cast(node.kind())
|
||||
});
|
||||
if has_parent_expr_stmt_or_stmt_list {
|
||||
// Only emit diagnostic if parent or direct ancestor is either
|
||||
// an expr stmt or a stmt list.
|
||||
break;
|
||||
}
|
||||
let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
|
||||
// Bail if parent is neither an if expr, an expr stmt nor a stmt list.
|
||||
return;
|
||||
};
|
||||
// Check parent if expr.
|
||||
top_if_expr = parent_if_expr;
|
||||
}
|
||||
|
||||
self.diagnostics
|
||||
.push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id })
|
||||
}
|
||||
|
@ -447,7 +527,7 @@ fn missing_match_arms<'p>(
|
|||
cx: &MatchCheckCtx<'p>,
|
||||
scrut_ty: &Ty,
|
||||
witnesses: Vec<WitnessPat<'p>>,
|
||||
arms: &[MatchArm],
|
||||
arms_is_empty: bool,
|
||||
) -> String {
|
||||
struct DisplayWitness<'a, 'p>(&'a WitnessPat<'p>, &'a MatchCheckCtx<'p>);
|
||||
impl fmt::Display for DisplayWitness<'_, '_> {
|
||||
|
@ -462,7 +542,7 @@ fn missing_match_arms<'p>(
|
|||
Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(),
|
||||
_ => false,
|
||||
};
|
||||
if arms.is_empty() && !non_empty_enum {
|
||||
if arms_is_empty && !non_empty_enum {
|
||||
format!("type `{}` is non-empty", scrut_ty.display(cx.db))
|
||||
} else {
|
||||
let pat_display = |witness| DisplayWitness(witness, cx);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Interface with `rustc_pattern_analysis`.
|
||||
|
||||
use std::fmt;
|
||||
use tracing::debug;
|
||||
|
||||
use hir_def::{DefWithBodyId, EnumVariantId, HasModule, LocalFieldId, ModuleId, VariantId};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
@ -11,7 +12,6 @@ use rustc_pattern_analysis::{
|
|||
};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use stdx::never;
|
||||
use typed_arena::Arena;
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase,
|
||||
|
@ -26,7 +26,7 @@ use Constructor::*;
|
|||
|
||||
// Re-export r-a-specific versions of all these types.
|
||||
pub(crate) type DeconstructedPat<'p> =
|
||||
rustc_pattern_analysis::pat::DeconstructedPat<'p, MatchCheckCtx<'p>>;
|
||||
rustc_pattern_analysis::pat::DeconstructedPat<MatchCheckCtx<'p>>;
|
||||
pub(crate) type MatchArm<'p> = rustc_pattern_analysis::MatchArm<'p, MatchCheckCtx<'p>>;
|
||||
pub(crate) type WitnessPat<'p> = rustc_pattern_analysis::pat::WitnessPat<MatchCheckCtx<'p>>;
|
||||
|
||||
|
@ -40,7 +40,6 @@ pub(crate) struct MatchCheckCtx<'p> {
|
|||
module: ModuleId,
|
||||
body: DefWithBodyId,
|
||||
pub(crate) db: &'p dyn HirDatabase,
|
||||
pub(crate) pattern_arena: &'p Arena<DeconstructedPat<'p>>,
|
||||
exhaustive_patterns: bool,
|
||||
min_exhaustive_patterns: bool,
|
||||
}
|
||||
|
@ -52,17 +51,12 @@ pub(crate) struct PatData<'p> {
|
|||
}
|
||||
|
||||
impl<'p> MatchCheckCtx<'p> {
|
||||
pub(crate) fn new(
|
||||
module: ModuleId,
|
||||
body: DefWithBodyId,
|
||||
db: &'p dyn HirDatabase,
|
||||
pattern_arena: &'p Arena<DeconstructedPat<'p>>,
|
||||
) -> Self {
|
||||
pub(crate) fn new(module: ModuleId, body: DefWithBodyId, db: &'p dyn HirDatabase) -> Self {
|
||||
let def_map = db.crate_def_map(module.krate());
|
||||
let exhaustive_patterns = def_map.is_unstable_feature_enabled("exhaustive_patterns");
|
||||
let min_exhaustive_patterns =
|
||||
def_map.is_unstable_feature_enabled("min_exhaustive_patterns");
|
||||
Self { module, body, db, pattern_arena, exhaustive_patterns, min_exhaustive_patterns }
|
||||
Self { module, body, db, exhaustive_patterns, min_exhaustive_patterns }
|
||||
}
|
||||
|
||||
fn is_uninhabited(&self, ty: &Ty) -> bool {
|
||||
|
@ -131,15 +125,15 @@ impl<'p> MatchCheckCtx<'p> {
|
|||
}
|
||||
|
||||
pub(crate) fn lower_pat(&self, pat: &Pat) -> DeconstructedPat<'p> {
|
||||
let singleton = |pat| std::slice::from_ref(self.pattern_arena.alloc(pat));
|
||||
let singleton = |pat| vec![pat];
|
||||
let ctor;
|
||||
let fields: &[_];
|
||||
let fields: Vec<_>;
|
||||
|
||||
match pat.kind.as_ref() {
|
||||
PatKind::Binding { subpattern: Some(subpat), .. } => return self.lower_pat(subpat),
|
||||
PatKind::Binding { subpattern: None, .. } | PatKind::Wild => {
|
||||
ctor = Wildcard;
|
||||
fields = &[];
|
||||
fields = Vec::new();
|
||||
}
|
||||
PatKind::Deref { subpattern } => {
|
||||
ctor = match pat.ty.kind(Interner) {
|
||||
|
@ -157,7 +151,7 @@ impl<'p> MatchCheckCtx<'p> {
|
|||
match pat.ty.kind(Interner) {
|
||||
TyKind::Tuple(_, substs) => {
|
||||
ctor = Struct;
|
||||
let mut wilds: SmallVec<[_; 2]> = substs
|
||||
let mut wilds: Vec<_> = substs
|
||||
.iter(Interner)
|
||||
.map(|arg| arg.assert_ty_ref(Interner).clone())
|
||||
.map(DeconstructedPat::wildcard)
|
||||
|
@ -166,7 +160,7 @@ impl<'p> MatchCheckCtx<'p> {
|
|||
let idx: u32 = pat.field.into_raw().into();
|
||||
wilds[idx as usize] = self.lower_pat(&pat.pattern);
|
||||
}
|
||||
fields = self.pattern_arena.alloc_extend(wilds)
|
||||
fields = wilds
|
||||
}
|
||||
TyKind::Adt(adt, substs) if is_box(self.db, adt.0) => {
|
||||
// The only legal patterns of type `Box` (outside `std`) are `_` and box
|
||||
|
@ -216,33 +210,29 @@ impl<'p> MatchCheckCtx<'p> {
|
|||
field_id_to_id[field_idx as usize] = Some(i);
|
||||
ty
|
||||
});
|
||||
let mut wilds: SmallVec<[_; 2]> =
|
||||
tys.map(DeconstructedPat::wildcard).collect();
|
||||
let mut wilds: Vec<_> = tys.map(DeconstructedPat::wildcard).collect();
|
||||
for pat in subpatterns {
|
||||
let field_idx: u32 = pat.field.into_raw().into();
|
||||
if let Some(i) = field_id_to_id[field_idx as usize] {
|
||||
wilds[i] = self.lower_pat(&pat.pattern);
|
||||
}
|
||||
}
|
||||
fields = self.pattern_arena.alloc_extend(wilds);
|
||||
fields = wilds;
|
||||
}
|
||||
_ => {
|
||||
never!("pattern has unexpected type: pat: {:?}, ty: {:?}", pat, &pat.ty);
|
||||
ctor = Wildcard;
|
||||
fields = &[];
|
||||
fields = Vec::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
&PatKind::LiteralBool { value } => {
|
||||
ctor = Bool(value);
|
||||
fields = &[];
|
||||
fields = Vec::new();
|
||||
}
|
||||
PatKind::Or { pats } => {
|
||||
ctor = Or;
|
||||
// Collect here because `Arena::alloc_extend` panics on reentrancy.
|
||||
let subpats: SmallVec<[_; 2]> =
|
||||
pats.iter().map(|pat| self.lower_pat(pat)).collect();
|
||||
fields = self.pattern_arena.alloc_extend(subpats);
|
||||
fields = pats.iter().map(|pat| self.lower_pat(pat)).collect();
|
||||
}
|
||||
}
|
||||
let data = PatData { db: self.db };
|
||||
|
@ -307,7 +297,7 @@ impl<'p> MatchCheckCtx<'p> {
|
|||
}
|
||||
|
||||
impl<'p> TypeCx for MatchCheckCtx<'p> {
|
||||
type Error = Void;
|
||||
type Error = ();
|
||||
type Ty = Ty;
|
||||
type VariantIdx = EnumVariantId;
|
||||
type StrLit = Void;
|
||||
|
@ -463,7 +453,7 @@ impl<'p> TypeCx for MatchCheckCtx<'p> {
|
|||
|
||||
fn write_variant_name(
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
pat: &rustc_pattern_analysis::pat::DeconstructedPat<'_, Self>,
|
||||
pat: &rustc_pattern_analysis::pat::DeconstructedPat<Self>,
|
||||
) -> fmt::Result {
|
||||
let variant =
|
||||
pat.ty().as_adt().and_then(|(adt, _)| Self::variant_id_for_adt(pat.ctor(), adt));
|
||||
|
@ -485,8 +475,8 @@ impl<'p> TypeCx for MatchCheckCtx<'p> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn bug(&self, fmt: fmt::Arguments<'_>) -> ! {
|
||||
panic!("{}", fmt)
|
||||
fn bug(&self, fmt: fmt::Arguments<'_>) {
|
||||
debug!("{}", fmt)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! Originates from `rustc_hir::pat_util`
|
||||
|
||||
use std::iter::{Enumerate, ExactSizeIterator};
|
||||
use std::iter::Enumerate;
|
||||
|
||||
pub(crate) struct EnumerateAndAdjust<I> {
|
||||
enumerate: Enumerate<I>,
|
||||
|
|
|
@ -26,7 +26,7 @@ use std::{convert::identity, ops::Index};
|
|||
|
||||
use chalk_ir::{
|
||||
cast::Cast, fold::TypeFoldable, interner::HasInterner, DebruijnIndex, Mutability, Safety,
|
||||
Scalar, TyKind, TypeFlags,
|
||||
Scalar, TyKind, TypeFlags, Variance,
|
||||
};
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
|
@ -58,8 +58,9 @@ use crate::{
|
|||
static_lifetime, to_assoc_type_id,
|
||||
traits::FnTrait,
|
||||
utils::{InTypeConstIdMetadata, UnevaluatedConstEvaluatorFolder},
|
||||
AliasEq, AliasTy, ClosureId, DomainGoal, GenericArg, Goal, ImplTraitId, InEnvironment,
|
||||
Interner, ProjectionTy, RpitId, Substitution, TraitEnvironment, TraitRef, Ty, TyBuilder, TyExt,
|
||||
AliasEq, AliasTy, Binders, ClosureId, Const, DomainGoal, GenericArg, Goal, ImplTraitId,
|
||||
InEnvironment, Interner, Lifetime, ProjectionTy, RpitId, Substitution, TraitEnvironment,
|
||||
TraitRef, Ty, TyBuilder, TyExt,
|
||||
};
|
||||
|
||||
// This lint has a false positive here. See the link below for details.
|
||||
|
@ -68,7 +69,7 @@ use crate::{
|
|||
#[allow(unreachable_pub)]
|
||||
pub use coerce::could_coerce;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use unify::could_unify;
|
||||
pub use unify::{could_unify, could_unify_deeply};
|
||||
|
||||
use cast::CastCheck;
|
||||
pub(crate) use closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy};
|
||||
|
@ -220,6 +221,9 @@ pub enum InferenceDiagnostic {
|
|||
UnresolvedAssocItem {
|
||||
id: ExprOrPatId,
|
||||
},
|
||||
UnresolvedIdent {
|
||||
expr: ExprId,
|
||||
},
|
||||
// FIXME: This should be emitted in body lowering
|
||||
BreakOutsideOfLoop {
|
||||
expr: ExprId,
|
||||
|
@ -688,10 +692,17 @@ impl<'a> InferenceContext<'a> {
|
|||
for ty in type_of_for_iterator.values_mut() {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
}
|
||||
for mismatch in type_mismatches.values_mut() {
|
||||
type_mismatches.retain(|_, mismatch| {
|
||||
mismatch.expected = table.resolve_completely(mismatch.expected.clone());
|
||||
mismatch.actual = table.resolve_completely(mismatch.actual.clone());
|
||||
}
|
||||
chalk_ir::zip::Zip::zip_with(
|
||||
&mut UnknownMismatch(self.db),
|
||||
Variance::Invariant,
|
||||
&mismatch.expected,
|
||||
&mismatch.actual,
|
||||
)
|
||||
.is_ok()
|
||||
});
|
||||
diagnostics.retain_mut(|diagnostic| {
|
||||
use InferenceDiagnostic::*;
|
||||
match diagnostic {
|
||||
|
@ -1502,3 +1513,116 @@ impl std::ops::BitOrAssign for Diverges {
|
|||
*self = *self | other;
|
||||
}
|
||||
}
|
||||
/// A zipper that checks for unequal `{unknown}` occurrences in the two types. Used to filter out
|
||||
/// mismatch diagnostics that only differ in `{unknown}`. These mismatches are usually not helpful.
|
||||
/// As the cause is usually an underlying name resolution problem.
|
||||
struct UnknownMismatch<'db>(&'db dyn HirDatabase);
|
||||
impl chalk_ir::zip::Zipper<Interner> for UnknownMismatch<'_> {
|
||||
fn zip_tys(&mut self, variance: Variance, a: &Ty, b: &Ty) -> chalk_ir::Fallible<()> {
|
||||
let zip_substs = |this: &mut Self,
|
||||
variances,
|
||||
sub_a: &Substitution,
|
||||
sub_b: &Substitution| {
|
||||
this.zip_substs(variance, variances, sub_a.as_slice(Interner), sub_b.as_slice(Interner))
|
||||
};
|
||||
match (a.kind(Interner), b.kind(Interner)) {
|
||||
(TyKind::Adt(id_a, sub_a), TyKind::Adt(id_b, sub_b)) if id_a == id_b => zip_substs(
|
||||
self,
|
||||
Some(self.unification_database().adt_variance(*id_a)),
|
||||
sub_a,
|
||||
sub_b,
|
||||
)?,
|
||||
(
|
||||
TyKind::AssociatedType(assoc_ty_a, sub_a),
|
||||
TyKind::AssociatedType(assoc_ty_b, sub_b),
|
||||
) if assoc_ty_a == assoc_ty_b => zip_substs(self, None, sub_a, sub_b)?,
|
||||
(TyKind::Tuple(arity_a, sub_a), TyKind::Tuple(arity_b, sub_b))
|
||||
if arity_a == arity_b =>
|
||||
{
|
||||
zip_substs(self, None, sub_a, sub_b)?
|
||||
}
|
||||
(TyKind::OpaqueType(opaque_ty_a, sub_a), TyKind::OpaqueType(opaque_ty_b, sub_b))
|
||||
if opaque_ty_a == opaque_ty_b =>
|
||||
{
|
||||
zip_substs(self, None, sub_a, sub_b)?
|
||||
}
|
||||
(TyKind::Slice(ty_a), TyKind::Slice(ty_b)) => self.zip_tys(variance, ty_a, ty_b)?,
|
||||
(TyKind::FnDef(fn_def_a, sub_a), TyKind::FnDef(fn_def_b, sub_b))
|
||||
if fn_def_a == fn_def_b =>
|
||||
{
|
||||
zip_substs(
|
||||
self,
|
||||
Some(self.unification_database().fn_def_variance(*fn_def_a)),
|
||||
sub_a,
|
||||
sub_b,
|
||||
)?
|
||||
}
|
||||
(TyKind::Ref(mutability_a, _, ty_a), TyKind::Ref(mutability_b, _, ty_b))
|
||||
if mutability_a == mutability_b =>
|
||||
{
|
||||
self.zip_tys(variance, ty_a, ty_b)?
|
||||
}
|
||||
(TyKind::Raw(mutability_a, ty_a), TyKind::Raw(mutability_b, ty_b))
|
||||
if mutability_a == mutability_b =>
|
||||
{
|
||||
self.zip_tys(variance, ty_a, ty_b)?
|
||||
}
|
||||
(TyKind::Array(ty_a, const_a), TyKind::Array(ty_b, const_b)) if const_a == const_b => {
|
||||
self.zip_tys(variance, ty_a, ty_b)?
|
||||
}
|
||||
(TyKind::Closure(id_a, sub_a), TyKind::Closure(id_b, sub_b)) if id_a == id_b => {
|
||||
zip_substs(self, None, sub_a, sub_b)?
|
||||
}
|
||||
(TyKind::Coroutine(coroutine_a, sub_a), TyKind::Coroutine(coroutine_b, sub_b))
|
||||
if coroutine_a == coroutine_b =>
|
||||
{
|
||||
zip_substs(self, None, sub_a, sub_b)?
|
||||
}
|
||||
(
|
||||
TyKind::CoroutineWitness(coroutine_a, sub_a),
|
||||
TyKind::CoroutineWitness(coroutine_b, sub_b),
|
||||
) if coroutine_a == coroutine_b => zip_substs(self, None, sub_a, sub_b)?,
|
||||
(TyKind::Function(fn_ptr_a), TyKind::Function(fn_ptr_b))
|
||||
if fn_ptr_a.sig == fn_ptr_b.sig && fn_ptr_a.num_binders == fn_ptr_b.num_binders =>
|
||||
{
|
||||
zip_substs(self, None, &fn_ptr_a.substitution.0, &fn_ptr_b.substitution.0)?
|
||||
}
|
||||
(TyKind::Error, TyKind::Error) => (),
|
||||
(TyKind::Error, _) | (_, TyKind::Error) => return Err(chalk_ir::NoSolution),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn zip_lifetimes(&mut self, _: Variance, _: &Lifetime, _: &Lifetime) -> chalk_ir::Fallible<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn zip_consts(&mut self, _: Variance, _: &Const, _: &Const) -> chalk_ir::Fallible<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn zip_binders<T>(
|
||||
&mut self,
|
||||
variance: Variance,
|
||||
a: &Binders<T>,
|
||||
b: &Binders<T>,
|
||||
) -> chalk_ir::Fallible<()>
|
||||
where
|
||||
T: Clone
|
||||
+ HasInterner<Interner = Interner>
|
||||
+ chalk_ir::zip::Zip<Interner>
|
||||
+ TypeFoldable<Interner>,
|
||||
{
|
||||
chalk_ir::zip::Zip::zip_with(self, variance, a.skip_binders(), b.skip_binders())
|
||||
}
|
||||
|
||||
fn interner(&self) -> Interner {
|
||||
Interner
|
||||
}
|
||||
|
||||
fn unification_database(&self) -> &dyn chalk_ir::UnificationDatabase<Interner> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -485,6 +485,7 @@ impl InferenceContext<'_> {
|
|||
Statement::Expr { expr, has_semi: _ } => {
|
||||
self.consume_expr(*expr);
|
||||
}
|
||||
Statement::Item => (),
|
||||
}
|
||||
}
|
||||
if let Some(tail) = tail {
|
||||
|
@ -531,6 +532,9 @@ impl InferenceContext<'_> {
|
|||
self.consume_expr(expr);
|
||||
}
|
||||
}
|
||||
&Expr::Become { expr } => {
|
||||
self.consume_expr(expr);
|
||||
}
|
||||
Expr::RecordLit { fields, spread, .. } => {
|
||||
if let &Some(expr) = spread {
|
||||
self.consume_expr(expr);
|
||||
|
|
|
@ -13,7 +13,7 @@ use hir_def::{
|
|||
ArithOp, Array, BinaryOp, ClosureKind, Expr, ExprId, LabelId, Literal, Statement, UnaryOp,
|
||||
},
|
||||
lang_item::{LangItem, LangItemTarget},
|
||||
path::{GenericArg, GenericArgs},
|
||||
path::{GenericArg, GenericArgs, Path},
|
||||
BlockId, ConstParamId, FieldId, ItemContainerId, Lookup, TupleFieldId, TupleId,
|
||||
};
|
||||
use hir_expand::name::{name, Name};
|
||||
|
@ -439,7 +439,17 @@ impl InferenceContext<'_> {
|
|||
}
|
||||
Expr::Path(p) => {
|
||||
let g = self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, tgt_expr);
|
||||
let ty = self.infer_path(p, tgt_expr.into()).unwrap_or_else(|| self.err_ty());
|
||||
let ty = match self.infer_path(p, tgt_expr.into()) {
|
||||
Some(ty) => ty,
|
||||
None => {
|
||||
if matches!(p, Path::Normal { mod_path, .. } if mod_path.is_ident()) {
|
||||
self.push_diagnostic(InferenceDiagnostic::UnresolvedIdent {
|
||||
expr: tgt_expr,
|
||||
});
|
||||
}
|
||||
self.err_ty()
|
||||
}
|
||||
};
|
||||
self.resolver.reset_to_guard(g);
|
||||
ty
|
||||
}
|
||||
|
@ -502,6 +512,7 @@ impl InferenceContext<'_> {
|
|||
self.result.standard_types.never.clone()
|
||||
}
|
||||
&Expr::Return { expr } => self.infer_expr_return(tgt_expr, expr),
|
||||
&Expr::Become { expr } => self.infer_expr_become(expr),
|
||||
Expr::Yield { expr } => {
|
||||
if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() {
|
||||
if let Some(expr) = expr {
|
||||
|
@ -1084,6 +1095,27 @@ impl InferenceContext<'_> {
|
|||
self.result.standard_types.never.clone()
|
||||
}
|
||||
|
||||
fn infer_expr_become(&mut self, expr: ExprId) -> Ty {
|
||||
match &self.return_coercion {
|
||||
Some(return_coercion) => {
|
||||
let ret_ty = return_coercion.expected_ty();
|
||||
|
||||
let call_expr_ty =
|
||||
self.infer_expr_inner(expr, &Expectation::HasType(ret_ty.clone()));
|
||||
|
||||
// NB: this should *not* coerce.
|
||||
// tail calls don't support any coercions except lifetimes ones (like `&'static u8 -> &'a u8`).
|
||||
self.unify(&call_expr_ty, &ret_ty);
|
||||
}
|
||||
None => {
|
||||
// FIXME: diagnose `become` outside of functions
|
||||
self.infer_expr_no_expect(expr);
|
||||
}
|
||||
}
|
||||
|
||||
self.result.standard_types.never.clone()
|
||||
}
|
||||
|
||||
fn infer_expr_box(&mut self, inner_expr: ExprId, expected: &Expectation) -> Ty {
|
||||
if let Some(box_id) = self.resolve_boxed_box() {
|
||||
let table = &mut self.table;
|
||||
|
@ -1367,6 +1399,7 @@ impl InferenceContext<'_> {
|
|||
);
|
||||
}
|
||||
}
|
||||
Statement::Item => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ impl InferenceContext<'_> {
|
|||
Statement::Expr { expr, has_semi: _ } => {
|
||||
self.infer_mut_expr(*expr, Mutability::Not);
|
||||
}
|
||||
Statement::Item => (),
|
||||
}
|
||||
}
|
||||
if let Some(tail) = tail {
|
||||
|
@ -93,6 +94,9 @@ impl InferenceContext<'_> {
|
|||
self.infer_mut_expr(expr, Mutability::Not);
|
||||
}
|
||||
}
|
||||
Expr::Become { expr } => {
|
||||
self.infer_mut_expr(*expr, Mutability::Not);
|
||||
}
|
||||
Expr::RecordLit { path: _, fields, spread, ellipsis: _, is_assignee_expr: _ } => {
|
||||
self.infer_mut_not_expr_iter(fields.iter().map(|it| it.expr).chain(*spread))
|
||||
}
|
||||
|
|
|
@ -74,6 +74,12 @@ impl<T: HasInterner<Interner = Interner>> Canonicalized<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Check if types unify.
|
||||
///
|
||||
/// Note that we consider placeholder types to unify with everything.
|
||||
/// This means that there may be some unresolved goals that actually set bounds for the placeholder
|
||||
/// type for the types to unify. For example `Option<T>` and `Option<U>` unify although there is
|
||||
/// unresolved goal `T = U`.
|
||||
pub fn could_unify(
|
||||
db: &dyn HirDatabase,
|
||||
env: Arc<TraitEnvironment>,
|
||||
|
@ -82,21 +88,35 @@ pub fn could_unify(
|
|||
unify(db, env, tys).is_some()
|
||||
}
|
||||
|
||||
/// Check if types unify eagerly making sure there are no unresolved goals.
|
||||
///
|
||||
/// This means that placeholder types are not considered to unify if there are any bounds set on
|
||||
/// them. For example `Option<T>` and `Option<U>` do not unify as we cannot show that `T = U`
|
||||
pub fn could_unify_deeply(
|
||||
db: &dyn HirDatabase,
|
||||
env: Arc<TraitEnvironment>,
|
||||
tys: &Canonical<(Ty, Ty)>,
|
||||
) -> bool {
|
||||
let mut table = InferenceTable::new(db, env);
|
||||
let vars = make_substitutions(tys, &mut table);
|
||||
let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner);
|
||||
let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner);
|
||||
let ty1_with_vars = table.normalize_associated_types_in(ty1_with_vars);
|
||||
let ty2_with_vars = table.normalize_associated_types_in(ty2_with_vars);
|
||||
table.resolve_obligations_as_possible();
|
||||
table.propagate_diverging_flag();
|
||||
let ty1_with_vars = table.resolve_completely(ty1_with_vars);
|
||||
let ty2_with_vars = table.resolve_completely(ty2_with_vars);
|
||||
table.unify_deeply(&ty1_with_vars, &ty2_with_vars)
|
||||
}
|
||||
|
||||
pub(crate) fn unify(
|
||||
db: &dyn HirDatabase,
|
||||
env: Arc<TraitEnvironment>,
|
||||
tys: &Canonical<(Ty, Ty)>,
|
||||
) -> Option<Substitution> {
|
||||
let mut table = InferenceTable::new(db, env);
|
||||
let vars = Substitution::from_iter(
|
||||
Interner,
|
||||
tys.binders.iter(Interner).map(|it| match &it.kind {
|
||||
chalk_ir::VariableKind::Ty(_) => table.new_type_var().cast(Interner),
|
||||
// FIXME: maybe wrong?
|
||||
chalk_ir::VariableKind::Lifetime => table.new_type_var().cast(Interner),
|
||||
chalk_ir::VariableKind::Const(ty) => table.new_const_var(ty.clone()).cast(Interner),
|
||||
}),
|
||||
);
|
||||
let vars = make_substitutions(tys, &mut table);
|
||||
let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner);
|
||||
let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner);
|
||||
if !table.unify(&ty1_with_vars, &ty2_with_vars) {
|
||||
|
@ -125,6 +145,21 @@ pub(crate) fn unify(
|
|||
))
|
||||
}
|
||||
|
||||
fn make_substitutions(
|
||||
tys: &chalk_ir::Canonical<(chalk_ir::Ty<Interner>, chalk_ir::Ty<Interner>)>,
|
||||
table: &mut InferenceTable<'_>,
|
||||
) -> chalk_ir::Substitution<Interner> {
|
||||
Substitution::from_iter(
|
||||
Interner,
|
||||
tys.binders.iter(Interner).map(|it| match &it.kind {
|
||||
chalk_ir::VariableKind::Ty(_) => table.new_type_var().cast(Interner),
|
||||
// FIXME: maybe wrong?
|
||||
chalk_ir::VariableKind::Lifetime => table.new_type_var().cast(Interner),
|
||||
chalk_ir::VariableKind::Const(ty) => table.new_const_var(ty.clone()).cast(Interner),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub(crate) struct TypeVariableFlags: u8 {
|
||||
|
@ -431,6 +466,18 @@ impl<'a> InferenceTable<'a> {
|
|||
true
|
||||
}
|
||||
|
||||
/// Unify two relatable values (e.g. `Ty`) and check whether trait goals which arise from that could be fulfilled
|
||||
pub(crate) fn unify_deeply<T: ?Sized + Zip<Interner>>(&mut self, ty1: &T, ty2: &T) -> bool {
|
||||
let result = match self.try_unify(ty1, ty2) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return false,
|
||||
};
|
||||
result.goals.iter().all(|goal| {
|
||||
let canonicalized = self.canonicalize(goal.clone());
|
||||
self.try_resolve_obligation(&canonicalized).is_some()
|
||||
})
|
||||
}
|
||||
|
||||
/// Unify two relatable values (e.g. `Ty`) and return new trait goals arising from it, so the
|
||||
/// caller needs to deal with them.
|
||||
pub(crate) fn try_unify<T: ?Sized + Zip<Interner>>(
|
||||
|
@ -501,7 +548,8 @@ impl<'a> InferenceTable<'a> {
|
|||
|
||||
fn register_obligation_in_env(&mut self, goal: InEnvironment<Goal>) {
|
||||
let canonicalized = self.canonicalize(goal);
|
||||
if !self.try_resolve_obligation(&canonicalized) {
|
||||
let solution = self.try_resolve_obligation(&canonicalized);
|
||||
if matches!(solution, Some(Solution::Ambig(_))) {
|
||||
self.pending_obligations.push(canonicalized);
|
||||
}
|
||||
}
|
||||
|
@ -627,38 +675,35 @@ impl<'a> InferenceTable<'a> {
|
|||
fn try_resolve_obligation(
|
||||
&mut self,
|
||||
canonicalized: &Canonicalized<InEnvironment<Goal>>,
|
||||
) -> bool {
|
||||
) -> Option<chalk_solve::Solution<Interner>> {
|
||||
let solution = self.db.trait_solve(
|
||||
self.trait_env.krate,
|
||||
self.trait_env.block,
|
||||
canonicalized.value.clone(),
|
||||
);
|
||||
|
||||
match solution {
|
||||
match &solution {
|
||||
Some(Solution::Unique(canonical_subst)) => {
|
||||
canonicalized.apply_solution(
|
||||
self,
|
||||
Canonical {
|
||||
binders: canonical_subst.binders,
|
||||
binders: canonical_subst.binders.clone(),
|
||||
// FIXME: handle constraints
|
||||
value: canonical_subst.value.subst,
|
||||
value: canonical_subst.value.subst.clone(),
|
||||
},
|
||||
);
|
||||
true
|
||||
}
|
||||
Some(Solution::Ambig(Guidance::Definite(substs))) => {
|
||||
canonicalized.apply_solution(self, substs);
|
||||
false
|
||||
canonicalized.apply_solution(self, substs.clone());
|
||||
}
|
||||
Some(_) => {
|
||||
// FIXME use this when trying to resolve everything at the end
|
||||
false
|
||||
}
|
||||
None => {
|
||||
// FIXME obligation cannot be fulfilled => diagnostic
|
||||
true
|
||||
}
|
||||
}
|
||||
solution
|
||||
}
|
||||
|
||||
pub(crate) fn callable_sig(
|
||||
|
|
|
@ -11,10 +11,8 @@ pub fn target_data_layout_query(
|
|||
db: &dyn HirDatabase,
|
||||
krate: CrateId,
|
||||
) -> Result<Arc<TargetDataLayout>, Arc<str>> {
|
||||
let crate_graph = db.crate_graph();
|
||||
let res = crate_graph[krate].target_layout.as_deref();
|
||||
match res {
|
||||
Ok(it) => match TargetDataLayout::parse_from_llvm_datalayout_string(it) {
|
||||
match db.data_layout(krate) {
|
||||
Ok(it) => match TargetDataLayout::parse_from_llvm_datalayout_string(&it) {
|
||||
Ok(it) => Ok(Arc::new(it)),
|
||||
Err(e) => {
|
||||
Err(match e {
|
||||
|
@ -44,6 +42,6 @@ pub fn target_data_layout_query(
|
|||
}.into())
|
||||
}
|
||||
},
|
||||
Err(e) => Err(Arc::from(&**e)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use chalk_ir::{AdtId, TyKind};
|
||||
use either::Either;
|
||||
use hir_def::db::DefDatabase;
|
||||
use project_model::target_data_layout::RustcDataLayoutConfig;
|
||||
use rustc_hash::FxHashMap;
|
||||
use test_fixture::WithFixture;
|
||||
use triomphe::Arc;
|
||||
|
@ -15,13 +16,18 @@ use crate::{
|
|||
mod closure;
|
||||
|
||||
fn current_machine_data_layout() -> String {
|
||||
project_model::target_data_layout::get(None, None, &FxHashMap::default()).unwrap()
|
||||
project_model::target_data_layout::get(
|
||||
RustcDataLayoutConfig::Rustc(None),
|
||||
None,
|
||||
&FxHashMap::default(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutError> {
|
||||
let target_data_layout = current_machine_data_layout();
|
||||
let ra_fixture = format!(
|
||||
"{minicore}//- /main.rs crate:test target_data_layout:{target_data_layout}\n{ra_fixture}",
|
||||
"//- target_data_layout: {target_data_layout}\n{minicore}//- /main.rs crate:test\n{ra_fixture}",
|
||||
);
|
||||
|
||||
let (db, file_ids) = TestDB::with_many_files(&ra_fixture);
|
||||
|
@ -70,7 +76,7 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutErro
|
|||
fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutError> {
|
||||
let target_data_layout = current_machine_data_layout();
|
||||
let ra_fixture = format!(
|
||||
"{minicore}//- /main.rs crate:test target_data_layout:{target_data_layout}\nfn main(){{let goal = {{{ra_fixture}}};}}",
|
||||
"//- target_data_layout: {target_data_layout}\n{minicore}//- /main.rs crate:test\nfn main(){{let goal = {{{ra_fixture}}};}}",
|
||||
);
|
||||
|
||||
let (db, file_id) = TestDB::with_single_file(&ra_fixture);
|
||||
|
|
|
@ -79,8 +79,8 @@ pub use builder::{ParamKind, TyBuilder};
|
|||
pub use chalk_ext::*;
|
||||
pub use infer::{
|
||||
closure::{CaptureKind, CapturedItem},
|
||||
could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic,
|
||||
InferenceResult, OverloadedDeref, PointerCast,
|
||||
could_coerce, could_unify, could_unify_deeply, Adjust, Adjustment, AutoBorrow, BindingMode,
|
||||
InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
|
||||
};
|
||||
pub use interner::Interner;
|
||||
pub use lower::{
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::iter;
|
|||
|
||||
use hir_def::{DefWithBodyId, HasModule};
|
||||
use la_arena::ArenaMap;
|
||||
use rustc_hash::FxHashMap;
|
||||
use stdx::never;
|
||||
use triomphe::Arc;
|
||||
|
||||
|
@ -14,7 +15,7 @@ use crate::{
|
|||
db::{HirDatabase, InternedClosure},
|
||||
mir::Operand,
|
||||
utils::ClosureSubst,
|
||||
ClosureId, Interner, Ty, TyExt, TypeFlags,
|
||||
ClosureId, Interner, Substitution, Ty, TyExt, TypeFlags,
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
@ -36,11 +37,27 @@ pub struct MovedOutOfRef {
|
|||
pub span: MirSpan,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PartiallyMoved {
|
||||
pub ty: Ty,
|
||||
pub span: MirSpan,
|
||||
pub local: LocalId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BorrowRegion {
|
||||
pub local: LocalId,
|
||||
pub kind: BorrowKind,
|
||||
pub places: Vec<MirSpan>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BorrowckResult {
|
||||
pub mir_body: Arc<MirBody>,
|
||||
pub mutability_of_locals: ArenaMap<LocalId, MutabilityReason>,
|
||||
pub moved_out_of_ref: Vec<MovedOutOfRef>,
|
||||
pub partially_moved: Vec<PartiallyMoved>,
|
||||
pub borrow_regions: Vec<BorrowRegion>,
|
||||
}
|
||||
|
||||
fn all_mir_bodies(
|
||||
|
@ -80,12 +97,26 @@ pub fn borrowck_query(
|
|||
res.push(BorrowckResult {
|
||||
mutability_of_locals: mutability_of_locals(db, &body),
|
||||
moved_out_of_ref: moved_out_of_ref(db, &body),
|
||||
partially_moved: partially_moved(db, &body),
|
||||
borrow_regions: borrow_regions(db, &body),
|
||||
mir_body: body,
|
||||
});
|
||||
})?;
|
||||
Ok(res.into())
|
||||
}
|
||||
|
||||
fn make_fetch_closure_field(
|
||||
db: &dyn HirDatabase,
|
||||
) -> impl FnOnce(ClosureId, &Substitution, usize) -> Ty + '_ {
|
||||
|c: ClosureId, subst: &Substitution, f: usize| {
|
||||
let InternedClosure(def, _) = db.lookup_intern_closure(c.into());
|
||||
let infer = db.infer(def);
|
||||
let (captures, _) = infer.closure_info(&c);
|
||||
let parent_subst = ClosureSubst(subst).parent_subst();
|
||||
captures.get(f).expect("broken closure field").ty.clone().substitute(Interner, parent_subst)
|
||||
}
|
||||
}
|
||||
|
||||
fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef> {
|
||||
let mut result = vec![];
|
||||
let mut for_operand = |op: &Operand, span: MirSpan| match op {
|
||||
|
@ -99,18 +130,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef>
|
|||
ty = proj.projected_ty(
|
||||
ty,
|
||||
db,
|
||||
|c, subst, f| {
|
||||
let InternedClosure(def, _) = db.lookup_intern_closure(c.into());
|
||||
let infer = db.infer(def);
|
||||
let (captures, _) = infer.closure_info(&c);
|
||||
let parent_subst = ClosureSubst(subst).parent_subst();
|
||||
captures
|
||||
.get(f)
|
||||
.expect("broken closure field")
|
||||
.ty
|
||||
.clone()
|
||||
.substitute(Interner, parent_subst)
|
||||
},
|
||||
make_fetch_closure_field(db),
|
||||
body.owner.module(db.upcast()).krate(),
|
||||
);
|
||||
}
|
||||
|
@ -188,6 +208,132 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef>
|
|||
result
|
||||
}
|
||||
|
||||
fn partially_moved(db: &dyn HirDatabase, body: &MirBody) -> Vec<PartiallyMoved> {
|
||||
let mut result = vec![];
|
||||
let mut for_operand = |op: &Operand, span: MirSpan| match op {
|
||||
Operand::Copy(p) | Operand::Move(p) => {
|
||||
let mut ty: Ty = body.locals[p.local].ty.clone();
|
||||
for proj in p.projection.lookup(&body.projection_store) {
|
||||
ty = proj.projected_ty(
|
||||
ty,
|
||||
db,
|
||||
make_fetch_closure_field(db),
|
||||
body.owner.module(db.upcast()).krate(),
|
||||
);
|
||||
}
|
||||
if !ty.clone().is_copy(db, body.owner)
|
||||
&& !ty.data(Interner).flags.intersects(TypeFlags::HAS_ERROR)
|
||||
{
|
||||
result.push(PartiallyMoved { span, ty, local: p.local });
|
||||
}
|
||||
}
|
||||
Operand::Constant(_) | Operand::Static(_) => (),
|
||||
};
|
||||
for (_, block) in body.basic_blocks.iter() {
|
||||
db.unwind_if_cancelled();
|
||||
for statement in &block.statements {
|
||||
match &statement.kind {
|
||||
StatementKind::Assign(_, r) => match r {
|
||||
Rvalue::ShallowInitBoxWithAlloc(_) => (),
|
||||
Rvalue::ShallowInitBox(o, _)
|
||||
| Rvalue::UnaryOp(_, o)
|
||||
| Rvalue::Cast(_, o, _)
|
||||
| Rvalue::Repeat(o, _)
|
||||
| Rvalue::Use(o) => for_operand(o, statement.span),
|
||||
Rvalue::CopyForDeref(_)
|
||||
| Rvalue::Discriminant(_)
|
||||
| Rvalue::Len(_)
|
||||
| Rvalue::Ref(_, _) => (),
|
||||
Rvalue::CheckedBinaryOp(_, o1, o2) => {
|
||||
for_operand(o1, statement.span);
|
||||
for_operand(o2, statement.span);
|
||||
}
|
||||
Rvalue::Aggregate(_, ops) => {
|
||||
for op in ops.iter() {
|
||||
for_operand(op, statement.span);
|
||||
}
|
||||
}
|
||||
},
|
||||
StatementKind::FakeRead(_)
|
||||
| StatementKind::Deinit(_)
|
||||
| StatementKind::StorageLive(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::Nop => (),
|
||||
}
|
||||
}
|
||||
match &block.terminator {
|
||||
Some(terminator) => match &terminator.kind {
|
||||
TerminatorKind::SwitchInt { discr, .. } => for_operand(discr, terminator.span),
|
||||
TerminatorKind::FalseEdge { .. }
|
||||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::CoroutineDrop
|
||||
| TerminatorKind::Abort
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Drop { .. } => (),
|
||||
TerminatorKind::DropAndReplace { value, .. } => {
|
||||
for_operand(value, terminator.span);
|
||||
}
|
||||
TerminatorKind::Call { func, args, .. } => {
|
||||
for_operand(func, terminator.span);
|
||||
args.iter().for_each(|it| for_operand(it, terminator.span));
|
||||
}
|
||||
TerminatorKind::Assert { cond, .. } => {
|
||||
for_operand(cond, terminator.span);
|
||||
}
|
||||
TerminatorKind::Yield { value, .. } => {
|
||||
for_operand(value, terminator.span);
|
||||
}
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
result.shrink_to_fit();
|
||||
result
|
||||
}
|
||||
|
||||
fn borrow_regions(db: &dyn HirDatabase, body: &MirBody) -> Vec<BorrowRegion> {
|
||||
let mut borrows = FxHashMap::default();
|
||||
for (_, block) in body.basic_blocks.iter() {
|
||||
db.unwind_if_cancelled();
|
||||
for statement in &block.statements {
|
||||
if let StatementKind::Assign(_, Rvalue::Ref(kind, p)) = &statement.kind {
|
||||
borrows
|
||||
.entry(p.local)
|
||||
.and_modify(|it: &mut BorrowRegion| {
|
||||
it.places.push(statement.span);
|
||||
})
|
||||
.or_insert_with(|| BorrowRegion {
|
||||
local: p.local,
|
||||
kind: *kind,
|
||||
places: vec![statement.span],
|
||||
});
|
||||
}
|
||||
}
|
||||
match &block.terminator {
|
||||
Some(terminator) => match &terminator.kind {
|
||||
TerminatorKind::FalseEdge { .. }
|
||||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::CoroutineDrop
|
||||
| TerminatorKind::Abort
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Drop { .. } => (),
|
||||
TerminatorKind::DropAndReplace { .. } => {}
|
||||
TerminatorKind::Call { .. } => {}
|
||||
_ => (),
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
borrows.into_values().collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ProjectionCase {
|
||||
/// Projection is a local
|
||||
|
@ -217,18 +363,7 @@ fn place_case(db: &dyn HirDatabase, body: &MirBody, lvalue: &Place) -> Projectio
|
|||
ty = proj.projected_ty(
|
||||
ty,
|
||||
db,
|
||||
|c, subst, f| {
|
||||
let InternedClosure(def, _) = db.lookup_intern_closure(c.into());
|
||||
let infer = db.infer(def);
|
||||
let (captures, _) = infer.closure_info(&c);
|
||||
let parent_subst = ClosureSubst(subst).parent_subst();
|
||||
captures
|
||||
.get(f)
|
||||
.expect("broken closure field")
|
||||
.ty
|
||||
.clone()
|
||||
.substitute(Interner, parent_subst)
|
||||
},
|
||||
make_fetch_closure_field(db),
|
||||
body.owner.module(db.upcast()).krate(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,9 +8,13 @@ use hir_def::{
|
|||
builtin_type::{BuiltinInt, BuiltinUint},
|
||||
resolver::HasResolver,
|
||||
};
|
||||
use hir_expand::mod_path::ModPath;
|
||||
|
||||
use super::*;
|
||||
use crate::mir::eval::{
|
||||
name, pad16, static_lifetime, Address, AdtId, Arc, BuiltinType, Evaluator, FunctionId,
|
||||
HasModule, HirDisplay, Interned, InternedClosure, Interner, Interval, IntervalAndTy,
|
||||
IntervalOrOwned, ItemContainerId, LangItem, Layout, Locals, Lookup, MirEvalError, MirSpan,
|
||||
ModPath, Mutability, Result, Substitution, Ty, TyBuilder, TyExt,
|
||||
};
|
||||
|
||||
mod simd;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::consteval::try_const_usize;
|
||||
use crate::TyKind;
|
||||
|
||||
use super::*;
|
||||
|
|
|
@ -31,14 +31,20 @@ use crate::{
|
|||
inhabitedness::is_ty_uninhabited_from,
|
||||
layout::LayoutError,
|
||||
mapping::ToChalk,
|
||||
mir::{
|
||||
intern_const_scalar, return_slot, AggregateKind, Arena, BasicBlock, BasicBlockId, BinOp,
|
||||
BorrowKind, CastKind, ClosureId, ConstScalar, Either, Expr, FieldId, Idx, InferenceResult,
|
||||
Interner, Local, LocalId, MemoryMap, MirBody, MirSpan, Mutability, Operand, Place,
|
||||
PlaceElem, PointerCast, ProjectionElem, ProjectionStore, RawIdx, Rvalue, Statement,
|
||||
StatementKind, Substitution, SwitchTargets, Terminator, TerminatorKind, TupleFieldId, Ty,
|
||||
UnOp, VariantId,
|
||||
},
|
||||
static_lifetime,
|
||||
traits::FnTrait,
|
||||
utils::{generics, ClosureSubst},
|
||||
Adjust, Adjustment, AutoBorrow, CallableDefId, TyBuilder, TyExt,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
mod as_place;
|
||||
mod pattern_matching;
|
||||
|
||||
|
@ -775,6 +781,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
|
|||
self.set_terminator(current, TerminatorKind::Return, expr_id.into());
|
||||
Ok(None)
|
||||
}
|
||||
Expr::Become { .. } => not_supported!("tail-calls"),
|
||||
Expr::Yield { .. } => not_supported!("yield"),
|
||||
Expr::RecordLit { fields, path, spread, ellipsis: _, is_assignee_expr: _ } => {
|
||||
let spread_place = match spread {
|
||||
|
@ -1246,7 +1253,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
|
|||
self.push_assignment(current, place, op.into(), expr_id.into());
|
||||
Ok(Some(current))
|
||||
}
|
||||
Expr::Underscore => not_supported!("underscore"),
|
||||
Expr::Underscore => Ok(Some(current)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1780,6 +1787,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
|
|||
self.push_fake_read(c, p, expr.into());
|
||||
current = scope2.pop_and_drop(self, c, expr.into());
|
||||
}
|
||||
hir_def::hir::Statement::Item => (),
|
||||
}
|
||||
}
|
||||
if let Some(tail) = tail {
|
||||
|
|
|
@ -2,9 +2,16 @@
|
|||
|
||||
use hir_def::{hir::LiteralOrConst, resolver::HasResolver, AssocItemId};
|
||||
|
||||
use crate::BindingMode;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
mir::lower::{
|
||||
BasicBlockId, BinOp, BindingId, BorrowKind, Either, Expr, FieldId, Idx, Interner,
|
||||
MemoryMap, MirLowerCtx, MirLowerError, MirSpan, Mutability, Operand, Pat, PatId, Place,
|
||||
PlaceElem, ProjectionElem, RecordFieldPat, ResolveValueResult, Result, Rvalue,
|
||||
Substitution, SwitchTargets, TerminatorKind, TupleFieldId, TupleId, TyBuilder, TyKind,
|
||||
ValueNs, VariantData, VariantId,
|
||||
},
|
||||
BindingMode,
|
||||
};
|
||||
|
||||
macro_rules! not_supported {
|
||||
($x: expr) => {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use crate::tests::check_no_mismatches;
|
||||
|
||||
use super::check;
|
||||
|
||||
#[test]
|
||||
|
@ -94,3 +96,43 @@ fn test(x: bool) {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_mismatches_on_atpit() {
|
||||
check_no_mismatches(
|
||||
r#"
|
||||
//- minicore: option, sized
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
|
||||
trait WrappedAssoc {
|
||||
type Assoc;
|
||||
fn do_thing(&self) -> Option<Self::Assoc>;
|
||||
}
|
||||
|
||||
struct Foo;
|
||||
impl WrappedAssoc for Foo {
|
||||
type Assoc = impl Sized;
|
||||
|
||||
fn do_thing(&self) -> Option<Self::Assoc> {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_no_mismatches(
|
||||
r#"
|
||||
//- minicore: option, sized
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
|
||||
trait Trait {
|
||||
type Assoc;
|
||||
const DEFINE: Option<Self::Assoc>;
|
||||
}
|
||||
|
||||
impl Trait for () {
|
||||
type Assoc = impl Sized;
|
||||
const DEFINE: Option<Self::Assoc> = Option::Some(());
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3376,11 +3376,8 @@ fn main() {
|
|||
[x,] = &[1,];
|
||||
//^^^^expected &[i32; 1], got [{unknown}; _]
|
||||
|
||||
// FIXME we only want the outermost error, but this matches the current
|
||||
// behavior of slice patterns
|
||||
let x;
|
||||
[(x,),] = &[(1,),];
|
||||
// ^^^^expected {unknown}, got ({unknown},)
|
||||
//^^^^^^^expected &[(i32,); 1], got [{unknown}; _]
|
||||
|
||||
let x;
|
||||
|
|
|
@ -4553,3 +4553,58 @@ fn foo() {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_trait_bound() {
|
||||
check_types(
|
||||
r#"
|
||||
//- minicore: sized
|
||||
auto trait Send {}
|
||||
impl<T> !Send for *const T {}
|
||||
|
||||
struct Yes;
|
||||
trait IsSend { const IS_SEND: Yes; }
|
||||
impl<T: Send> IsSend for T { const IS_SEND: Yes = Yes; }
|
||||
|
||||
struct Struct<T>(T);
|
||||
enum Enum<T> { A, B(T) }
|
||||
union Union<T> { t: T }
|
||||
|
||||
#[lang = "phantom_data"]
|
||||
struct PhantomData<T: ?Sized>;
|
||||
|
||||
fn f<T: Send, U>() {
|
||||
T::IS_SEND;
|
||||
//^^^^^^^^^^Yes
|
||||
U::IS_SEND;
|
||||
//^^^^^^^^^^{unknown}
|
||||
<*const T>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^{unknown}
|
||||
Struct::<T>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^^Yes
|
||||
Struct::<U>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^^Yes
|
||||
Struct::<*const T>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^^^^^^^^^Yes
|
||||
Enum::<T>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^Yes
|
||||
Enum::<U>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^Yes
|
||||
Enum::<*const T>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^^^^^^^Yes
|
||||
Union::<T>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^Yes
|
||||
Union::<U>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^Yes
|
||||
Union::<*const T>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^^^^^^^^Yes
|
||||
PhantomData::<T>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^^^^^^^Yes
|
||||
PhantomData::<U>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^^^^^^^{unknown}
|
||||
PhantomData::<*const T>::IS_SEND;
|
||||
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^{unknown}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ diagnostics![
|
|||
MissingUnsafe,
|
||||
MovedOutOfRef,
|
||||
NeedMut,
|
||||
NonExhaustiveLet,
|
||||
NoSuchField,
|
||||
PrivateAssocItem,
|
||||
PrivateField,
|
||||
|
@ -86,6 +87,7 @@ diagnostics![
|
|||
UnresolvedMacroCall,
|
||||
UnresolvedMethodCall,
|
||||
UnresolvedModule,
|
||||
UnresolvedIdent,
|
||||
UnresolvedProcMacro,
|
||||
UnusedMut,
|
||||
UnusedVariable,
|
||||
|
@ -241,6 +243,11 @@ pub struct UnresolvedAssocItem {
|
|||
pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, Either<ast::Pat, ast::SelfParam>>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnresolvedIdent {
|
||||
pub expr: InFile<AstPtr<ast::Expr>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PrivateField {
|
||||
pub expr: InFile<AstPtr<ast::Expr>>,
|
||||
|
@ -280,6 +287,12 @@ pub struct MissingMatchArms {
|
|||
pub uncovered_patterns: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NonExhaustiveLet {
|
||||
pub pat: InFile<AstPtr<ast::Pat>>,
|
||||
pub uncovered_patterns: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypeMismatch {
|
||||
pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
|
||||
|
@ -456,6 +469,22 @@ impl AnyDiagnostic {
|
|||
Err(SyntheticSyntax) => (),
|
||||
}
|
||||
}
|
||||
BodyValidationDiagnostic::NonExhaustiveLet { pat, uncovered_patterns } => {
|
||||
match source_map.pat_syntax(pat) {
|
||||
Ok(source_ptr) => {
|
||||
if let Some(ast_pat) = source_ptr.value.cast::<ast::Pat>() {
|
||||
return Some(
|
||||
NonExhaustiveLet {
|
||||
pat: InFile::new(source_ptr.file_id, ast_pat),
|
||||
uncovered_patterns,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(SyntheticSyntax) => {}
|
||||
}
|
||||
}
|
||||
BodyValidationDiagnostic::RemoveTrailingReturn { return_expr } => {
|
||||
if let Ok(source_ptr) = source_map.expr_syntax(return_expr) {
|
||||
// Filters out desugared return expressions (e.g. desugared try operators).
|
||||
|
@ -565,6 +594,10 @@ impl AnyDiagnostic {
|
|||
};
|
||||
UnresolvedAssocItem { expr_or_pat }.into()
|
||||
}
|
||||
&InferenceDiagnostic::UnresolvedIdent { expr } => {
|
||||
let expr = expr_syntax(expr);
|
||||
UnresolvedIdent { expr }.into()
|
||||
}
|
||||
&InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break, bad_value_break } => {
|
||||
let expr = expr_syntax(expr);
|
||||
BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()
|
||||
|
|
|
@ -31,6 +31,7 @@ mod has_source;
|
|||
pub mod db;
|
||||
pub mod diagnostics;
|
||||
pub mod symbols;
|
||||
pub mod term_search;
|
||||
|
||||
mod display;
|
||||
|
||||
|
@ -1084,6 +1085,27 @@ impl Field {
|
|||
Type::new(db, var_id, ty)
|
||||
}
|
||||
|
||||
// FIXME: Find better API to also handle const generics
|
||||
pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator<Item = Type>) -> Type {
|
||||
let var_id = self.parent.into();
|
||||
let def_id: AdtId = match self.parent {
|
||||
VariantDef::Struct(it) => it.id.into(),
|
||||
VariantDef::Union(it) => it.id.into(),
|
||||
VariantDef::Variant(it) => it.parent_enum(db).id.into(),
|
||||
};
|
||||
let mut generics = generics.map(|it| it.ty.clone());
|
||||
let substs = TyBuilder::subst_for_def(db, def_id, None)
|
||||
.fill(|x| match x {
|
||||
ParamKind::Type => {
|
||||
generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)).cast(Interner)
|
||||
}
|
||||
ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
|
||||
})
|
||||
.build();
|
||||
let ty = db.field_types(var_id)[self.id].clone().substitute(Interner, &substs);
|
||||
Type::new(db, var_id, ty)
|
||||
}
|
||||
|
||||
pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
|
||||
db.layout_of_ty(
|
||||
self.ty(db).ty,
|
||||
|
@ -1152,6 +1174,10 @@ impl Struct {
|
|||
fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
|
||||
db.struct_data(self.id).variant_data.clone()
|
||||
}
|
||||
|
||||
pub fn is_unstable(self, db: &dyn HirDatabase) -> bool {
|
||||
db.attrs(self.id.into()).is_unstable()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasVisibility for Struct {
|
||||
|
@ -1194,6 +1220,10 @@ impl Union {
|
|||
fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
|
||||
db.union_data(self.id).variant_data.clone()
|
||||
}
|
||||
|
||||
pub fn is_unstable(self, db: &dyn HirDatabase) -> bool {
|
||||
db.attrs(self.id.into()).is_unstable()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasVisibility for Union {
|
||||
|
@ -1269,6 +1299,10 @@ impl Enum {
|
|||
pub fn layout(self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
|
||||
Adt::from(self).layout(db)
|
||||
}
|
||||
|
||||
pub fn is_unstable(self, db: &dyn HirDatabase) -> bool {
|
||||
db.attrs(self.id.into()).is_unstable()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasVisibility for Enum {
|
||||
|
@ -1344,6 +1378,10 @@ impl Variant {
|
|||
_ => parent_layout,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_unstable(self, db: &dyn HirDatabase) -> bool {
|
||||
db.attrs(self.id.into()).is_unstable()
|
||||
}
|
||||
}
|
||||
|
||||
/// Variants inherit visibility from the parent enum.
|
||||
|
@ -1394,9 +1432,9 @@ impl Adt {
|
|||
|
||||
/// Turns this ADT into a type with the given type parameters. This isn't
|
||||
/// the greatest API, FIXME find a better one.
|
||||
pub fn ty_with_args(self, db: &dyn HirDatabase, args: &[Type]) -> Type {
|
||||
pub fn ty_with_args(self, db: &dyn HirDatabase, args: impl Iterator<Item = Type>) -> Type {
|
||||
let id = AdtId::from(self);
|
||||
let mut it = args.iter().map(|t| t.ty.clone());
|
||||
let mut it = args.map(|t| t.ty.clone());
|
||||
let ty = TyBuilder::def_ty(db, id.into(), None)
|
||||
.fill(|x| {
|
||||
let r = it.next().unwrap_or_else(|| TyKind::Error.intern(Interner));
|
||||
|
@ -1789,6 +1827,35 @@ impl Function {
|
|||
Type::new_with_resolver_inner(db, &resolver, ty)
|
||||
}
|
||||
|
||||
// FIXME: Find better API to also handle const generics
|
||||
pub fn ret_type_with_args(
|
||||
self,
|
||||
db: &dyn HirDatabase,
|
||||
generics: impl Iterator<Item = Type>,
|
||||
) -> Type {
|
||||
let resolver = self.id.resolver(db.upcast());
|
||||
let parent_id: Option<GenericDefId> = match self.id.lookup(db.upcast()).container {
|
||||
ItemContainerId::ImplId(it) => Some(it.into()),
|
||||
ItemContainerId::TraitId(it) => Some(it.into()),
|
||||
ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None,
|
||||
};
|
||||
let mut generics = generics.map(|it| it.ty.clone());
|
||||
let mut filler = |x: &_| match x {
|
||||
ParamKind::Type => {
|
||||
generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)).cast(Interner)
|
||||
}
|
||||
ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
|
||||
};
|
||||
|
||||
let parent_substs =
|
||||
parent_id.map(|id| TyBuilder::subst_for_def(db, id, None).fill(&mut filler).build());
|
||||
let substs = TyBuilder::subst_for_def(db, self.id, parent_substs).fill(&mut filler).build();
|
||||
|
||||
let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs);
|
||||
let ty = callable_sig.ret().clone();
|
||||
Type::new_with_resolver_inner(db, &resolver, ty)
|
||||
}
|
||||
|
||||
pub fn async_ret_type(self, db: &dyn HirDatabase) -> Option<Type> {
|
||||
if !self.is_async(db) {
|
||||
return None;
|
||||
|
@ -1855,6 +1922,51 @@ impl Function {
|
|||
.collect()
|
||||
}
|
||||
|
||||
// FIXME: Find better API to also handle const generics
|
||||
pub fn params_without_self_with_args(
|
||||
self,
|
||||
db: &dyn HirDatabase,
|
||||
generics: impl Iterator<Item = Type>,
|
||||
) -> Vec<Param> {
|
||||
let environment = db.trait_environment(self.id.into());
|
||||
let parent_id: Option<GenericDefId> = match self.id.lookup(db.upcast()).container {
|
||||
ItemContainerId::ImplId(it) => Some(it.into()),
|
||||
ItemContainerId::TraitId(it) => Some(it.into()),
|
||||
ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None,
|
||||
};
|
||||
let mut generics = generics.map(|it| it.ty.clone());
|
||||
let parent_substs = parent_id.map(|id| {
|
||||
TyBuilder::subst_for_def(db, id, None)
|
||||
.fill(|x| match x {
|
||||
ParamKind::Type => generics
|
||||
.next()
|
||||
.unwrap_or_else(|| TyKind::Error.intern(Interner))
|
||||
.cast(Interner),
|
||||
ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
|
||||
})
|
||||
.build()
|
||||
});
|
||||
|
||||
let substs = TyBuilder::subst_for_def(db, self.id, parent_substs)
|
||||
.fill(|_| {
|
||||
let ty = generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner));
|
||||
GenericArg::new(Interner, GenericArgData::Ty(ty))
|
||||
})
|
||||
.build();
|
||||
let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs);
|
||||
let skip = if db.function_data(self.id).has_self_param() { 1 } else { 0 };
|
||||
callable_sig
|
||||
.params()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(skip)
|
||||
.map(|(idx, ty)| {
|
||||
let ty = Type { env: environment.clone(), ty: ty.clone() };
|
||||
Param { func: self, ty, idx }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn is_const(self, db: &dyn HirDatabase) -> bool {
|
||||
db.function_data(self.id).has_const_kw()
|
||||
}
|
||||
|
@ -1889,6 +2001,11 @@ impl Function {
|
|||
db.function_data(self.id).attrs.is_bench()
|
||||
}
|
||||
|
||||
/// Is this function marked as unstable with `#[feature]` attribute?
|
||||
pub fn is_unstable(self, db: &dyn HirDatabase) -> bool {
|
||||
db.function_data(self.id).attrs.is_unstable()
|
||||
}
|
||||
|
||||
pub fn is_unsafe_to_call(self, db: &dyn HirDatabase) -> bool {
|
||||
hir_ty::is_fn_unsafe_to_call(db, self.id)
|
||||
}
|
||||
|
@ -2052,6 +2169,34 @@ impl SelfParam {
|
|||
let ty = callable_sig.params()[0].clone();
|
||||
Type { env: environment, ty }
|
||||
}
|
||||
|
||||
// FIXME: Find better API to also handle const generics
|
||||
pub fn ty_with_args(&self, db: &dyn HirDatabase, generics: impl Iterator<Item = Type>) -> Type {
|
||||
let parent_id: GenericDefId = match self.func.lookup(db.upcast()).container {
|
||||
ItemContainerId::ImplId(it) => it.into(),
|
||||
ItemContainerId::TraitId(it) => it.into(),
|
||||
ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => {
|
||||
panic!("Never get here")
|
||||
}
|
||||
};
|
||||
|
||||
let mut generics = generics.map(|it| it.ty.clone());
|
||||
let mut filler = |x: &_| match x {
|
||||
ParamKind::Type => {
|
||||
generics.next().unwrap_or_else(|| TyKind::Error.intern(Interner)).cast(Interner)
|
||||
}
|
||||
ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
|
||||
};
|
||||
|
||||
let parent_substs = TyBuilder::subst_for_def(db, parent_id, None).fill(&mut filler).build();
|
||||
let substs =
|
||||
TyBuilder::subst_for_def(db, self.func, Some(parent_substs)).fill(&mut filler).build();
|
||||
let callable_sig =
|
||||
db.callable_item_signature(self.func.into()).substitute(Interner, &substs);
|
||||
let environment = db.trait_environment(self.func.into());
|
||||
let ty = callable_sig.params()[0].clone();
|
||||
Type { env: environment, ty }
|
||||
}
|
||||
}
|
||||
|
||||
impl HasVisibility for Function {
|
||||
|
@ -2508,6 +2653,37 @@ impl ItemInNs {
|
|||
}
|
||||
}
|
||||
|
||||
/// Invariant: `inner.as_extern_assoc_item(db).is_some()`
|
||||
/// We do not actively enforce this invariant.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ExternAssocItem {
|
||||
Function(Function),
|
||||
Static(Static),
|
||||
TypeAlias(TypeAlias),
|
||||
}
|
||||
|
||||
pub trait AsExternAssocItem {
|
||||
fn as_extern_assoc_item(self, db: &dyn HirDatabase) -> Option<ExternAssocItem>;
|
||||
}
|
||||
|
||||
impl AsExternAssocItem for Function {
|
||||
fn as_extern_assoc_item(self, db: &dyn HirDatabase) -> Option<ExternAssocItem> {
|
||||
as_extern_assoc_item(db, ExternAssocItem::Function, self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsExternAssocItem for Static {
|
||||
fn as_extern_assoc_item(self, db: &dyn HirDatabase) -> Option<ExternAssocItem> {
|
||||
as_extern_assoc_item(db, ExternAssocItem::Static, self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsExternAssocItem for TypeAlias {
|
||||
fn as_extern_assoc_item(self, db: &dyn HirDatabase) -> Option<ExternAssocItem> {
|
||||
as_extern_assoc_item(db, ExternAssocItem::TypeAlias, self.id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Invariant: `inner.as_assoc_item(db).is_some()`
|
||||
/// We do not actively enforce this invariant.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -2582,6 +2758,63 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn as_extern_assoc_item<'db, ID, DEF, LOC>(
|
||||
db: &(dyn HirDatabase + 'db),
|
||||
ctor: impl FnOnce(DEF) -> ExternAssocItem,
|
||||
id: ID,
|
||||
) -> Option<ExternAssocItem>
|
||||
where
|
||||
ID: Lookup<Database<'db> = dyn DefDatabase + 'db, Data = AssocItemLoc<LOC>>,
|
||||
DEF: From<ID>,
|
||||
LOC: ItemTreeNode,
|
||||
{
|
||||
match id.lookup(db.upcast()).container {
|
||||
ItemContainerId::ExternBlockId(_) => Some(ctor(DEF::from(id))),
|
||||
ItemContainerId::TraitId(_) | ItemContainerId::ImplId(_) | ItemContainerId::ModuleId(_) => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternAssocItem {
|
||||
pub fn name(self, db: &dyn HirDatabase) -> Name {
|
||||
match self {
|
||||
Self::Function(it) => it.name(db),
|
||||
Self::Static(it) => it.name(db),
|
||||
Self::TypeAlias(it) => it.name(db),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn module(self, db: &dyn HirDatabase) -> Module {
|
||||
match self {
|
||||
Self::Function(f) => f.module(db),
|
||||
Self::Static(c) => c.module(db),
|
||||
Self::TypeAlias(t) => t.module(db),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_function(self) -> Option<Function> {
|
||||
match self {
|
||||
Self::Function(v) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_static(self) -> Option<Static> {
|
||||
match self {
|
||||
Self::Static(v) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_type_alias(self) -> Option<TypeAlias> {
|
||||
match self {
|
||||
Self::TypeAlias(v) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AssocItem {
|
||||
pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
|
||||
match self {
|
||||
|
@ -2754,7 +2987,7 @@ impl GenericDef {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn type_params(self, db: &dyn HirDatabase) -> Vec<TypeOrConstParam> {
|
||||
pub fn type_or_const_params(self, db: &dyn HirDatabase) -> Vec<TypeOrConstParam> {
|
||||
let generics = db.generic_params(self.into());
|
||||
generics
|
||||
.type_or_consts
|
||||
|
@ -3126,12 +3359,16 @@ impl TypeParam {
|
|||
let ty = generic_arg_from_param(db, self.id.into())?;
|
||||
let resolver = self.id.parent().resolver(db.upcast());
|
||||
match ty.data(Interner) {
|
||||
GenericArgData::Ty(it) => {
|
||||
GenericArgData::Ty(it) if *it.kind(Interner) != TyKind::Error => {
|
||||
Some(Type::new_with_resolver_inner(db, &resolver, it.clone()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_unstable(self, db: &dyn HirDatabase) -> bool {
|
||||
db.attrs(GenericParamId::from(self.id).into()).is_unstable()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -3241,6 +3478,26 @@ impl TypeOrConstParam {
|
|||
Either::Right(it) => it.ty(db),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_type_param(self, db: &dyn HirDatabase) -> Option<TypeParam> {
|
||||
let params = db.generic_params(self.id.parent);
|
||||
match ¶ms.type_or_consts[self.id.local_id] {
|
||||
hir_def::generics::TypeOrConstParamData::TypeParamData(_) => {
|
||||
Some(TypeParam { id: TypeParamId::from_unchecked(self.id) })
|
||||
}
|
||||
hir_def::generics::TypeOrConstParamData::ConstParamData(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_const_param(self, db: &dyn HirDatabase) -> Option<ConstParam> {
|
||||
let params = db.generic_params(self.id.parent);
|
||||
match ¶ms.type_or_consts[self.id.local_id] {
|
||||
hir_def::generics::TypeOrConstParamData::TypeParamData(_) => None,
|
||||
hir_def::generics::TypeOrConstParamData::ConstParamData(_) => {
|
||||
Some(ConstParam { id: ConstParamId::from_unchecked(self.id) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
@ -3285,12 +3542,11 @@ impl Impl {
|
|||
.filter(filter),
|
||||
)
|
||||
});
|
||||
|
||||
for id in def_crates
|
||||
.iter()
|
||||
.flat_map(|&id| Crate { id }.transitive_reverse_dependencies(db))
|
||||
.map(|Crate { id }| id)
|
||||
.chain(def_crates.iter().copied())
|
||||
.unique()
|
||||
{
|
||||
all.extend(
|
||||
db.trait_impls_in_crate(id)
|
||||
|
@ -3520,7 +3776,7 @@ pub enum CaptureKind {
|
|||
Move,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct Type {
|
||||
env: Arc<TraitEnvironment>,
|
||||
ty: Ty,
|
||||
|
@ -3620,6 +3876,50 @@ impl Type {
|
|||
matches!(self.ty.kind(Interner), TyKind::Ref(..))
|
||||
}
|
||||
|
||||
pub fn contains_reference(&self, db: &dyn HirDatabase) -> bool {
|
||||
return go(db, self.env.krate, &self.ty);
|
||||
|
||||
fn go(db: &dyn HirDatabase, krate: CrateId, ty: &Ty) -> bool {
|
||||
match ty.kind(Interner) {
|
||||
// Reference itself
|
||||
TyKind::Ref(_, _, _) => true,
|
||||
|
||||
// For non-phantom_data adts we check variants/fields as well as generic parameters
|
||||
TyKind::Adt(adt_id, substitution)
|
||||
if !db.adt_datum(krate, *adt_id).flags.phantom_data =>
|
||||
{
|
||||
let adt_datum = &db.adt_datum(krate, *adt_id);
|
||||
let adt_datum_bound =
|
||||
adt_datum.binders.clone().substitute(Interner, substitution);
|
||||
adt_datum_bound
|
||||
.variants
|
||||
.into_iter()
|
||||
.flat_map(|variant| variant.fields.into_iter())
|
||||
.any(|ty| go(db, krate, &ty))
|
||||
|| substitution
|
||||
.iter(Interner)
|
||||
.filter_map(|x| x.ty(Interner))
|
||||
.any(|ty| go(db, krate, ty))
|
||||
}
|
||||
// And for `PhantomData<T>`, we check `T`.
|
||||
TyKind::Adt(_, substitution)
|
||||
| TyKind::Tuple(_, substitution)
|
||||
| TyKind::OpaqueType(_, substitution)
|
||||
| TyKind::AssociatedType(_, substitution)
|
||||
| TyKind::FnDef(_, substitution) => substitution
|
||||
.iter(Interner)
|
||||
.filter_map(|x| x.ty(Interner))
|
||||
.any(|ty| go(db, krate, ty)),
|
||||
|
||||
// For `[T]` or `*T` we check `T`
|
||||
TyKind::Array(ty, _) | TyKind::Slice(ty) | TyKind::Raw(_, ty) => go(db, krate, ty),
|
||||
|
||||
// Consider everything else as not reference
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_reference(&self) -> Option<(Type, Mutability)> {
|
||||
let (ty, _lt, m) = self.ty.as_reference()?;
|
||||
let m = Mutability::from_mutable(matches!(m, hir_ty::Mutability::Mut));
|
||||
|
@ -3727,14 +4027,16 @@ impl Type {
|
|||
)
|
||||
}
|
||||
|
||||
// FIXME: Find better API that also handles const generics
|
||||
pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool {
|
||||
let mut it = args.iter().map(|t| t.ty.clone());
|
||||
let trait_ref = TyBuilder::trait_ref(db, trait_.id)
|
||||
.push(self.ty.clone())
|
||||
.fill(|x| {
|
||||
let r = it.next().unwrap();
|
||||
match x {
|
||||
ParamKind::Type => r.cast(Interner),
|
||||
ParamKind::Type => {
|
||||
it.next().unwrap_or_else(|| TyKind::Error.intern(Interner)).cast(Interner)
|
||||
}
|
||||
ParamKind::Const(ty) => {
|
||||
// FIXME: this code is not covered in tests.
|
||||
unknown_const_as_generic(ty.clone())
|
||||
|
@ -4368,12 +4670,24 @@ impl Type {
|
|||
|
||||
walk_type(db, self, &mut cb);
|
||||
}
|
||||
|
||||
/// Check if type unifies with another type.
|
||||
///
|
||||
/// Note that we consider placeholder types to unify with everything.
|
||||
/// For example `Option<T>` and `Option<U>` unify although there is unresolved goal `T = U`.
|
||||
pub fn could_unify_with(&self, db: &dyn HirDatabase, other: &Type) -> bool {
|
||||
let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), other.ty.clone()));
|
||||
hir_ty::could_unify(db, self.env.clone(), &tys)
|
||||
}
|
||||
|
||||
/// Check if type unifies with another type eagerly making sure there are no unresolved goals.
|
||||
///
|
||||
/// This means that placeholder types are not considered to unify if there are any bounds set on
|
||||
/// them. For example `Option<T>` and `Option<U>` do not unify as we cannot show that `T = U`
|
||||
pub fn could_unify_with_deeply(&self, db: &dyn HirDatabase, other: &Type) -> bool {
|
||||
let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), other.ty.clone()));
|
||||
hir_ty::could_unify_deeply(db, self.env.clone(), &tys)
|
||||
}
|
||||
|
||||
pub fn could_coerce_to(&self, db: &dyn HirDatabase, to: &Type) -> bool {
|
||||
let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), to.ty.clone()));
|
||||
hir_ty::could_coerce(db, self.env.clone(), &tys)
|
||||
|
|
298
crates/hir/src/term_search.rs
Normal file
298
crates/hir/src/term_search.rs
Normal file
|
@ -0,0 +1,298 @@
|
|||
//! Term search
|
||||
|
||||
use hir_def::type_ref::Mutability;
|
||||
use hir_ty::db::HirDatabase;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::{ModuleDef, ScopeDef, Semantics, SemanticsScope, Type};
|
||||
|
||||
mod expr;
|
||||
pub use expr::Expr;
|
||||
|
||||
mod tactics;
|
||||
|
||||
/// Key for lookup table to query new types reached.
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
enum NewTypesKey {
|
||||
ImplMethod,
|
||||
StructProjection,
|
||||
}
|
||||
|
||||
/// Helper enum to squash big number of alternative trees into `Many` variant as there is too many
|
||||
/// to take into account.
|
||||
#[derive(Debug)]
|
||||
enum AlternativeExprs {
|
||||
/// There are few trees, so we keep track of them all
|
||||
Few(FxHashSet<Expr>),
|
||||
/// There are too many trees to keep track of
|
||||
Many,
|
||||
}
|
||||
|
||||
impl AlternativeExprs {
|
||||
/// Construct alternative trees
|
||||
///
|
||||
/// # Arguments
|
||||
/// `threshold` - threshold value for many trees (more than that is many)
|
||||
/// `exprs` - expressions iterator
|
||||
fn new(threshold: usize, exprs: impl Iterator<Item = Expr>) -> AlternativeExprs {
|
||||
let mut it = AlternativeExprs::Few(Default::default());
|
||||
it.extend_with_threshold(threshold, exprs);
|
||||
it
|
||||
}
|
||||
|
||||
/// Get type trees stored in alternative trees (or `Expr::Many` in case of many)
|
||||
///
|
||||
/// # Arguments
|
||||
/// `ty` - Type of expressions queried (this is used to give type to `Expr::Many`)
|
||||
fn exprs(&self, ty: &Type) -> Vec<Expr> {
|
||||
match self {
|
||||
AlternativeExprs::Few(exprs) => exprs.iter().cloned().collect(),
|
||||
AlternativeExprs::Many => vec![Expr::Many(ty.clone())],
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend alternative expressions
|
||||
///
|
||||
/// # Arguments
|
||||
/// `threshold` - threshold value for many trees (more than that is many)
|
||||
/// `exprs` - expressions iterator
|
||||
fn extend_with_threshold(&mut self, threshold: usize, exprs: impl Iterator<Item = Expr>) {
|
||||
match self {
|
||||
AlternativeExprs::Few(tts) => {
|
||||
for it in exprs {
|
||||
if tts.len() > threshold {
|
||||
*self = AlternativeExprs::Many;
|
||||
break;
|
||||
}
|
||||
|
||||
tts.insert(it);
|
||||
}
|
||||
}
|
||||
AlternativeExprs::Many => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Lookup table for term search
|
||||
///
|
||||
/// Lookup table keeps all the state during term search.
|
||||
/// This means it knows what types and how are reachable.
|
||||
///
|
||||
/// The secondary functionality for lookup table is to keep track of new types reached since last
|
||||
/// iteration as well as keeping track of which `ScopeDef` items have been used.
|
||||
/// Both of them are to speed up the term search by leaving out types / ScopeDefs that likely do
|
||||
/// not produce any new results.
|
||||
#[derive(Default, Debug)]
|
||||
struct LookupTable {
|
||||
/// All the `Expr`s in "value" produce the type of "key"
|
||||
data: FxHashMap<Type, AlternativeExprs>,
|
||||
/// New types reached since last query by the `NewTypesKey`
|
||||
new_types: FxHashMap<NewTypesKey, Vec<Type>>,
|
||||
/// ScopeDefs that are not interesting any more
|
||||
exhausted_scopedefs: FxHashSet<ScopeDef>,
|
||||
/// ScopeDefs that were used in current round
|
||||
round_scopedef_hits: FxHashSet<ScopeDef>,
|
||||
/// Amount of rounds since scopedef was first used.
|
||||
rounds_since_sopedef_hit: FxHashMap<ScopeDef, u32>,
|
||||
/// Types queried but not present
|
||||
types_wishlist: FxHashSet<Type>,
|
||||
/// Threshold to squash trees to `Many`
|
||||
many_threshold: usize,
|
||||
}
|
||||
|
||||
impl LookupTable {
|
||||
/// Initialize lookup table
|
||||
fn new(many_threshold: usize) -> Self {
|
||||
let mut res = Self { many_threshold, ..Default::default() };
|
||||
res.new_types.insert(NewTypesKey::ImplMethod, Vec::new());
|
||||
res.new_types.insert(NewTypesKey::StructProjection, Vec::new());
|
||||
res
|
||||
}
|
||||
|
||||
/// Find all `Expr`s that unify with the `ty`
|
||||
fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
|
||||
self.data
|
||||
.iter()
|
||||
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
|
||||
.map(|(t, tts)| tts.exprs(t))
|
||||
}
|
||||
|
||||
/// Same as find but automatically creates shared reference of types in the lookup
|
||||
///
|
||||
/// For example if we have type `i32` in data and we query for `&i32` it map all the type
|
||||
/// trees we have for `i32` with `Expr::Reference` and returns them.
|
||||
fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
|
||||
self.data
|
||||
.iter()
|
||||
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
|
||||
.map(|(t, it)| it.exprs(t))
|
||||
.or_else(|| {
|
||||
self.data
|
||||
.iter()
|
||||
.find(|(t, _)| {
|
||||
Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, ty)
|
||||
})
|
||||
.map(|(t, it)| {
|
||||
it.exprs(t)
|
||||
.into_iter()
|
||||
.map(|expr| Expr::Reference(Box::new(expr)))
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Insert new type trees for type
|
||||
///
|
||||
/// Note that the types have to be the same, unification is not enough as unification is not
|
||||
/// transitive. For example Vec<i32> and FxHashSet<i32> both unify with Iterator<Item = i32>,
|
||||
/// but they clearly do not unify themselves.
|
||||
fn insert(&mut self, ty: Type, exprs: impl Iterator<Item = Expr>) {
|
||||
match self.data.get_mut(&ty) {
|
||||
Some(it) => it.extend_with_threshold(self.many_threshold, exprs),
|
||||
None => {
|
||||
self.data.insert(ty.clone(), AlternativeExprs::new(self.many_threshold, exprs));
|
||||
for it in self.new_types.values_mut() {
|
||||
it.push(ty.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate all the reachable types
|
||||
fn iter_types(&self) -> impl Iterator<Item = Type> + '_ {
|
||||
self.data.keys().cloned()
|
||||
}
|
||||
|
||||
/// Query new types reached since last query by key
|
||||
///
|
||||
/// Create new key if you wish to query it to avoid conflicting with existing queries.
|
||||
fn new_types(&mut self, key: NewTypesKey) -> Vec<Type> {
|
||||
match self.new_types.get_mut(&key) {
|
||||
Some(it) => std::mem::take(it),
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark `ScopeDef` as exhausted meaning it is not interesting for us any more
|
||||
fn mark_exhausted(&mut self, def: ScopeDef) {
|
||||
self.exhausted_scopedefs.insert(def);
|
||||
}
|
||||
|
||||
/// Mark `ScopeDef` as used meaning we managed to produce something useful from it
|
||||
fn mark_fulfilled(&mut self, def: ScopeDef) {
|
||||
self.round_scopedef_hits.insert(def);
|
||||
}
|
||||
|
||||
/// Start new round (meant to be called at the beginning of iteration in `term_search`)
|
||||
///
|
||||
/// This functions marks some `ScopeDef`s as exhausted if there have been
|
||||
/// `MAX_ROUNDS_AFTER_HIT` rounds after first using a `ScopeDef`.
|
||||
fn new_round(&mut self) {
|
||||
for def in &self.round_scopedef_hits {
|
||||
let hits =
|
||||
self.rounds_since_sopedef_hit.entry(*def).and_modify(|n| *n += 1).or_insert(0);
|
||||
const MAX_ROUNDS_AFTER_HIT: u32 = 2;
|
||||
if *hits > MAX_ROUNDS_AFTER_HIT {
|
||||
self.exhausted_scopedefs.insert(*def);
|
||||
}
|
||||
}
|
||||
self.round_scopedef_hits.clear();
|
||||
}
|
||||
|
||||
/// Get exhausted `ScopeDef`s
|
||||
fn exhausted_scopedefs(&self) -> &FxHashSet<ScopeDef> {
|
||||
&self.exhausted_scopedefs
|
||||
}
|
||||
|
||||
/// Types queried but not found
|
||||
fn take_types_wishlist(&mut self) -> FxHashSet<Type> {
|
||||
std::mem::take(&mut self.types_wishlist)
|
||||
}
|
||||
}
|
||||
|
||||
/// Context for the `term_search` function
|
||||
#[derive(Debug)]
|
||||
pub struct TermSearchCtx<'a, DB: HirDatabase> {
|
||||
/// Semantics for the program
|
||||
pub sema: &'a Semantics<'a, DB>,
|
||||
/// Semantic scope, captures context for the term search
|
||||
pub scope: &'a SemanticsScope<'a>,
|
||||
/// Target / expected output type
|
||||
pub goal: Type,
|
||||
/// Configuration for term search
|
||||
pub config: TermSearchConfig,
|
||||
}
|
||||
|
||||
/// Configuration options for the term search
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TermSearchConfig {
|
||||
/// Enable borrow checking, this guarantees the outputs of the `term_search` to borrow-check
|
||||
pub enable_borrowcheck: bool,
|
||||
/// Indicate when to squash multiple trees to `Many` as there are too many to keep track
|
||||
pub many_alternatives_threshold: usize,
|
||||
/// Depth of the search eg. number of cycles to run
|
||||
pub depth: usize,
|
||||
}
|
||||
|
||||
impl Default for TermSearchConfig {
|
||||
fn default() -> Self {
|
||||
Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 6 }
|
||||
}
|
||||
}
|
||||
|
||||
/// # Term search
|
||||
///
|
||||
/// Search for terms (expressions) that unify with the `goal` type.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - Context for term search
|
||||
///
|
||||
/// Internally this function uses Breadth First Search to find path to `goal` type.
|
||||
/// The general idea is following:
|
||||
/// 1. Populate lookup (frontier for BFS) from values (local variables, statics, constants, etc)
|
||||
/// as well as from well knows values (such as `true/false` and `()`)
|
||||
/// 2. Iteratively expand the frontier (or contents of the lookup) by trying different type
|
||||
/// transformation tactics. For example functions take as from set of types (arguments) to some
|
||||
/// type (return type). Other transformations include methods on type, type constructors and
|
||||
/// projections to struct fields (field access).
|
||||
/// 3. Once we manage to find path to type we are interested in we continue for single round to see
|
||||
/// if we can find more paths that take us to the `goal` type.
|
||||
/// 4. Return all the paths (type trees) that take us to the `goal` type.
|
||||
///
|
||||
/// Note that there are usually more ways we can get to the `goal` type but some are discarded to
|
||||
/// reduce the memory consumption. It is also unlikely anyone is willing ti browse through
|
||||
/// thousands of possible responses so we currently take first 10 from every tactic.
|
||||
pub fn term_search<DB: HirDatabase>(ctx: &TermSearchCtx<'_, DB>) -> Vec<Expr> {
|
||||
let module = ctx.scope.module();
|
||||
let mut defs = FxHashSet::default();
|
||||
defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(module)));
|
||||
|
||||
ctx.scope.process_all_names(&mut |_, def| {
|
||||
defs.insert(def);
|
||||
});
|
||||
|
||||
let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold);
|
||||
|
||||
// Try trivial tactic first, also populates lookup table
|
||||
let mut solutions: Vec<Expr> = tactics::trivial(ctx, &defs, &mut lookup).collect();
|
||||
// Use well known types tactic before iterations as it does not depend on other tactics
|
||||
solutions.extend(tactics::famous_types(ctx, &defs, &mut lookup));
|
||||
|
||||
for _ in 0..ctx.config.depth {
|
||||
lookup.new_round();
|
||||
|
||||
solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup));
|
||||
solutions.extend(tactics::free_function(ctx, &defs, &mut lookup));
|
||||
solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup));
|
||||
solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup));
|
||||
solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup));
|
||||
|
||||
// Discard not interesting `ScopeDef`s for speedup
|
||||
for def in lookup.exhausted_scopedefs() {
|
||||
defs.remove(def);
|
||||
}
|
||||
}
|
||||
|
||||
solutions.into_iter().filter(|it| !it.is_many()).unique().collect()
|
||||
}
|
468
crates/hir/src/term_search/expr.rs
Normal file
468
crates/hir/src/term_search/expr.rs
Normal file
|
@ -0,0 +1,468 @@
|
|||
//! Type tree for term search
|
||||
|
||||
use hir_def::find_path::PrefixKind;
|
||||
use hir_expand::mod_path::ModPath;
|
||||
use hir_ty::{
|
||||
db::HirDatabase,
|
||||
display::{DisplaySourceCodeError, HirDisplay},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
Adt, AsAssocItem, Const, ConstParam, Field, Function, GenericDef, Local, ModuleDef,
|
||||
SemanticsScope, Static, Struct, StructKind, Trait, Type, Variant,
|
||||
};
|
||||
|
||||
/// Helper function to get path to `ModuleDef`
|
||||
fn mod_item_path(
|
||||
sema_scope: &SemanticsScope<'_>,
|
||||
def: &ModuleDef,
|
||||
prefer_no_std: bool,
|
||||
prefer_prelude: bool,
|
||||
) -> Option<ModPath> {
|
||||
let db = sema_scope.db;
|
||||
// Account for locals shadowing items from module
|
||||
let name_hit_count = def.name(db).map(|def_name| {
|
||||
let mut name_hit_count = 0;
|
||||
sema_scope.process_all_names(&mut |name, _| {
|
||||
if name == def_name {
|
||||
name_hit_count += 1;
|
||||
}
|
||||
});
|
||||
name_hit_count
|
||||
});
|
||||
|
||||
let m = sema_scope.module();
|
||||
match name_hit_count {
|
||||
Some(0..=1) | None => m.find_use_path(db.upcast(), *def, prefer_no_std, prefer_prelude),
|
||||
Some(_) => m.find_use_path_prefixed(
|
||||
db.upcast(),
|
||||
*def,
|
||||
PrefixKind::ByCrate,
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to get path to `ModuleDef` as string
|
||||
fn mod_item_path_str(
|
||||
sema_scope: &SemanticsScope<'_>,
|
||||
def: &ModuleDef,
|
||||
prefer_no_std: bool,
|
||||
prefer_prelude: bool,
|
||||
) -> Result<String, DisplaySourceCodeError> {
|
||||
let path = mod_item_path(sema_scope, def, prefer_no_std, prefer_prelude);
|
||||
path.map(|it| it.display(sema_scope.db.upcast()).to_string())
|
||||
.ok_or(DisplaySourceCodeError::PathNotFound)
|
||||
}
|
||||
|
||||
/// Helper function to get path to `Type`
|
||||
fn type_path(
|
||||
sema_scope: &SemanticsScope<'_>,
|
||||
ty: &Type,
|
||||
prefer_no_std: bool,
|
||||
prefer_prelude: bool,
|
||||
) -> Result<String, DisplaySourceCodeError> {
|
||||
let db = sema_scope.db;
|
||||
let m = sema_scope.module();
|
||||
|
||||
match ty.as_adt() {
|
||||
Some(adt) => {
|
||||
let ty_name = ty.display_source_code(db, m.id, true)?;
|
||||
|
||||
let mut path =
|
||||
mod_item_path(sema_scope, &ModuleDef::Adt(adt), prefer_no_std, prefer_prelude)
|
||||
.unwrap();
|
||||
path.pop_segment();
|
||||
let path = path.display(db.upcast()).to_string();
|
||||
let res = match path.is_empty() {
|
||||
true => ty_name,
|
||||
false => format!("{path}::{ty_name}"),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
None => ty.display_source_code(db, m.id, true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to filter out generic parameters that are default
|
||||
fn non_default_generics(db: &dyn HirDatabase, def: GenericDef, generics: &[Type]) -> Vec<Type> {
|
||||
def.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.filter_map(|it| it.as_type_param(db))
|
||||
.zip(generics)
|
||||
.filter(|(tp, arg)| tp.default(db).as_ref() != Some(arg))
|
||||
.map(|(_, arg)| arg.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Type tree shows how can we get from set of types to some type.
|
||||
///
|
||||
/// Consider the following code as an example
|
||||
/// ```
|
||||
/// fn foo(x: i32, y: bool) -> Option<i32> { None }
|
||||
/// fn bar() {
|
||||
/// let a = 1;
|
||||
/// let b = true;
|
||||
/// let c: Option<i32> = _;
|
||||
/// }
|
||||
/// ```
|
||||
/// If we generate type tree in the place of `_` we get
|
||||
/// ```txt
|
||||
/// Option<i32>
|
||||
/// |
|
||||
/// foo(i32, bool)
|
||||
/// / \
|
||||
/// a: i32 b: bool
|
||||
/// ```
|
||||
/// So in short it pretty much gives us a way to get type `Option<i32>` using the items we have in
|
||||
/// scope.
|
||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||
pub enum Expr {
|
||||
/// Constant
|
||||
Const(Const),
|
||||
/// Static variable
|
||||
Static(Static),
|
||||
/// Local variable
|
||||
Local(Local),
|
||||
/// Constant generic parameter
|
||||
ConstParam(ConstParam),
|
||||
/// Well known type (such as `true` for bool)
|
||||
FamousType { ty: Type, value: &'static str },
|
||||
/// Function call (does not take self param)
|
||||
Function { func: Function, generics: Vec<Type>, params: Vec<Expr> },
|
||||
/// Method call (has self param)
|
||||
Method { func: Function, generics: Vec<Type>, target: Box<Expr>, params: Vec<Expr> },
|
||||
/// Enum variant construction
|
||||
Variant { variant: Variant, generics: Vec<Type>, params: Vec<Expr> },
|
||||
/// Struct construction
|
||||
Struct { strukt: Struct, generics: Vec<Type>, params: Vec<Expr> },
|
||||
/// Struct field access
|
||||
Field { expr: Box<Expr>, field: Field },
|
||||
/// Passing type as reference (with `&`)
|
||||
Reference(Box<Expr>),
|
||||
/// Indicates possibility of many different options that all evaluate to `ty`
|
||||
Many(Type),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
/// Generate source code for type tree.
|
||||
///
|
||||
/// Note that trait imports are not added to generated code.
|
||||
/// To make sure that the code is valid, callee has to also ensure that all the traits listed
|
||||
/// by `traits_used` method are also imported.
|
||||
pub fn gen_source_code(
|
||||
&self,
|
||||
sema_scope: &SemanticsScope<'_>,
|
||||
many_formatter: &mut dyn FnMut(&Type) -> String,
|
||||
prefer_no_std: bool,
|
||||
prefer_prelude: bool,
|
||||
) -> Result<String, DisplaySourceCodeError> {
|
||||
let db = sema_scope.db;
|
||||
let mod_item_path_str = |s, def| mod_item_path_str(s, def, prefer_no_std, prefer_prelude);
|
||||
match self {
|
||||
Expr::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)),
|
||||
Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)),
|
||||
Expr::Local(it) => Ok(it.name(db).display(db.upcast()).to_string()),
|
||||
Expr::ConstParam(it) => Ok(it.name(db).display(db.upcast()).to_string()),
|
||||
Expr::FamousType { value, .. } => Ok(value.to_string()),
|
||||
Expr::Function { func, params, .. } => {
|
||||
let args = params
|
||||
.iter()
|
||||
.map(|f| {
|
||||
f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
|
||||
match func.as_assoc_item(db).map(|it| it.container(db)) {
|
||||
Some(container) => {
|
||||
let container_name = match container {
|
||||
crate::AssocItemContainer::Trait(trait_) => {
|
||||
mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))?
|
||||
}
|
||||
crate::AssocItemContainer::Impl(imp) => {
|
||||
let self_ty = imp.self_ty(db);
|
||||
// Should it be guaranteed that `mod_item_path` always exists?
|
||||
match self_ty.as_adt().and_then(|adt| {
|
||||
mod_item_path(
|
||||
sema_scope,
|
||||
&adt.into(),
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
)
|
||||
}) {
|
||||
Some(path) => path.display(sema_scope.db.upcast()).to_string(),
|
||||
None => self_ty.display(db).to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
let fn_name = func.name(db).display(db.upcast()).to_string();
|
||||
Ok(format!("{container_name}::{fn_name}({args})"))
|
||||
}
|
||||
None => {
|
||||
let fn_name = mod_item_path_str(sema_scope, &ModuleDef::Function(*func))?;
|
||||
Ok(format!("{fn_name}({args})"))
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Method { func, target, params, .. } => {
|
||||
if target.contains_many_in_illegal_pos() {
|
||||
return Ok(many_formatter(&target.ty(db)));
|
||||
}
|
||||
|
||||
let func_name = func.name(db).display(db.upcast()).to_string();
|
||||
let self_param = func.self_param(db).unwrap();
|
||||
let target = target.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
)?;
|
||||
let args = params
|
||||
.iter()
|
||||
.map(|f| {
|
||||
f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
|
||||
match func.as_assoc_item(db).and_then(|it| it.container_or_implemented_trait(db)) {
|
||||
Some(trait_) => {
|
||||
let trait_name = mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))?;
|
||||
let target = match self_param.access(db) {
|
||||
crate::Access::Shared => format!("&{target}"),
|
||||
crate::Access::Exclusive => format!("&mut {target}"),
|
||||
crate::Access::Owned => target,
|
||||
};
|
||||
let res = match args.is_empty() {
|
||||
true => format!("{trait_name}::{func_name}({target})",),
|
||||
false => format!("{trait_name}::{func_name}({target}, {args})",),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
None => Ok(format!("{target}.{func_name}({args})")),
|
||||
}
|
||||
}
|
||||
Expr::Variant { variant, generics, params } => {
|
||||
let generics = non_default_generics(db, (*variant).into(), generics);
|
||||
let generics_str = match generics.is_empty() {
|
||||
true => String::new(),
|
||||
false => {
|
||||
let generics = generics
|
||||
.iter()
|
||||
.map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude))
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("::<{generics}>")
|
||||
}
|
||||
};
|
||||
let inner = match variant.kind(db) {
|
||||
StructKind::Tuple => {
|
||||
let args = params
|
||||
.iter()
|
||||
.map(|f| {
|
||||
f.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("{generics_str}({args})")
|
||||
}
|
||||
StructKind::Record => {
|
||||
let fields = variant.fields(db);
|
||||
let args = params
|
||||
.iter()
|
||||
.zip(fields.iter())
|
||||
.map(|(a, f)| {
|
||||
let tmp = format!(
|
||||
"{}: {}",
|
||||
f.name(db).display(db.upcast()),
|
||||
a.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude
|
||||
)?
|
||||
);
|
||||
Ok(tmp)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("{generics_str}{{ {args} }}")
|
||||
}
|
||||
StructKind::Unit => generics_str,
|
||||
};
|
||||
|
||||
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant))?;
|
||||
Ok(format!("{prefix}{inner}"))
|
||||
}
|
||||
Expr::Struct { strukt, generics, params } => {
|
||||
let generics = non_default_generics(db, (*strukt).into(), generics);
|
||||
let inner = match strukt.kind(db) {
|
||||
StructKind::Tuple => {
|
||||
let args = params
|
||||
.iter()
|
||||
.map(|a| {
|
||||
a.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("({args})")
|
||||
}
|
||||
StructKind::Record => {
|
||||
let fields = strukt.fields(db);
|
||||
let args = params
|
||||
.iter()
|
||||
.zip(fields.iter())
|
||||
.map(|(a, f)| {
|
||||
let tmp = format!(
|
||||
"{}: {}",
|
||||
f.name(db).display(db.upcast()),
|
||||
a.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude
|
||||
)?
|
||||
);
|
||||
Ok(tmp)
|
||||
})
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!(" {{ {args} }}")
|
||||
}
|
||||
StructKind::Unit => match generics.is_empty() {
|
||||
true => String::new(),
|
||||
false => {
|
||||
let generics = generics
|
||||
.iter()
|
||||
.map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude))
|
||||
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
|
||||
.into_iter()
|
||||
.join(", ");
|
||||
format!("::<{generics}>")
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)))?;
|
||||
Ok(format!("{prefix}{inner}"))
|
||||
}
|
||||
Expr::Field { expr, field } => {
|
||||
if expr.contains_many_in_illegal_pos() {
|
||||
return Ok(many_formatter(&expr.ty(db)));
|
||||
}
|
||||
|
||||
let strukt = expr.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
)?;
|
||||
let field = field.name(db).display(db.upcast()).to_string();
|
||||
Ok(format!("{strukt}.{field}"))
|
||||
}
|
||||
Expr::Reference(expr) => {
|
||||
if expr.contains_many_in_illegal_pos() {
|
||||
return Ok(many_formatter(&expr.ty(db)));
|
||||
}
|
||||
|
||||
let inner = expr.gen_source_code(
|
||||
sema_scope,
|
||||
many_formatter,
|
||||
prefer_no_std,
|
||||
prefer_prelude,
|
||||
)?;
|
||||
Ok(format!("&{inner}"))
|
||||
}
|
||||
Expr::Many(ty) => Ok(many_formatter(ty)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get type of the type tree.
|
||||
///
|
||||
/// Same as getting the type of root node
|
||||
pub fn ty(&self, db: &dyn HirDatabase) -> Type {
|
||||
match self {
|
||||
Expr::Const(it) => it.ty(db),
|
||||
Expr::Static(it) => it.ty(db),
|
||||
Expr::Local(it) => it.ty(db),
|
||||
Expr::ConstParam(it) => it.ty(db),
|
||||
Expr::FamousType { ty, .. } => ty.clone(),
|
||||
Expr::Function { func, generics, .. } => {
|
||||
func.ret_type_with_args(db, generics.iter().cloned())
|
||||
}
|
||||
Expr::Method { func, generics, target, .. } => func.ret_type_with_args(
|
||||
db,
|
||||
target.ty(db).type_arguments().chain(generics.iter().cloned()),
|
||||
),
|
||||
Expr::Variant { variant, generics, .. } => {
|
||||
Adt::from(variant.parent_enum(db)).ty_with_args(db, generics.iter().cloned())
|
||||
}
|
||||
Expr::Struct { strukt, generics, .. } => {
|
||||
Adt::from(*strukt).ty_with_args(db, generics.iter().cloned())
|
||||
}
|
||||
Expr::Field { expr, field } => field.ty_with_args(db, expr.ty(db).type_arguments()),
|
||||
Expr::Reference(it) => it.ty(db),
|
||||
Expr::Many(ty) => ty.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// List the traits used in type tree
|
||||
pub fn traits_used(&self, db: &dyn HirDatabase) -> Vec<Trait> {
|
||||
let mut res = Vec::new();
|
||||
|
||||
if let Expr::Method { func, params, .. } = self {
|
||||
res.extend(params.iter().flat_map(|it| it.traits_used(db)));
|
||||
if let Some(it) = func.as_assoc_item(db) {
|
||||
if let Some(it) = it.container_or_implemented_trait(db) {
|
||||
res.push(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Check in the tree contains `Expr::Many` variant in illegal place to insert `todo`,
|
||||
/// `unimplemented` or similar macro
|
||||
///
|
||||
/// Some examples are following
|
||||
/// ```no_compile
|
||||
/// macro!().foo
|
||||
/// macro!().bar()
|
||||
/// ¯o!()
|
||||
/// ```
|
||||
fn contains_many_in_illegal_pos(&self) -> bool {
|
||||
match self {
|
||||
Expr::Method { target, .. } => target.contains_many_in_illegal_pos(),
|
||||
Expr::Field { expr, .. } => expr.contains_many_in_illegal_pos(),
|
||||
Expr::Reference(target) => target.is_many(),
|
||||
Expr::Many(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to check if outermost type tree is `Expr::Many` variant
|
||||
pub fn is_many(&self) -> bool {
|
||||
matches!(self, Expr::Many(_))
|
||||
}
|
||||
}
|
859
crates/hir/src/term_search/tactics.rs
Normal file
859
crates/hir/src/term_search/tactics.rs
Normal file
|
@ -0,0 +1,859 @@
|
|||
//! Tactics for term search
|
||||
//!
|
||||
//! All the tactics take following arguments
|
||||
//! * `ctx` - Context for the term search
|
||||
//! * `defs` - Set of items in scope at term search target location
|
||||
//! * `lookup` - Lookup table for types
|
||||
//! And they return iterator that yields type trees that unify with the `goal` type.
|
||||
|
||||
use std::iter;
|
||||
|
||||
use hir_ty::db::HirDatabase;
|
||||
use hir_ty::mir::BorrowKind;
|
||||
use hir_ty::TyBuilder;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{
|
||||
Adt, AssocItem, Enum, GenericDef, GenericParam, HasVisibility, Impl, ModuleDef, ScopeDef, Type,
|
||||
TypeParam, Variant,
|
||||
};
|
||||
|
||||
use crate::term_search::{Expr, TermSearchConfig};
|
||||
|
||||
use super::{LookupTable, NewTypesKey, TermSearchCtx};
|
||||
|
||||
/// # Trivial tactic
|
||||
///
|
||||
/// Attempts to fulfill the goal by trying items in scope
|
||||
/// Also works as a starting point to move all items in scope to lookup table.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - Context for the term search
|
||||
/// * `defs` - Set of items in scope at term search target location
|
||||
/// * `lookup` - Lookup table for types
|
||||
///
|
||||
/// Returns iterator that yields elements that unify with `goal`.
|
||||
///
|
||||
/// _Note that there is no use of calling this tactic in every iteration as the output does not
|
||||
/// depend on the current state of `lookup`_
|
||||
pub(super) fn trivial<'a, DB: HirDatabase>(
|
||||
ctx: &'a TermSearchCtx<'a, DB>,
|
||||
defs: &'a FxHashSet<ScopeDef>,
|
||||
lookup: &'a mut LookupTable,
|
||||
) -> impl Iterator<Item = Expr> + 'a {
|
||||
let db = ctx.sema.db;
|
||||
defs.iter().filter_map(|def| {
|
||||
let expr = match def {
|
||||
ScopeDef::ModuleDef(ModuleDef::Const(it)) => Some(Expr::Const(*it)),
|
||||
ScopeDef::ModuleDef(ModuleDef::Static(it)) => Some(Expr::Static(*it)),
|
||||
ScopeDef::GenericParam(GenericParam::ConstParam(it)) => Some(Expr::ConstParam(*it)),
|
||||
ScopeDef::Local(it) => {
|
||||
if ctx.config.enable_borrowcheck {
|
||||
let borrowck = db.borrowck(it.parent).ok()?;
|
||||
|
||||
let invalid = borrowck.iter().any(|b| {
|
||||
b.partially_moved.iter().any(|moved| {
|
||||
Some(&moved.local) == b.mir_body.binding_locals.get(it.binding_id)
|
||||
}) || b.borrow_regions.iter().any(|region| {
|
||||
// Shared borrows are fine
|
||||
Some(®ion.local) == b.mir_body.binding_locals.get(it.binding_id)
|
||||
&& region.kind != BorrowKind::Shared
|
||||
})
|
||||
});
|
||||
|
||||
if invalid {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(Expr::Local(*it))
|
||||
}
|
||||
_ => None,
|
||||
}?;
|
||||
|
||||
lookup.mark_exhausted(*def);
|
||||
|
||||
let ty = expr.ty(db);
|
||||
lookup.insert(ty.clone(), std::iter::once(expr.clone()));
|
||||
|
||||
// Don't suggest local references as they are not valid for return
|
||||
if matches!(expr, Expr::Local(_)) && ty.contains_reference(db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
ty.could_unify_with_deeply(db, &ctx.goal).then_some(expr)
|
||||
})
|
||||
}
|
||||
|
||||
/// # Type constructor tactic
|
||||
///
|
||||
/// Attempts different type constructors for enums and structs in scope
|
||||
///
|
||||
/// Updates lookup by new types reached and returns iterator that yields
|
||||
/// elements that unify with `goal`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - Context for the term search
|
||||
/// * `defs` - Set of items in scope at term search target location
|
||||
/// * `lookup` - Lookup table for types
|
||||
pub(super) fn type_constructor<'a, DB: HirDatabase>(
|
||||
ctx: &'a TermSearchCtx<'a, DB>,
|
||||
defs: &'a FxHashSet<ScopeDef>,
|
||||
lookup: &'a mut LookupTable,
|
||||
) -> impl Iterator<Item = Expr> + 'a {
|
||||
let db = ctx.sema.db;
|
||||
let module = ctx.scope.module();
|
||||
fn variant_helper(
|
||||
db: &dyn HirDatabase,
|
||||
lookup: &mut LookupTable,
|
||||
parent_enum: Enum,
|
||||
variant: Variant,
|
||||
goal: &Type,
|
||||
config: &TermSearchConfig,
|
||||
) -> Vec<(Type, Vec<Expr>)> {
|
||||
// Ignore unstable
|
||||
if variant.is_unstable(db) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let generics = GenericDef::from(variant.parent_enum(db));
|
||||
let Some(type_params) = generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()
|
||||
else {
|
||||
// Ignore enums with const generics
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
// We currently do not check lifetime bounds so ignore all types that have something to do
|
||||
// with them
|
||||
if !generics.lifetime_params(db).is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let non_default_type_params_len =
|
||||
type_params.iter().filter(|it| it.default(db).is_none()).count();
|
||||
|
||||
let generic_params = lookup
|
||||
.iter_types()
|
||||
.collect::<Vec<_>>() // Force take ownership
|
||||
.into_iter()
|
||||
.permutations(non_default_type_params_len);
|
||||
|
||||
generic_params
|
||||
.filter_map(move |generics| {
|
||||
// Insert default type params
|
||||
let mut g = generics.into_iter();
|
||||
let generics: Vec<_> = type_params
|
||||
.iter()
|
||||
.map(|it| it.default(db).unwrap_or_else(|| g.next().expect("No generic")))
|
||||
.collect();
|
||||
|
||||
let enum_ty = Adt::from(parent_enum).ty_with_args(db, generics.iter().cloned());
|
||||
|
||||
// Allow types with generics only if they take us straight to goal for
|
||||
// performance reasons
|
||||
if !generics.is_empty() && !enum_ty.could_unify_with_deeply(db, goal) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ignore types that have something to do with lifetimes
|
||||
if config.enable_borrowcheck && enum_ty.contains_reference(db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Early exit if some param cannot be filled from lookup
|
||||
let param_exprs: Vec<Vec<Expr>> = variant
|
||||
.fields(db)
|
||||
.into_iter()
|
||||
.map(|field| lookup.find(db, &field.ty_with_args(db, generics.iter().cloned())))
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
// Note that we need special case for 0 param constructors because of multi cartesian
|
||||
// product
|
||||
let variant_exprs: Vec<Expr> = if param_exprs.is_empty() {
|
||||
vec![Expr::Variant { variant, generics: generics.clone(), params: Vec::new() }]
|
||||
} else {
|
||||
param_exprs
|
||||
.into_iter()
|
||||
.multi_cartesian_product()
|
||||
.map(|params| Expr::Variant { variant, generics: generics.clone(), params })
|
||||
.collect()
|
||||
};
|
||||
lookup.insert(enum_ty.clone(), variant_exprs.iter().cloned());
|
||||
|
||||
Some((enum_ty, variant_exprs))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
defs.iter()
|
||||
.filter_map(move |def| match def {
|
||||
ScopeDef::ModuleDef(ModuleDef::Variant(it)) => {
|
||||
let variant_exprs =
|
||||
variant_helper(db, lookup, it.parent_enum(db), *it, &ctx.goal, &ctx.config);
|
||||
if variant_exprs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Variant(*it)));
|
||||
Some(variant_exprs)
|
||||
}
|
||||
ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(enum_))) => {
|
||||
let exprs: Vec<(Type, Vec<Expr>)> = enum_
|
||||
.variants(db)
|
||||
.into_iter()
|
||||
.flat_map(|it| variant_helper(db, lookup, *enum_, it, &ctx.goal, &ctx.config))
|
||||
.collect();
|
||||
|
||||
if !exprs.is_empty() {
|
||||
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(*enum_))));
|
||||
}
|
||||
|
||||
Some(exprs)
|
||||
}
|
||||
ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(it))) => {
|
||||
// Ignore unstable and not visible
|
||||
if it.is_unstable(db) || !it.is_visible_from(db, module) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let generics = GenericDef::from(*it);
|
||||
|
||||
// Ignore const params for now
|
||||
let type_params = generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// We currently do not check lifetime bounds so ignore all types that have something to do
|
||||
// with them
|
||||
if !generics.lifetime_params(db).is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let non_default_type_params_len =
|
||||
type_params.iter().filter(|it| it.default(db).is_none()).count();
|
||||
|
||||
let generic_params = lookup
|
||||
.iter_types()
|
||||
.collect::<Vec<_>>() // Force take ownership
|
||||
.into_iter()
|
||||
.permutations(non_default_type_params_len);
|
||||
|
||||
let exprs = generic_params
|
||||
.filter_map(|generics| {
|
||||
// Insert default type params
|
||||
let mut g = generics.into_iter();
|
||||
let generics: Vec<_> = type_params
|
||||
.iter()
|
||||
.map(|it| {
|
||||
it.default(db)
|
||||
.unwrap_or_else(|| g.next().expect("Missing type param"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let struct_ty = Adt::from(*it).ty_with_args(db, generics.iter().cloned());
|
||||
|
||||
// Allow types with generics only if they take us straight to goal for
|
||||
// performance reasons
|
||||
if non_default_type_params_len != 0
|
||||
&& struct_ty.could_unify_with_deeply(db, &ctx.goal)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ignore types that have something to do with lifetimes
|
||||
if ctx.config.enable_borrowcheck && struct_ty.contains_reference(db) {
|
||||
return None;
|
||||
}
|
||||
let fields = it.fields(db);
|
||||
// Check if all fields are visible, otherwise we cannot fill them
|
||||
if fields.iter().any(|it| !it.is_visible_from(db, module)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Early exit if some param cannot be filled from lookup
|
||||
let param_exprs: Vec<Vec<Expr>> = fields
|
||||
.into_iter()
|
||||
.map(|field| lookup.find(db, &field.ty(db)))
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
// Note that we need special case for 0 param constructors because of multi cartesian
|
||||
// product
|
||||
let struct_exprs: Vec<Expr> = if param_exprs.is_empty() {
|
||||
vec![Expr::Struct { strukt: *it, generics, params: Vec::new() }]
|
||||
} else {
|
||||
param_exprs
|
||||
.into_iter()
|
||||
.multi_cartesian_product()
|
||||
.map(|params| Expr::Struct {
|
||||
strukt: *it,
|
||||
generics: generics.clone(),
|
||||
params,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
lookup
|
||||
.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(*it))));
|
||||
lookup.insert(struct_ty.clone(), struct_exprs.iter().cloned());
|
||||
|
||||
Some((struct_ty, struct_exprs))
|
||||
})
|
||||
.collect();
|
||||
Some(exprs)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// # Free function tactic
|
||||
///
|
||||
/// Attempts to call different functions in scope with parameters from lookup table.
|
||||
/// Functions that include generics are not used for performance reasons.
|
||||
///
|
||||
/// Updates lookup by new types reached and returns iterator that yields
|
||||
/// elements that unify with `goal`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - Context for the term search
|
||||
/// * `defs` - Set of items in scope at term search target location
|
||||
/// * `lookup` - Lookup table for types
|
||||
pub(super) fn free_function<'a, DB: HirDatabase>(
|
||||
ctx: &'a TermSearchCtx<'a, DB>,
|
||||
defs: &'a FxHashSet<ScopeDef>,
|
||||
lookup: &'a mut LookupTable,
|
||||
) -> impl Iterator<Item = Expr> + 'a {
|
||||
let db = ctx.sema.db;
|
||||
let module = ctx.scope.module();
|
||||
defs.iter()
|
||||
.filter_map(move |def| match def {
|
||||
ScopeDef::ModuleDef(ModuleDef::Function(it)) => {
|
||||
let generics = GenericDef::from(*it);
|
||||
|
||||
// Ignore const params for now
|
||||
let type_params = generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore lifetimes as we do not check them
|
||||
if !generics.lifetime_params(db).is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let non_default_type_params_len =
|
||||
type_params.iter().filter(|it| it.default(db).is_none()).count();
|
||||
|
||||
// Ignore bigger number of generics for now as they kill the performance
|
||||
if non_default_type_params_len > 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let generic_params = lookup
|
||||
.iter_types()
|
||||
.collect::<Vec<_>>() // Force take ownership
|
||||
.into_iter()
|
||||
.permutations(non_default_type_params_len);
|
||||
|
||||
let exprs: Vec<_> = generic_params
|
||||
.filter_map(|generics| {
|
||||
// Insert default type params
|
||||
let mut g = generics.into_iter();
|
||||
let generics: Vec<_> = type_params
|
||||
.iter()
|
||||
.map(|it| match it.default(db) {
|
||||
Some(ty) => Some(ty),
|
||||
None => {
|
||||
let generic = g.next().expect("Missing type param");
|
||||
// Filter out generics that do not unify due to trait bounds
|
||||
it.ty(db).could_unify_with(db, &generic).then_some(generic)
|
||||
}
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
let ret_ty = it.ret_type_with_args(db, generics.iter().cloned());
|
||||
// Filter out private and unsafe functions
|
||||
if !it.is_visible_from(db, module)
|
||||
|| it.is_unsafe_to_call(db)
|
||||
|| it.is_unstable(db)
|
||||
|| ctx.config.enable_borrowcheck && ret_ty.contains_reference(db)
|
||||
|| ret_ty.is_raw_ptr()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Early exit if some param cannot be filled from lookup
|
||||
let param_exprs: Vec<Vec<Expr>> = it
|
||||
.params_without_self_with_args(db, generics.iter().cloned())
|
||||
.into_iter()
|
||||
.map(|field| {
|
||||
let ty = field.ty();
|
||||
match ty.is_mutable_reference() {
|
||||
true => None,
|
||||
false => lookup.find_autoref(db, ty),
|
||||
}
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
// Note that we need special case for 0 param constructors because of multi cartesian
|
||||
// product
|
||||
let fn_exprs: Vec<Expr> = if param_exprs.is_empty() {
|
||||
vec![Expr::Function { func: *it, generics, params: Vec::new() }]
|
||||
} else {
|
||||
param_exprs
|
||||
.into_iter()
|
||||
.multi_cartesian_product()
|
||||
.map(|params| Expr::Function {
|
||||
func: *it,
|
||||
generics: generics.clone(),
|
||||
|
||||
params,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Function(*it)));
|
||||
lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned());
|
||||
Some((ret_ty, fn_exprs))
|
||||
})
|
||||
.collect();
|
||||
Some(exprs)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// # Impl method tactic
|
||||
///
|
||||
/// Attempts to to call methods on types from lookup table.
|
||||
/// This includes both functions from direct impl blocks as well as functions from traits.
|
||||
/// Methods defined in impl blocks that are generic and methods that are themselves have
|
||||
/// generics are ignored for performance reasons.
|
||||
///
|
||||
/// Updates lookup by new types reached and returns iterator that yields
|
||||
/// elements that unify with `goal`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - Context for the term search
|
||||
/// * `defs` - Set of items in scope at term search target location
|
||||
/// * `lookup` - Lookup table for types
|
||||
pub(super) fn impl_method<'a, DB: HirDatabase>(
|
||||
ctx: &'a TermSearchCtx<'a, DB>,
|
||||
_defs: &'a FxHashSet<ScopeDef>,
|
||||
lookup: &'a mut LookupTable,
|
||||
) -> impl Iterator<Item = Expr> + 'a {
|
||||
let db = ctx.sema.db;
|
||||
let module = ctx.scope.module();
|
||||
lookup
|
||||
.new_types(NewTypesKey::ImplMethod)
|
||||
.into_iter()
|
||||
.flat_map(|ty| {
|
||||
Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp))
|
||||
})
|
||||
.flat_map(|(ty, imp)| imp.items(db).into_iter().map(move |item| (imp, ty.clone(), item)))
|
||||
.filter_map(|(imp, ty, it)| match it {
|
||||
AssocItem::Function(f) => Some((imp, ty, f)),
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(move |(imp, ty, it)| {
|
||||
let fn_generics = GenericDef::from(it);
|
||||
let imp_generics = GenericDef::from(imp);
|
||||
|
||||
// Ignore const params for now
|
||||
let imp_type_params = imp_generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore const params for now
|
||||
let fn_type_params = fn_generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore all functions that have something to do with lifetimes as we don't check them
|
||||
if !fn_generics.lifetime_params(db).is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ignore functions without self param
|
||||
if !it.has_self_param(db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Filter out private and unsafe functions
|
||||
if !it.is_visible_from(db, module) || it.is_unsafe_to_call(db) || it.is_unstable(db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if imp_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
|
||||
|| fn_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let non_default_type_params_len = imp_type_params
|
||||
.iter()
|
||||
.chain(fn_type_params.iter())
|
||||
.filter(|it| it.default(db).is_none())
|
||||
.count();
|
||||
|
||||
// Ignore bigger number of generics for now as they kill the performance
|
||||
if non_default_type_params_len > 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let generic_params = lookup
|
||||
.iter_types()
|
||||
.collect::<Vec<_>>() // Force take ownership
|
||||
.into_iter()
|
||||
.permutations(non_default_type_params_len);
|
||||
|
||||
let exprs: Vec<_> = generic_params
|
||||
.filter_map(|generics| {
|
||||
// Insert default type params
|
||||
let mut g = generics.into_iter();
|
||||
let generics: Vec<_> = imp_type_params
|
||||
.iter()
|
||||
.chain(fn_type_params.iter())
|
||||
.map(|it| match it.default(db) {
|
||||
Some(ty) => Some(ty),
|
||||
None => {
|
||||
let generic = g.next().expect("Missing type param");
|
||||
// Filter out generics that do not unify due to trait bounds
|
||||
it.ty(db).could_unify_with(db, &generic).then_some(generic)
|
||||
}
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
let ret_ty = it.ret_type_with_args(
|
||||
db,
|
||||
ty.type_arguments().chain(generics.iter().cloned()),
|
||||
);
|
||||
// Filter out functions that return references
|
||||
if ctx.config.enable_borrowcheck && ret_ty.contains_reference(db)
|
||||
|| ret_ty.is_raw_ptr()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ignore functions that do not change the type
|
||||
if ty.could_unify_with_deeply(db, &ret_ty) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let self_ty = it
|
||||
.self_param(db)
|
||||
.expect("No self param")
|
||||
.ty_with_args(db, ty.type_arguments().chain(generics.iter().cloned()));
|
||||
|
||||
// Ignore functions that have different self type
|
||||
if !self_ty.autoderef(db).any(|s_ty| ty == s_ty) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target_type_exprs = lookup.find(db, &ty).expect("Type not in lookup");
|
||||
|
||||
// Early exit if some param cannot be filled from lookup
|
||||
let param_exprs: Vec<Vec<Expr>> = it
|
||||
.params_without_self_with_args(
|
||||
db,
|
||||
ty.type_arguments().chain(generics.iter().cloned()),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|field| lookup.find_autoref(db, field.ty()))
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
let fn_exprs: Vec<Expr> = std::iter::once(target_type_exprs)
|
||||
.chain(param_exprs)
|
||||
.multi_cartesian_product()
|
||||
.map(|params| {
|
||||
let mut params = params.into_iter();
|
||||
let target = Box::new(params.next().unwrap());
|
||||
Expr::Method {
|
||||
func: it,
|
||||
generics: generics.clone(),
|
||||
target,
|
||||
params: params.collect(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned());
|
||||
Some((ret_ty, fn_exprs))
|
||||
})
|
||||
.collect();
|
||||
Some(exprs)
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// # Struct projection tactic
|
||||
///
|
||||
/// Attempts different struct fields (`foo.bar.baz`)
|
||||
///
|
||||
/// Updates lookup by new types reached and returns iterator that yields
|
||||
/// elements that unify with `goal`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - Context for the term search
|
||||
/// * `defs` - Set of items in scope at term search target location
|
||||
/// * `lookup` - Lookup table for types
|
||||
pub(super) fn struct_projection<'a, DB: HirDatabase>(
|
||||
ctx: &'a TermSearchCtx<'a, DB>,
|
||||
_defs: &'a FxHashSet<ScopeDef>,
|
||||
lookup: &'a mut LookupTable,
|
||||
) -> impl Iterator<Item = Expr> + 'a {
|
||||
let db = ctx.sema.db;
|
||||
let module = ctx.scope.module();
|
||||
lookup
|
||||
.new_types(NewTypesKey::StructProjection)
|
||||
.into_iter()
|
||||
.map(|ty| (ty.clone(), lookup.find(db, &ty).expect("Expr not in lookup")))
|
||||
.flat_map(move |(ty, targets)| {
|
||||
ty.fields(db).into_iter().filter_map(move |(field, filed_ty)| {
|
||||
if !field.is_visible_from(db, module) {
|
||||
return None;
|
||||
}
|
||||
let exprs = targets
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(move |target| Expr::Field { field, expr: Box::new(target) });
|
||||
Some((filed_ty, exprs))
|
||||
})
|
||||
})
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// # Famous types tactic
|
||||
///
|
||||
/// Attempts different values of well known types such as `true` or `false`.
|
||||
///
|
||||
/// Updates lookup by new types reached and returns iterator that yields
|
||||
/// elements that unify with `goal`.
|
||||
///
|
||||
/// _Note that there is no point of calling it iteratively as the output is always the same_
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - Context for the term search
|
||||
/// * `defs` - Set of items in scope at term search target location
|
||||
/// * `lookup` - Lookup table for types
|
||||
pub(super) fn famous_types<'a, DB: HirDatabase>(
|
||||
ctx: &'a TermSearchCtx<'a, DB>,
|
||||
_defs: &'a FxHashSet<ScopeDef>,
|
||||
lookup: &'a mut LookupTable,
|
||||
) -> impl Iterator<Item = Expr> + 'a {
|
||||
let db = ctx.sema.db;
|
||||
let module = ctx.scope.module();
|
||||
[
|
||||
Expr::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "true" },
|
||||
Expr::FamousType { ty: Type::new(db, module.id, TyBuilder::bool()), value: "false" },
|
||||
Expr::FamousType { ty: Type::new(db, module.id, TyBuilder::unit()), value: "()" },
|
||||
]
|
||||
.into_iter()
|
||||
.map(|exprs| {
|
||||
lookup.insert(exprs.ty(db), std::iter::once(exprs.clone()));
|
||||
exprs
|
||||
})
|
||||
.filter(|expr| expr.ty(db).could_unify_with_deeply(db, &ctx.goal))
|
||||
}
|
||||
|
||||
/// # Impl static method (without self type) tactic
|
||||
///
|
||||
/// Attempts different functions from impl blocks that take no self parameter.
|
||||
///
|
||||
/// Updates lookup by new types reached and returns iterator that yields
|
||||
/// elements that unify with `goal`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - Context for the term search
|
||||
/// * `defs` - Set of items in scope at term search target location
|
||||
/// * `lookup` - Lookup table for types
|
||||
pub(super) fn impl_static_method<'a, DB: HirDatabase>(
|
||||
ctx: &'a TermSearchCtx<'a, DB>,
|
||||
_defs: &'a FxHashSet<ScopeDef>,
|
||||
lookup: &'a mut LookupTable,
|
||||
) -> impl Iterator<Item = Expr> + 'a {
|
||||
let db = ctx.sema.db;
|
||||
let module = ctx.scope.module();
|
||||
lookup
|
||||
.take_types_wishlist()
|
||||
.into_iter()
|
||||
.chain(iter::once(ctx.goal.clone()))
|
||||
.flat_map(|ty| {
|
||||
Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp))
|
||||
})
|
||||
.filter(|(_, imp)| !imp.is_unsafe(db))
|
||||
.flat_map(|(ty, imp)| imp.items(db).into_iter().map(move |item| (imp, ty.clone(), item)))
|
||||
.filter_map(|(imp, ty, it)| match it {
|
||||
AssocItem::Function(f) => Some((imp, ty, f)),
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(move |(imp, ty, it)| {
|
||||
let fn_generics = GenericDef::from(it);
|
||||
let imp_generics = GenericDef::from(imp);
|
||||
|
||||
// Ignore const params for now
|
||||
let imp_type_params = imp_generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore const params for now
|
||||
let fn_type_params = fn_generics
|
||||
.type_or_const_params(db)
|
||||
.into_iter()
|
||||
.map(|it| it.as_type_param(db))
|
||||
.collect::<Option<Vec<TypeParam>>>()?;
|
||||
|
||||
// Ignore all functions that have something to do with lifetimes as we don't check them
|
||||
if !fn_generics.lifetime_params(db).is_empty()
|
||||
|| !imp_generics.lifetime_params(db).is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ignore functions with self param
|
||||
if it.has_self_param(db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Filter out private and unsafe functions
|
||||
if !it.is_visible_from(db, module) || it.is_unsafe_to_call(db) || it.is_unstable(db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Only account for stable type parameters for now, unstable params can be default
|
||||
// tho, for example in `Box<T, #[unstable] A: Allocator>`
|
||||
if imp_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
|
||||
|| fn_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let non_default_type_params_len = imp_type_params
|
||||
.iter()
|
||||
.chain(fn_type_params.iter())
|
||||
.filter(|it| it.default(db).is_none())
|
||||
.count();
|
||||
|
||||
// Ignore bigger number of generics for now as they kill the performance
|
||||
if non_default_type_params_len > 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let generic_params = lookup
|
||||
.iter_types()
|
||||
.collect::<Vec<_>>() // Force take ownership
|
||||
.into_iter()
|
||||
.permutations(non_default_type_params_len);
|
||||
|
||||
let exprs: Vec<_> = generic_params
|
||||
.filter_map(|generics| {
|
||||
// Insert default type params
|
||||
let mut g = generics.into_iter();
|
||||
let generics: Vec<_> = imp_type_params
|
||||
.iter()
|
||||
.chain(fn_type_params.iter())
|
||||
.map(|it| match it.default(db) {
|
||||
Some(ty) => Some(ty),
|
||||
None => {
|
||||
let generic = g.next().expect("Missing type param");
|
||||
it.trait_bounds(db)
|
||||
.into_iter()
|
||||
.all(|bound| generic.impls_trait(db, bound, &[]));
|
||||
// Filter out generics that do not unify due to trait bounds
|
||||
it.ty(db).could_unify_with(db, &generic).then_some(generic)
|
||||
}
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
let ret_ty = it.ret_type_with_args(
|
||||
db,
|
||||
ty.type_arguments().chain(generics.iter().cloned()),
|
||||
);
|
||||
// Filter out functions that return references
|
||||
if ctx.config.enable_borrowcheck && ret_ty.contains_reference(db)
|
||||
|| ret_ty.is_raw_ptr()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ignore functions that do not change the type
|
||||
// if ty.could_unify_with_deeply(db, &ret_ty) {
|
||||
// return None;
|
||||
// }
|
||||
|
||||
// Early exit if some param cannot be filled from lookup
|
||||
let param_exprs: Vec<Vec<Expr>> = it
|
||||
.params_without_self_with_args(
|
||||
db,
|
||||
ty.type_arguments().chain(generics.iter().cloned()),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|field| lookup.find_autoref(db, field.ty()))
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
// Note that we need special case for 0 param constructors because of multi cartesian
|
||||
// product
|
||||
let fn_exprs: Vec<Expr> = if param_exprs.is_empty() {
|
||||
vec![Expr::Function { func: it, generics, params: Vec::new() }]
|
||||
} else {
|
||||
param_exprs
|
||||
.into_iter()
|
||||
.multi_cartesian_product()
|
||||
.map(|params| Expr::Function {
|
||||
func: it,
|
||||
generics: generics.clone(),
|
||||
params,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned());
|
||||
Some((ret_ty, fn_exprs))
|
||||
})
|
||||
.collect();
|
||||
Some(exprs)
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
|
||||
.flatten()
|
||||
}
|
|
@ -79,7 +79,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
|
|||
edit.edit_file(target_file);
|
||||
|
||||
let vis_owner = edit.make_mut(vis_owner);
|
||||
vis_owner.set_visibility(missing_visibility.clone_for_update());
|
||||
vis_owner.set_visibility(Some(missing_visibility.clone_for_update()));
|
||||
|
||||
if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
|
||||
edit.add_tabstop_before(cap, vis);
|
||||
|
@ -131,7 +131,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
|
|||
edit.edit_file(target_file);
|
||||
|
||||
let vis_owner = edit.make_mut(vis_owner);
|
||||
vis_owner.set_visibility(missing_visibility.clone_for_update());
|
||||
vis_owner.set_visibility(Some(missing_visibility.clone_for_update()));
|
||||
|
||||
if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
|
||||
edit.add_tabstop_before(cap, vis);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use hir::{self, HasCrate, HasVisibility};
|
||||
use hir::{HasCrate, HasVisibility};
|
||||
use ide_db::{path_transform::PathTransform, FxHashSet};
|
||||
use syntax::{
|
||||
ast::{
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
use crate::assist_context::{AssistContext, Assists};
|
||||
use ide_db::assists::AssistId;
|
||||
use syntax::{
|
||||
ast::{self, edit::IndentLevel, make, HasGenericParams, HasVisibility},
|
||||
ted, AstNode, SyntaxKind,
|
||||
ast::{
|
||||
self,
|
||||
edit_in_place::{HasVisibilityEdit, Indent},
|
||||
make, HasGenericParams, HasName,
|
||||
},
|
||||
ted::{self, Position},
|
||||
AstNode, SyntaxKind, T,
|
||||
};
|
||||
|
||||
// NOTES :
|
||||
|
@ -44,7 +49,7 @@ use syntax::{
|
|||
// };
|
||||
// }
|
||||
//
|
||||
// trait ${0:TraitName}<const N: usize> {
|
||||
// trait ${0:NewTrait}<const N: usize> {
|
||||
// // Used as an associated constant.
|
||||
// const CONST_ASSOC: usize = N * 4;
|
||||
//
|
||||
|
@ -53,7 +58,7 @@ use syntax::{
|
|||
// const_maker! {i32, 7}
|
||||
// }
|
||||
//
|
||||
// impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
|
||||
// impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
||||
// // Used as an associated constant.
|
||||
// const CONST_ASSOC: usize = N * 4;
|
||||
//
|
||||
|
@ -94,8 +99,10 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
|
|||
"Generate trait from impl",
|
||||
impl_ast.syntax().text_range(),
|
||||
|builder| {
|
||||
let impl_ast = builder.make_mut(impl_ast);
|
||||
let trait_items = assoc_items.clone_for_update();
|
||||
let impl_items = assoc_items.clone_for_update();
|
||||
let impl_items = builder.make_mut(assoc_items);
|
||||
let impl_name = builder.make_mut(impl_name);
|
||||
|
||||
trait_items.assoc_items().for_each(|item| {
|
||||
strip_body(&item);
|
||||
|
@ -112,46 +119,42 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
|
|||
impl_ast.generic_param_list(),
|
||||
impl_ast.where_clause(),
|
||||
trait_items,
|
||||
);
|
||||
)
|
||||
.clone_for_update();
|
||||
|
||||
let trait_name = trait_ast.name().expect("new trait should have a name");
|
||||
let trait_name_ref = make::name_ref(&trait_name.to_string()).clone_for_update();
|
||||
|
||||
// Change `impl Foo` to `impl NewTrait for Foo`
|
||||
let arg_list = if let Some(genpars) = impl_ast.generic_param_list() {
|
||||
genpars.to_generic_args().to_string()
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
let mut elements = vec![
|
||||
trait_name_ref.syntax().clone().into(),
|
||||
make::tokens::single_space().into(),
|
||||
make::token(T![for]).into(),
|
||||
];
|
||||
|
||||
if let Some(snippet_cap) = ctx.config.snippet_cap {
|
||||
builder.replace_snippet(
|
||||
snippet_cap,
|
||||
impl_name.syntax().text_range(),
|
||||
format!("${{0:TraitName}}{} for {}", arg_list, impl_name),
|
||||
);
|
||||
|
||||
// Insert trait before TraitImpl
|
||||
builder.insert_snippet(
|
||||
snippet_cap,
|
||||
impl_ast.syntax().text_range().start(),
|
||||
format!(
|
||||
"{}\n\n{}",
|
||||
trait_ast.to_string().replace("NewTrait", "${0:TraitName}"),
|
||||
IndentLevel::from_node(impl_ast.syntax())
|
||||
),
|
||||
);
|
||||
} else {
|
||||
builder.replace(
|
||||
impl_name.syntax().text_range(),
|
||||
format!("NewTrait{} for {}", arg_list, impl_name),
|
||||
);
|
||||
|
||||
// Insert trait before TraitImpl
|
||||
builder.insert(
|
||||
impl_ast.syntax().text_range().start(),
|
||||
format!("{}\n\n{}", trait_ast, IndentLevel::from_node(impl_ast.syntax())),
|
||||
);
|
||||
if let Some(params) = impl_ast.generic_param_list() {
|
||||
let gen_args = ¶ms.to_generic_args().clone_for_update();
|
||||
elements.insert(1, gen_args.syntax().clone().into());
|
||||
}
|
||||
|
||||
builder.replace(assoc_items.syntax().text_range(), impl_items.to_string());
|
||||
ted::insert_all(Position::before(impl_name.syntax()), elements);
|
||||
|
||||
// Insert trait before TraitImpl
|
||||
ted::insert_all_raw(
|
||||
Position::before(impl_ast.syntax()),
|
||||
vec![
|
||||
trait_ast.syntax().clone().into(),
|
||||
make::tokens::whitespace(&format!("\n\n{}", impl_ast.indent_level())).into(),
|
||||
],
|
||||
);
|
||||
|
||||
// Link the trait name & trait ref names together as a placeholder snippet group
|
||||
if let Some(cap) = ctx.config.snippet_cap {
|
||||
builder.add_placeholder_snippet_group(
|
||||
cap,
|
||||
vec![trait_name.syntax().clone(), trait_name_ref.syntax().clone()],
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -160,23 +163,8 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
|
|||
|
||||
/// `E0449` Trait items always share the visibility of their trait
|
||||
fn remove_items_visibility(item: &ast::AssocItem) {
|
||||
match item {
|
||||
ast::AssocItem::Const(c) => {
|
||||
if let Some(vis) = c.visibility() {
|
||||
ted::remove(vis.syntax());
|
||||
}
|
||||
}
|
||||
ast::AssocItem::Fn(f) => {
|
||||
if let Some(vis) = f.visibility() {
|
||||
ted::remove(vis.syntax());
|
||||
}
|
||||
}
|
||||
ast::AssocItem::TypeAlias(t) => {
|
||||
if let Some(vis) = t.visibility() {
|
||||
ted::remove(vis.syntax());
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
if let Some(has_vis) = ast::AnyHasVisibility::cast(item.syntax().clone()) {
|
||||
has_vis.set_visibility(None);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,12 +392,12 @@ impl<const N: usize> F$0oo<N> {
|
|||
r#"
|
||||
struct Foo<const N: usize>([i32; N]);
|
||||
|
||||
trait ${0:TraitName}<const N: usize> {
|
||||
trait ${0:NewTrait}<const N: usize> {
|
||||
// Used as an associated constant.
|
||||
const CONST: usize = N * 4;
|
||||
}
|
||||
|
||||
impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
|
||||
impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
||||
// Used as an associated constant.
|
||||
const CONST: usize = N * 4;
|
||||
}
|
||||
|
|
253
crates/ide-assists/src/handlers/term_search.rs
Normal file
253
crates/ide-assists/src/handlers/term_search.rs
Normal file
|
@ -0,0 +1,253 @@
|
|||
//! Term search assist
|
||||
use hir::term_search::TermSearchCtx;
|
||||
use ide_db::{
|
||||
assists::{AssistId, AssistKind, GroupLabel},
|
||||
famous_defs::FamousDefs,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use syntax::{ast, AstNode};
|
||||
|
||||
use crate::assist_context::{AssistContext, Assists};
|
||||
|
||||
pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||
let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?;
|
||||
let syntax = unexpanded.syntax();
|
||||
let goal_range = syntax.text_range();
|
||||
|
||||
let parent = syntax.parent()?;
|
||||
let scope = ctx.sema.scope(&parent)?;
|
||||
|
||||
let macro_call = ctx.sema.resolve_macro_call(&unexpanded)?;
|
||||
|
||||
let famous_defs = FamousDefs(&ctx.sema, scope.krate());
|
||||
let std_todo = famous_defs.core_macros_todo()?;
|
||||
let std_unimplemented = famous_defs.core_macros_unimplemented()?;
|
||||
|
||||
if macro_call != std_todo && macro_call != std_unimplemented {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target_ty = ctx.sema.type_of_expr(&ast::Expr::cast(parent.clone())?)?.adjusted();
|
||||
|
||||
let term_search_ctx = TermSearchCtx {
|
||||
sema: &ctx.sema,
|
||||
scope: &scope,
|
||||
goal: target_ty,
|
||||
config: Default::default(),
|
||||
};
|
||||
let paths = hir::term_search::term_search(&term_search_ctx);
|
||||
|
||||
if paths.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut formatter = |_: &hir::Type| String::from("todo!()");
|
||||
|
||||
let paths = paths
|
||||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
path.gen_source_code(
|
||||
&scope,
|
||||
&mut formatter,
|
||||
ctx.config.prefer_no_std,
|
||||
ctx.config.prefer_prelude,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.unique();
|
||||
|
||||
for code in paths {
|
||||
acc.add_group(
|
||||
&GroupLabel(String::from("Term search")),
|
||||
AssistId("term_search", AssistKind::Generate),
|
||||
format!("Replace todo!() with {code}"),
|
||||
goal_range,
|
||||
|builder| {
|
||||
builder.replace(goal_range, code);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_complete_local() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
fn f() { let a: u128 = 1; let b: u128 = todo$0!() }"#,
|
||||
r#"fn f() { let a: u128 = 1; let b: u128 = a }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_todo_with_msg() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#,
|
||||
r#"fn f() { let a: u128 = 1; let b: u128 = a }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_unimplemented_with_msg() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#,
|
||||
r#"fn f() { let a: u128 = 1; let b: u128 = a }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_unimplemented() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#,
|
||||
r#"fn f() { let a: u128 = 1; let b: u128 = a }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_struct_field() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
struct A { pub x: i32, y: bool }
|
||||
fn f() { let a = A { x: 1, y: true }; let b: i32 = todo$0!(); }"#,
|
||||
r#"struct A { pub x: i32, y: bool }
|
||||
fn f() { let a = A { x: 1, y: true }; let b: i32 = a.x; }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_with_generics() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented, option
|
||||
fn f() { let a: i32 = 1; let b: Option<i32> = todo$0!(); }"#,
|
||||
r#"fn f() { let a: i32 = 1; let b: Option<i32> = None; }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_with_generics2() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
enum Option<T> { None, Some(T) }
|
||||
fn f() { let a: i32 = 1; let b: Option<i32> = todo$0!(); }"#,
|
||||
r#"enum Option<T> { None, Some(T) }
|
||||
fn f() { let a: i32 = 1; let b: Option<i32> = Option::Some(a); }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_with_generics3() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
enum Option<T> { None, Some(T) }
|
||||
fn f() { let a: Option<i32> = Option::None; let b: Option<Option<i32>> = todo$0!(); }"#,
|
||||
r#"enum Option<T> { None, Some(T) }
|
||||
fn f() { let a: Option<i32> = Option::None; let b: Option<Option<i32>> = Option::Some(a); }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_with_generics4() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
enum Foo<T = i32> { Foo(T) }
|
||||
fn f() { let a = 0; let b: Foo = todo$0!(); }"#,
|
||||
r#"enum Foo<T = i32> { Foo(T) }
|
||||
fn f() { let a = 0; let b: Foo = Foo::Foo(a); }"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
enum Foo<T = i32> { Foo(T) }
|
||||
fn f() { let a: Foo<u32> = Foo::Foo(0); let b: Foo<u32> = todo$0!(); }"#,
|
||||
r#"enum Foo<T = i32> { Foo(T) }
|
||||
fn f() { let a: Foo<u32> = Foo::Foo(0); let b: Foo<u32> = a; }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_newtype() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
struct Foo(i32);
|
||||
fn f() { let a: i32 = 1; let b: Foo = todo$0!(); }"#,
|
||||
r#"struct Foo(i32);
|
||||
fn f() { let a: i32 = 1; let b: Foo = Foo(a); }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shadowing() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
fn f() { let a: i32 = 1; let b: i32 = 2; let a: u32 = 0; let c: i32 = todo$0!(); }"#,
|
||||
r#"fn f() { let a: i32 = 1; let b: i32 = 2; let a: u32 = 0; let c: i32 = b; }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_famous_bool() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
fn f() { let a: bool = todo$0!(); }"#,
|
||||
r#"fn f() { let a: bool = false; }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fn_with_reference_types() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
fn f(a: &i32) -> f32 { a as f32 }
|
||||
fn g() { let a = 1; let b: f32 = todo$0!(); }"#,
|
||||
r#"fn f(a: &i32) -> f32 { a as f32 }
|
||||
fn g() { let a = 1; let b: f32 = f(&a); }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fn_with_reference_types2() {
|
||||
check_assist(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
fn f(a: &i32) -> f32 { a as f32 }
|
||||
fn g() { let a = &1; let b: f32 = todo$0!(); }"#,
|
||||
r#"fn f(a: &i32) -> f32 { a as f32 }
|
||||
fn g() { let a = &1; let b: f32 = f(a); }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fn_with_reference_types3() {
|
||||
check_assist_not_applicable(
|
||||
term_search,
|
||||
r#"//- minicore: todo, unimplemented
|
||||
fn f(a: &i32) -> f32 { a as f32 }
|
||||
fn g() { let a = &mut 1; let b: f32 = todo$0!(); }"#,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -60,11 +60,6 @@
|
|||
|
||||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
|
||||
#[allow(unused)]
|
||||
macro_rules! eprintln {
|
||||
($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
|
||||
}
|
||||
|
||||
mod assist_config;
|
||||
mod assist_context;
|
||||
#[cfg(test)]
|
||||
|
@ -210,6 +205,7 @@ mod handlers {
|
|||
mod replace_turbofish_with_explicit_type;
|
||||
mod sort_items;
|
||||
mod split_import;
|
||||
mod term_search;
|
||||
mod toggle_ignore;
|
||||
mod unmerge_match_arm;
|
||||
mod unmerge_use;
|
||||
|
@ -332,6 +328,7 @@ mod handlers {
|
|||
replace_arith_op::replace_arith_with_saturating,
|
||||
sort_items::sort_items,
|
||||
split_import::split_import,
|
||||
term_search::term_search,
|
||||
toggle_ignore::toggle_ignore,
|
||||
unmerge_match_arm::unmerge_match_arm,
|
||||
unmerge_use::unmerge_use,
|
||||
|
|
|
@ -1665,7 +1665,7 @@ macro_rules! const_maker {
|
|||
};
|
||||
}
|
||||
|
||||
trait ${0:TraitName}<const N: usize> {
|
||||
trait ${0:NewTrait}<const N: usize> {
|
||||
// Used as an associated constant.
|
||||
const CONST_ASSOC: usize = N * 4;
|
||||
|
||||
|
@ -1674,7 +1674,7 @@ trait ${0:TraitName}<const N: usize> {
|
|||
const_maker! {i32, 7}
|
||||
}
|
||||
|
||||
impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
|
||||
impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
||||
// Used as an associated constant.
|
||||
const CONST_ASSOC: usize = N * 4;
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ use crate::{
|
|||
literal::{render_struct_literal, render_variant_lit},
|
||||
macro_::render_macro,
|
||||
pattern::{render_struct_pat, render_variant_pat},
|
||||
render_field, render_path_resolution, render_pattern_resolution, render_tuple_field,
|
||||
render_expr, render_field, render_path_resolution, render_pattern_resolution,
|
||||
render_tuple_field,
|
||||
type_alias::{render_type_alias, render_type_alias_with_eq},
|
||||
union_literal::render_union_literal,
|
||||
RenderContext,
|
||||
|
@ -157,6 +158,12 @@ impl Completions {
|
|||
item.add_to(self, ctx.db);
|
||||
}
|
||||
|
||||
pub(crate) fn add_expr(&mut self, ctx: &CompletionContext<'_>, expr: &hir::term_search::Expr) {
|
||||
if let Some(item) = render_expr(ctx, expr) {
|
||||
item.add_to(self, ctx.db)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_crate_roots(
|
||||
&mut self,
|
||||
ctx: &CompletionContext<'_>,
|
||||
|
@ -694,6 +701,7 @@ pub(super) fn complete_name_ref(
|
|||
match &path_ctx.kind {
|
||||
PathKind::Expr { expr_ctx } => {
|
||||
expr::complete_expr_path(acc, ctx, path_ctx, expr_ctx);
|
||||
expr::complete_expr(acc, ctx);
|
||||
|
||||
dot::complete_undotted_self(acc, ctx, path_ctx, expr_ctx);
|
||||
item_list::complete_item_list_in_expr(acc, ctx, path_ctx, expr_ctx);
|
||||
|
|
|
@ -328,3 +328,59 @@ pub(crate) fn complete_expr_path(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) {
|
||||
let _p = tracing::span!(tracing::Level::INFO, "complete_expr").entered();
|
||||
|
||||
if !ctx.config.enable_term_search {
|
||||
return;
|
||||
}
|
||||
|
||||
if !ctx.qualifier_ctx.none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ty) = &ctx.expected_type {
|
||||
// Ignore unit types as they are not very interesting
|
||||
if ty.is_unit() || ty.is_unknown() {
|
||||
return;
|
||||
}
|
||||
|
||||
let term_search_ctx = hir::term_search::TermSearchCtx {
|
||||
sema: &ctx.sema,
|
||||
scope: &ctx.scope,
|
||||
goal: ty.clone(),
|
||||
config: hir::term_search::TermSearchConfig {
|
||||
enable_borrowcheck: false,
|
||||
many_alternatives_threshold: 1,
|
||||
depth: 6,
|
||||
},
|
||||
};
|
||||
let exprs = hir::term_search::term_search(&term_search_ctx);
|
||||
for expr in exprs {
|
||||
// Expand method calls
|
||||
match expr {
|
||||
hir::term_search::Expr::Method { func, generics, target, params }
|
||||
if target.is_many() =>
|
||||
{
|
||||
let target_ty = target.ty(ctx.db);
|
||||
let term_search_ctx =
|
||||
hir::term_search::TermSearchCtx { goal: target_ty, ..term_search_ctx };
|
||||
let target_exprs = hir::term_search::term_search(&term_search_ctx);
|
||||
|
||||
for expr in target_exprs {
|
||||
let expanded_expr = hir::term_search::Expr::Method {
|
||||
func,
|
||||
generics: generics.clone(),
|
||||
target: Box::new(expr),
|
||||
params: params.clone(),
|
||||
};
|
||||
|
||||
acc.add_expr(ctx, &expanded_expr)
|
||||
}
|
||||
}
|
||||
_ => acc.add_expr(ctx, &expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -238,6 +238,8 @@ fn import_on_the_fly(
|
|||
(PathKind::Type { location }, ItemInNs::Types(ty)) => {
|
||||
if matches!(location, TypeLocation::TypeBound) {
|
||||
matches!(ty, ModuleDef::Trait(_))
|
||||
} else if matches!(location, TypeLocation::ImplTrait) {
|
||||
matches!(ty, ModuleDef::Trait(_) | ModuleDef::Module(_))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
//! }
|
||||
//! ```
|
||||
|
||||
use hir::{self, HasAttrs};
|
||||
use hir::HasAttrs;
|
||||
use ide_db::{
|
||||
documentation::HasDocs, path_transform::PathTransform,
|
||||
syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind,
|
||||
|
|
|
@ -31,6 +31,11 @@ pub(crate) fn complete_type_path(
|
|||
ScopeDef::ImplSelfType(_) => location.complete_self_type(),
|
||||
// Don't suggest attribute macros and derives.
|
||||
ScopeDef::ModuleDef(Macro(mac)) => mac.is_fn_like(ctx.db),
|
||||
ScopeDef::ModuleDef(Trait(_) | Module(_))
|
||||
if matches!(location, TypeLocation::ImplTrait) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
// Type things are fine
|
||||
ScopeDef::ModuleDef(
|
||||
BuiltinType(_) | Adt(_) | Module(_) | Trait(_) | TraitAlias(_) | TypeAlias(_),
|
||||
|
@ -184,6 +189,21 @@ pub(crate) fn complete_type_path(
|
|||
}
|
||||
}
|
||||
}
|
||||
TypeLocation::ImplTrait => {
|
||||
acc.add_nameref_keywords_with_colon(ctx);
|
||||
ctx.process_all_names(&mut |name, def, doc_aliases| {
|
||||
let is_trait_or_module = matches!(
|
||||
def,
|
||||
ScopeDef::ModuleDef(
|
||||
hir::ModuleDef::Module(_) | hir::ModuleDef::Trait(_)
|
||||
)
|
||||
);
|
||||
if is_trait_or_module {
|
||||
acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ pub struct CompletionConfig {
|
|||
pub enable_imports_on_the_fly: bool,
|
||||
pub enable_self_on_the_fly: bool,
|
||||
pub enable_private_editable: bool,
|
||||
pub enable_term_search: bool,
|
||||
pub full_function_signatures: bool,
|
||||
pub callable: Option<CallableSnippets>,
|
||||
pub snippet_cap: Option<SnippetCap>,
|
||||
|
|
|
@ -202,6 +202,7 @@ impl TypeLocation {
|
|||
}
|
||||
TypeLocation::AssocConstEq => false,
|
||||
TypeLocation::AssocTypeEq => true,
|
||||
TypeLocation::ImplTrait => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
@ -716,7 +717,7 @@ impl<'a> CompletionContext<'a> {
|
|||
let krate = scope.krate();
|
||||
let module = scope.module();
|
||||
|
||||
let toolchain = db.crate_graph()[krate.into()].channel();
|
||||
let toolchain = db.toolchain_channel(krate.into());
|
||||
// `toolchain == None` means we're in some detached files. Since we have no information on
|
||||
// the toolchain being used, let's just allow unstable items to be listed.
|
||||
let is_nightly = matches!(toolchain, Some(base_db::ReleaseChannel::Nightly) | None);
|
||||
|
|
|
@ -166,6 +166,8 @@ pub struct CompletionRelevance {
|
|||
pub postfix_match: Option<CompletionRelevancePostfixMatch>,
|
||||
/// This is set for type inference results
|
||||
pub is_definite: bool,
|
||||
/// This is set for items that are function (associated or method)
|
||||
pub function: Option<CompletionRelevanceFn>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
|
@ -207,6 +209,24 @@ pub enum CompletionRelevancePostfixMatch {
|
|||
Exact,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct CompletionRelevanceFn {
|
||||
pub has_params: bool,
|
||||
pub has_self_param: bool,
|
||||
pub return_type: CompletionRelevanceReturnType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum CompletionRelevanceReturnType {
|
||||
Other,
|
||||
/// Returns the Self type of the impl/trait
|
||||
DirectConstructor,
|
||||
/// Returns something that indirectly constructs the `Self` type of the impl/trait e.g. `Result<Self, ()>`, `Option<Self>`
|
||||
Constructor,
|
||||
/// Returns a possible builder for the type
|
||||
Builder,
|
||||
}
|
||||
|
||||
impl CompletionRelevance {
|
||||
/// Provides a relevance score. Higher values are more relevant.
|
||||
///
|
||||
|
@ -231,6 +251,7 @@ impl CompletionRelevance {
|
|||
postfix_match,
|
||||
is_definite,
|
||||
is_item_from_notable_trait,
|
||||
function,
|
||||
} = self;
|
||||
|
||||
// lower rank private things
|
||||
|
@ -275,6 +296,33 @@ impl CompletionRelevance {
|
|||
if is_definite {
|
||||
score += 10;
|
||||
}
|
||||
|
||||
score += function
|
||||
.map(|asf| {
|
||||
let mut fn_score = match asf.return_type {
|
||||
CompletionRelevanceReturnType::DirectConstructor => 15,
|
||||
CompletionRelevanceReturnType::Builder => 10,
|
||||
CompletionRelevanceReturnType::Constructor => 5,
|
||||
CompletionRelevanceReturnType::Other => 0,
|
||||
};
|
||||
|
||||
// When a fn is bumped due to return type:
|
||||
// Bump Constructor or Builder methods with no arguments,
|
||||
// over them than with self arguments
|
||||
if fn_score > 0 {
|
||||
if !asf.has_params {
|
||||
// bump associated functions
|
||||
fn_score += 1;
|
||||
} else if asf.has_self_param {
|
||||
// downgrade methods (below Constructor)
|
||||
fn_score = 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn_score
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
score
|
||||
}
|
||||
|
||||
|
@ -297,6 +345,7 @@ pub enum CompletionItemKind {
|
|||
Method,
|
||||
Snippet,
|
||||
UnresolvedReference,
|
||||
Expression,
|
||||
}
|
||||
|
||||
impl_from!(SymbolKind for CompletionItemKind);
|
||||
|
@ -341,6 +390,7 @@ impl CompletionItemKind {
|
|||
CompletionItemKind::Method => "me",
|
||||
CompletionItemKind::Snippet => "sn",
|
||||
CompletionItemKind::UnresolvedReference => "??",
|
||||
CompletionItemKind::Expression => "ex",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use ide_db::{
|
|||
imports::import_assets::LocatedImport,
|
||||
RootDatabase, SnippetCap, SymbolKind,
|
||||
};
|
||||
use syntax::{format_smolstr, AstNode, SmolStr, SyntaxKind, TextRange};
|
||||
use syntax::{ast, format_smolstr, AstNode, SmolStr, SyntaxKind, TextRange};
|
||||
use text_edit::TextEdit;
|
||||
|
||||
use crate::{
|
||||
|
@ -272,6 +272,82 @@ pub(crate) fn render_resolution_with_import_pat(
|
|||
Some(render_resolution_pat(ctx, pattern_ctx, local_name, Some(import_edit), resolution))
|
||||
}
|
||||
|
||||
pub(crate) fn render_expr(
|
||||
ctx: &CompletionContext<'_>,
|
||||
expr: &hir::term_search::Expr,
|
||||
) -> Option<Builder> {
|
||||
let mut i = 1;
|
||||
let mut snippet_formatter = |ty: &hir::Type| {
|
||||
let arg_name = ty
|
||||
.as_adt()
|
||||
.and_then(|adt| adt.name(ctx.db).as_text())
|
||||
.map(|s| stdx::to_lower_snake_case(s.as_str()))
|
||||
.unwrap_or_else(|| String::from("_"));
|
||||
let res = format!("${{{i}:{arg_name}}}");
|
||||
i += 1;
|
||||
res
|
||||
};
|
||||
|
||||
let mut label_formatter = |ty: &hir::Type| {
|
||||
ty.as_adt()
|
||||
.and_then(|adt| adt.name(ctx.db).as_text())
|
||||
.map(|s| stdx::to_lower_snake_case(s.as_str()))
|
||||
.unwrap_or_else(|| String::from("..."))
|
||||
};
|
||||
|
||||
let label = expr
|
||||
.gen_source_code(
|
||||
&ctx.scope,
|
||||
&mut label_formatter,
|
||||
ctx.config.prefer_no_std,
|
||||
ctx.config.prefer_prelude,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
let source_range = match ctx.original_token.parent() {
|
||||
Some(node) => match node.ancestors().find_map(ast::Path::cast) {
|
||||
Some(path) => path.syntax().text_range(),
|
||||
None => node.text_range(),
|
||||
},
|
||||
None => ctx.source_range(),
|
||||
};
|
||||
|
||||
let mut item = CompletionItem::new(CompletionItemKind::Expression, source_range, label.clone());
|
||||
|
||||
let snippet = format!(
|
||||
"{}$0",
|
||||
expr.gen_source_code(
|
||||
&ctx.scope,
|
||||
&mut snippet_formatter,
|
||||
ctx.config.prefer_no_std,
|
||||
ctx.config.prefer_prelude
|
||||
)
|
||||
.ok()?
|
||||
);
|
||||
let edit = TextEdit::replace(source_range, snippet);
|
||||
item.snippet_edit(ctx.config.snippet_cap?, edit);
|
||||
item.documentation(Documentation::new(String::from("Autogenerated expression by term search")));
|
||||
item.set_relevance(crate::CompletionRelevance {
|
||||
type_match: compute_type_match(ctx, &expr.ty(ctx.db)),
|
||||
..Default::default()
|
||||
});
|
||||
for trait_ in expr.traits_used(ctx.db) {
|
||||
let trait_item = hir::ItemInNs::from(hir::ModuleDef::from(trait_));
|
||||
let Some(path) = ctx.module.find_use_path(
|
||||
ctx.db,
|
||||
trait_item,
|
||||
ctx.config.prefer_no_std,
|
||||
ctx.config.prefer_prelude,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
item.add_import(LocatedImport::new(path, trait_item, trait_item));
|
||||
}
|
||||
|
||||
Some(item)
|
||||
}
|
||||
|
||||
fn scope_def_to_name(
|
||||
resolution: ScopeDef,
|
||||
ctx: &RenderContext<'_>,
|
||||
|
@ -599,6 +675,16 @@ mod tests {
|
|||
expect.assert_debug_eq(&actual);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_function_relevance(ra_fixture: &str, expect: Expect) {
|
||||
let actual: Vec<_> = do_completion(ra_fixture, CompletionItemKind::Method)
|
||||
.into_iter()
|
||||
.map(|item| (item.detail.unwrap_or_default(), item.relevance.function))
|
||||
.collect();
|
||||
|
||||
expect.assert_debug_eq(&actual);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
|
||||
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
|
||||
|
@ -961,6 +1047,7 @@ fn func(input: Struct) { }
|
|||
st Self [type]
|
||||
sp Self [type]
|
||||
st Struct [type]
|
||||
ex Struct [type]
|
||||
lc self [local]
|
||||
fn func(…) []
|
||||
me self.test() []
|
||||
|
@ -985,6 +1072,9 @@ fn main() {
|
|||
"#,
|
||||
expect![[r#"
|
||||
lc input [type+name+local]
|
||||
ex input [type]
|
||||
ex true [type]
|
||||
ex false [type]
|
||||
lc inputbad [local]
|
||||
fn main() []
|
||||
fn test(…) []
|
||||
|
@ -1174,6 +1264,7 @@ fn main() { let _: m::Spam = S$0 }
|
|||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: None,
|
||||
},
|
||||
trigger_call_info: true,
|
||||
},
|
||||
|
@ -1201,6 +1292,7 @@ fn main() { let _: m::Spam = S$0 }
|
|||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: None,
|
||||
},
|
||||
trigger_call_info: true,
|
||||
},
|
||||
|
@ -1280,6 +1372,7 @@ fn foo() { A { the$0 } }
|
|||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: None,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
@ -1313,6 +1406,26 @@ impl S {
|
|||
documentation: Documentation(
|
||||
"Method docs",
|
||||
),
|
||||
relevance: CompletionRelevance {
|
||||
exact_name_match: false,
|
||||
type_match: None,
|
||||
is_local: false,
|
||||
is_item_from_trait: false,
|
||||
is_item_from_notable_trait: false,
|
||||
is_name_already_imported: false,
|
||||
requires_import: false,
|
||||
is_op_method: false,
|
||||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: Other,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
CompletionItem {
|
||||
label: "foo",
|
||||
|
@ -1418,6 +1531,26 @@ fn foo(s: S) { s.$0 }
|
|||
kind: Method,
|
||||
lookup: "the_method",
|
||||
detail: "fn(&self)",
|
||||
relevance: CompletionRelevance {
|
||||
exact_name_match: false,
|
||||
type_match: None,
|
||||
is_local: false,
|
||||
is_item_from_trait: false,
|
||||
is_item_from_notable_trait: false,
|
||||
is_name_already_imported: false,
|
||||
requires_import: false,
|
||||
is_op_method: false,
|
||||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: Other,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
|
@ -1665,6 +1798,10 @@ fn f() { A { bar: b$0 }; }
|
|||
expect![[r#"
|
||||
fn bar() [type+name]
|
||||
fn baz() [type]
|
||||
ex baz() [type]
|
||||
ex bar() [type]
|
||||
ex A { bar: baz() }.bar [type]
|
||||
ex A { bar: bar() }.bar [type]
|
||||
st A []
|
||||
fn f() []
|
||||
"#]],
|
||||
|
@ -1749,6 +1886,8 @@ fn main() {
|
|||
lc s [type+name+local]
|
||||
st S [type]
|
||||
st S [type]
|
||||
ex s [type]
|
||||
ex S [type]
|
||||
fn foo(…) []
|
||||
fn main() []
|
||||
"#]],
|
||||
|
@ -1766,6 +1905,8 @@ fn main() {
|
|||
lc ssss [type+local]
|
||||
st S [type]
|
||||
st S [type]
|
||||
ex ssss [type]
|
||||
ex S [type]
|
||||
fn foo(…) []
|
||||
fn main() []
|
||||
"#]],
|
||||
|
@ -1798,6 +1939,8 @@ fn main() {
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ex core::ops::Deref::deref(&T(S)) (use core::ops::Deref) [type_could_unify]
|
||||
ex core::ops::Deref::deref(&t) (use core::ops::Deref) [type_could_unify]
|
||||
lc m [local]
|
||||
lc t [local]
|
||||
lc &t [type+local]
|
||||
|
@ -1846,6 +1989,8 @@ fn main() {
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ex core::ops::DerefMut::deref_mut(&mut T(S)) (use core::ops::DerefMut) [type_could_unify]
|
||||
ex core::ops::DerefMut::deref_mut(&mut t) (use core::ops::DerefMut) [type_could_unify]
|
||||
lc m [local]
|
||||
lc t [local]
|
||||
lc &mut t [type+local]
|
||||
|
@ -1894,6 +2039,8 @@ fn bar(t: Foo) {}
|
|||
ev Foo::A [type]
|
||||
ev Foo::B [type]
|
||||
en Foo [type]
|
||||
ex Foo::A [type]
|
||||
ex Foo::B [type]
|
||||
fn bar(…) []
|
||||
fn foo() []
|
||||
"#]],
|
||||
|
@ -1947,6 +2094,8 @@ fn main() {
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ex core::ops::Deref::deref(&T(S)) (use core::ops::Deref) [type_could_unify]
|
||||
ex core::ops::Deref::deref(&bar()) (use core::ops::Deref) [type_could_unify]
|
||||
st S []
|
||||
st &S [type]
|
||||
st S []
|
||||
|
@ -2002,6 +2151,254 @@ fn main() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constructor_order_simple() {
|
||||
check_relevance(
|
||||
r#"
|
||||
struct Foo;
|
||||
struct Other;
|
||||
struct Option<T>(T);
|
||||
|
||||
impl Foo {
|
||||
fn fn_ctr() -> Foo { unimplemented!() }
|
||||
fn fn_another(n: u32) -> Other { unimplemented!() }
|
||||
fn fn_ctr_self() -> Option<Self> { unimplemented!() }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let a = Foo::$0;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn fn_ctr() [type_could_unify]
|
||||
fn fn_ctr_self() [type_could_unify]
|
||||
fn fn_another(…) [type_could_unify]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constructor_order_kind() {
|
||||
check_function_relevance(
|
||||
r#"
|
||||
struct Foo;
|
||||
struct Bar;
|
||||
struct Option<T>(T);
|
||||
enum Result<T, E> { Ok(T), Err(E) };
|
||||
|
||||
impl Foo {
|
||||
fn fn_ctr(&self) -> Foo { unimplemented!() }
|
||||
fn fn_ctr_with_args(&self, n: u32) -> Foo { unimplemented!() }
|
||||
fn fn_another(&self, n: u32) -> Bar { unimplemented!() }
|
||||
fn fn_ctr_wrapped(&self, ) -> Option<Self> { unimplemented!() }
|
||||
fn fn_ctr_wrapped_2(&self, ) -> Result<Self, Bar> { unimplemented!() }
|
||||
fn fn_ctr_wrapped_3(&self, ) -> Result<Bar, Self> { unimplemented!() } // Self is not the first type
|
||||
fn fn_ctr_wrapped_with_args(&self, m: u32) -> Option<Self> { unimplemented!() }
|
||||
fn fn_another_unit(&self) { unimplemented!() }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let a = self::Foo::$0;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
(
|
||||
"fn(&self, u32) -> Bar",
|
||||
Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: Other,
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"fn(&self)",
|
||||
Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: Other,
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"fn(&self) -> Foo",
|
||||
Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: DirectConstructor,
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"fn(&self, u32) -> Foo",
|
||||
Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: DirectConstructor,
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"fn(&self) -> Option<Foo>",
|
||||
Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: Constructor,
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"fn(&self) -> Result<Foo, Bar>",
|
||||
Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: Constructor,
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"fn(&self) -> Result<Bar, Foo>",
|
||||
Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: Constructor,
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"fn(&self, u32) -> Option<Foo>",
|
||||
Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: Constructor,
|
||||
},
|
||||
),
|
||||
),
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constructor_order_relevance() {
|
||||
check_relevance(
|
||||
r#"
|
||||
struct Foo;
|
||||
struct FooBuilder;
|
||||
struct Result<T>(T);
|
||||
|
||||
impl Foo {
|
||||
fn fn_no_ret(&self) {}
|
||||
fn fn_ctr_with_args(input: u32) -> Foo { unimplemented!() }
|
||||
fn fn_direct_ctr() -> Self { unimplemented!() }
|
||||
fn fn_ctr() -> Result<Self> { unimplemented!() }
|
||||
fn fn_other() -> Result<u32> { unimplemented!() }
|
||||
fn fn_builder() -> FooBuilder { unimplemented!() }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let a = self::Foo::$0;
|
||||
}
|
||||
"#,
|
||||
// preference:
|
||||
// Direct Constructor
|
||||
// Direct Constructor with args
|
||||
// Builder
|
||||
// Constructor
|
||||
// Others
|
||||
expect![[r#"
|
||||
fn fn_direct_ctr() [type_could_unify]
|
||||
fn fn_ctr_with_args(…) [type_could_unify]
|
||||
fn fn_builder() [type_could_unify]
|
||||
fn fn_ctr() [type_could_unify]
|
||||
me fn_no_ret(…) [type_could_unify]
|
||||
fn fn_other() [type_could_unify]
|
||||
"#]],
|
||||
);
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_relevance_generic_1() {
|
||||
check_relevance(
|
||||
r#"
|
||||
struct Foo<T: Default>(T);
|
||||
struct FooBuilder;
|
||||
struct Option<T>(T);
|
||||
enum Result<T, E>{Ok(T), Err(E)};
|
||||
|
||||
impl<T: Default> Foo<T> {
|
||||
fn fn_returns_unit(&self) {}
|
||||
fn fn_ctr_with_args(input: T) -> Foo<T> { unimplemented!() }
|
||||
fn fn_direct_ctr() -> Self { unimplemented!() }
|
||||
fn fn_ctr_wrapped() -> Option<Self> { unimplemented!() }
|
||||
fn fn_ctr_wrapped_2() -> Result<Self, u32> { unimplemented!() }
|
||||
fn fn_other() -> Option<u32> { unimplemented!() }
|
||||
fn fn_builder() -> FooBuilder { unimplemented!() }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let a = self::Foo::<u32>::$0;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn fn_direct_ctr() [type_could_unify]
|
||||
fn fn_ctr_with_args(…) [type_could_unify]
|
||||
fn fn_builder() [type_could_unify]
|
||||
fn fn_ctr_wrapped() [type_could_unify]
|
||||
fn fn_ctr_wrapped_2() [type_could_unify]
|
||||
me fn_returns_unit(…) [type_could_unify]
|
||||
fn fn_other() [type_could_unify]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_relevance_generic_2() {
|
||||
// Generic 2
|
||||
check_relevance(
|
||||
r#"
|
||||
struct Foo<T: Default>(T);
|
||||
struct FooBuilder;
|
||||
struct Option<T>(T);
|
||||
enum Result<T, E>{Ok(T), Err(E)};
|
||||
|
||||
impl<T: Default> Foo<T> {
|
||||
fn fn_no_ret(&self) {}
|
||||
fn fn_ctr_with_args(input: T) -> Foo<T> { unimplemented!() }
|
||||
fn fn_direct_ctr() -> Self { unimplemented!() }
|
||||
fn fn_ctr() -> Option<Self> { unimplemented!() }
|
||||
fn fn_ctr2() -> Result<Self, u32> { unimplemented!() }
|
||||
fn fn_other() -> Option<u32> { unimplemented!() }
|
||||
fn fn_builder() -> FooBuilder { unimplemented!() }
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let a : Res<Foo<u32>> = Foo::$0;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn fn_direct_ctr() [type_could_unify]
|
||||
fn fn_ctr_with_args(…) [type_could_unify]
|
||||
fn fn_builder() [type_could_unify]
|
||||
fn fn_ctr() [type_could_unify]
|
||||
fn fn_ctr2() [type_could_unify]
|
||||
me fn_no_ret(…) [type_could_unify]
|
||||
fn fn_other() [type_could_unify]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_field_method_ref() {
|
||||
check_kinds(
|
||||
|
@ -2022,6 +2419,26 @@ fn foo(f: Foo) { let _: &u32 = f.b$0 }
|
|||
kind: Method,
|
||||
lookup: "baz",
|
||||
detail: "fn(&self) -> u32",
|
||||
relevance: CompletionRelevance {
|
||||
exact_name_match: false,
|
||||
type_match: None,
|
||||
is_local: false,
|
||||
is_item_from_trait: false,
|
||||
is_item_from_notable_trait: false,
|
||||
is_name_already_imported: false,
|
||||
requires_import: false,
|
||||
is_op_method: false,
|
||||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: true,
|
||||
has_self_param: true,
|
||||
return_type: Other,
|
||||
},
|
||||
),
|
||||
},
|
||||
ref_match: "&@107",
|
||||
},
|
||||
CompletionItem {
|
||||
|
@ -2096,6 +2513,7 @@ fn foo() {
|
|||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: None,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
@ -2133,6 +2551,26 @@ fn main() {
|
|||
),
|
||||
lookup: "foo",
|
||||
detail: "fn() -> S",
|
||||
relevance: CompletionRelevance {
|
||||
exact_name_match: false,
|
||||
type_match: None,
|
||||
is_local: false,
|
||||
is_item_from_trait: false,
|
||||
is_item_from_notable_trait: false,
|
||||
is_name_already_imported: false,
|
||||
requires_import: false,
|
||||
is_op_method: false,
|
||||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: Some(
|
||||
CompletionRelevanceFn {
|
||||
has_params: false,
|
||||
has_self_param: false,
|
||||
return_type: Other,
|
||||
},
|
||||
),
|
||||
},
|
||||
ref_match: "&@92",
|
||||
},
|
||||
]
|
||||
|
@ -2160,6 +2598,7 @@ fn foo() {
|
|||
"#,
|
||||
expect![[r#"
|
||||
lc foo [type+local]
|
||||
ex foo [type]
|
||||
ev Foo::A(…) [type_could_unify]
|
||||
ev Foo::B [type_could_unify]
|
||||
en Foo [type_could_unify]
|
||||
|
@ -2493,6 +2932,7 @@ fn main() {
|
|||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: None,
|
||||
},
|
||||
},
|
||||
CompletionItem {
|
||||
|
@ -2515,6 +2955,7 @@ fn main() {
|
|||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
is_definite: false,
|
||||
function: None,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -8,8 +8,13 @@ use syntax::{format_smolstr, AstNode, SmolStr};
|
|||
|
||||
use crate::{
|
||||
context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
|
||||
item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
|
||||
render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
|
||||
item::{
|
||||
Builder, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevanceFn,
|
||||
CompletionRelevanceReturnType,
|
||||
},
|
||||
render::{
|
||||
compute_exact_name_match, compute_ref_match, compute_type_match, match_types, RenderContext,
|
||||
},
|
||||
CallableSnippets,
|
||||
};
|
||||
|
||||
|
@ -61,9 +66,9 @@ fn render(
|
|||
),
|
||||
_ => (name.unescaped().to_smol_str(), name.to_smol_str()),
|
||||
};
|
||||
|
||||
let has_self_param = func.self_param(db).is_some();
|
||||
let mut item = CompletionItem::new(
|
||||
if func.self_param(db).is_some() {
|
||||
if has_self_param {
|
||||
CompletionItemKind::Method
|
||||
} else {
|
||||
CompletionItemKind::SymbolKind(SymbolKind::Function)
|
||||
|
@ -99,6 +104,15 @@ fn render(
|
|||
.filter(|_| !has_call_parens)
|
||||
.and_then(|cap| Some((cap, params(ctx.completion, func, &func_kind, has_dot_receiver)?)));
|
||||
|
||||
let function = assoc_item
|
||||
.and_then(|assoc_item| assoc_item.implementing_ty(db))
|
||||
.map(|self_type| compute_return_type_match(db, &ctx, self_type, &ret_type))
|
||||
.map(|return_type| CompletionRelevanceFn {
|
||||
has_params: has_self_param || func.num_params(db) > 0,
|
||||
has_self_param,
|
||||
return_type,
|
||||
});
|
||||
|
||||
item.set_relevance(CompletionRelevance {
|
||||
type_match: if has_call_parens || complete_call_parens.is_some() {
|
||||
compute_type_match(completion, &ret_type)
|
||||
|
@ -106,6 +120,7 @@ fn render(
|
|||
compute_type_match(completion, &func.ty(db))
|
||||
},
|
||||
exact_name_match: compute_exact_name_match(completion, &call),
|
||||
function,
|
||||
is_op_method,
|
||||
is_item_from_notable_trait,
|
||||
..ctx.completion_relevance()
|
||||
|
@ -156,6 +171,33 @@ fn render(
|
|||
item
|
||||
}
|
||||
|
||||
fn compute_return_type_match(
|
||||
db: &dyn HirDatabase,
|
||||
ctx: &RenderContext<'_>,
|
||||
self_type: hir::Type,
|
||||
ret_type: &hir::Type,
|
||||
) -> CompletionRelevanceReturnType {
|
||||
if match_types(ctx.completion, &self_type, ret_type).is_some() {
|
||||
// fn([..]) -> Self
|
||||
CompletionRelevanceReturnType::DirectConstructor
|
||||
} else if ret_type
|
||||
.type_arguments()
|
||||
.any(|ret_type_arg| match_types(ctx.completion, &self_type, &ret_type_arg).is_some())
|
||||
{
|
||||
// fn([..]) -> Result<Self, E> OR Wrapped<Foo, Self>
|
||||
CompletionRelevanceReturnType::Constructor
|
||||
} else if ret_type
|
||||
.as_adt()
|
||||
.and_then(|adt| adt.name(db).as_str().map(|name| name.ends_with("Builder")))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
// fn([..]) -> [..]Builder
|
||||
CompletionRelevanceReturnType::Builder
|
||||
} else {
|
||||
CompletionRelevanceReturnType::Other
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_call_parens<'b>(
|
||||
builder: &'b mut Builder,
|
||||
ctx: &CompletionContext<'_>,
|
||||
|
|
|
@ -65,6 +65,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
|
|||
enable_imports_on_the_fly: true,
|
||||
enable_self_on_the_fly: true,
|
||||
enable_private_editable: false,
|
||||
enable_term_search: true,
|
||||
full_function_signatures: false,
|
||||
callable: Some(CallableSnippets::FillArguments),
|
||||
snippet_cap: SnippetCap::new(true),
|
||||
|
|
|
@ -97,6 +97,11 @@ fn func(param0 @ (param1, param2): (i32, i32)) {
|
|||
kw unsafe
|
||||
kw while
|
||||
kw while let
|
||||
ex ifletlocal
|
||||
ex letlocal
|
||||
ex matcharm
|
||||
ex param1
|
||||
ex param2
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -241,6 +246,8 @@ fn complete_in_block() {
|
|||
sn macro_rules
|
||||
sn pd
|
||||
sn ppd
|
||||
ex false
|
||||
ex true
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
@ -542,7 +549,26 @@ fn quux(x: i32) {
|
|||
m!(x$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#""#]],
|
||||
expect![[r#"
|
||||
fn quux(…) fn(i32)
|
||||
lc x i32
|
||||
lc y i32
|
||||
ma m!(…) macro_rules! m
|
||||
bt u32 u32
|
||||
kw crate::
|
||||
kw false
|
||||
kw for
|
||||
kw if
|
||||
kw if let
|
||||
kw loop
|
||||
kw match
|
||||
kw return
|
||||
kw self::
|
||||
kw true
|
||||
kw unsafe
|
||||
kw while
|
||||
kw while let
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -683,6 +709,8 @@ fn main() {
|
|||
"#,
|
||||
expect![[r#"
|
||||
fn test() fn() -> Zulu
|
||||
ex Zulu
|
||||
ex Zulu::test()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1397,3 +1397,22 @@ pub use bridge2::server2::Span2;
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flyimport_only_traits_in_impl_trait_block() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:dep
|
||||
pub struct Bar;
|
||||
|
||||
impl Foo$0 for Bar { }
|
||||
//- /lib.rs crate:dep
|
||||
pub trait FooTrait;
|
||||
|
||||
pub struct FooStruct;
|
||||
"#,
|
||||
expect![[r#"
|
||||
tt FooTrait (use dep::FooTrait)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -192,6 +192,8 @@ fn main() {
|
|||
bt u32 u32
|
||||
kw crate::
|
||||
kw self::
|
||||
ex Foo::default()
|
||||
ex foo
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
|
|
|
@ -695,6 +695,8 @@ fn bar() -> Bar {
|
|||
"#,
|
||||
expect![[r#"
|
||||
fn foo() (as Foo) fn() -> Self
|
||||
ex Bar
|
||||
ex bar()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -722,6 +724,8 @@ fn bar() -> Bar {
|
|||
expect![[r#"
|
||||
fn bar() fn()
|
||||
fn foo() (as Foo) fn() -> Self
|
||||
ex Bar
|
||||
ex bar()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -748,6 +752,8 @@ fn bar() -> Bar {
|
|||
"#,
|
||||
expect![[r#"
|
||||
fn foo() (as Foo) fn() -> Self
|
||||
ex Bar
|
||||
ex bar()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -989,3 +989,43 @@ fn foo<'a>() { S::<'static, F$0, _, _>; }
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_traits_on_impl_trait_block() {
|
||||
check(
|
||||
r#"
|
||||
trait Foo {}
|
||||
|
||||
struct Bar;
|
||||
|
||||
impl $0 for Bar { }
|
||||
"#,
|
||||
expect![[r#"
|
||||
md module
|
||||
tt Foo
|
||||
tt Trait
|
||||
kw crate::
|
||||
kw self::
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_traits_with_path_on_impl_trait_block() {
|
||||
check(
|
||||
r#"
|
||||
mod outer {
|
||||
pub trait Foo {}
|
||||
pub struct Bar;
|
||||
pub mod inner {
|
||||
}
|
||||
}
|
||||
|
||||
impl outer::$0 for Bar { }
|
||||
"#,
|
||||
expect![[r#"
|
||||
md inner
|
||||
tt Foo
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ impl RootDatabase {
|
|||
pub fn request_cancellation(&mut self) {
|
||||
let _p =
|
||||
tracing::span!(tracing::Level::INFO, "RootDatabase::request_cancellation").entered();
|
||||
self.salsa_runtime_mut().synthetic_write(Durability::LOW);
|
||||
self.synthetic_write(Durability::LOW);
|
||||
}
|
||||
|
||||
pub fn apply_change(&mut self, change: Change) {
|
||||
|
@ -124,7 +124,7 @@ impl RootDatabase {
|
|||
hir::db::InternCoroutineQuery
|
||||
hir::db::AssociatedTyDataQuery
|
||||
hir::db::TraitDatumQuery
|
||||
hir::db::StructDatumQuery
|
||||
hir::db::AdtDatumQuery
|
||||
hir::db::ImplDatumQuery
|
||||
hir::db::FnDefDatumQuery
|
||||
hir::db::FnDefVarianceQuery
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
use arrayvec::ArrayVec;
|
||||
use either::Either;
|
||||
use hir::{
|
||||
Adt, AsAssocItem, AssocItem, AttributeTemplate, BuiltinAttr, BuiltinType, Const, Crate,
|
||||
DefWithBody, DeriveHelper, DocLinkDef, ExternCrateDecl, Field, Function, GenericParam,
|
||||
HasVisibility, HirDisplay, Impl, Label, Local, Macro, Module, ModuleDef, Name, PathResolution,
|
||||
Semantics, Static, ToolModule, Trait, TraitAlias, TupleField, TypeAlias, Variant, VariantDef,
|
||||
Visibility,
|
||||
Adt, AsAssocItem, AsExternAssocItem, AssocItem, AttributeTemplate, BuiltinAttr, BuiltinType,
|
||||
Const, Crate, DefWithBody, DeriveHelper, DocLinkDef, ExternAssocItem, ExternCrateDecl, Field,
|
||||
Function, GenericParam, HasVisibility, HirDisplay, Impl, Label, Local, Macro, Module,
|
||||
ModuleDef, Name, PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias, TupleField,
|
||||
TypeAlias, Variant, VariantDef, Visibility,
|
||||
};
|
||||
use stdx::{format_to, impl_from};
|
||||
use syntax::{
|
||||
|
@ -213,8 +213,8 @@ impl Definition {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn label(&self, db: &RootDatabase) -> Option<String> {
|
||||
let label = match *self {
|
||||
pub fn label(&self, db: &RootDatabase) -> String {
|
||||
match *self {
|
||||
Definition::Macro(it) => it.display(db).to_string(),
|
||||
Definition::Field(it) => it.display(db).to_string(),
|
||||
Definition::TupleField(it) => it.display(db).to_string(),
|
||||
|
@ -241,7 +241,11 @@ impl Definition {
|
|||
}
|
||||
}
|
||||
Definition::SelfType(impl_def) => {
|
||||
impl_def.self_ty(db).as_adt().and_then(|adt| Definition::Adt(adt).label(db))?
|
||||
let self_ty = &impl_def.self_ty(db);
|
||||
match self_ty.as_adt() {
|
||||
Some(it) => it.display(db).to_string(),
|
||||
None => self_ty.display(db).to_string(),
|
||||
}
|
||||
}
|
||||
Definition::GenericParam(it) => it.display(db).to_string(),
|
||||
Definition::Label(it) => it.name(db).display(db).to_string(),
|
||||
|
@ -249,8 +253,7 @@ impl Definition {
|
|||
Definition::BuiltinAttr(it) => format!("#[{}]", it.name(db)),
|
||||
Definition::ToolModule(it) => it.name(db).to_string(),
|
||||
Definition::DeriveHelper(it) => format!("derive_helper {}", it.name(db).display(db)),
|
||||
};
|
||||
Some(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -739,6 +742,17 @@ impl AsAssocItem for Definition {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsExternAssocItem for Definition {
|
||||
fn as_extern_assoc_item(self, db: &dyn hir::db::HirDatabase) -> Option<ExternAssocItem> {
|
||||
match self {
|
||||
Definition::Function(it) => it.as_extern_assoc_item(db),
|
||||
Definition::Static(it) => it.as_extern_assoc_item(db),
|
||||
Definition::TypeAlias(it) => it.as_extern_assoc_item(db),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AssocItem> for Definition {
|
||||
fn from(assoc_item: AssocItem) -> Self {
|
||||
match assoc_item {
|
||||
|
|
|
@ -114,6 +114,14 @@ impl FamousDefs<'_, '_> {
|
|||
self.find_function("core:mem:drop")
|
||||
}
|
||||
|
||||
pub fn core_macros_todo(&self) -> Option<Macro> {
|
||||
self.find_macro("core:todo")
|
||||
}
|
||||
|
||||
pub fn core_macros_unimplemented(&self) -> Option<Macro> {
|
||||
self.find_macro("core:unimplemented")
|
||||
}
|
||||
|
||||
pub fn builtin_crates(&self) -> impl Iterator<Item = Crate> {
|
||||
IntoIterator::into_iter([
|
||||
self.std(),
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use hir::PrefixKind;
|
||||
use stdx::trim_indent;
|
||||
use test_fixture::WithFixture;
|
||||
use test_utils::{assert_eq_text, CURSOR_MARKER};
|
||||
|
|
|
@ -280,7 +280,7 @@ impl RootDatabase {
|
|||
// hir_db::InternCoroutineQuery
|
||||
hir_db::AssociatedTyDataQuery
|
||||
hir_db::TraitDatumQuery
|
||||
hir_db::StructDatumQuery
|
||||
hir_db::AdtDatumQuery
|
||||
hir_db::ImplDatumQuery
|
||||
hir_db::FnDefDatumQuery
|
||||
hir_db::FnDefVarianceQuery
|
||||
|
|
|
@ -148,7 +148,7 @@ impl<'a> PathTransform<'a> {
|
|||
let mut defaulted_params: Vec<DefaultedParam> = Default::default();
|
||||
self.generic_def
|
||||
.into_iter()
|
||||
.flat_map(|it| it.type_params(db))
|
||||
.flat_map(|it| it.type_or_const_params(db))
|
||||
.skip(skip)
|
||||
// The actual list of trait type parameters may be longer than the one
|
||||
// used in the `impl` block due to trailing default type parameters.
|
||||
|
|
|
@ -71,7 +71,6 @@ impl Definition {
|
|||
&self,
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
new_name: &str,
|
||||
rename_external: bool,
|
||||
) -> Result<SourceChange> {
|
||||
// self.krate() returns None if
|
||||
// self is a built-in attr, built-in type or tool module.
|
||||
|
@ -80,8 +79,8 @@ impl Definition {
|
|||
if let Some(krate) = self.krate(sema.db) {
|
||||
// Can we not rename non-local items?
|
||||
// Then bail if non-local
|
||||
if !rename_external && !krate.origin(sema.db).is_local() {
|
||||
bail!("Cannot rename a non-local definition as the config for it is disabled")
|
||||
if !krate.origin(sema.db).is_local() {
|
||||
bail!("Cannot rename a non-local definition")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ impl SnippetEdit {
|
|||
.into_iter()
|
||||
.zip(1..)
|
||||
.with_position()
|
||||
.map(|pos| {
|
||||
.flat_map(|pos| {
|
||||
let (snippet, index) = match pos {
|
||||
(itertools::Position::First, it) | (itertools::Position::Middle, it) => it,
|
||||
// last/only snippet gets index 0
|
||||
|
@ -146,11 +146,13 @@ impl SnippetEdit {
|
|||
| (itertools::Position::Only, (snippet, _)) => (snippet, 0),
|
||||
};
|
||||
|
||||
let range = match snippet {
|
||||
Snippet::Tabstop(pos) => TextRange::empty(pos),
|
||||
Snippet::Placeholder(range) => range,
|
||||
};
|
||||
(index, range)
|
||||
match snippet {
|
||||
Snippet::Tabstop(pos) => vec![(index, TextRange::empty(pos))],
|
||||
Snippet::Placeholder(range) => vec![(index, range)],
|
||||
Snippet::PlaceholderGroup(ranges) => {
|
||||
ranges.into_iter().map(|range| (index, range)).collect()
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
|
@ -248,7 +250,7 @@ impl SourceChangeBuilder {
|
|||
fn commit(&mut self) {
|
||||
let snippet_edit = self.snippet_builder.take().map(|builder| {
|
||||
SnippetEdit::new(
|
||||
builder.places.into_iter().map(PlaceSnippet::finalize_position).collect_vec(),
|
||||
builder.places.into_iter().flat_map(PlaceSnippet::finalize_position).collect(),
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -287,30 +289,10 @@ impl SourceChangeBuilder {
|
|||
pub fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
|
||||
self.edit.insert(offset, text.into())
|
||||
}
|
||||
/// Append specified `snippet` at the given `offset`
|
||||
pub fn insert_snippet(
|
||||
&mut self,
|
||||
_cap: SnippetCap,
|
||||
offset: TextSize,
|
||||
snippet: impl Into<String>,
|
||||
) {
|
||||
self.source_change.is_snippet = true;
|
||||
self.insert(offset, snippet);
|
||||
}
|
||||
/// Replaces specified `range` of text with a given string.
|
||||
pub fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
|
||||
self.edit.replace(range, replace_with.into())
|
||||
}
|
||||
/// Replaces specified `range` of text with a given `snippet`.
|
||||
pub fn replace_snippet(
|
||||
&mut self,
|
||||
_cap: SnippetCap,
|
||||
range: TextRange,
|
||||
snippet: impl Into<String>,
|
||||
) {
|
||||
self.source_change.is_snippet = true;
|
||||
self.replace(range, snippet);
|
||||
}
|
||||
pub fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
|
||||
algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
|
||||
}
|
||||
|
@ -356,6 +338,17 @@ impl SourceChangeBuilder {
|
|||
self.add_snippet(PlaceSnippet::Over(node.syntax().clone().into()))
|
||||
}
|
||||
|
||||
/// Adds a snippet to move the cursor selected over `nodes`
|
||||
///
|
||||
/// This allows for renaming newly generated items without having to go
|
||||
/// through a separate rename step.
|
||||
pub fn add_placeholder_snippet_group(&mut self, _cap: SnippetCap, nodes: Vec<SyntaxNode>) {
|
||||
assert!(nodes.iter().all(|node| node.parent().is_some()));
|
||||
self.add_snippet(PlaceSnippet::OverGroup(
|
||||
nodes.into_iter().map(|node| node.into()).collect(),
|
||||
))
|
||||
}
|
||||
|
||||
fn add_snippet(&mut self, snippet: PlaceSnippet) {
|
||||
let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] });
|
||||
snippet_builder.places.push(snippet);
|
||||
|
@ -400,6 +393,13 @@ pub enum Snippet {
|
|||
Tabstop(TextSize),
|
||||
/// A placeholder snippet (e.g. `${0:placeholder}`).
|
||||
Placeholder(TextRange),
|
||||
/// A group of placeholder snippets, e.g.
|
||||
///
|
||||
/// ```no_run
|
||||
/// let ${0:new_var} = 4;
|
||||
/// fun(1, 2, 3, ${0:new_var});
|
||||
/// ```
|
||||
PlaceholderGroup(Vec<TextRange>),
|
||||
}
|
||||
|
||||
enum PlaceSnippet {
|
||||
|
@ -409,14 +409,20 @@ enum PlaceSnippet {
|
|||
After(SyntaxElement),
|
||||
/// Place a placeholder snippet in place of the element
|
||||
Over(SyntaxElement),
|
||||
/// Place a group of placeholder snippets which are linked together
|
||||
/// in place of the elements
|
||||
OverGroup(Vec<SyntaxElement>),
|
||||
}
|
||||
|
||||
impl PlaceSnippet {
|
||||
fn finalize_position(self) -> Snippet {
|
||||
fn finalize_position(self) -> Vec<Snippet> {
|
||||
match self {
|
||||
PlaceSnippet::Before(it) => Snippet::Tabstop(it.text_range().start()),
|
||||
PlaceSnippet::After(it) => Snippet::Tabstop(it.text_range().end()),
|
||||
PlaceSnippet::Over(it) => Snippet::Placeholder(it.text_range()),
|
||||
PlaceSnippet::Before(it) => vec![Snippet::Tabstop(it.text_range().start())],
|
||||
PlaceSnippet::After(it) => vec![Snippet::Tabstop(it.text_range().end())],
|
||||
PlaceSnippet::Over(it) => vec![Snippet::Placeholder(it.text_range())],
|
||||
PlaceSnippet::OverGroup(it) => {
|
||||
vec![Snippet::PlaceholderGroup(it.into_iter().map(|it| it.text_range()).collect())]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue