mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-10 07:04:22 +00:00
Merge commit 'ac998a74b3c8ff4b81c3eeb9a18811d4cc76226d' into sync-from-ra
This commit is contained in:
parent
d33d8675d0
commit
6b17dba68c
178 changed files with 7101 additions and 1965 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
|
||||
|
||||
|
|
20
.github/workflows/ci.yaml
vendored
20
.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
|
||||
|
|
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
|
||||
|
|
16
.github/workflows/metrics.yaml
vendored
16
.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 }}
|
||||
|
@ -73,10 +73,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/
|
||||
|
@ -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
|
||||
|
|
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
|
||||
|
||||
|
|
14
.github/workflows/release.yaml
vendored
14
.github/workflows/release.yaml
vendored
|
@ -59,7 +59,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: ${{ env.FETCH_DEPTH }}
|
||||
|
||||
|
@ -78,9 +78,9 @@ jobs:
|
|||
rustup component add rust-src
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- name: Update apt repositories
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'arm-unknown-linux-gnueabihf'
|
||||
|
@ -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
|
||||
|
|
17
Cargo.lock
generated
17
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",
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
(cmd, args)
|
||||
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(
|
||||
db.upcast(),
|
||||
src.with_value(&src.value[it.local_id()]),
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
)
|
||||
// FIXME: We should be never getting `None` here.
|
||||
match src.value.get(it.local_id()) {
|
||||
Some(val) => RawAttrs::from_attrs_owner(
|
||||
db.upcast(),
|
||||
src.with_value(val),
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
),
|
||||
None => RawAttrs::EMPTY,
|
||||
}
|
||||
}
|
||||
GenericParamId::TypeParamId(it) => {
|
||||
let src = it.parent().child_source(db);
|
||||
RawAttrs::from_attrs_owner(
|
||||
db.upcast(),
|
||||
src.with_value(&src.value[it.local_id()]),
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
)
|
||||
// FIXME: We should be never getting `None` here.
|
||||
match src.value.get(it.local_id()) {
|
||||
Some(val) => RawAttrs::from_attrs_owner(
|
||||
db.upcast(),
|
||||
src.with_value(val),
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
),
|
||||
None => RawAttrs::EMPTY,
|
||||
}
|
||||
}
|
||||
GenericParamId::LifetimeParamId(it) => {
|
||||
let src = it.parent.child_source(db);
|
||||
RawAttrs::from_attrs_owner(
|
||||
db.upcast(),
|
||||
src.with_value(&src.value[it.local_id]),
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
)
|
||||
// FIXME: We should be never getting `None` here.
|
||||
match src.value.get(it.local_id) {
|
||||
Some(val) => RawAttrs::from_attrs_owner(
|
||||
db.upcast(),
|
||||
src.with_value(val),
|
||||
db.span_map(src.file_id).as_ref(),
|
||||
),
|
||||
None => RawAttrs::EMPTY,
|
||||
}
|
||||
}
|
||||
},
|
||||
AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it),
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -169,9 +169,9 @@ impl ExprValidator {
|
|||
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 {
|
||||
|
@ -196,8 +196,9 @@ impl ExprValidator {
|
|||
// 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, &body, &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 +224,7 @@ impl ExprValidator {
|
|||
ValidityConstraint::ValidOnly,
|
||||
) {
|
||||
Ok(report) => report,
|
||||
Err(void) => match void {},
|
||||
Err(()) => return,
|
||||
};
|
||||
|
||||
// FIXME Report unreachable arms
|
||||
|
@ -245,10 +246,10 @@ impl ExprValidator {
|
|||
db: &dyn HirDatabase,
|
||||
body: &Body,
|
||||
have_errors: &mut bool,
|
||||
) -> &'p DeconstructedPat<'p> {
|
||||
) -> DeconstructedPat<'p> {
|
||||
let mut patcx = match_check::PatCtxt::new(db, &self.infer, 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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
@ -688,10 +689,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 +1510,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);
|
||||
|
|
|
@ -502,6 +502,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 +1085,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 +1389,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(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -775,6 +775,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 +1247,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 +1781,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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
@ -2754,7 +2899,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 +3271,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 +3390,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 +3454,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 +3688,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 +3788,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.struct_datum(krate, *adt_id).flags.phantom_data =>
|
||||
{
|
||||
let adt_datum = &db.struct_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 +3939,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 +4582,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 fileds = it.fields(db);
|
||||
// Check if all fields are visible, otherwise we cannot fill them
|
||||
if fileds.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>> = fileds
|
||||
.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,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,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 tha 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
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -682,7 +708,9 @@ fn main() {
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn test() fn() -> Zulu
|
||||
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(
|
||||
|
|
|
@ -225,10 +225,10 @@ impl S {
|
|||
fn foo() { let _ = lib::S::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
ct PUBLIC_CONST pub const PUBLIC_CONST: u32
|
||||
fn public_method() fn()
|
||||
ta PublicType pub type PublicType = u32
|
||||
"#]],
|
||||
ct PUBLIC_CONST pub const PUBLIC_CONST: u32
|
||||
fn public_method() fn()
|
||||
ta PublicType pub type PublicType = u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -242,8 +242,8 @@ impl U { fn m() { } }
|
|||
fn foo() { let _ = U::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn m() fn()
|
||||
"#]],
|
||||
fn m() fn()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -256,8 +256,8 @@ trait Trait { fn m(); }
|
|||
fn foo() { let _ = Trait::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn m() (as Trait) fn()
|
||||
"#]],
|
||||
fn m() (as Trait) fn()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -273,8 +273,8 @@ impl Trait for S {}
|
|||
fn foo() { let _ = S::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn m() (as Trait) fn()
|
||||
"#]],
|
||||
fn m() (as Trait) fn()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -290,8 +290,8 @@ impl Trait for S {}
|
|||
fn foo() { let _ = <S as Trait>::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn m() (as Trait) fn()
|
||||
"#]],
|
||||
fn m() (as Trait) fn()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -396,9 +396,9 @@ macro_rules! foo { () => {} }
|
|||
fn main() { let _ = crate::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn main() fn()
|
||||
ma foo!(…) macro_rules! foo
|
||||
"#]],
|
||||
fn main() fn()
|
||||
ma foo!(…) macro_rules! foo
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -694,8 +694,10 @@ fn bar() -> Bar {
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn foo() (as Foo) fn() -> Self
|
||||
"#]],
|
||||
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
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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())]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -329,6 +329,7 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
|
|||
| ast::Expr::RecordExpr(_)
|
||||
| ast::Expr::RefExpr(_)
|
||||
| ast::Expr::ReturnExpr(_)
|
||||
| ast::Expr::BecomeExpr(_)
|
||||
| ast::Expr::TryExpr(_)
|
||||
| ast::Expr::TupleExpr(_)
|
||||
| ast::Expr::LetExpr(_)
|
||||
|
|
|
@ -43,7 +43,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Ass
|
|||
let label = format!("Rename to {}", d.suggested_text);
|
||||
let mut res = unresolved_fix("change_case", &label, frange.range);
|
||||
if ctx.resolve.should_resolve(&res.id) {
|
||||
let source_change = def.rename(&ctx.sema, &d.suggested_text, true);
|
||||
let source_change = def.rename(&ctx.sema, &d.suggested_text);
|
||||
res.source_change = Some(source_change.ok().unwrap_or_default());
|
||||
}
|
||||
|
||||
|
|
|
@ -242,7 +242,7 @@ macro_rules! foo {
|
|||
|
||||
fn f() {
|
||||
foo!();
|
||||
//^^^ error: invalid macro definition: expected subtree
|
||||
//^^^ error: macro definition has parse errors
|
||||
|
||||
}
|
||||
"#,
|
||||
|
|
|
@ -310,6 +310,24 @@ fn main() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatched_types_issue_15883() {
|
||||
// Check we don't panic.
|
||||
check_diagnostics_no_bails(
|
||||
r#"
|
||||
//- minicore: option
|
||||
fn main() {
|
||||
match Some((true, false)) {
|
||||
Some(true) | Some(false) => {}
|
||||
// ^^^^ error: expected (bool, bool), found bool
|
||||
// ^^^^^ error: expected (bool, bool), found bool
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatched_types_in_or_patterns() {
|
||||
cov_mark::check_count!(validate_match_bailed_out, 2);
|
||||
|
|
|
@ -182,6 +182,18 @@ fn foo() -> u8 {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_diagnostic_if_not_last_statement2() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn foo() -> u8 {
|
||||
return 2;
|
||||
fn bar() {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_with_expr() {
|
||||
check_fix(
|
||||
|
|
|
@ -112,7 +112,8 @@ fn add_missing_ok_or_some(
|
|||
|
||||
let variant_name = if Some(expected_enum) == core_result { "Ok" } else { "Some" };
|
||||
|
||||
let wrapped_actual_ty = expected_adt.ty_with_args(ctx.sema.db, &[d.actual.clone()]);
|
||||
let wrapped_actual_ty =
|
||||
expected_adt.ty_with_args(ctx.sema.db, std::iter::once(d.actual.clone()));
|
||||
|
||||
if !d.expected.could_unify_with(ctx.sema.db, &wrapped_actual_ty) {
|
||||
return None;
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, StructKind};
|
||||
use hir::{
|
||||
db::ExpandDatabase,
|
||||
term_search::{term_search, TermSearchCtx},
|
||||
ClosureStyle, HirDisplay,
|
||||
};
|
||||
use ide_db::{
|
||||
assists::{Assist, AssistId, AssistKind, GroupLabel},
|
||||
label::Label,
|
||||
source_change::SourceChange,
|
||||
};
|
||||
use syntax::AstNode;
|
||||
use itertools::Itertools;
|
||||
use text_edit::TextEdit;
|
||||
|
||||
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
|
||||
|
||||
use syntax::AstNode;
|
||||
|
||||
// Diagnostic: typed-hole
|
||||
//
|
||||
// This diagnostic is triggered when an underscore expression is used in an invalid position.
|
||||
|
@ -36,50 +42,54 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>
|
|||
let (original_range, _) =
|
||||
d.expr.as_ref().map(|it| it.to_node(&root)).syntax().original_file_range_opt(db)?;
|
||||
let scope = ctx.sema.scope(d.expr.value.to_node(&root).syntax())?;
|
||||
let mut assists = vec![];
|
||||
scope.process_all_names(&mut |name, def| {
|
||||
let ty = match def {
|
||||
hir::ScopeDef::ModuleDef(it) => match it {
|
||||
hir::ModuleDef::Function(it) => it.ty(db),
|
||||
hir::ModuleDef::Adt(hir::Adt::Struct(it)) if it.kind(db) != StructKind::Record => {
|
||||
it.constructor_ty(db)
|
||||
}
|
||||
hir::ModuleDef::Variant(it) if it.kind(db) != StructKind::Record => {
|
||||
it.constructor_ty(db)
|
||||
}
|
||||
hir::ModuleDef::Const(it) => it.ty(db),
|
||||
hir::ModuleDef::Static(it) => it.ty(db),
|
||||
_ => return,
|
||||
},
|
||||
hir::ScopeDef::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
|
||||
hir::ScopeDef::Local(it) => it.ty(db),
|
||||
_ => return,
|
||||
};
|
||||
// FIXME: should also check coercions if it is at a coercion site
|
||||
if !ty.contains_unknown() && ty.could_unify_with(db, &d.expected) {
|
||||
assists.push(Assist {
|
||||
id: AssistId("typed-hole", AssistKind::QuickFix),
|
||||
label: Label::new(format!("Replace `_` with `{}`", name.display(db))),
|
||||
group: Some(GroupLabel("Replace `_` with a matching entity in scope".to_owned())),
|
||||
target: original_range.range,
|
||||
source_change: Some(SourceChange::from_text_edit(
|
||||
original_range.file_id,
|
||||
TextEdit::replace(original_range.range, name.display(db).to_string()),
|
||||
)),
|
||||
trigger_signature_help: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
if assists.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
||||
let term_search_ctx = TermSearchCtx {
|
||||
sema: &ctx.sema,
|
||||
scope: &scope,
|
||||
goal: d.expected.clone(),
|
||||
config: Default::default(),
|
||||
};
|
||||
let paths = term_search(&term_search_ctx);
|
||||
|
||||
let mut formatter = |_: &hir::Type| String::from("_");
|
||||
|
||||
let assists: Vec<Assist> = paths
|
||||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
path.gen_source_code(
|
||||
&scope,
|
||||
&mut formatter,
|
||||
ctx.config.prefer_no_std,
|
||||
ctx.config.prefer_prelude,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.unique()
|
||||
.map(|code| Assist {
|
||||
id: AssistId("typed-hole", AssistKind::QuickFix),
|
||||
label: Label::new(format!("Replace `_` with `{}`", &code)),
|
||||
group: Some(GroupLabel("Replace `_` with a term".to_owned())),
|
||||
target: original_range.range,
|
||||
source_change: Some(SourceChange::from_text_edit(
|
||||
original_range.file_id,
|
||||
TextEdit::replace(original_range.range, code),
|
||||
)),
|
||||
trigger_signature_help: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !assists.is_empty() {
|
||||
Some(assists)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_diagnostics, check_fixes};
|
||||
use crate::tests::{
|
||||
check_diagnostics, check_fixes_unordered, check_has_fix, check_has_single_fix,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn unknown() {
|
||||
|
@ -99,7 +109,7 @@ fn main() {
|
|||
r#"
|
||||
fn main() {
|
||||
if _ {}
|
||||
//^ error: invalid `_` expression, expected type `bool`
|
||||
//^ 💡 error: invalid `_` expression, expected type `bool`
|
||||
let _: fn() -> i32 = _;
|
||||
//^ error: invalid `_` expression, expected type `fn() -> i32`
|
||||
let _: fn() -> () = _; // FIXME: This should trigger an assist because `main` matches via *coercion*
|
||||
|
@ -129,7 +139,7 @@ fn main() {
|
|||
fn main() {
|
||||
let mut x = t();
|
||||
x = _;
|
||||
//^ 💡 error: invalid `_` expression, expected type `&str`
|
||||
//^ error: invalid `_` expression, expected type `&str`
|
||||
x = "";
|
||||
}
|
||||
fn t<T>() -> T { loop {} }
|
||||
|
@ -143,7 +153,8 @@ fn t<T>() -> T { loop {} }
|
|||
r#"
|
||||
fn main() {
|
||||
let _x = [(); _];
|
||||
let _y: [(); 10] = [(); _];
|
||||
// FIXME: This should trigger error
|
||||
// let _y: [(); 10] = [(); _];
|
||||
_ = 0;
|
||||
(_,) = (1,);
|
||||
}
|
||||
|
@ -153,7 +164,7 @@ fn main() {
|
|||
|
||||
#[test]
|
||||
fn check_quick_fix() {
|
||||
check_fixes(
|
||||
check_fixes_unordered(
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar
|
||||
|
@ -173,6 +184,18 @@ enum Foo {
|
|||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = Bar;
|
||||
//^ error: invalid `_` expression, expected type `fn()`
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar
|
||||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = local;
|
||||
|
@ -209,18 +232,6 @@ enum Foo {
|
|||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = Bar;
|
||||
//^ error: invalid `_` expression, expected type `fn()`
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar
|
||||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = C;
|
||||
|
@ -230,4 +241,153 @@ fn main<const CP: Foo>(param: Foo) {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_item_use_trait() {
|
||||
check_has_fix(
|
||||
r#"
|
||||
struct Bar;
|
||||
struct Baz;
|
||||
trait Foo {
|
||||
fn foo(self) -> Bar;
|
||||
}
|
||||
impl Foo for Baz {
|
||||
fn foo(self) -> Bar {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
fn asd() -> Bar {
|
||||
let a = Baz;
|
||||
_$0
|
||||
}
|
||||
"#,
|
||||
r"
|
||||
struct Bar;
|
||||
struct Baz;
|
||||
trait Foo {
|
||||
fn foo(self) -> Bar;
|
||||
}
|
||||
impl Foo for Baz {
|
||||
fn foo(self) -> Bar {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
fn asd() -> Bar {
|
||||
let a = Baz;
|
||||
Foo::foo(a)
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_struct() {
|
||||
check_has_fix(
|
||||
r#"struct Abc {}
|
||||
struct Qwe { a: i32, b: Abc }
|
||||
fn main() {
|
||||
let a: i32 = 1;
|
||||
let c: Qwe = _$0;
|
||||
}"#,
|
||||
r#"struct Abc {}
|
||||
struct Qwe { a: i32, b: Abc }
|
||||
fn main() {
|
||||
let a: i32 = 1;
|
||||
let c: Qwe = Qwe { a: a, b: Abc { } };
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_impl_func_with_incorrect_return() {
|
||||
check_has_single_fix(
|
||||
r#"
|
||||
struct Bar {}
|
||||
trait Foo {
|
||||
type Res;
|
||||
fn foo(&self) -> Self::Res;
|
||||
}
|
||||
impl Foo for i32 {
|
||||
type Res = Self;
|
||||
fn foo(&self) -> Self::Res { 1 }
|
||||
}
|
||||
fn main() {
|
||||
let a: i32 = 1;
|
||||
let c: Bar = _$0;
|
||||
}"#,
|
||||
r#"
|
||||
struct Bar {}
|
||||
trait Foo {
|
||||
type Res;
|
||||
fn foo(&self) -> Self::Res;
|
||||
}
|
||||
impl Foo for i32 {
|
||||
type Res = Self;
|
||||
fn foo(&self) -> Self::Res { 1 }
|
||||
}
|
||||
fn main() {
|
||||
let a: i32 = 1;
|
||||
let c: Bar = Bar { };
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_impl_func_with_correct_return() {
|
||||
check_has_fix(
|
||||
r#"
|
||||
struct Bar {}
|
||||
struct A;
|
||||
trait Foo {
|
||||
type Res;
|
||||
fn foo(&self) -> Self::Res;
|
||||
}
|
||||
impl Foo for A {
|
||||
type Res = Bar;
|
||||
fn foo(&self) -> Self::Res { Bar { } }
|
||||
}
|
||||
fn main() {
|
||||
let a = A;
|
||||
let c: Bar = _$0;
|
||||
}"#,
|
||||
r#"
|
||||
struct Bar {}
|
||||
struct A;
|
||||
trait Foo {
|
||||
type Res;
|
||||
fn foo(&self) -> Self::Res;
|
||||
}
|
||||
impl Foo for A {
|
||||
type Res = Bar;
|
||||
fn foo(&self) -> Self::Res { Bar { } }
|
||||
}
|
||||
fn main() {
|
||||
let a = A;
|
||||
let c: Bar = Foo::foo(&a);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_shadow_fn() {
|
||||
check_fixes_unordered(
|
||||
r#"
|
||||
fn f() {
|
||||
let f: i32 = 0;
|
||||
_$0
|
||||
}"#,
|
||||
vec![
|
||||
r#"
|
||||
fn f() {
|
||||
let f: i32 = 0;
|
||||
()
|
||||
}"#,
|
||||
r#"
|
||||
fn f() {
|
||||
let f: i32 = 0;
|
||||
crate::f()
|
||||
}"#,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,6 +91,91 @@ fn check_nth_fix_with_config(
|
|||
assert_eq_text!(&after, &actual);
|
||||
}
|
||||
|
||||
pub(crate) fn check_fixes_unordered(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) {
|
||||
for ra_fixture_after in ra_fixtures_after.iter() {
|
||||
check_has_fix(ra_fixture_before, ra_fixture_after)
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn check_has_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
let after = trim_indent(ra_fixture_after);
|
||||
|
||||
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
|
||||
let mut conf = DiagnosticsConfig::test_sample();
|
||||
conf.expr_fill_default = ExprFillDefaultMode::Default;
|
||||
let fix = super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id)
|
||||
.into_iter()
|
||||
.find(|d| {
|
||||
d.fixes
|
||||
.as_ref()
|
||||
.and_then(|fixes| {
|
||||
fixes.iter().find(|fix| {
|
||||
if !fix.target.contains_inclusive(file_position.offset) {
|
||||
return false;
|
||||
}
|
||||
let actual = {
|
||||
let source_change = fix.source_change.as_ref().unwrap();
|
||||
let file_id = *source_change.source_file_edits.keys().next().unwrap();
|
||||
let mut actual = db.file_text(file_id).to_string();
|
||||
|
||||
for (edit, snippet_edit) in source_change.source_file_edits.values() {
|
||||
edit.apply(&mut actual);
|
||||
if let Some(snippet_edit) = snippet_edit {
|
||||
snippet_edit.apply(&mut actual);
|
||||
}
|
||||
}
|
||||
actual
|
||||
};
|
||||
after == actual
|
||||
})
|
||||
})
|
||||
.is_some()
|
||||
});
|
||||
assert!(fix.is_some(), "no diagnostic with desired fix");
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn check_has_single_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
let after = trim_indent(ra_fixture_after);
|
||||
|
||||
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
|
||||
let mut conf = DiagnosticsConfig::test_sample();
|
||||
conf.expr_fill_default = ExprFillDefaultMode::Default;
|
||||
let mut n_fixes = 0;
|
||||
let fix = super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id)
|
||||
.into_iter()
|
||||
.find(|d| {
|
||||
d.fixes
|
||||
.as_ref()
|
||||
.and_then(|fixes| {
|
||||
n_fixes += fixes.len();
|
||||
fixes.iter().find(|fix| {
|
||||
if !fix.target.contains_inclusive(file_position.offset) {
|
||||
return false;
|
||||
}
|
||||
let actual = {
|
||||
let source_change = fix.source_change.as_ref().unwrap();
|
||||
let file_id = *source_change.source_file_edits.keys().next().unwrap();
|
||||
let mut actual = db.file_text(file_id).to_string();
|
||||
|
||||
for (edit, snippet_edit) in source_change.source_file_edits.values() {
|
||||
edit.apply(&mut actual);
|
||||
if let Some(snippet_edit) = snippet_edit {
|
||||
snippet_edit.apply(&mut actual);
|
||||
}
|
||||
}
|
||||
actual
|
||||
};
|
||||
after == actual
|
||||
})
|
||||
})
|
||||
.is_some()
|
||||
});
|
||||
assert!(fix.is_some(), "no diagnostic with desired fix");
|
||||
assert!(n_fixes == 1, "Too many fixes suggested");
|
||||
}
|
||||
|
||||
/// Checks that there's a diagnostic *without* fix at `$0`.
|
||||
pub(crate) fn check_no_fix(ra_fixture: &str) {
|
||||
let (db, file_position) = RootDatabase::with_position(ra_fixture);
|
||||
|
|
|
@ -501,7 +501,7 @@ fn get_doc_base_urls(
|
|||
let Some(krate) = def.krate(db) else { return Default::default() };
|
||||
let Some(display_name) = krate.display_name(db) else { return Default::default() };
|
||||
let crate_data = &db.crate_graph()[krate.into()];
|
||||
let channel = crate_data.channel().unwrap_or(ReleaseChannel::Nightly).as_str();
|
||||
let channel = db.toolchain_channel(krate.into()).unwrap_or(ReleaseChannel::Nightly).as_str();
|
||||
|
||||
let (web_base, local_base) = match &crate_data.origin {
|
||||
// std and co do not specify `html_root_url` any longer so we gotta handwrite this ourself.
|
||||
|
|
|
@ -7263,8 +7263,8 @@ impl Iterator for S {
|
|||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 6157..6365,
|
||||
focus_range: 6222..6228,
|
||||
full_range: 6290..6498,
|
||||
focus_range: 6355..6361,
|
||||
name: "Future",
|
||||
kind: Trait,
|
||||
container_name: "future",
|
||||
|
@ -7277,8 +7277,8 @@ impl Iterator for S {
|
|||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 6995..7461,
|
||||
focus_range: 7039..7047,
|
||||
full_range: 7128..7594,
|
||||
focus_range: 7172..7180,
|
||||
name: "Iterator",
|
||||
kind: Trait,
|
||||
container_name: "iterator",
|
||||
|
|
|
@ -12,11 +12,6 @@
|
|||
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
|
||||
#![recursion_limit = "128"]
|
||||
|
||||
#[allow(unused)]
|
||||
macro_rules! eprintln {
|
||||
($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod fixture;
|
||||
|
||||
|
@ -258,11 +253,11 @@ impl Analysis {
|
|||
Env::default(),
|
||||
false,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Err("Analysis::from_single_file has no target layout".into()),
|
||||
None,
|
||||
);
|
||||
change.change_file(file_id, Some(Arc::from(text)));
|
||||
change.set_crate_graph(crate_graph);
|
||||
change.set_target_data_layouts(vec![Err("fixture has no layout".into())]);
|
||||
change.set_toolchains(vec![None]);
|
||||
host.apply_change(change);
|
||||
(host.analysis(), file_id)
|
||||
}
|
||||
|
@ -680,9 +675,8 @@ impl Analysis {
|
|||
&self,
|
||||
position: FilePosition,
|
||||
new_name: &str,
|
||||
rename_external: bool,
|
||||
) -> Cancellable<Result<SourceChange, RenameError>> {
|
||||
self.with_db(|db| rename::rename(db, position, new_name, rename_external))
|
||||
self.with_db(|db| rename::rename(db, position, new_name))
|
||||
}
|
||||
|
||||
pub fn prepare_rename(
|
||||
|
|
|
@ -54,7 +54,7 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<Na
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns `Vec` for the same reason as `parent_module`
|
||||
/// This returns `Vec` because a module may be included from several places.
|
||||
pub(crate) fn crates_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
|
||||
db.relevant_crates(file_id)
|
||||
.iter()
|
||||
|
|
|
@ -84,7 +84,6 @@ pub(crate) fn rename(
|
|||
db: &RootDatabase,
|
||||
position: FilePosition,
|
||||
new_name: &str,
|
||||
rename_external: bool,
|
||||
) -> RenameResult<SourceChange> {
|
||||
let sema = Semantics::new(db);
|
||||
let source_file = sema.parse(position.file_id);
|
||||
|
@ -104,7 +103,7 @@ pub(crate) fn rename(
|
|||
return rename_to_self(&sema, local);
|
||||
}
|
||||
}
|
||||
def.rename(&sema, new_name, rename_external)
|
||||
def.rename(&sema, new_name)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -123,9 +122,9 @@ pub(crate) fn will_rename_file(
|
|||
let module = sema.to_module_def(file_id)?;
|
||||
let def = Definition::Module(module);
|
||||
let mut change = if is_raw_identifier(new_name_stem) {
|
||||
def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem]), true).ok()?
|
||||
def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem])).ok()?
|
||||
} else {
|
||||
def.rename(&sema, new_name_stem, true).ok()?
|
||||
def.rename(&sema, new_name_stem).ok()?
|
||||
};
|
||||
change.file_system_edits.clear();
|
||||
Some(change)
|
||||
|
@ -377,16 +376,11 @@ mod tests {
|
|||
use super::{RangeInfo, RenameError};
|
||||
|
||||
fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
check_with_rename_config(new_name, ra_fixture_before, ra_fixture_after, true);
|
||||
check_with_rename_config(new_name, ra_fixture_before, ra_fixture_after);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_with_rename_config(
|
||||
new_name: &str,
|
||||
ra_fixture_before: &str,
|
||||
ra_fixture_after: &str,
|
||||
rename_external: bool,
|
||||
) {
|
||||
fn check_with_rename_config(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
let ra_fixture_after = &trim_indent(ra_fixture_after);
|
||||
let (analysis, position) = fixture::position(ra_fixture_before);
|
||||
if !ra_fixture_after.starts_with("error: ") {
|
||||
|
@ -395,7 +389,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
let rename_result = analysis
|
||||
.rename(position, new_name, rename_external)
|
||||
.rename(position, new_name)
|
||||
.unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));
|
||||
match rename_result {
|
||||
Ok(source_change) => {
|
||||
|
@ -426,10 +420,8 @@ mod tests {
|
|||
|
||||
fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
|
||||
let (analysis, position) = fixture::position(ra_fixture);
|
||||
let source_change = analysis
|
||||
.rename(position, new_name, true)
|
||||
.unwrap()
|
||||
.expect("Expect returned a RenameError");
|
||||
let source_change =
|
||||
analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError");
|
||||
expect.assert_eq(&filter_expect(source_change))
|
||||
}
|
||||
|
||||
|
@ -2636,19 +2628,7 @@ pub struct S;
|
|||
//- /main.rs crate:main deps:lib new_source_root:local
|
||||
use lib::S$0;
|
||||
"#,
|
||||
"error: Cannot rename a non-local definition as the config for it is disabled",
|
||||
false,
|
||||
);
|
||||
|
||||
check(
|
||||
"Baz",
|
||||
r#"
|
||||
//- /lib.rs crate:lib new_source_root:library
|
||||
pub struct S;
|
||||
//- /main.rs crate:main deps:lib new_source_root:local
|
||||
use lib::S$0;
|
||||
"#,
|
||||
"use lib::Baz;\n",
|
||||
"error: Cannot rename a non-local definition",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2663,8 +2643,7 @@ use core::hash::Hash;
|
|||
#[derive(H$0ash)]
|
||||
struct A;
|
||||
"#,
|
||||
"error: Cannot rename a non-local definition as the config for it is disabled",
|
||||
false,
|
||||
"error: Cannot rename a non-local definition",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,8 +39,6 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
|
|||
data.env.clone(),
|
||||
data.is_proc_macro,
|
||||
data.origin.clone(),
|
||||
data.target_layout.clone(),
|
||||
data.toolchain.clone(),
|
||||
);
|
||||
new_proc_macros.insert(new_id, proc_macros[&old_id].clone());
|
||||
map.insert(old_id, new_id);
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
//! This module provides `StaticIndex` which is used for powering
|
||||
//! read-only code browsers and emitting LSIF
|
||||
|
||||
use hir::{db::HirDatabase, Crate, HirFileIdExt, Module};
|
||||
use hir::{db::HirDatabase, Crate, HirFileIdExt, Module, Semantics};
|
||||
use ide_db::{
|
||||
base_db::{FileId, FileRange, SourceDatabaseExt},
|
||||
defs::Definition,
|
||||
documentation::Documentation,
|
||||
famous_defs::FamousDefs,
|
||||
helpers::get_definition,
|
||||
FxHashMap, FxHashSet, RootDatabase,
|
||||
};
|
||||
use syntax::{AstNode, SyntaxKind::*, TextRange, T};
|
||||
use syntax::{AstNode, SyntaxKind::*, SyntaxNode, TextRange, T};
|
||||
|
||||
use crate::inlay_hints::InlayFieldsToResolve;
|
||||
use crate::navigation_target::UpmappingResult;
|
||||
|
@ -22,7 +24,7 @@ use crate::{
|
|||
|
||||
/// A static representation of fully analyzed source code.
|
||||
///
|
||||
/// The intended use-case is powering read-only code browsers and emitting LSIF
|
||||
/// The intended use-case is powering read-only code browsers and emitting LSIF/SCIP.
|
||||
#[derive(Debug)]
|
||||
pub struct StaticIndex<'a> {
|
||||
pub files: Vec<StaticIndexedFile>,
|
||||
|
@ -40,6 +42,7 @@ pub struct ReferenceData {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct TokenStaticData {
|
||||
pub documentation: Option<Documentation>,
|
||||
pub hover: Option<HoverResult>,
|
||||
pub definition: Option<FileRange>,
|
||||
pub references: Vec<ReferenceData>,
|
||||
|
@ -103,6 +106,19 @@ fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
|
|||
modules
|
||||
}
|
||||
|
||||
fn documentation_for_definition(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
def: Definition,
|
||||
scope_node: &SyntaxNode,
|
||||
) -> Option<Documentation> {
|
||||
let famous_defs = match &def {
|
||||
Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(scope_node)?.krate())),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
def.docs(sema.db, famous_defs.as_ref())
|
||||
}
|
||||
|
||||
impl StaticIndex<'_> {
|
||||
fn add_file(&mut self, file_id: FileId) {
|
||||
let current_crate = crates_for(self.db, file_id).pop().map(Into::into);
|
||||
|
@ -169,6 +185,7 @@ impl StaticIndex<'_> {
|
|||
*it
|
||||
} else {
|
||||
let it = self.tokens.insert(TokenStaticData {
|
||||
documentation: documentation_for_definition(&sema, def, &node),
|
||||
hover: hover_for_definition(&sema, file_id, def, &node, &hover_config),
|
||||
definition: def.try_to_nav(self.db).map(UpmappingResult::call_site).map(|it| {
|
||||
FileRange { file_id: it.file_id, range: it.focus_or_full_range() }
|
||||
|
|
|
@ -72,8 +72,6 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
|
|||
dependencies,
|
||||
origin,
|
||||
is_proc_macro,
|
||||
target_layout,
|
||||
toolchain,
|
||||
} = &crate_graph[crate_id];
|
||||
format_to!(
|
||||
buf,
|
||||
|
@ -91,12 +89,6 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
|
|||
format_to!(buf, " Env: {:?}\n", env);
|
||||
format_to!(buf, " Origin: {:?}\n", origin);
|
||||
format_to!(buf, " Is a proc macro crate: {}\n", is_proc_macro);
|
||||
format_to!(buf, " Workspace Target Layout: {:?}\n", target_layout);
|
||||
format_to!(
|
||||
buf,
|
||||
" Workspace Toolchain: {}\n",
|
||||
toolchain.as_ref().map_or_else(|| "n/a".into(), |v| v.to_string())
|
||||
);
|
||||
let deps = dependencies
|
||||
.iter()
|
||||
.map(|dep| format!("{}={}", dep.name, dep.crate_id.into_raw()))
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//! for incorporating changes.
|
||||
// Note, don't remove any public api from this. This API is consumed by external tools
|
||||
// to run rust-analyzer as a library.
|
||||
use std::{collections::hash_map::Entry, mem, path::Path, sync};
|
||||
use std::{collections::hash_map::Entry, iter, mem, path::Path, sync};
|
||||
|
||||
use crossbeam_channel::{unbounded, Receiver};
|
||||
use hir_expand::proc_macro::{
|
||||
|
@ -18,7 +18,6 @@ use itertools::Itertools;
|
|||
use proc_macro_api::{MacroDylib, ProcMacroServer};
|
||||
use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace};
|
||||
use span::Span;
|
||||
use tt::DelimSpan;
|
||||
use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf, VfsPath};
|
||||
|
||||
pub struct LoadCargoConfig {
|
||||
|
@ -68,9 +67,9 @@ pub fn load_workspace(
|
|||
let proc_macro_server = match &load_config.with_proc_macro_server {
|
||||
ProcMacroServerChoice::Sysroot => ws
|
||||
.find_sysroot_proc_macro_srv()
|
||||
.and_then(|it| ProcMacroServer::spawn(it).map_err(Into::into)),
|
||||
.and_then(|it| ProcMacroServer::spawn(it, extra_env).map_err(Into::into)),
|
||||
ProcMacroServerChoice::Explicit(path) => {
|
||||
ProcMacroServer::spawn(path.clone()).map_err(Into::into)
|
||||
ProcMacroServer::spawn(path.clone(), extra_env).map_err(Into::into)
|
||||
}
|
||||
ProcMacroServerChoice::None => Err(anyhow::format_err!("proc macro server disabled")),
|
||||
};
|
||||
|
@ -107,7 +106,7 @@ pub fn load_workspace(
|
|||
.collect()
|
||||
};
|
||||
|
||||
let project_folders = ProjectFolders::new(&[ws], &[]);
|
||||
let project_folders = ProjectFolders::new(std::slice::from_ref(&ws), &[]);
|
||||
loader.set_config(vfs::loader::Config {
|
||||
load: project_folders.load,
|
||||
watch: vec![],
|
||||
|
@ -115,6 +114,7 @@ pub fn load_workspace(
|
|||
});
|
||||
|
||||
let host = load_crate_graph(
|
||||
&ws,
|
||||
crate_graph,
|
||||
proc_macros,
|
||||
project_folders.source_root_config,
|
||||
|
@ -273,7 +273,7 @@ impl SourceRootConfig {
|
|||
pub fn load_proc_macro(
|
||||
server: &ProcMacroServer,
|
||||
path: &AbsPath,
|
||||
dummy_replace: &[Box<str>],
|
||||
ignored_macros: &[Box<str>],
|
||||
) -> ProcMacroLoadResult {
|
||||
let res: Result<Vec<_>, String> = (|| {
|
||||
let dylib = MacroDylib::new(path.to_path_buf());
|
||||
|
@ -283,7 +283,7 @@ pub fn load_proc_macro(
|
|||
}
|
||||
Ok(vec
|
||||
.into_iter()
|
||||
.map(|expander| expander_to_proc_macro(expander, dummy_replace))
|
||||
.map(|expander| expander_to_proc_macro(expander, ignored_macros))
|
||||
.collect())
|
||||
})();
|
||||
match res {
|
||||
|
@ -302,6 +302,7 @@ pub fn load_proc_macro(
|
|||
}
|
||||
|
||||
fn load_crate_graph(
|
||||
ws: &ProjectWorkspace,
|
||||
crate_graph: CrateGraph,
|
||||
proc_macros: ProcMacros,
|
||||
source_root_config: SourceRootConfig,
|
||||
|
@ -340,8 +341,17 @@ fn load_crate_graph(
|
|||
let source_roots = source_root_config.partition(vfs);
|
||||
analysis_change.set_roots(source_roots);
|
||||
|
||||
let num_crates = crate_graph.len();
|
||||
analysis_change.set_crate_graph(crate_graph);
|
||||
analysis_change.set_proc_macros(proc_macros);
|
||||
if let ProjectWorkspace::Cargo { toolchain, target_layout, .. }
|
||||
| ProjectWorkspace::Json { toolchain, target_layout, .. } = ws
|
||||
{
|
||||
analysis_change.set_target_data_layouts(
|
||||
iter::repeat(target_layout.clone()).take(num_crates).collect(),
|
||||
);
|
||||
analysis_change.set_toolchains(iter::repeat(toolchain.clone()).take(num_crates).collect());
|
||||
}
|
||||
|
||||
host.apply_change(analysis_change);
|
||||
host
|
||||
|
@ -349,7 +359,7 @@ fn load_crate_graph(
|
|||
|
||||
fn expander_to_proc_macro(
|
||||
expander: proc_macro_api::ProcMacro,
|
||||
dummy_replace: &[Box<str>],
|
||||
ignored_macros: &[Box<str>],
|
||||
) -> ProcMacro {
|
||||
let name = From::from(expander.name());
|
||||
let kind = match expander.kind() {
|
||||
|
@ -357,16 +367,8 @@ fn expander_to_proc_macro(
|
|||
proc_macro_api::ProcMacroKind::FuncLike => ProcMacroKind::FuncLike,
|
||||
proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
|
||||
};
|
||||
let expander: sync::Arc<dyn ProcMacroExpander> =
|
||||
if dummy_replace.iter().any(|replace| **replace == name) {
|
||||
match kind {
|
||||
ProcMacroKind::Attr => sync::Arc::new(IdentityExpander),
|
||||
_ => sync::Arc::new(EmptyExpander),
|
||||
}
|
||||
} else {
|
||||
sync::Arc::new(Expander(expander))
|
||||
};
|
||||
ProcMacro { name, kind, expander }
|
||||
let disabled = ignored_macros.iter().any(|replace| **replace == name);
|
||||
ProcMacro { name, kind, expander: sync::Arc::new(Expander(expander)), disabled }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -391,42 +393,6 @@ impl ProcMacroExpander for Expander {
|
|||
}
|
||||
}
|
||||
|
||||
/// Dummy identity expander, used for attribute proc-macros that are deliberately ignored by the user.
|
||||
#[derive(Debug)]
|
||||
struct IdentityExpander;
|
||||
|
||||
impl ProcMacroExpander for IdentityExpander {
|
||||
fn expand(
|
||||
&self,
|
||||
subtree: &tt::Subtree<Span>,
|
||||
_: Option<&tt::Subtree<Span>>,
|
||||
_: &Env,
|
||||
_: Span,
|
||||
_: Span,
|
||||
_: Span,
|
||||
) -> Result<tt::Subtree<Span>, ProcMacroExpansionError> {
|
||||
Ok(subtree.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Empty expander, used for proc-macros that are deliberately ignored by the user.
|
||||
#[derive(Debug)]
|
||||
struct EmptyExpander;
|
||||
|
||||
impl ProcMacroExpander for EmptyExpander {
|
||||
fn expand(
|
||||
&self,
|
||||
_: &tt::Subtree<Span>,
|
||||
_: Option<&tt::Subtree<Span>>,
|
||||
_: &Env,
|
||||
call_site: Span,
|
||||
_: Span,
|
||||
_: Span,
|
||||
) -> Result<tt::Subtree<Span>, ProcMacroExpansionError> {
|
||||
Ok(tt::Subtree::empty(DelimSpan { open: call_site, close: call_site }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ide_db::base_db::SourceDatabase;
|
||||
|
|
|
@ -101,10 +101,20 @@ impl<S: Span> Bindings<S> {
|
|||
})))
|
||||
}
|
||||
MetaVarKind::Lifetime => {
|
||||
Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
|
||||
text: SmolStr::new_static("'missing"),
|
||||
span,
|
||||
})))
|
||||
Fragment::Tokens(tt::TokenTree::Subtree(tt::Subtree {
|
||||
delimiter: tt::Delimiter::invisible_spanned(span),
|
||||
token_trees: Box::new([
|
||||
tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct {
|
||||
char: '\'',
|
||||
span,
|
||||
spacing: tt::Spacing::Joint,
|
||||
})),
|
||||
tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
|
||||
text: SmolStr::new_static("missing"),
|
||||
span,
|
||||
})),
|
||||
]),
|
||||
}))
|
||||
}
|
||||
MetaVarKind::Literal => {
|
||||
Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
|
||||
|
|
|
@ -700,10 +700,12 @@ impl<S> SynToken<S> {
|
|||
}
|
||||
|
||||
impl<SpanMap, S: std::fmt::Debug> SrcToken<Converter<SpanMap, S>, S> for SynToken<S> {
|
||||
fn kind(&self, ctx: &Converter<SpanMap, S>) -> SyntaxKind {
|
||||
fn kind(&self, _ctx: &Converter<SpanMap, S>) -> SyntaxKind {
|
||||
match self {
|
||||
SynToken::Ordinary(token) => token.kind(),
|
||||
SynToken::Punct { .. } => SyntaxKind::from_char(self.to_char(ctx).unwrap()).unwrap(),
|
||||
SynToken::Punct { token, offset: i } => {
|
||||
SyntaxKind::from_char(token.text().chars().nth(*i).unwrap()).unwrap()
|
||||
}
|
||||
SynToken::Leaf(_) => {
|
||||
never!();
|
||||
SyntaxKind::ERROR
|
||||
|
|
|
@ -678,27 +678,38 @@ pub(crate) fn record_expr_field_list(p: &mut Parser<'_>) {
|
|||
attributes::outer_attrs(p);
|
||||
|
||||
match p.current() {
|
||||
IDENT | INT_NUMBER => {
|
||||
IDENT | INT_NUMBER if p.nth_at(1, T![::]) => {
|
||||
// test_err record_literal_missing_ellipsis_recovery
|
||||
// fn main() {
|
||||
// S { S::default() }
|
||||
// }
|
||||
if p.nth_at(1, T![::]) {
|
||||
m.abandon(p);
|
||||
p.expect(T![..]);
|
||||
expr(p);
|
||||
} else {
|
||||
m.abandon(p);
|
||||
p.expect(T![..]);
|
||||
expr(p);
|
||||
}
|
||||
IDENT | INT_NUMBER => {
|
||||
if p.nth_at(1, T![..]) {
|
||||
// test_err record_literal_before_ellipsis_recovery
|
||||
// fn main() {
|
||||
// S { field ..S::default() }
|
||||
// }
|
||||
if p.nth_at(1, T![:]) || p.nth_at(1, T![..]) {
|
||||
name_ref_or_index(p);
|
||||
p.error("expected `:`");
|
||||
} else {
|
||||
// test_err record_literal_field_eq_recovery
|
||||
// fn main() {
|
||||
// S { field = foo }
|
||||
// }
|
||||
if p.nth_at(1, T![:]) {
|
||||
name_ref_or_index(p);
|
||||
p.expect(T![:]);
|
||||
p.bump(T![:]);
|
||||
} else if p.nth_at(1, T![=]) {
|
||||
name_ref_or_index(p);
|
||||
p.err_and_bump("expected `:`");
|
||||
}
|
||||
expr(p);
|
||||
m.complete(p, RECORD_EXPR_FIELD);
|
||||
}
|
||||
m.complete(p, RECORD_EXPR_FIELD);
|
||||
}
|
||||
T![.] if p.at(T![..]) => {
|
||||
m.abandon(p);
|
||||
|
|
|
@ -58,6 +58,7 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet =
|
|||
T![match],
|
||||
T![move],
|
||||
T![return],
|
||||
T![become],
|
||||
T![static],
|
||||
T![try],
|
||||
T![unsafe],
|
||||
|
@ -102,6 +103,7 @@ pub(super) fn atom_expr(
|
|||
T![try] => try_block_expr(p, None),
|
||||
T![match] => match_expr(p),
|
||||
T![return] => return_expr(p),
|
||||
T![become] => become_expr(p),
|
||||
T![yield] => yield_expr(p),
|
||||
T![do] if p.nth_at_contextual_kw(1, T![yeet]) => yeet_expr(p),
|
||||
T![continue] => continue_expr(p),
|
||||
|
@ -621,6 +623,18 @@ fn return_expr(p: &mut Parser<'_>) -> CompletedMarker {
|
|||
m.complete(p, RETURN_EXPR)
|
||||
}
|
||||
|
||||
// test become_expr
|
||||
// fn foo() {
|
||||
// become foo();
|
||||
// }
|
||||
fn become_expr(p: &mut Parser<'_>) -> CompletedMarker {
|
||||
assert!(p.at(T![become]));
|
||||
let m = p.start();
|
||||
p.bump(T![become]);
|
||||
expr(p);
|
||||
m.complete(p, BECOME_EXPR)
|
||||
}
|
||||
|
||||
// test yield_expr
|
||||
// fn foo() {
|
||||
// yield;
|
||||
|
|
|
@ -157,6 +157,16 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
|
|||
p.bump_any();
|
||||
p.expect(T![const]);
|
||||
}
|
||||
// test const_trait_bound
|
||||
// const fn foo(_: impl const Trait) {}
|
||||
T![const] => {
|
||||
p.bump_any();
|
||||
}
|
||||
// test async_trait_bound
|
||||
// fn async_foo(_: impl async Fn(&i32)) {}
|
||||
T![async] => {
|
||||
p.bump_any();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
if paths::is_use_path_start(p) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue