Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--.codecov.yml10
-rw-r--r--.github/workflows/ci.yaml57
-rw-r--r--.github/workflows/coverage.yaml44
l---------AGENTS.md1
-rw-r--r--CLAUDE.md40
-rw-r--r--Cargo.lock15
-rw-r--r--Cargo.toml4
-rw-r--r--crates/base-db/src/editioned_file_id.rs2
-rw-r--r--crates/base-db/src/input.rs2
-rw-r--r--crates/base-db/src/lib.rs2
-rw-r--r--crates/hir-def/src/expr_store/lower.rs42
-rw-r--r--crates/hir-def/src/hir/generics.rs2
-rw-r--r--crates/hir-def/src/item_scope.rs5
-rw-r--r--crates/hir-def/src/lib.rs16
-rw-r--r--crates/hir-def/src/nameres.rs8
-rw-r--r--crates/hir-def/src/nameres/collector.rs12
-rw-r--r--crates/hir-def/src/nameres/tests/macros.rs6
-rw-r--r--crates/hir-def/src/resolver.rs73
-rw-r--r--crates/hir-def/src/visibility.rs6
-rw-r--r--crates/hir-expand/src/builtin/fn_macro.rs5
-rw-r--r--crates/hir-expand/src/inert_attr_macro.rs9
-rw-r--r--crates/hir-ty/src/builtin_derive.rs73
-rw-r--r--crates/hir-ty/src/diagnostics/unsafe_check.rs2
-rw-r--r--crates/hir-ty/src/generics.rs16
-rw-r--r--crates/hir-ty/src/infer.rs4
-rw-r--r--crates/hir-ty/src/infer/cast.rs6
-rw-r--r--crates/hir-ty/src/infer/expr.rs4
-rw-r--r--crates/hir-ty/src/infer/op.rs12
-rw-r--r--crates/hir-ty/src/infer/pat.rs2
-rw-r--r--crates/hir-ty/src/infer/path.rs4
-rw-r--r--crates/hir-ty/src/lib.rs64
-rw-r--r--crates/hir-ty/src/lower.rs832
-rw-r--r--crates/hir-ty/src/lower/path.rs117
-rw-r--r--crates/hir-ty/src/mir/eval.rs2
-rw-r--r--crates/hir-ty/src/mir/lower.rs4
-rw-r--r--crates/hir-ty/src/mir/lower/pattern_matching.rs4
-rw-r--r--crates/hir-ty/src/next_solver/abi.rs3
-rw-r--r--crates/hir-ty/src/next_solver/predicate.rs7
-rw-r--r--crates/hir-ty/src/next_solver/util.rs31
-rw-r--r--crates/hir-ty/src/representability.rs131
-rw-r--r--crates/hir-ty/src/tests/never_type.rs88
-rw-r--r--crates/hir-ty/src/tests/regression.rs129
-rw-r--r--crates/hir-ty/src/tests/simple.rs35
-rw-r--r--crates/hir-ty/src/tests/traits.rs34
-rw-r--r--crates/hir-ty/src/utils.rs65
-rw-r--r--crates/hir/src/display.rs13
-rw-r--r--crates/hir/src/lib.rs59
-rw-r--r--crates/hir/src/semantics.rs22
-rw-r--r--crates/ide-assists/src/handlers/add_missing_impl_members.rs80
-rw-r--r--crates/ide-assists/src/handlers/add_missing_match_arms.rs39
-rw-r--r--crates/ide-assists/src/handlers/auto_import.rs33
-rw-r--r--crates/ide-assists/src/handlers/bind_unused_param.rs2
-rw-r--r--crates/ide-assists/src/handlers/convert_bool_then.rs30
-rw-r--r--crates/ide-assists/src/handlers/convert_bool_to_enum.rs6
-rw-r--r--crates/ide-assists/src/handlers/convert_let_else_to_match.rs6
-rw-r--r--crates/ide-assists/src/handlers/convert_to_guarded_return.rs26
-rw-r--r--crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs127
-rw-r--r--crates/ide-assists/src/handlers/destructure_struct_binding.rs231
-rw-r--r--crates/ide-assists/src/handlers/destructure_tuple_binding.rs86
-rw-r--r--crates/ide-assists/src/handlers/fix_visibility.rs43
-rw-r--r--crates/ide-assists/src/handlers/generate_derive.rs19
-rw-r--r--crates/ide-assists/src/handlers/generate_function.rs51
-rw-r--r--crates/ide-assists/src/handlers/generate_getter_or_setter.rs194
-rw-r--r--crates/ide-assists/src/handlers/generate_impl.rs21
-rw-r--r--crates/ide-assists/src/handlers/generate_mut_trait_impl.rs6
-rw-r--r--crates/ide-assists/src/handlers/generate_new.rs13
-rw-r--r--crates/ide-assists/src/handlers/generate_trait_from_impl.rs2
-rw-r--r--crates/ide-assists/src/handlers/inline_call.rs52
-rw-r--r--crates/ide-assists/src/handlers/introduce_named_lifetime.rs209
-rw-r--r--crates/ide-assists/src/handlers/move_const_to_impl.rs8
-rw-r--r--crates/ide-assists/src/handlers/move_guard.rs3
-rw-r--r--crates/ide-assists/src/handlers/promote_local_to_const.rs2
-rw-r--r--crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs23
-rw-r--r--crates/ide-assists/src/handlers/replace_if_let_with_match.rs150
-rw-r--r--crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs58
-rw-r--r--crates/ide-assists/src/handlers/replace_let_with_if_let.rs31
-rw-r--r--crates/ide-assists/src/handlers/toggle_macro_delimiter.rs55
-rw-r--r--crates/ide-assists/src/utils.rs53
-rw-r--r--crates/ide-assists/src/utils/ref_field_expr.rs18
-rw-r--r--crates/ide-completion/src/completions/fn_param.rs54
-rw-r--r--crates/ide-completion/src/completions/pattern.rs2
-rw-r--r--crates/ide-completion/src/completions/postfix.rs208
-rw-r--r--crates/ide-completion/src/context.rs5
-rw-r--r--crates/ide-completion/src/context/analysis.rs2
-rw-r--r--crates/ide-completion/src/render/function.rs19
-rw-r--r--crates/ide-completion/src/tests/attribute.rs66
-rw-r--r--crates/ide-completion/src/tests/expression.rs37
-rw-r--r--crates/ide-completion/src/tests/fn_param.rs54
-rw-r--r--crates/ide-completion/src/tests/pattern.rs32
-rw-r--r--crates/ide-completion/src/tests/record.rs6
-rw-r--r--crates/ide-db/src/imports/import_assets.rs88
-rw-r--r--crates/ide-db/src/imports/insert_use.rs239
-rw-r--r--crates/ide-db/src/imports/insert_use/tests.rs153
-rw-r--r--crates/ide-db/src/imports/merge_imports.rs16
-rw-r--r--crates/ide-db/src/path_transform.rs117
-rw-r--r--crates/ide-db/src/search.rs2
-rw-r--r--crates/ide-db/src/ty_filter.rs10
-rw-r--r--crates/ide-diagnostics/src/handlers/invalid_cast.rs2
-rw-r--r--crates/ide/src/goto_definition.rs69
-rw-r--r--crates/ide/src/hover/tests.rs93
-rw-r--r--crates/ide/src/lib.rs6
-rw-r--r--crates/ide/src/references.rs26
-rw-r--r--crates/ide/src/signature_help.rs4
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_macros.html3
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs8
-rw-r--r--crates/load-cargo/src/lib.rs92
-rw-r--r--crates/proc-macro-api/Cargo.toml1
-rw-r--r--crates/proc-macro-api/src/bidirectional_protocol/msg.rs21
-rw-r--r--crates/proc-macro-api/src/lib.rs10
-rw-r--r--crates/proc-macro-api/src/pool.rs28
-rw-r--r--crates/proc-macro-api/src/process.rs16
-rw-r--r--crates/proc-macro-srv-cli/src/main_loop.rs70
-rw-r--r--crates/proc-macro-srv/src/lib.rs5
-rw-r--r--crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs24
-rw-r--r--crates/proc-macro-srv/src/server_impl/token_id.rs14
-rw-r--r--crates/proc-macro-srv/src/tests/utils.rs8
-rw-r--r--crates/profile/src/google_cpu_profiler.rs2
-rw-r--r--crates/project-model/src/build_dependencies.rs34
-rw-r--r--crates/project-model/src/cargo_config_file.rs63
-rw-r--r--crates/project-model/src/cargo_workspace.rs49
-rw-r--r--crates/project-model/src/project_json.rs4
-rw-r--r--crates/rust-analyzer/src/flycheck.rs126
-rw-r--r--crates/rust-analyzer/src/global_state.rs8
-rw-r--r--crates/rust-analyzer/src/handlers/dispatch.rs20
-rw-r--r--crates/rust-analyzer/src/tracing/config.rs2
-rw-r--r--crates/rust-analyzer/tests/slow-tests/flycheck.rs112
-rw-r--r--crates/rust-analyzer/tests/slow-tests/main.rs13
-rw-r--r--crates/rust-analyzer/tests/slow-tests/support.rs51
-rw-r--r--crates/span/src/ast_id.rs2
-rw-r--r--crates/span/src/hygiene.rs51
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs173
-rw-r--r--crates/syntax/src/ast/make.rs31
-rw-r--r--crates/syntax/src/ast/syntax_factory/constructors.rs81
-rw-r--r--crates/test-utils/src/minicore.rs24
-rw-r--r--crates/tt/src/storage.rs2
-rw-r--r--docs/book/src/non_cargo_based_projects.md7
-rw-r--r--docs/book/src/vs_code.md6
-rw-r--r--editors/code/package-lock.json112
-rw-r--r--lib/lsp-server/src/req_queue.rs14
-rw-r--r--lib/smol_str/CHANGELOG.md3
-rw-r--r--lib/smol_str/Cargo.toml2
-rw-r--r--lib/smol_str/src/borsh.rs5
-rw-r--r--lib/smol_str/tests/test.rs2
-rw-r--r--rust-version2
-rw-r--r--triagebot.toml3
-rw-r--r--xtask/src/codegen/grammar/ast_src.rs4
146 files changed, 4779 insertions, 1640 deletions
diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000000..68eacb7d08
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,10 @@
+coverage:
+ range: 40...60
+ status:
+ patch: off
+ project:
+ default:
+ informational: true
+
+# Don't leave comments on PRs
+comment: false
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 1a0deee564..04de6d11e3 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -50,7 +50,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- name: Install rustup-toolchain-install-master
- run: cargo install [email protected]
+ run: cargo install [email protected]
# Install a pinned rustc commit to avoid surprises
- name: Install Rust toolchain
@@ -96,7 +96,7 @@ jobs:
run: |
rustup update --no-self-update stable
rustup default stable
- rustup component add --toolchain stable rust-src clippy rustfmt
+ rustup component add --toolchain stable rust-src rustfmt
# We also install a nightly rustfmt, because we use `--file-lines` in
# a test.
rustup toolchain install nightly --profile minimal --component rustfmt
@@ -128,10 +128,6 @@ jobs:
- name: Run cargo-machete
run: cargo machete
- - name: Run Clippy
- if: matrix.os == 'macos-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
-
analysis-stats:
if: github.repository == 'rust-lang/rust-analyzer'
runs-on: ubuntu-latest
@@ -178,6 +174,28 @@ jobs:
- run: cargo fmt -- --check
+ clippy:
+ if: github.repository == 'rust-lang/rust-analyzer'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ # Note that clippy output is currently dependent on whether rust-src is installed,
+ # https://github.com/rust-lang/rust-clippy/issues/14625
+ - name: Install Rust toolchain
+ run: |
+ rustup update --no-self-update stable
+ rustup default stable
+ rustup component add --toolchain stable rust-src clippy
+
+ # https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/rust.json
+ - name: Install Rust Problem Matcher
+ run: echo "::add-matcher::.github/rust.json"
+
+ - run: cargo clippy --all-targets -- -D clippy::disallowed_macros -D clippy::dbg_macro -D clippy::todo -D clippy::print_stdout -D clippy::print_stderr
+
miri:
if: github.repository == 'rust-lang/rust-analyzer'
runs-on: ubuntu-latest
@@ -188,8 +206,11 @@ jobs:
- name: Install Rust toolchain
run: |
- rustup update --no-self-update nightly
- rustup default nightly
+ # FIXME: Pin nightly due to a regression in miri on nightly-2026-02-12.
+ # See https://github.com/rust-lang/miri/issues/4855.
+ # Revert to plain `nightly` once this is fixed upstream.
+ rustup toolchain install nightly-2026-02-10
+ rustup default nightly-2026-02-10
rustup component add miri
# - name: Cache Dependencies
@@ -205,7 +226,12 @@ jobs:
strategy:
matrix:
- target: [powerpc-unknown-linux-gnu, x86_64-unknown-linux-musl, wasm32-unknown-unknown]
+ target:
+ [
+ powerpc-unknown-linux-gnu,
+ x86_64-unknown-linux-musl,
+ wasm32-unknown-unknown,
+ ]
include:
# The rust-analyzer binary is not expected to compile on WASM, but the IDE
# crate should
@@ -309,7 +335,18 @@ jobs:
run: typos
conclusion:
- needs: [rust, rust-cross, typescript, typo-check, proc-macro-srv, miri, rustfmt, analysis-stats]
+ needs:
+ [
+ rust,
+ rust-cross,
+ typescript,
+ typo-check,
+ proc-macro-srv,
+ miri,
+ rustfmt,
+ clippy,
+ analysis-stats,
+ ]
# We need to ensure this job does *not* get skipped if its dependencies fail,
# because a skipped job is considered a success by GitHub. So we have to
# overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml
new file mode 100644
index 0000000000..bd4edba747
--- /dev/null
+++ b/.github/workflows/coverage.yaml
@@ -0,0 +1,44 @@
+name: Coverage
+
+on: [pull_request, push]
+
+env:
+ CARGO_INCREMENTAL: 0
+ CARGO_NET_RETRY: 10
+ CI: 1
+ RUST_BACKTRACE: short
+ RUSTUP_MAX_RETRIES: 10
+
+jobs:
+ coverage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Rust toolchain
+ run: |
+ rustup update --no-self-update nightly
+ rustup default nightly
+ rustup component add --toolchain nightly rust-src rustc-dev rustfmt
+ # We also install a nightly rustfmt, because we use `--file-lines` in
+ # a test.
+ rustup toolchain install nightly --profile minimal --component rustfmt
+
+ rustup toolchain install nightly --component llvm-tools-preview
+
+ - name: Install cargo-llvm-cov
+ uses: taiki-e/install-action@cargo-llvm-cov
+
+ - name: Install nextest
+ uses: taiki-e/install-action@nextest
+
+ - name: Generate code coverage
+ run: cargo llvm-cov --workspace --lcov --output-path lcov.info
+
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v5
+ with:
+ files: lcov.info
+ fail_ci_if_error: false
+ token: ${{ secrets.CODECOV_TOKEN }}
+ verbose: true
diff --git a/AGENTS.md b/AGENTS.md
new file mode 120000
index 0000000000..681311eb9c
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1 @@
+CLAUDE.md \ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000000..e8f699d928
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,40 @@
+**Reminder: All AI usage must be disclosed in commit messages, see
+CONTRIBUTING.md for more details.**
+
+## Build Commands
+
+```bash
+cargo build # Build all crates
+cargo test # Run all tests
+cargo test -p <crate> # Run tests for a specific crate (e.g., cargo test -p hir-ty)
+cargo lint # Run clippy on all targets
+cargo xtask codegen # Run code generation
+cargo xtask tidy # Run tidy checks
+UPDATE_EXPECT=1 cargo test # Update test expectations (snapshot tests)
+RUN_SLOW_TESTS=1 cargo test # Run heavy/slow tests
+```
+
+## Key Architectural Invariants
+
+- Typing in a function body never invalidates global derived data
+- Parser/syntax tree is built per-file to enable parallel parsing
+- The server is stateless (HTTP-like); context must be re-created from request parameters
+- Cancellation uses salsa's cancellation mechanism; computations panic with a `Cancelled` payload
+
+### Code Generation
+
+Generated code is committed to the repo. Grammar and AST are generated from `ungrammar`. Run `cargo test -p xtask` after adding inline parser tests (`// test test_name` comments).
+
+## Testing
+
+Tests are snapshot-based using `expect-test`. Test fixtures use a mini-language:
+- `$0` marks cursor position
+- `// ^^^^` labels attach to the line above
+- `//- minicore: sized, fn` includes parts of minicore (minimal core library)
+- `//- /path/to/file.rs crate:name deps:dep1,dep2` declares files/crates
+
+## Style Notes
+
+- Use `stdx::never!` and `stdx::always!` instead of `assert!` for recoverable invariants
+- Use `T![fn]` macro instead of `SyntaxKind::FN_KW`
+- Use keyword name mangling over underscore prefixing for identifiers: `crate` → `krate`, `fn` → `func`, `struct` → `strukt`, `type` → `ty`
diff --git a/Cargo.lock b/Cargo.lock
index 755ae55eea..f31dda4a10 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1845,6 +1845,7 @@ dependencies = [
"paths",
"postcard",
"proc-macro-srv",
+ "rayon",
"rustc-hash 2.1.1",
"semver",
"serde",
@@ -2453,9 +2454,9 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
-version = "0.26.0"
+version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f77debccd43ba198e9cee23efd7f10330ff445e46a98a2b107fed9094a1ee676"
+checksum = "e2e2aa2fca57727371eeafc975acc8e6f4c52f8166a78035543f6ee1c74c2dcc"
dependencies = [
"boxcar",
"crossbeam-queue",
@@ -2478,15 +2479,15 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
-version = "0.26.0"
+version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea07adbf42d91cc076b7daf3b38bc8168c19eb362c665964118a89bc55ef19a5"
+checksum = "1bfc2a1e7bf06964105515451d728f2422dedc3a112383324a00b191a5c397a3"
[[package]]
name = "salsa-macros"
-version = "0.26.0"
+version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d16d4d8b66451b9c75ddf740b7fc8399bc7b8ba33e854a5d7526d18708f67b05"
+checksum = "3d844c1aa34946da46af683b5c27ec1088a3d9d84a2b837a108223fd830220e1"
dependencies = [
"proc-macro2",
"quote",
@@ -2634,7 +2635,7 @@ dependencies = [
[[package]]
name = "smol_str"
-version = "0.3.5"
+version = "0.3.6"
dependencies = [
"arbitrary",
"borsh",
diff --git a/Cargo.toml b/Cargo.toml
index 04559f15ed..9f31e1903a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -135,13 +135,13 @@ rayon = "1.10.0"
rowan = "=0.15.17"
# Ideally we'd not enable the macros feature but unfortunately the `tracked` attribute does not work
# on impls without it
-salsa = { version = "0.26", default-features = false, features = [
+salsa = { version = "0.25.2", default-features = false, features = [
"rayon",
"salsa_unstable",
"macros",
"inventory",
] }
-salsa-macros = "0.26"
+salsa-macros = "0.25.2"
semver = "1.0.26"
serde = { version = "1.0.219" }
serde_derive = { version = "1.0.219" }
diff --git a/crates/base-db/src/editioned_file_id.rs b/crates/base-db/src/editioned_file_id.rs
index dd419f48fc..13fb05d565 100644
--- a/crates/base-db/src/editioned_file_id.rs
+++ b/crates/base-db/src/editioned_file_id.rs
@@ -60,7 +60,7 @@ const _: () = {
}
}
- impl zalsa_::HashEqLike<WithoutCrate> for EditionedFileIdData {
+ impl zalsa_struct_::HashEqLike<WithoutCrate> for EditionedFileIdData {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(self, state);
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index 94793a3618..151aba82a2 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -742,7 +742,7 @@ impl CrateGraphBuilder {
deps.into_iter()
}
- /// Returns all crates in the graph, sorted in topological order (ie. dependencies of a crate
+ /// Returns all crates in the graph, sorted in topological order (i.e. dependencies of a crate
/// come before the crate itself).
fn crates_in_topological_order(&self) -> Vec<CrateBuilderId> {
let mut res = Vec::new();
diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs
index 24f6dd59a9..5baf4ce6f9 100644
--- a/crates/base-db/src/lib.rs
+++ b/crates/base-db/src/lib.rs
@@ -32,7 +32,7 @@ pub use crate::{
},
};
use dashmap::{DashMap, mapref::entry::Entry};
-pub use query_group::{self};
+pub use query_group;
use rustc_hash::{FxHashSet, FxHasher};
use salsa::{Durability, Setter};
pub use semver::{BuildMetadata, Prerelease, Version, VersionReq};
diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs
index 701586c258..1cecd1976b 100644
--- a/crates/hir-def/src/expr_store/lower.rs
+++ b/crates/hir-def/src/expr_store/lower.rs
@@ -32,8 +32,8 @@ use triomphe::Arc;
use tt::TextRange;
use crate::{
- AdtId, BlockId, BlockLoc, DefWithBodyId, FunctionId, GenericDefId, ImplId, MacroId,
- ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro,
+ AdtId, BlockId, BlockLoc, DefWithBodyId, FunctionId, GenericDefId, ImplId, ItemContainerId,
+ MacroId, ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro,
attrs::AttrFlags,
db::DefDatabase,
expr_store::{
@@ -141,9 +141,19 @@ pub(super) fn lower_body(
source_map_self_param = Some(collector.expander.in_file(AstPtr::new(&self_param_syn)));
}
+ let is_extern = matches!(
+ owner,
+ DefWithBodyId::FunctionId(id)
+ if matches!(id.loc(db).container, ItemContainerId::ExternBlockId(_)),
+ );
+
for param in param_list.params() {
if collector.check_cfg(&param) {
- let param_pat = collector.collect_pat_top(param.pat());
+ let param_pat = if is_extern {
+ collector.collect_extern_fn_param(param.pat())
+ } else {
+ collector.collect_pat_top(param.pat())
+ };
params.push(param_pat);
}
}
@@ -2248,6 +2258,32 @@ impl<'db> ExprCollector<'db> {
}
}
+ fn collect_extern_fn_param(&mut self, pat: Option<ast::Pat>) -> PatId {
+ // `extern` functions cannot have pattern-matched parameters, and furthermore, the identifiers
+ // in their parameters are always interpreted as bindings, even if in a normal function they
+ // won't be, because they would refer to a path pattern.
+ let Some(pat) = pat else { return self.missing_pat() };
+
+ match &pat {
+ ast::Pat::IdentPat(bp) => {
+ // FIXME: Emit an error if `!bp.is_simple_ident()`.
+
+ let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
+ let hygiene = bp
+ .name()
+ .map(|name| self.hygiene_id_for(name.syntax().text_range()))
+ .unwrap_or(HygieneId::ROOT);
+ let binding = self.alloc_binding(name, BindingAnnotation::Unannotated, hygiene);
+ let pat =
+ self.alloc_pat(Pat::Bind { id: binding, subpat: None }, AstPtr::new(&pat));
+ self.add_definition_to_binding(binding, pat);
+ pat
+ }
+ // FIXME: Emit an error.
+ _ => self.missing_pat(),
+ }
+ }
+
// region: patterns
fn collect_pat_top(&mut self, pat: Option<ast::Pat>) -> PatId {
diff --git a/crates/hir-def/src/hir/generics.rs b/crates/hir-def/src/hir/generics.rs
index 482cf36f95..022f8adfdb 100644
--- a/crates/hir-def/src/hir/generics.rs
+++ b/crates/hir-def/src/hir/generics.rs
@@ -184,7 +184,7 @@ static EMPTY: LazyLock<Arc<GenericParams>> = LazyLock::new(|| {
impl GenericParams {
/// The index of the self param in the generic of the non-parent definition.
- pub(crate) const SELF_PARAM_ID_IN_SELF: la_arena::Idx<TypeOrConstParamData> =
+ pub const SELF_PARAM_ID_IN_SELF: la_arena::Idx<TypeOrConstParamData> =
LocalTypeOrConstParamId::from_raw(RawIdx::from_u32(0));
pub fn new(db: &dyn DefDatabase, def: GenericDefId) -> Arc<GenericParams> {
diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs
index 1303773b59..b11a8bcd90 100644
--- a/crates/hir-def/src/item_scope.rs
+++ b/crates/hir-def/src/item_scope.rs
@@ -483,6 +483,11 @@ impl ItemScope {
self.declarations.push(def)
}
+ pub(crate) fn remove_from_value_ns(&mut self, name: &Name, def: ModuleDefId) {
+ let entry = self.values.shift_remove(name);
+ assert!(entry.is_some_and(|entry| entry.def == def))
+ }
+
pub(crate) fn get_legacy_macro(&self, name: &Name) -> Option<&[MacroId]> {
self.legacy_macros.get(name).map(|it| &**it)
}
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index 8d6c418d75..de674be05f 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -86,7 +86,7 @@ use crate::{
builtin_type::BuiltinType,
db::DefDatabase,
expr_store::ExpressionStoreSourceMap,
- hir::generics::{LocalLifetimeParamId, LocalTypeOrConstParamId},
+ hir::generics::{GenericParams, LocalLifetimeParamId, LocalTypeOrConstParamId},
nameres::{
LocalDefMap,
assoc::{ImplItems, TraitItems},
@@ -553,15 +553,25 @@ pub struct TypeOrConstParamId {
pub struct TypeParamId(TypeOrConstParamId);
impl TypeParamId {
+ #[inline]
pub fn parent(&self) -> GenericDefId {
self.0.parent
}
+
+ #[inline]
pub fn local_id(&self) -> LocalTypeOrConstParamId {
self.0.local_id
}
-}
-impl TypeParamId {
+ #[inline]
+ pub fn trait_self(trait_: TraitId) -> TypeParamId {
+ TypeParamId::from_unchecked(TypeOrConstParamId {
+ parent: trait_.into(),
+ local_id: GenericParams::SELF_PARAM_ID_IN_SELF,
+ })
+ }
+
+ #[inline]
/// Caller should check if this toc id really belongs to a type
pub fn from_unchecked(it: TypeOrConstParamId) -> Self {
Self(it)
diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs
index 1e3ea50c5a..5fda1beab4 100644
--- a/crates/hir-def/src/nameres.rs
+++ b/crates/hir-def/src/nameres.rs
@@ -211,6 +211,7 @@ struct DefMapCrateData {
/// Side table for resolving derive helpers.
exported_derives: FxHashMap<MacroId, Box<[Name]>>,
fn_proc_macro_mapping: FxHashMap<FunctionId, ProcMacroId>,
+ fn_proc_macro_mapping_back: FxHashMap<ProcMacroId, FunctionId>,
/// Custom tool modules registered with `#![register_tool]`.
registered_tools: Vec<Symbol>,
@@ -230,6 +231,7 @@ impl DefMapCrateData {
Self {
exported_derives: FxHashMap::default(),
fn_proc_macro_mapping: FxHashMap::default(),
+ fn_proc_macro_mapping_back: FxHashMap::default(),
registered_tools: PREDEFINED_TOOLS.iter().map(|it| Symbol::intern(it)).collect(),
unstable_features: FxHashSet::default(),
rustc_coherence_is_core: false,
@@ -244,6 +246,7 @@ impl DefMapCrateData {
let Self {
exported_derives,
fn_proc_macro_mapping,
+ fn_proc_macro_mapping_back,
registered_tools,
unstable_features,
rustc_coherence_is_core: _,
@@ -254,6 +257,7 @@ impl DefMapCrateData {
} = self;
exported_derives.shrink_to_fit();
fn_proc_macro_mapping.shrink_to_fit();
+ fn_proc_macro_mapping_back.shrink_to_fit();
registered_tools.shrink_to_fit();
unstable_features.shrink_to_fit();
}
@@ -570,6 +574,10 @@ impl DefMap {
self.data.fn_proc_macro_mapping.get(&id).copied()
}
+ pub fn proc_macro_as_fn(&self, id: ProcMacroId) -> Option<FunctionId> {
+ self.data.fn_proc_macro_mapping_back.get(&id).copied()
+ }
+
pub fn krate(&self) -> Crate {
self.krate
}
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index f51524c1b5..e672e83f01 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -634,6 +634,7 @@ impl<'db> DefCollector<'db> {
crate_data.exported_derives.insert(proc_macro_id.into(), helpers);
}
crate_data.fn_proc_macro_mapping.insert(fn_id, proc_macro_id);
+ crate_data.fn_proc_macro_mapping_back.insert(proc_macro_id, fn_id);
}
/// Define a macro with `macro_rules`.
@@ -2095,6 +2096,8 @@ impl ModCollector<'_, '_> {
let vis = resolve_vis(def_map, local_def_map, &self.item_tree[it.visibility]);
+ update_def(self.def_collector, fn_id.into(), &it.name, vis, false);
+
if self.def_collector.def_map.block.is_none()
&& self.def_collector.is_proc_macro
&& self.module_id == self.def_collector.def_map.root
@@ -2105,9 +2108,14 @@ impl ModCollector<'_, '_> {
InFile::new(self.file_id(), id),
fn_id,
);
- }
- update_def(self.def_collector, fn_id.into(), &it.name, vis, false);
+ // A proc macro is implemented as a function, but it's treated as a macro, not a function.
+ // You cannot call it like a function, for example, except in its defining crate.
+ // So we keep the function definition, but remove it from the scope, leaving only the macro.
+ self.def_collector.def_map[module_id]
+ .scope
+ .remove_from_value_ns(&it.name, fn_id.into());
+ }
}
ModItemId::Struct(id) => {
let it = &self.item_tree[id];
diff --git a/crates/hir-def/src/nameres/tests/macros.rs b/crates/hir-def/src/nameres/tests/macros.rs
index a943f6f0ac..a013f8b2bc 100644
--- a/crates/hir-def/src/nameres/tests/macros.rs
+++ b/crates/hir-def/src/nameres/tests/macros.rs
@@ -1068,10 +1068,8 @@ pub fn derive_macro_2(_item: TokenStream) -> TokenStream {
- AnotherTrait : macro#
- DummyTrait : macro#
- TokenStream : type value
- - attribute_macro : value macro#
- - derive_macro : value
- - derive_macro_2 : value
- - function_like_macro : value macro!
+ - attribute_macro : macro#
+ - function_like_macro : macro!
"#]],
);
}
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index 2ac0f90fb2..d32e53fc6b 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -32,7 +32,7 @@ use crate::{
BindingId, ExprId, LabelId,
generics::{GenericParams, TypeOrConstParamData},
},
- item_scope::{BUILTIN_SCOPE, BuiltinShadowMode, ImportOrExternCrate, ImportOrGlob, ItemScope},
+ item_scope::{BUILTIN_SCOPE, BuiltinShadowMode, ImportOrExternCrate, ItemScope},
lang_item::LangItemTarget,
nameres::{DefMap, LocalDefMap, MacroSubNs, ResolvePathResultPrefixInfo, block_def_map},
per_ns::PerNs,
@@ -111,8 +111,8 @@ pub enum TypeNs {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ResolveValueResult {
- ValueNs(ValueNs, Option<ImportOrGlob>),
- Partial(TypeNs, usize, Option<ImportOrExternCrate>),
+ ValueNs(ValueNs),
+ Partial(TypeNs, usize),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -332,20 +332,17 @@ impl<'db> Resolver<'db> {
Path::Normal(it) => &it.mod_path,
Path::LangItem(l, None) => {
return Some((
- ResolveValueResult::ValueNs(
- match *l {
- LangItemTarget::FunctionId(it) => ValueNs::FunctionId(it),
- LangItemTarget::StaticId(it) => ValueNs::StaticId(it),
- LangItemTarget::StructId(it) => ValueNs::StructId(it),
- LangItemTarget::EnumVariantId(it) => ValueNs::EnumVariantId(it),
- LangItemTarget::UnionId(_)
- | LangItemTarget::ImplId(_)
- | LangItemTarget::TypeAliasId(_)
- | LangItemTarget::TraitId(_)
- | LangItemTarget::EnumId(_) => return None,
- },
- None,
- ),
+ ResolveValueResult::ValueNs(match *l {
+ LangItemTarget::FunctionId(it) => ValueNs::FunctionId(it),
+ LangItemTarget::StaticId(it) => ValueNs::StaticId(it),
+ LangItemTarget::StructId(it) => ValueNs::StructId(it),
+ LangItemTarget::EnumVariantId(it) => ValueNs::EnumVariantId(it),
+ LangItemTarget::UnionId(_)
+ | LangItemTarget::ImplId(_)
+ | LangItemTarget::TypeAliasId(_)
+ | LangItemTarget::TraitId(_)
+ | LangItemTarget::EnumId(_) => return None,
+ }),
ResolvePathResultPrefixInfo::default(),
));
}
@@ -363,7 +360,7 @@ impl<'db> Resolver<'db> {
};
// Remaining segments start from 0 because lang paths have no segments other than the remaining.
return Some((
- ResolveValueResult::Partial(type_ns, 0, None),
+ ResolveValueResult::Partial(type_ns, 0),
ResolvePathResultPrefixInfo::default(),
));
}
@@ -388,10 +385,7 @@ impl<'db> Resolver<'db> {
if let Some(e) = entry {
return Some((
- ResolveValueResult::ValueNs(
- ValueNs::LocalBinding(e.binding()),
- None,
- ),
+ ResolveValueResult::ValueNs(ValueNs::LocalBinding(e.binding())),
ResolvePathResultPrefixInfo::default(),
));
}
@@ -404,14 +398,14 @@ impl<'db> Resolver<'db> {
&& *first_name == sym::Self_
{
return Some((
- ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_), None),
+ ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_)),
ResolvePathResultPrefixInfo::default(),
));
}
if let Some(id) = params.find_const_by_name(first_name, *def) {
let val = ValueNs::GenericParam(id);
return Some((
- ResolveValueResult::ValueNs(val, None),
+ ResolveValueResult::ValueNs(val),
ResolvePathResultPrefixInfo::default(),
));
}
@@ -431,7 +425,7 @@ impl<'db> Resolver<'db> {
if let &GenericDefId::ImplId(impl_) = def {
if *first_name == sym::Self_ {
return Some((
- ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1, None),
+ ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1),
ResolvePathResultPrefixInfo::default(),
));
}
@@ -440,14 +434,14 @@ impl<'db> Resolver<'db> {
{
let ty = TypeNs::AdtSelfType(adt);
return Some((
- ResolveValueResult::Partial(ty, 1, None),
+ ResolveValueResult::Partial(ty, 1),
ResolvePathResultPrefixInfo::default(),
));
}
if let Some(id) = params.find_type_by_name(first_name, *def) {
let ty = TypeNs::GenericParam(id);
return Some((
- ResolveValueResult::Partial(ty, 1, None),
+ ResolveValueResult::Partial(ty, 1),
ResolvePathResultPrefixInfo::default(),
));
}
@@ -473,7 +467,7 @@ impl<'db> Resolver<'db> {
&& let Some(builtin) = BuiltinType::by_name(first_name)
{
return Some((
- ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1, None),
+ ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1),
ResolvePathResultPrefixInfo::default(),
));
}
@@ -488,7 +482,7 @@ impl<'db> Resolver<'db> {
hygiene: HygieneId,
) -> Option<ValueNs> {
match self.resolve_path_in_value_ns(db, path, hygiene)? {
- ResolveValueResult::ValueNs(it, _) => Some(it),
+ ResolveValueResult::ValueNs(it) => Some(it),
ResolveValueResult::Partial(..) => None,
}
}
@@ -1153,12 +1147,12 @@ impl<'db> ModuleItemMap<'db> {
);
match unresolved_idx {
None => {
- let (value, import) = to_value_ns(module_def)?;
- Some((ResolveValueResult::ValueNs(value, import), prefix_info))
+ let value = to_value_ns(module_def, self.def_map)?;
+ Some((ResolveValueResult::ValueNs(value), prefix_info))
}
Some(unresolved_idx) => {
- let def = module_def.take_types_full()?;
- let ty = match def.def {
+ let def = module_def.take_types()?;
+ let ty = match def {
ModuleDefId::AdtId(it) => TypeNs::AdtId(it),
ModuleDefId::TraitId(it) => TypeNs::TraitId(it),
ModuleDefId::TypeAliasId(it) => TypeNs::TypeAliasId(it),
@@ -1171,7 +1165,7 @@ impl<'db> ModuleItemMap<'db> {
| ModuleDefId::MacroId(_)
| ModuleDefId::StaticId(_) => return None,
};
- Some((ResolveValueResult::Partial(ty, unresolved_idx, def.import), prefix_info))
+ Some((ResolveValueResult::Partial(ty, unresolved_idx), prefix_info))
}
}
}
@@ -1194,8 +1188,13 @@ impl<'db> ModuleItemMap<'db> {
}
}
-fn to_value_ns(per_ns: PerNs) -> Option<(ValueNs, Option<ImportOrGlob>)> {
- let (def, import) = per_ns.take_values_import()?;
+fn to_value_ns(per_ns: PerNs, def_map: &DefMap) -> Option<ValueNs> {
+ let def = per_ns.take_values().or_else(|| {
+ let Some(MacroId::ProcMacroId(proc_macro)) = per_ns.take_macros() else { return None };
+ // If we cannot resolve to value ns, but we can resolve to a proc macro, and this is the crate
+ // defining this proc macro - inside this crate, we should treat the macro as a function.
+ def_map.proc_macro_as_fn(proc_macro).map(ModuleDefId::FunctionId)
+ })?;
let res = match def {
ModuleDefId::FunctionId(it) => ValueNs::FunctionId(it),
ModuleDefId::AdtId(AdtId::StructId(it)) => ValueNs::StructId(it),
@@ -1210,7 +1209,7 @@ fn to_value_ns(per_ns: PerNs) -> Option<(ValueNs, Option<ImportOrGlob>)> {
| ModuleDefId::MacroId(_)
| ModuleDefId::ModuleId(_) => return None,
};
- Some((res, import))
+ Some(res)
}
fn to_type_ns(per_ns: PerNs) -> Option<(TypeNs, Option<ImportOrExternCrate>)> {
diff --git a/crates/hir-def/src/visibility.rs b/crates/hir-def/src/visibility.rs
index a1645de6ec..cb5eed1b8b 100644
--- a/crates/hir-def/src/visibility.rs
+++ b/crates/hir-def/src/visibility.rs
@@ -146,7 +146,7 @@ impl Visibility {
/// Returns the most permissive visibility of `self` and `other`.
///
- /// If there is no subset relation between `self` and `other`, returns `None` (ie. they're only
+ /// If there is no subset relation between `self` and `other`, returns `None` (i.e. they're only
/// visible in unrelated modules).
pub(crate) fn max(
self,
@@ -212,7 +212,7 @@ impl Visibility {
/// Returns the least permissive visibility of `self` and `other`.
///
- /// If there is no subset relation between `self` and `other`, returns `None` (ie. they're only
+ /// If there is no subset relation between `self` and `other`, returns `None` (i.e. they're only
/// visible in unrelated modules).
pub(crate) fn min(
self,
@@ -234,7 +234,7 @@ impl Visibility {
if mod_.krate(db) == krate { Some(Visibility::Module(mod_, exp)) } else { None }
}
(Visibility::Module(mod_a, expl_a), Visibility::Module(mod_b, expl_b)) => {
- if mod_a.krate(db) != mod_b.krate(db) {
+ if mod_a == mod_b {
// Most module visibilities are `pub(self)`, and assuming no errors
// this will be the common and thus fast path.
return Some(Visibility::Module(
diff --git a/crates/hir-expand/src/builtin/fn_macro.rs b/crates/hir-expand/src/builtin/fn_macro.rs
index 3029bca1d5..949b22b0ad 100644
--- a/crates/hir-expand/src/builtin/fn_macro.rs
+++ b/crates/hir-expand/src/builtin/fn_macro.rs
@@ -5,10 +5,7 @@ use std::borrow::Cow;
use base_db::AnchoredPath;
use cfg::CfgExpr;
use either::Either;
-use intern::{
- Symbol,
- sym::{self},
-};
+use intern::{Symbol, sym};
use itertools::Itertools;
use mbe::{DelimiterKind, expect_fragment};
use span::{Edition, FileId, Span};
diff --git a/crates/hir-expand/src/inert_attr_macro.rs b/crates/hir-expand/src/inert_attr_macro.rs
index 6dec2c5b32..53b624d9a6 100644
--- a/crates/hir-expand/src/inert_attr_macro.rs
+++ b/crates/hir-expand/src/inert_attr_macro.rs
@@ -124,8 +124,6 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
should_panic, Normal,
template!(Word, List: r#"expected = "reason""#, NameValueStr: "reason"), FutureWarnFollowing,
),
- // FIXME(Centril): This can be used on stable but shouldn't.
- ungated!(reexport_test_harness_main, CrateLevel, template!(NameValueStr: "name"), ErrorFollowing),
// Macros:
ungated!(automatically_derived, Normal, template!(Word), WarnFollowing),
@@ -264,6 +262,13 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
test_runner, CrateLevel, template!(List: "path"), ErrorFollowing, custom_test_frameworks,
"custom test frameworks are an unstable feature",
),
+
+ gated!(
+ reexport_test_harness_main, CrateLevel, template!(NameValueStr: "name"),
+ ErrorFollowing, custom_test_frameworks,
+ "custom test frameworks are an unstable feature",
+ ),
+
// RFC #1268
gated!(
marker, Normal, template!(Word), WarnFollowing, @only_local: true,
diff --git a/crates/hir-ty/src/builtin_derive.rs b/crates/hir-ty/src/builtin_derive.rs
index 0c3c513668..5a93c2b536 100644
--- a/crates/hir-ty/src/builtin_derive.rs
+++ b/crates/hir-ty/src/builtin_derive.rs
@@ -35,6 +35,24 @@ fn coerce_pointee_new_type_param(trait_id: TraitId) -> TypeParamId {
})
}
+fn trait_args(trait_: BuiltinDeriveImplTrait, self_ty: Ty<'_>) -> GenericArgs<'_> {
+ match trait_ {
+ BuiltinDeriveImplTrait::Copy
+ | BuiltinDeriveImplTrait::Clone
+ | BuiltinDeriveImplTrait::Default
+ | BuiltinDeriveImplTrait::Debug
+ | BuiltinDeriveImplTrait::Hash
+ | BuiltinDeriveImplTrait::Eq
+ | BuiltinDeriveImplTrait::Ord => GenericArgs::new_from_slice(&[self_ty.into()]),
+ BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => {
+ GenericArgs::new_from_slice(&[self_ty.into(), self_ty.into()])
+ }
+ BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => {
+ panic!("`CoerceUnsized` and `DispatchFromDyn` have special generics")
+ }
+ }
+}
+
pub(crate) fn generics_of<'db>(interner: DbInterner<'db>, id: BuiltinDeriveImplId) -> Generics {
let db = interner.db;
let loc = id.loc(db);
@@ -95,21 +113,19 @@ pub fn impl_trait<'db>(
| BuiltinDeriveImplTrait::Debug
| BuiltinDeriveImplTrait::Hash
| BuiltinDeriveImplTrait::Ord
- | BuiltinDeriveImplTrait::Eq => {
+ | BuiltinDeriveImplTrait::Eq
+ | BuiltinDeriveImplTrait::PartialOrd
+ | BuiltinDeriveImplTrait::PartialEq => {
let self_ty = Ty::new_adt(
interner,
loc.adt,
GenericArgs::identity_for_item(interner, loc.adt.into()),
);
- EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty]))
- }
- BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => {
- let self_ty = Ty::new_adt(
+ EarlyBinder::bind(TraitRef::new_from_args(
interner,
- loc.adt,
- GenericArgs::identity_for_item(interner, loc.adt.into()),
- );
- EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty, self_ty]))
+ trait_id.into(),
+ trait_args(loc.trait_, self_ty),
+ ))
}
BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => {
let generic_params = GenericParams::new(db, loc.adt.into());
@@ -260,7 +276,8 @@ fn simple_trait_predicates<'db>(
let param_idx =
param_idx.into_raw().into_u32() + (generic_params.len_lifetimes() as u32);
let param_ty = Ty::new_param(interner, param_id, param_idx);
- let trait_ref = TraitRef::new(interner, trait_id.into(), [param_ty]);
+ let trait_args = trait_args(loc.trait_, param_ty);
+ let trait_ref = TraitRef::new_from_args(interner, trait_id.into(), trait_args);
trait_ref.upcast(interner)
});
let mut assoc_type_bounds = Vec::new();
@@ -270,12 +287,14 @@ fn simple_trait_predicates<'db>(
&mut assoc_type_bounds,
interner.db.field_types(id.into()),
trait_id,
+ loc.trait_,
),
AdtId::UnionId(id) => extend_assoc_type_bounds(
interner,
&mut assoc_type_bounds,
interner.db.field_types(id.into()),
trait_id,
+ loc.trait_,
),
AdtId::EnumId(id) => {
for &(variant_id, _, _) in &id.enum_variants(interner.db).variants {
@@ -284,6 +303,7 @@ fn simple_trait_predicates<'db>(
&mut assoc_type_bounds,
interner.db.field_types(variant_id.into()),
trait_id,
+ loc.trait_,
)
}
}
@@ -305,12 +325,14 @@ fn extend_assoc_type_bounds<'db>(
interner: DbInterner<'db>,
assoc_type_bounds: &mut Vec<Clause<'db>>,
fields: &ArenaMap<LocalFieldId, StoredEarlyBinder<StoredTy>>,
- trait_: TraitId,
+ trait_id: TraitId,
+ trait_: BuiltinDeriveImplTrait,
) {
struct ProjectionFinder<'a, 'db> {
interner: DbInterner<'db>,
assoc_type_bounds: &'a mut Vec<Clause<'db>>,
- trait_: TraitId,
+ trait_id: TraitId,
+ trait_: BuiltinDeriveImplTrait,
}
impl<'db> TypeVisitor<DbInterner<'db>> for ProjectionFinder<'_, 'db> {
@@ -319,7 +341,12 @@ fn extend_assoc_type_bounds<'db>(
fn visit_ty(&mut self, t: Ty<'db>) -> Self::Result {
if let TyKind::Alias(AliasTyKind::Projection, _) = t.kind() {
self.assoc_type_bounds.push(
- TraitRef::new(self.interner, self.trait_.into(), [t]).upcast(self.interner),
+ TraitRef::new_from_args(
+ self.interner,
+ self.trait_id.into(),
+ trait_args(self.trait_, t),
+ )
+ .upcast(self.interner),
);
}
@@ -327,7 +354,7 @@ fn extend_assoc_type_bounds<'db>(
}
}
- let mut visitor = ProjectionFinder { interner, assoc_type_bounds, trait_ };
+ let mut visitor = ProjectionFinder { interner, assoc_type_bounds, trait_id, trait_ };
for (_, field) in fields.iter() {
field.get().instantiate_identity().visit_with(&mut visitor);
}
@@ -488,10 +515,12 @@ struct MultiGenericParams<'a, T, #[pointee] U: ?Sized, const N: usize>(*const U)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Simple;
-trait Trait {}
+trait Trait {
+ type Assoc;
+}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]);
+struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N], T::Assoc);
"#,
expect![[r#"
@@ -514,41 +543,49 @@ struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]);
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Debug, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Debug, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Clone, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Clone, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Copy, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Copy, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
- Clause(Binder { value: TraitPredicate(#1: PartialEq, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(#1: PartialEq<[#1]>, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): PartialEq<[Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. })]>, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Eq, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Eq, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
- Clause(Binder { value: TraitPredicate(#1: PartialOrd, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(#1: PartialOrd<[#1]>, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): PartialOrd<[Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. })]>, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Ord, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Ord, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] })
Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] })
Clause(Binder { value: TraitPredicate(#1: Hash, polarity:Positive), bound_vars: [] })
+ Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Hash, polarity:Positive), bound_vars: [] })
"#]],
);
diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs
index 50d4517d01..21f263723b 100644
--- a/crates/hir-ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs
@@ -430,7 +430,7 @@ impl<'db> UnsafeVisitor<'db> {
fn mark_unsafe_path(&mut self, node: ExprOrPatId, path: &Path) {
let hygiene = self.body.expr_or_pat_path_hygiene(node);
let value_or_partial = self.resolver.resolve_path_in_value_ns(self.db, path, hygiene);
- if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
+ if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial {
let static_data = self.db.static_signature(id);
if static_data.flags.contains(StaticFlags::MUTABLE) {
self.on_unsafe_op(node, UnsafetyReason::MutableStatic);
diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs
index 5f0261437b..b1500bcdb7 100644
--- a/crates/hir-ty/src/generics.rs
+++ b/crates/hir-ty/src/generics.rs
@@ -224,22 +224,6 @@ impl Generics {
}
}
-pub(crate) fn trait_self_param_idx(db: &dyn DefDatabase, def: GenericDefId) -> Option<usize> {
- match def {
- GenericDefId::TraitId(_) => {
- let params = db.generic_params(def);
- params.trait_self_param().map(|idx| idx.into_raw().into_u32() as usize)
- }
- GenericDefId::ImplId(_) => None,
- _ => {
- let parent_def = parent_generic_def(db, def)?;
- let parent_params = db.generic_params(parent_def);
- let parent_self_idx = parent_params.trait_self_param()?.into_raw().into_u32() as usize;
- Some(parent_self_idx)
- }
- }
-}
-
pub(crate) fn parent_generic_def(db: &dyn DefDatabase, def: GenericDefId) -> Option<GenericDefId> {
let container = match def {
GenericDefId::FunctionId(it) => it.lookup(db).container,
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 35d744e7d1..991acda14b 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -1584,7 +1584,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
return (self.err_ty(), None);
};
match res {
- ResolveValueResult::ValueNs(value, _) => match value {
+ ResolveValueResult::ValueNs(value) => match value {
ValueNs::EnumVariantId(var) => {
let args = path_ctx.substs_from_path(var.into(), true, false);
drop(ctx);
@@ -1608,7 +1608,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> {
return (self.err_ty(), None);
}
},
- ResolveValueResult::Partial(typens, unresolved, _) => (typens, Some(unresolved)),
+ ResolveValueResult::Partial(typens, unresolved) => (typens, Some(unresolved)),
}
} else {
match path_ctx.resolve_path_in_type_ns() {
diff --git a/crates/hir-ty/src/infer/cast.rs b/crates/hir-ty/src/infer/cast.rs
index d69b00adb7..fc38361d7e 100644
--- a/crates/hir-ty/src/infer/cast.rs
+++ b/crates/hir-ty/src/infer/cast.rs
@@ -328,11 +328,7 @@ impl<'db> CastCheck<'db> {
//
// Note that trait upcasting goes through a different mechanism (`coerce_unsized`)
// and is unaffected by this check.
- (Some(src_principal), Some(dst_principal)) => {
- if src_principal == dst_principal {
- return Ok(());
- }
-
+ (Some(src_principal), Some(_)) => {
// We need to reconstruct trait object types.
// `m_src` and `m_dst` won't work for us here because they will potentially
// contain wrappers, which we do not care about.
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 9f2d9d25b9..45b181eff8 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -658,7 +658,7 @@ impl<'db> InferenceContext<'_, 'db> {
}
}
if let RecordSpread::Expr(expr) = *spread {
- self.infer_expr(expr, &Expectation::has_type(ty), ExprIsRead::Yes);
+ self.infer_expr_coerce_never(expr, &Expectation::has_type(ty), ExprIsRead::Yes);
}
ty
}
@@ -751,7 +751,7 @@ impl<'db> InferenceContext<'_, 'db> {
if let Some(lhs_ty) = lhs_ty {
self.write_pat_ty(target, lhs_ty);
- self.infer_expr_coerce(value, &Expectation::has_type(lhs_ty), ExprIsRead::No);
+ self.infer_expr_coerce(value, &Expectation::has_type(lhs_ty), ExprIsRead::Yes);
} else {
let rhs_ty = self.infer_expr(value, &Expectation::none(), ExprIsRead::Yes);
let resolver_guard =
diff --git a/crates/hir-ty/src/infer/op.rs b/crates/hir-ty/src/infer/op.rs
index c79c828cd4..95d63ffb50 100644
--- a/crates/hir-ty/src/infer/op.rs
+++ b/crates/hir-ty/src/infer/op.rs
@@ -178,9 +178,9 @@ impl<'a, 'db> InferenceContext<'a, 'db> {
// trait matching creating lifetime constraints that are too strict.
// e.g., adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result
// in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`.
- let lhs_ty = self.infer_expr_no_expect(lhs_expr, ExprIsRead::No);
+ let lhs_ty = self.infer_expr_no_expect(lhs_expr, ExprIsRead::Yes);
let fresh_var = self.table.next_ty_var();
- self.demand_coerce(lhs_expr, lhs_ty, fresh_var, AllowTwoPhase::No, ExprIsRead::No)
+ self.demand_coerce(lhs_expr, lhs_ty, fresh_var, AllowTwoPhase::No, ExprIsRead::Yes)
}
};
let lhs_ty = self.table.resolve_vars_with_obligations(lhs_ty);
@@ -200,7 +200,7 @@ impl<'a, 'db> InferenceContext<'a, 'db> {
// see `NB` above
let rhs_ty =
- self.infer_expr_coerce(rhs_expr, &Expectation::HasType(rhs_ty_var), ExprIsRead::No);
+ self.infer_expr_coerce(rhs_expr, &Expectation::HasType(rhs_ty_var), ExprIsRead::Yes);
let rhs_ty = self.table.resolve_vars_with_obligations(rhs_ty);
let return_ty = match result {
@@ -320,7 +320,11 @@ impl<'a, 'db> InferenceContext<'a, 'db> {
if let Some((rhs_expr, rhs_ty)) = opt_rhs
&& rhs_ty.is_ty_var()
{
- self.infer_expr_coerce(rhs_expr, &Expectation::HasType(rhs_ty), ExprIsRead::No);
+ self.infer_expr_coerce(
+ rhs_expr,
+ &Expectation::HasType(rhs_ty),
+ ExprIsRead::Yes,
+ );
}
// Construct an obligation `self_ty : Trait<input_tys>`
diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs
index 1b8ce5ceaf..87fd0dace3 100644
--- a/crates/hir-ty/src/infer/pat.rs
+++ b/crates/hir-ty/src/infer/pat.rs
@@ -673,7 +673,7 @@ impl<'db> InferenceContext<'_, 'db> {
pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool {
let mut res = false;
body.walk_pats(pat_id, &mut |pat| {
- res |= matches!(body[pat], Pat::Bind { id, .. } if body[id].mode == BindingAnnotation::Ref);
+ res |= matches!(body[pat], Pat::Bind { id, .. } if matches!(body[id].mode, BindingAnnotation::Ref | BindingAnnotation::RefMut));
});
res
}
diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs
index ef1a610a32..40c6fdf3cc 100644
--- a/crates/hir-ty/src/infer/path.rs
+++ b/crates/hir-ty/src/infer/path.rs
@@ -164,11 +164,11 @@ impl<'db> InferenceContext<'_, 'db> {
let value_or_partial = path_ctx.resolve_path_in_value_ns(hygiene)?;
match value_or_partial {
- ResolveValueResult::ValueNs(it, _) => {
+ ResolveValueResult::ValueNs(it) => {
drop_ctx(ctx, no_diagnostics);
(it, None)
}
- ResolveValueResult::Partial(def, remaining_index, _) => {
+ ResolveValueResult::Partial(def, remaining_index) => {
// there may be more intermediate segments between the resolved one and
// the end. Only the last segment needs to be resolved to a value; from
// the segments before that, we need to get either a type or a trait ref.
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index f8920904f0..8d87276a0b 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -31,6 +31,7 @@ mod inhabitedness;
mod lower;
pub mod next_solver;
mod opaques;
+mod representability;
mod specialization;
mod target_feature;
mod utils;
@@ -57,9 +58,12 @@ mod test_db;
#[cfg(test)]
mod tests;
-use std::hash::Hash;
+use std::{hash::Hash, ops::ControlFlow};
-use hir_def::{CallableDefId, TypeOrConstParamId, type_ref::Rawness};
+use hir_def::{
+ CallableDefId, GenericDefId, TypeAliasId, TypeOrConstParamId, TypeParamId,
+ hir::generics::GenericParams, resolver::TypeNs, type_ref::Rawness,
+};
use hir_expand::name::Name;
use indexmap::{IndexMap, map::Entry};
use intern::{Symbol, sym};
@@ -77,10 +81,11 @@ use crate::{
db::HirDatabase,
display::{DisplayTarget, HirDisplay},
infer::unify::InferenceTable,
+ lower::SupertraitsInfo,
next_solver::{
AliasTy, Binder, BoundConst, BoundRegion, BoundRegionKind, BoundTy, BoundTyKind, Canonical,
- CanonicalVarKind, CanonicalVars, Const, ConstKind, DbInterner, FnSig, GenericArgs,
- PolyFnSig, Predicate, Region, RegionKind, TraitRef, Ty, TyKind, Tys, abi,
+ CanonicalVarKind, CanonicalVars, ClauseKind, Const, ConstKind, DbInterner, FnSig,
+ GenericArgs, PolyFnSig, Predicate, Region, RegionKind, TraitRef, Ty, TyKind, Tys, abi,
},
};
@@ -94,7 +99,7 @@ pub use infer::{
};
pub use lower::{
GenericPredicates, ImplTraits, LifetimeElisionKind, TyDefId, TyLoweringContext, ValueTyDefId,
- associated_type_shorthand_candidates, diagnostics::*,
+ diagnostics::*,
};
pub use next_solver::interner::{attach_db, attach_db_allow_change, with_attached_db};
pub use target_feature::TargetFeatures;
@@ -479,6 +484,55 @@ where
}
/// To be used from `hir` only.
+pub fn associated_type_shorthand_candidates(
+ db: &dyn HirDatabase,
+ def: GenericDefId,
+ res: TypeNs,
+ mut cb: impl FnMut(&Name, TypeAliasId) -> bool,
+) -> Option<TypeAliasId> {
+ let interner = DbInterner::new_no_crate(db);
+ let (def, param) = match res {
+ TypeNs::GenericParam(param) => (def, param),
+ TypeNs::SelfType(impl_) => {
+ let impl_trait = db.impl_trait(impl_)?.skip_binder().def_id.0;
+ let param = TypeParamId::from_unchecked(TypeOrConstParamId {
+ parent: impl_trait.into(),
+ local_id: GenericParams::SELF_PARAM_ID_IN_SELF,
+ });
+ (impl_trait.into(), param)
+ }
+ _ => return None,
+ };
+
+ let mut dedup_map = FxHashSet::default();
+ let param_ty = Ty::new_param(interner, param, param_idx(db, param.into()).unwrap() as u32);
+ // We use the ParamEnv and not the predicates because the ParamEnv elaborates bounds.
+ let param_env = db.trait_environment(def);
+ for clause in param_env.clauses {
+ let ClauseKind::Trait(trait_clause) = clause.kind().skip_binder() else { continue };
+ if trait_clause.self_ty() != param_ty {
+ continue;
+ }
+ let trait_id = trait_clause.def_id().0;
+ dedup_map.extend(
+ SupertraitsInfo::query(db, trait_id)
+ .defined_assoc_types
+ .iter()
+ .map(|(name, id)| (name, *id)),
+ );
+ }
+
+ dedup_map
+ .into_iter()
+ .try_for_each(
+ |(name, id)| {
+ if cb(name, id) { ControlFlow::Break(id) } else { ControlFlow::Continue(()) }
+ },
+ )
+ .break_value()
+}
+
+/// To be used from `hir` only.
pub fn callable_sig_from_fn_trait<'db>(
self_ty: Ty<'db>,
trait_env: ParamEnvAndCrate<'db>,
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index 386556b156..c49e943437 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -20,7 +20,8 @@ use hir_def::{
builtin_type::BuiltinType,
expr_store::{ExpressionStore, HygieneId, path::Path},
hir::generics::{
- GenericParamDataRef, TypeOrConstParamData, TypeParamProvenance, WherePredicate,
+ GenericParamDataRef, GenericParams, TypeOrConstParamData, TypeParamProvenance,
+ WherePredicate,
},
item_tree::FieldsShape,
lang_item::LangItems,
@@ -36,27 +37,22 @@ use la_arena::{Arena, ArenaMap, Idx};
use path::{PathDiagnosticCallback, PathLoweringContext};
use rustc_ast_ir::Mutability;
use rustc_hash::FxHashSet;
-use rustc_pattern_analysis::Captures;
use rustc_type_ir::{
AliasTyKind, BoundVarIndexKind, ConstKind, DebruijnIndex, ExistentialPredicate,
ExistentialProjection, ExistentialTraitRef, FnSig, Interner, OutlivesPredicate, TermKind,
- TyKind::{self},
- TypeFoldable, TypeVisitableExt, Upcast, UpcastFrom, elaborate,
- inherent::{
- Clause as _, GenericArg as _, GenericArgs as _, IntoKind as _, Region as _, SliceLike,
- Ty as _,
- },
+ TyKind, TypeFoldable, TypeVisitableExt, Upcast, UpcastFrom, elaborate,
+ inherent::{Clause as _, GenericArgs as _, IntoKind as _, Region as _, Ty as _},
};
-use smallvec::{SmallVec, smallvec};
+use smallvec::SmallVec;
use stdx::{impl_from, never};
use tracing::debug;
use triomphe::{Arc, ThinArc};
use crate::{
- FnAbi, ImplTraitId, TyLoweringDiagnostic, TyLoweringDiagnosticKind, all_super_traits,
+ FnAbi, ImplTraitId, TyLoweringDiagnostic, TyLoweringDiagnosticKind,
consteval::intern_const_ref,
db::{HirDatabase, InternedOpaqueTyId},
- generics::{Generics, generics, trait_self_param_idx},
+ generics::{Generics, generics},
next_solver::{
AliasTy, Binder, BoundExistentialPredicates, Clause, ClauseKind, Clauses, Const,
DbInterner, EarlyBinder, EarlyParamRegion, ErrorGuaranteed, FxIndexMap, GenericArg,
@@ -618,33 +614,12 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> {
&'b mut self,
where_predicate: &'b WherePredicate,
ignore_bindings: bool,
- generics: &Generics,
- predicate_filter: PredicateFilter,
) -> impl Iterator<Item = (Clause<'db>, GenericPredicateSource)> + use<'a, 'b, 'db> {
match where_predicate {
WherePredicate::ForLifetime { target, bound, .. }
| WherePredicate::TypeBound { target, bound } => {
- if let PredicateFilter::SelfTrait = predicate_filter {
- let target_type = &self.store[*target];
- let self_type = 'is_self: {
- if let TypeRef::Path(path) = target_type
- && path.is_self_type()
- {
- break 'is_self true;
- }
- if let TypeRef::TypeParam(param) = target_type
- && generics[param.local_id()].is_trait_self()
- {
- break 'is_self true;
- }
- false
- };
- if !self_type {
- return Either::Left(Either::Left(iter::empty()));
- }
- }
let self_ty = self.lower_ty(*target);
- Either::Left(Either::Right(self.lower_type_bound(bound, self_ty, ignore_bindings)))
+ Either::Left(self.lower_type_bound(bound, self_ty, ignore_bindings))
}
&WherePredicate::Lifetime { bound, target } => Either::Right(iter::once((
Clause(Predicate::new(
@@ -1626,6 +1601,98 @@ pub(crate) fn field_types_with_diagnostics_query<'db>(
(res, create_diagnostics(ctx.diagnostics))
}
+#[derive(Debug, PartialEq, Eq, Default)]
+pub(crate) struct SupertraitsInfo {
+ /// This includes the trait itself.
+ pub(crate) all_supertraits: Box<[TraitId]>,
+ pub(crate) direct_supertraits: Box<[TraitId]>,
+ pub(crate) defined_assoc_types: Box<[(Name, TypeAliasId)]>,
+}
+
+impl SupertraitsInfo {
+ #[inline]
+ pub(crate) fn query(db: &dyn HirDatabase, trait_: TraitId) -> &Self {
+ return supertraits_info(db, trait_);
+
+ #[salsa::tracked(returns(ref), cycle_result = supertraits_info_cycle)]
+ fn supertraits_info(db: &dyn HirDatabase, trait_: TraitId) -> SupertraitsInfo {
+ let mut all_supertraits = FxHashSet::default();
+ let mut direct_supertraits = FxHashSet::default();
+ let mut defined_assoc_types = FxHashSet::default();
+
+ all_supertraits.insert(trait_);
+ defined_assoc_types.extend(trait_.trait_items(db).items.iter().filter_map(
+ |(name, id)| match *id {
+ AssocItemId::TypeAliasId(id) => Some((name.clone(), id)),
+ _ => None,
+ },
+ ));
+
+ let resolver = trait_.resolver(db);
+ let signature = db.trait_signature(trait_);
+ for pred in signature.generic_params.where_predicates() {
+ let (WherePredicate::TypeBound { target, bound }
+ | WherePredicate::ForLifetime { lifetimes: _, target, bound }) = pred
+ else {
+ continue;
+ };
+ let (TypeBound::Path(bounded_trait, TraitBoundModifier::None)
+ | TypeBound::ForLifetime(_, bounded_trait)) = *bound
+ else {
+ continue;
+ };
+ let target = &signature.store[*target];
+ match target {
+ TypeRef::TypeParam(param)
+ if param.local_id() == GenericParams::SELF_PARAM_ID_IN_SELF => {}
+ TypeRef::Path(path) if path.is_self_type() => {}
+ _ => continue,
+ }
+ let Some(TypeNs::TraitId(bounded_trait)) =
+ resolver.resolve_path_in_type_ns_fully(db, &signature.store[bounded_trait])
+ else {
+ continue;
+ };
+ let SupertraitsInfo {
+ all_supertraits: bounded_trait_all_supertraits,
+ direct_supertraits: _,
+ defined_assoc_types: bounded_traits_defined_assoc_types,
+ } = SupertraitsInfo::query(db, bounded_trait);
+ all_supertraits.extend(bounded_trait_all_supertraits);
+ direct_supertraits.insert(bounded_trait);
+ defined_assoc_types.extend(bounded_traits_defined_assoc_types.iter().cloned());
+ }
+
+ SupertraitsInfo {
+ all_supertraits: Box::from_iter(all_supertraits),
+ direct_supertraits: Box::from_iter(direct_supertraits),
+ defined_assoc_types: Box::from_iter(defined_assoc_types),
+ }
+ }
+
+ fn supertraits_info_cycle(
+ _db: &dyn HirDatabase,
+ _: salsa::Id,
+ _trait_: TraitId,
+ ) -> SupertraitsInfo {
+ SupertraitsInfo::default()
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+enum AssocTypeShorthandResolution {
+ Resolved(StoredEarlyBinder<(TypeAliasId, StoredGenericArgs)>),
+ Ambiguous {
+ /// If one resolution belongs to a sub-trait and one to a supertrait, this contains
+ /// the sub-trait's resolution. This can be `None` if there is no trait inheritance
+ /// relationship between the resolutions.
+ sub_trait_resolution: Option<StoredEarlyBinder<(TypeAliasId, StoredGenericArgs)>>,
+ },
+ NotFound,
+ Cycle,
+}
+
/// Predicates for `param_id` of the form `P: SomeTrait`. If
/// `assoc_name` is provided, only return predicates referencing traits
/// that have an associated type of that name.
@@ -1640,15 +1707,14 @@ pub(crate) fn field_types_with_diagnostics_query<'db>(
/// following bounds are disallowed: `T: Foo<U::Item>, U: Foo<T::Item>`, but
/// these are fine: `T: Foo<U::Item>, U: Foo<()>`.
#[tracing::instrument(skip(db), ret)]
-#[salsa::tracked(returns(ref), cycle_result = generic_predicates_for_param_cycle_result)]
-pub(crate) fn generic_predicates_for_param<'db>(
- db: &'db dyn HirDatabase,
+#[salsa::tracked(returns(ref), cycle_result = resolve_type_param_assoc_type_shorthand_cycle_result)]
+fn resolve_type_param_assoc_type_shorthand(
+ db: &dyn HirDatabase,
def: GenericDefId,
- param_id: TypeOrConstParamId,
- assoc_name: Option<Name>,
-) -> StoredEarlyBinder<StoredClauses> {
+ param: TypeParamId,
+ assoc_name: Name,
+) -> AssocTypeShorthandResolution {
let generics = generics(db, def);
- let interner = DbInterner::new_no_crate(db);
let resolver = def.resolver(db);
let mut ctx = TyLoweringContext::new(
db,
@@ -1657,128 +1723,129 @@ pub(crate) fn generic_predicates_for_param<'db>(
def,
LifetimeElisionKind::AnonymousReportError,
);
+ let interner = ctx.interner;
+ let param_ty = Ty::new_param(
+ interner,
+ param,
+ generics.type_or_const_param_idx(param.into()).unwrap() as u32,
+ );
- // we have to filter out all other predicates *first*, before attempting to lower them
- let has_relevant_bound = |pred: &_, ctx: &mut TyLoweringContext<'_, '_>| match pred {
- WherePredicate::ForLifetime { target, bound, .. }
- | WherePredicate::TypeBound { target, bound, .. } => {
- let invalid_target = { ctx.lower_ty_only_param(*target) != Some(param_id) };
- if invalid_target {
- // FIXME(sized-hierarchy): Revisit and adjust this properly once we have implemented
- // sized-hierarchy correctly.
- // If this is filtered out without lowering, `?Sized` or `PointeeSized` is not gathered into
- // `ctx.unsized_types`
- let lower = || -> bool {
- match bound {
- TypeBound::Path(_, TraitBoundModifier::Maybe) => true,
- TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
- let TypeRef::Path(path) = &ctx.store[path.type_ref()] else {
- return false;
- };
- let Some(pointee_sized) = ctx.lang_items.PointeeSized else {
- return false;
- };
- // Lower the path directly with `Resolver` instead of PathLoweringContext`
- // to prevent diagnostics duplications.
- ctx.resolver.resolve_path_in_type_ns_fully(ctx.db, path).is_some_and(
- |it| matches!(it, TypeNs::TraitId(tr) if tr == pointee_sized),
- )
- }
- _ => false,
- }
- }();
- if lower {
- ctx.lower_where_predicate(pred, true, &generics, PredicateFilter::All)
- .for_each(drop);
- }
- return false;
- }
-
- match bound {
- &TypeBound::ForLifetime(_, path) | &TypeBound::Path(path, _) => {
- // Only lower the bound if the trait could possibly define the associated
- // type we're looking for.
- let path = &ctx.store[path];
-
- let Some(assoc_name) = &assoc_name else { return true };
- let Some(TypeNs::TraitId(tr)) =
- resolver.resolve_path_in_type_ns_fully(db, path)
- else {
- return false;
- };
-
- trait_or_supertrait_has_assoc_type(db, tr, assoc_name)
- }
- TypeBound::Use(_) | TypeBound::Lifetime(_) | TypeBound::Error => false,
- }
+ let mut this_trait_resolution = None;
+ if let GenericDefId::TraitId(containing_trait) = param.parent()
+ && param.local_id() == GenericParams::SELF_PARAM_ID_IN_SELF
+ {
+ // Add the trait's own associated types.
+ if let Some(assoc_type) =
+ containing_trait.trait_items(db).associated_type_by_name(&assoc_name)
+ {
+ let args = GenericArgs::identity_for_item(interner, containing_trait.into());
+ this_trait_resolution = Some(StoredEarlyBinder::bind((assoc_type, args.store())));
}
- WherePredicate::Lifetime { .. } => false,
- };
- let mut predicates = Vec::new();
+ }
+
+ let mut supertraits_resolution = None;
for maybe_parent_generics in
std::iter::successors(Some(&generics), |generics| generics.parent_generics())
{
ctx.store = maybe_parent_generics.store();
for pred in maybe_parent_generics.where_predicates() {
- if has_relevant_bound(pred, &mut ctx) {
- predicates.extend(
- ctx.lower_where_predicate(
- pred,
- true,
- maybe_parent_generics,
- PredicateFilter::All,
- )
- .map(|(pred, _)| pred),
- );
+ let (WherePredicate::TypeBound { target, bound }
+ | WherePredicate::ForLifetime { lifetimes: _, target, bound }) = pred
+ else {
+ continue;
+ };
+ let (TypeBound::Path(bounded_trait_path, TraitBoundModifier::None)
+ | TypeBound::ForLifetime(_, bounded_trait_path)) = *bound
+ else {
+ continue;
+ };
+ let Some(target) = ctx.lower_ty_only_param(*target) else { continue };
+ if target != param.into() {
+ continue;
+ }
+ let Some(TypeNs::TraitId(bounded_trait)) =
+ resolver.resolve_path_in_type_ns_fully(db, &ctx.store[bounded_trait_path])
+ else {
+ continue;
+ };
+ if !SupertraitsInfo::query(db, bounded_trait)
+ .defined_assoc_types
+ .iter()
+ .any(|(name, _)| *name == assoc_name)
+ {
+ continue;
+ }
+
+ let Some((bounded_trait_ref, _)) =
+ ctx.lower_trait_ref_from_path(bounded_trait_path, param_ty)
+ else {
+ continue;
+ };
+ // Now, search from the start on the *bounded* trait like if we wrote `Self::Assoc`. Eventually, we'll get
+ // the correct trait ref (or a cycle).
+ let lookup_on_bounded_trait = resolve_type_param_assoc_type_shorthand(
+ db,
+ bounded_trait.into(),
+ TypeParamId::trait_self(bounded_trait),
+ assoc_name.clone(),
+ );
+ let assoc_type_and_args = match &lookup_on_bounded_trait {
+ AssocTypeShorthandResolution::Resolved(trait_ref) => trait_ref,
+ AssocTypeShorthandResolution::Ambiguous {
+ sub_trait_resolution: Some(trait_ref),
+ } => trait_ref,
+ AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: None } => {
+ return AssocTypeShorthandResolution::Ambiguous {
+ sub_trait_resolution: this_trait_resolution,
+ };
+ }
+ AssocTypeShorthandResolution::NotFound => {
+ never!("we checked that the trait defines this assoc type");
+ continue;
+ }
+ AssocTypeShorthandResolution::Cycle => return AssocTypeShorthandResolution::Cycle,
+ };
+ if let Some(this_trait_resolution) = this_trait_resolution {
+ return AssocTypeShorthandResolution::Ambiguous {
+ sub_trait_resolution: Some(this_trait_resolution),
+ };
+ } else if supertraits_resolution.is_some() {
+ return AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: None };
+ } else {
+ let (assoc_type, args) = assoc_type_and_args
+ .get_with(|(assoc_type, args)| (*assoc_type, args.as_ref()))
+ .skip_binder();
+ let args = EarlyBinder::bind(args).instantiate(interner, bounded_trait_ref.args);
+ let current_result = StoredEarlyBinder::bind((assoc_type, args.store()));
+ supertraits_resolution = Some(match lookup_on_bounded_trait {
+ AssocTypeShorthandResolution::Resolved(_) => {
+ AssocTypeShorthandResolution::Resolved(current_result)
+ }
+ AssocTypeShorthandResolution::Ambiguous { .. } => {
+ AssocTypeShorthandResolution::Ambiguous {
+ sub_trait_resolution: Some(current_result),
+ }
+ }
+ AssocTypeShorthandResolution::NotFound
+ | AssocTypeShorthandResolution::Cycle => unreachable!(),
+ });
}
}
}
- let args = GenericArgs::identity_for_item(interner, def.into());
- if !args.is_empty() {
- let explicitly_unsized_tys = ctx.unsized_types;
- if let Some(implicitly_sized_predicates) = implicitly_sized_clauses(
- db,
- ctx.lang_items,
- param_id.parent,
- &explicitly_unsized_tys,
- &args,
- ) {
- predicates.extend(implicitly_sized_predicates);
- };
- }
- StoredEarlyBinder::bind(Clauses::new_from_slice(&predicates).store())
+ supertraits_resolution
+ .or_else(|| this_trait_resolution.map(AssocTypeShorthandResolution::Resolved))
+ .unwrap_or(AssocTypeShorthandResolution::NotFound)
}
-pub(crate) fn generic_predicates_for_param_cycle_result(
- db: &dyn HirDatabase,
+fn resolve_type_param_assoc_type_shorthand_cycle_result(
+ _db: &dyn HirDatabase,
_: salsa::Id,
_def: GenericDefId,
- _param_id: TypeOrConstParamId,
- _assoc_name: Option<Name>,
-) -> StoredEarlyBinder<StoredClauses> {
- StoredEarlyBinder::bind(Clauses::empty(DbInterner::new_no_crate(db)).store())
-}
-
-/// Check if this trait or any of its supertraits define an associated
-/// type with the given name.
-fn trait_or_supertrait_has_assoc_type(
- db: &dyn HirDatabase,
- tr: TraitId,
- assoc_name: &Name,
-) -> bool {
- for trait_id in all_super_traits(db, tr) {
- if trait_id
- .trait_items(db)
- .items
- .iter()
- .any(|(name, item)| matches!(item, AssocItemId::TypeAliasId(_)) && name == assoc_name)
- {
- return true;
- }
- }
-
- false
+ _param: TypeParamId,
+ _assoc_name: Name,
+) -> AssocTypeShorthandResolution {
+ AssocTypeShorthandResolution::Cycle
}
#[inline]
@@ -1904,7 +1971,7 @@ impl<'db> GenericPredicates {
db: &'db dyn HirDatabase,
def: GenericDefId,
) -> (GenericPredicates, Diagnostics) {
- generic_predicates_filtered_by(db, def, PredicateFilter::All, |_| true)
+ generic_predicates(db, def)
}
}
@@ -2042,24 +2109,10 @@ pub(crate) fn trait_environment<'db>(db: &'db dyn HirDatabase, def: GenericDefId
}
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub(crate) enum PredicateFilter {
- SelfTrait,
- All,
-}
-
/// Resolve the where clause(s) of an item with generics,
/// with a given filter
-#[tracing::instrument(skip(db, filter), ret)]
-pub(crate) fn generic_predicates_filtered_by<F>(
- db: &dyn HirDatabase,
- def: GenericDefId,
- predicate_filter: PredicateFilter,
- filter: F,
-) -> (GenericPredicates, Diagnostics)
-where
- F: Fn(GenericDefId) -> bool,
-{
+#[tracing::instrument(skip(db), ret)]
+fn generic_predicates(db: &dyn HirDatabase, def: GenericDefId) -> (GenericPredicates, Diagnostics) {
let generics = generics(db, def);
let resolver = def.resolver(db);
let interner = DbInterner::new_no_crate(db);
@@ -2081,9 +2134,9 @@ where
let all_generics =
std::iter::successors(Some(&generics), |generics| generics.parent_generics())
.collect::<ArrayVec<_, 2>>();
- let own_implicit_trait_predicate = implicit_trait_predicate(interner, def, predicate_filter);
+ let own_implicit_trait_predicate = implicit_trait_predicate(interner, def);
let parent_implicit_trait_predicate = if all_generics.len() > 1 {
- implicit_trait_predicate(interner, all_generics.last().unwrap().def(), predicate_filter)
+ implicit_trait_predicate(interner, all_generics.last().unwrap().def())
} else {
None
};
@@ -2091,97 +2144,85 @@ where
// Collect only diagnostics from the child, not including parents.
ctx.diagnostics.clear();
- if filter(maybe_parent_generics.def()) {
- ctx.store = maybe_parent_generics.store();
- for pred in maybe_parent_generics.where_predicates() {
- tracing::debug!(?pred);
- for (pred, source) in
- ctx.lower_where_predicate(pred, false, maybe_parent_generics, predicate_filter)
- {
- match source {
- GenericPredicateSource::SelfOnly => {
- if maybe_parent_generics.def() == def {
- own_predicates.push(pred);
- } else {
- parent_predicates.push(pred);
- }
+ ctx.store = maybe_parent_generics.store();
+ for pred in maybe_parent_generics.where_predicates() {
+ tracing::debug!(?pred);
+ for (pred, source) in ctx.lower_where_predicate(pred, false) {
+ match source {
+ GenericPredicateSource::SelfOnly => {
+ if maybe_parent_generics.def() == def {
+ own_predicates.push(pred);
+ } else {
+ parent_predicates.push(pred);
}
- GenericPredicateSource::AssocTyBound => {
- if maybe_parent_generics.def() == def {
- own_assoc_ty_bounds.push(pred);
- } else {
- parent_assoc_ty_bounds.push(pred);
- }
+ }
+ GenericPredicateSource::AssocTyBound => {
+ if maybe_parent_generics.def() == def {
+ own_assoc_ty_bounds.push(pred);
+ } else {
+ parent_assoc_ty_bounds.push(pred);
}
}
}
}
+ }
- if maybe_parent_generics.def() == def {
- push_const_arg_has_type_predicates(db, &mut own_predicates, maybe_parent_generics);
- } else {
- push_const_arg_has_type_predicates(
- db,
- &mut parent_predicates,
- maybe_parent_generics,
- );
- }
+ if maybe_parent_generics.def() == def {
+ push_const_arg_has_type_predicates(db, &mut own_predicates, maybe_parent_generics);
+ } else {
+ push_const_arg_has_type_predicates(db, &mut parent_predicates, maybe_parent_generics);
+ }
- if let Some(sized_trait) = sized_trait {
- let mut add_sized_clause = |param_idx, param_id, param_data| {
- let (
- GenericParamId::TypeParamId(param_id),
- GenericParamDataRef::TypeParamData(param_data),
- ) = (param_id, param_data)
- else {
- return;
- };
+ if let Some(sized_trait) = sized_trait {
+ let mut add_sized_clause = |param_idx, param_id, param_data| {
+ let (
+ GenericParamId::TypeParamId(param_id),
+ GenericParamDataRef::TypeParamData(param_data),
+ ) = (param_id, param_data)
+ else {
+ return;
+ };
- if param_data.provenance == TypeParamProvenance::TraitSelf {
- return;
- }
+ if param_data.provenance == TypeParamProvenance::TraitSelf {
+ return;
+ }
- let param_ty = Ty::new_param(interner, param_id, param_idx);
- if ctx.unsized_types.contains(&param_ty) {
- return;
- }
- let trait_ref = TraitRef::new_from_args(
- interner,
- sized_trait.into(),
- GenericArgs::new_from_slice(&[param_ty.into()]),
- );
- let clause = Clause(Predicate::new(
- interner,
- Binder::dummy(rustc_type_ir::PredicateKind::Clause(
- rustc_type_ir::ClauseKind::Trait(TraitPredicate {
- trait_ref,
- polarity: rustc_type_ir::PredicatePolarity::Positive,
- }),
- )),
- ));
- if maybe_parent_generics.def() == def {
- own_predicates.push(clause);
- } else {
- parent_predicates.push(clause);
- }
- };
- let parent_params_len = maybe_parent_generics.len_parent();
- maybe_parent_generics.iter_self().enumerate().for_each(
- |(param_idx, (param_id, param_data))| {
- add_sized_clause(
- (param_idx + parent_params_len) as u32,
- param_id,
- param_data,
- );
- },
+ let param_ty = Ty::new_param(interner, param_id, param_idx);
+ if ctx.unsized_types.contains(&param_ty) {
+ return;
+ }
+ let trait_ref = TraitRef::new_from_args(
+ interner,
+ sized_trait.into(),
+ GenericArgs::new_from_slice(&[param_ty.into()]),
);
- }
-
- // We do not clear `ctx.unsized_types`, as the `?Sized` clause of a child (e.g. an associated type) can
- // be declared on the parent (e.g. the trait). It is nevertheless fine to register the implicit `Sized`
- // predicates before lowering the child, as a child cannot define a `?Sized` predicate for its parent.
- // But we do have to lower the parent first.
+ let clause = Clause(Predicate::new(
+ interner,
+ Binder::dummy(rustc_type_ir::PredicateKind::Clause(
+ rustc_type_ir::ClauseKind::Trait(TraitPredicate {
+ trait_ref,
+ polarity: rustc_type_ir::PredicatePolarity::Positive,
+ }),
+ )),
+ ));
+ if maybe_parent_generics.def() == def {
+ own_predicates.push(clause);
+ } else {
+ parent_predicates.push(clause);
+ }
+ };
+ let parent_params_len = maybe_parent_generics.len_parent();
+ maybe_parent_generics.iter_self().enumerate().for_each(
+ |(param_idx, (param_id, param_data))| {
+ add_sized_clause((param_idx + parent_params_len) as u32, param_id, param_data);
+ },
+ );
}
+
+ // We do not clear `ctx.unsized_types`, as the `?Sized` clause of a child (e.g. an associated type) can
+ // be declared on the parent (e.g. the trait). It is nevertheless fine to register the implicit `Sized`
+ // predicates before lowering the child, as a child cannot define a `?Sized` predicate for its parent.
+ // But we do have to lower the parent first.
}
let diagnostics = create_diagnostics(ctx.diagnostics);
@@ -2229,7 +2270,6 @@ where
fn implicit_trait_predicate<'db>(
interner: DbInterner<'db>,
def: GenericDefId,
- predicate_filter: PredicateFilter,
) -> Option<Clause<'db>> {
// For traits, add `Self: Trait` predicate. This is
// not part of the predicates that a user writes, but it
@@ -2243,9 +2283,7 @@ where
// prove that the trait applies to the types that were
// used, and adding the predicate into this list ensures
// that this is done.
- if let GenericDefId::TraitId(def_id) = def
- && predicate_filter == PredicateFilter::All
- {
+ if let GenericDefId::TraitId(def_id) = def {
Some(TraitRef::identity(interner, def_id.into()).upcast(interner))
} else {
None
@@ -2282,49 +2320,6 @@ fn push_const_arg_has_type_predicates<'db>(
}
}
-/// Generate implicit `: Sized` predicates for all generics that has no `?Sized` bound.
-/// Exception is Self of a trait def.
-fn implicitly_sized_clauses<'a, 'subst, 'db>(
- db: &'db dyn HirDatabase,
- lang_items: &LangItems,
- def: GenericDefId,
- explicitly_unsized_tys: &'a FxHashSet<Ty<'db>>,
- args: &'subst GenericArgs<'db>,
-) -> Option<impl Iterator<Item = Clause<'db>> + Captures<'a> + Captures<'subst>> {
- let interner = DbInterner::new_no_crate(db);
- let sized_trait = lang_items.Sized?;
-
- let trait_self_idx = trait_self_param_idx(db, def);
-
- Some(
- args.iter()
- .enumerate()
- .filter_map(
- move |(idx, generic_arg)| {
- if Some(idx) == trait_self_idx { None } else { Some(generic_arg) }
- },
- )
- .filter_map(|generic_arg| generic_arg.as_type())
- .filter(move |self_ty| !explicitly_unsized_tys.contains(self_ty))
- .map(move |self_ty| {
- let trait_ref = TraitRef::new_from_args(
- interner,
- sized_trait.into(),
- GenericArgs::new_from_slice(&[self_ty.into()]),
- );
- Clause(Predicate::new(
- interner,
- Binder::dummy(rustc_type_ir::PredicateKind::Clause(
- rustc_type_ir::ClauseKind::Trait(TraitPredicate {
- trait_ref,
- polarity: rustc_type_ir::PredicatePolarity::Positive,
- }),
- )),
- ))
- }),
- )
-}
-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GenericDefaults(Option<Arc<[Option<StoredEarlyBinder<StoredGenericArg>>]>>);
@@ -2498,7 +2493,7 @@ fn fn_sig_for_struct_constructor(
let inputs_and_output =
Tys::new_from_iter(DbInterner::new_no_crate(db), params.chain(Some(ret.as_ref())));
StoredEarlyBinder::bind(StoredPolyFnSig::new(Binder::dummy(FnSig {
- abi: FnAbi::RustCall,
+ abi: FnAbi::Rust,
c_variadic: false,
safety: Safety::Safe,
inputs_and_output,
@@ -2517,7 +2512,7 @@ fn fn_sig_for_enum_variant_constructor(
let inputs_and_output =
Tys::new_from_iter(DbInterner::new_no_crate(db), params.chain(Some(ret.as_ref())));
StoredEarlyBinder::bind(StoredPolyFnSig::new(Binder::dummy(FnSig {
- abi: FnAbi::RustCall,
+ abi: FnAbi::Rust,
c_variadic: false,
safety: Safety::Safe,
inputs_and_output,
@@ -2599,238 +2594,25 @@ pub(crate) fn associated_ty_item_bounds<'db>(
EarlyBinder::bind(BoundExistentialPredicates::new_from_slice(&bounds))
}
-pub(crate) fn associated_type_by_name_including_super_traits<'db>(
+pub(crate) fn associated_type_by_name_including_super_traits_allow_ambiguity<'db>(
db: &'db dyn HirDatabase,
trait_ref: TraitRef<'db>,
- name: &Name,
-) -> Option<(TraitRef<'db>, TypeAliasId)> {
- let module = trait_ref.def_id.0.module(db);
- let interner = DbInterner::new_with(db, module.krate(db));
- all_supertraits_trait_refs(db, trait_ref.def_id.0)
- .map(|t| t.instantiate(interner, trait_ref.args))
- .find_map(|t| {
- let trait_id = t.def_id.0;
- let assoc_type = trait_id.trait_items(db).associated_type_by_name(name)?;
- Some((t, assoc_type))
- })
-}
-
-pub fn associated_type_shorthand_candidates(
- db: &dyn HirDatabase,
- def: GenericDefId,
- res: TypeNs,
- mut cb: impl FnMut(&Name, TypeAliasId) -> bool,
-) -> Option<TypeAliasId> {
- let interner = DbInterner::new_no_crate(db);
- named_associated_type_shorthand_candidates(interner, def, res, None, |name, _, id| {
- cb(name, id).then_some(id)
- })
-}
-
-#[tracing::instrument(skip(interner, check_alias))]
-fn named_associated_type_shorthand_candidates<'db, R>(
- interner: DbInterner<'db>,
- // If the type parameter is defined in an impl and we're in a method, there
- // might be additional where clauses to consider
- def: GenericDefId,
- res: TypeNs,
- assoc_name: Option<Name>,
- mut check_alias: impl FnMut(&Name, TraitRef<'db>, TypeAliasId) -> Option<R>,
-) -> Option<R> {
- let db = interner.db;
- let mut search = |t: TraitRef<'db>| -> Option<R> {
- let mut checked_traits = FxHashSet::default();
- let mut check_trait = |trait_ref: TraitRef<'db>| {
- let trait_id = trait_ref.def_id.0;
- let name = &db.trait_signature(trait_id).name;
- tracing::debug!(?trait_id, ?name);
- if !checked_traits.insert(trait_id) {
- return None;
- }
- let data = trait_id.trait_items(db);
-
- tracing::debug!(?data.items);
- for (name, assoc_id) in &data.items {
- if let &AssocItemId::TypeAliasId(alias) = assoc_id
- && let Some(ty) = check_alias(name, trait_ref, alias)
- {
- return Some(ty);
- }
- }
- None
- };
- let mut stack: SmallVec<[_; 4]> = smallvec![t];
- while let Some(trait_ref) = stack.pop() {
- if let Some(alias) = check_trait(trait_ref) {
- return Some(alias);
- }
- let predicates = generic_predicates_filtered_by(
- db,
- GenericDefId::TraitId(trait_ref.def_id.0),
- PredicateFilter::SelfTrait,
- // We are likely in the midst of lowering generic predicates of `def`.
- // So, if we allow `pred == def` we might fall into an infinite recursion.
- // Actually, we have already checked for the case `pred == def` above as we started
- // with a stack including `trait_id`
- |pred| pred != def && pred == GenericDefId::TraitId(trait_ref.def_id.0),
- )
- .0
- .predicates;
- for pred in predicates.get().instantiate_identity() {
- tracing::debug!(?pred);
- let sup_trait_ref = match pred.kind().skip_binder() {
- rustc_type_ir::ClauseKind::Trait(pred) => pred.trait_ref,
- _ => continue,
- };
- let sup_trait_ref =
- EarlyBinder::bind(sup_trait_ref).instantiate(interner, trait_ref.args);
- stack.push(sup_trait_ref);
- }
- tracing::debug!(?stack);
- }
-
- None
+ name: Name,
+) -> Option<(TypeAliasId, GenericArgs<'db>)> {
+ let (AssocTypeShorthandResolution::Resolved(assoc_type)
+ | AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: Some(assoc_type) }) =
+ resolve_type_param_assoc_type_shorthand(
+ db,
+ trait_ref.def_id.0.into(),
+ TypeParamId::trait_self(trait_ref.def_id.0),
+ name.clone(),
+ )
+ else {
+ return None;
};
-
- match res {
- TypeNs::SelfType(impl_id) => {
- let trait_ref = db.impl_trait(impl_id)?;
-
- // FIXME(next-solver): same method in `lower` checks for impl or not
- // Is that needed here?
-
- // we're _in_ the impl -- the binders get added back later. Correct,
- // but it would be nice to make this more explicit
- search(trait_ref.skip_binder())
- }
- TypeNs::GenericParam(param_id) => {
- // Handle `Self::Type` referring to own associated type in trait definitions
- // This *must* be done first to avoid cycles with
- // `generic_predicates_for_param`, but not sure that it's sufficient,
- if let GenericDefId::TraitId(trait_id) = param_id.parent() {
- let trait_name = &db.trait_signature(trait_id).name;
- tracing::debug!(?trait_name);
- let trait_generics = generics(db, trait_id.into());
- tracing::debug!(?trait_generics);
- if trait_generics[param_id.local_id()].is_trait_self() {
- let args = GenericArgs::identity_for_item(interner, trait_id.into());
- let trait_ref = TraitRef::new_from_args(interner, trait_id.into(), args);
- tracing::debug!(?args, ?trait_ref);
- return search(trait_ref);
- }
- }
-
- let predicates =
- generic_predicates_for_param(db, def, param_id.into(), assoc_name.clone());
- predicates
- .get()
- .iter_identity()
- .find_map(|pred| match pred.kind().skip_binder() {
- rustc_type_ir::ClauseKind::Trait(trait_predicate) => Some(trait_predicate),
- _ => None,
- })
- .and_then(|trait_predicate| {
- let trait_ref = trait_predicate.trait_ref;
- assert!(
- !trait_ref.has_escaping_bound_vars(),
- "FIXME unexpected higher-ranked trait bound"
- );
- search(trait_ref)
- })
- }
- _ => None,
- }
-}
-
-/// During lowering, elaborating supertraits can cause cycles. To avoid that, we have a separate query
-/// to only collect supertraits.
-///
-/// Technically, it is possible to avoid even more cycles by only collecting the `TraitId` of supertraits
-/// without their args. However rustc doesn't do that, so we don't either.
-pub(crate) fn all_supertraits_trait_refs(
- db: &dyn HirDatabase,
- trait_: TraitId,
-) -> impl ExactSizeIterator<Item = EarlyBinder<'_, TraitRef<'_>>> {
+ let (assoc_type, trait_args) = assoc_type
+ .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref()))
+ .skip_binder();
let interner = DbInterner::new_no_crate(db);
- return all_supertraits_trait_refs_query(db, trait_).iter().map(move |trait_ref| {
- trait_ref.get_with(|(trait_, args)| {
- TraitRef::new_from_args(interner, (*trait_).into(), args.as_ref())
- })
- });
-
- #[salsa_macros::tracked(returns(deref), cycle_result = all_supertraits_trait_refs_cycle_result)]
- pub(crate) fn all_supertraits_trait_refs_query(
- db: &dyn HirDatabase,
- trait_: TraitId,
- ) -> Box<[StoredEarlyBinder<(TraitId, StoredGenericArgs)>]> {
- let resolver = trait_.resolver(db);
- let signature = db.trait_signature(trait_);
- let mut ctx = TyLoweringContext::new(
- db,
- &resolver,
- &signature.store,
- trait_.into(),
- LifetimeElisionKind::AnonymousReportError,
- );
- let interner = ctx.interner;
-
- let self_param_ty = Ty::new_param(
- interner,
- TypeParamId::from_unchecked(TypeOrConstParamId {
- parent: trait_.into(),
- local_id: Idx::from_raw(la_arena::RawIdx::from_u32(0)),
- }),
- 0,
- );
-
- let mut supertraits = FxHashSet::default();
- supertraits.insert(StoredEarlyBinder::bind((
- trait_,
- GenericArgs::identity_for_item(interner, trait_.into()).store(),
- )));
-
- for pred in signature.generic_params.where_predicates() {
- let WherePredicate::TypeBound { target, bound } = pred else {
- continue;
- };
- let target = &signature.store[*target];
- if let TypeRef::TypeParam(param_id) = target
- && param_id.local_id().into_raw().into_u32() == 0
- {
- // This is `Self`.
- } else if let TypeRef::Path(path) = target
- && path.is_self_type()
- {
- // Also `Self`.
- } else {
- // Not `Self`!
- continue;
- }
-
- ctx.lower_type_bound(bound, self_param_ty, true).for_each(|(clause, _)| {
- if let ClauseKind::Trait(trait_ref) = clause.kind().skip_binder() {
- supertraits.extend(
- all_supertraits_trait_refs(db, trait_ref.trait_ref.def_id.0).map(|t| {
- let trait_ref = t.instantiate(interner, trait_ref.trait_ref.args);
- StoredEarlyBinder::bind((trait_ref.def_id.0, trait_ref.args.store()))
- }),
- );
- }
- });
- }
-
- Box::from_iter(supertraits)
- }
-
- pub(crate) fn all_supertraits_trait_refs_cycle_result(
- db: &dyn HirDatabase,
- _: salsa::Id,
- trait_: TraitId,
- ) -> Box<[StoredEarlyBinder<(TraitId, StoredGenericArgs)>]> {
- let interner = DbInterner::new_no_crate(db);
- Box::new([StoredEarlyBinder::bind((
- trait_,
- GenericArgs::identity_for_item(interner, trait_.into()).store(),
- ))])
- }
+ Some((assoc_type, EarlyBinder::bind(trait_args).instantiate(interner, trait_ref.args)))
}
diff --git a/crates/hir-ty/src/lower/path.rs b/crates/hir-ty/src/lower/path.rs
index f3d0de1227..79f29d370f 100644
--- a/crates/hir-ty/src/lower/path.rs
+++ b/crates/hir-ty/src/lower/path.rs
@@ -2,7 +2,7 @@
use either::Either;
use hir_def::{
- GenericDefId, GenericParamId, Lookup, TraitId, TypeAliasId,
+ GenericDefId, GenericParamId, Lookup, TraitId, TypeParamId,
expr_store::{
ExpressionStore, HygieneId,
path::{
@@ -17,7 +17,6 @@ use hir_def::{
signatures::TraitFlags,
type_ref::{TypeRef, TypeRefId},
};
-use hir_expand::name::Name;
use rustc_type_ir::{
AliasTerm, AliasTy, AliasTyKind,
inherent::{GenericArgs as _, Region as _, Ty as _},
@@ -32,18 +31,18 @@ use crate::{
db::HirDatabase,
generics::{Generics, generics},
lower::{
- GenericPredicateSource, LifetimeElisionKind, PathDiagnosticCallbackData,
- named_associated_type_shorthand_candidates,
+ AssocTypeShorthandResolution, GenericPredicateSource, LifetimeElisionKind,
+ PathDiagnosticCallbackData,
},
next_solver::{
- Binder, Clause, Const, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, Predicate,
- ProjectionPredicate, Region, TraitRef, Ty,
+ Binder, Clause, Const, DbInterner, EarlyBinder, ErrorGuaranteed, GenericArg, GenericArgs,
+ Predicate, ProjectionPredicate, Region, TraitRef, Ty,
},
};
use super::{
- ImplTraitLoweringMode, TyLoweringContext, associated_type_by_name_including_super_traits,
- const_param_ty_query, ty_query,
+ ImplTraitLoweringMode, TyLoweringContext,
+ associated_type_by_name_including_super_traits_allow_ambiguity, const_param_ty_query, ty_query,
};
type CallbackData<'a> =
@@ -396,12 +395,10 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
}
let (mod_segments, enum_segment, resolved_segment_idx) = match res {
- ResolveValueResult::Partial(_, unresolved_segment, _) => {
+ ResolveValueResult::Partial(_, unresolved_segment) => {
(segments.take(unresolved_segment - 1), None, unresolved_segment - 1)
}
- ResolveValueResult::ValueNs(ValueNs::EnumVariantId(_), _)
- if prefix_info.enum_variant =>
- {
+ ResolveValueResult::ValueNs(ValueNs::EnumVariantId(_)) if prefix_info.enum_variant => {
(segments.strip_last_two(), segments.len().checked_sub(2), segments.len() - 1)
}
ResolveValueResult::ValueNs(..) => (segments.strip_last(), None, segments.len() - 1),
@@ -431,7 +428,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
}
match &res {
- ResolveValueResult::ValueNs(resolution, _) => {
+ ResolveValueResult::ValueNs(resolution) => {
let resolved_segment_idx = self.current_segment_u32();
let resolved_segment = self.current_or_prev_segment;
@@ -469,7 +466,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
| ValueNs::ConstId(_) => {}
}
}
- ResolveValueResult::Partial(resolution, _, _) => {
+ ResolveValueResult::Partial(resolution, _) => {
if !self.handle_type_ns_resolution(resolution) {
return None;
}
@@ -481,43 +478,63 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
#[tracing::instrument(skip(self), ret)]
fn select_associated_type(&mut self, res: Option<TypeNs>, infer_args: bool) -> Ty<'db> {
let interner = self.ctx.interner;
- let Some(res) = res else {
- return Ty::new_error(self.ctx.interner, ErrorGuaranteed);
- };
+ let db = self.ctx.db;
let def = self.ctx.def;
let segment = self.current_or_prev_segment;
let assoc_name = segment.name;
- let check_alias = |name: &Name, t: TraitRef<'db>, associated_ty: TypeAliasId| {
- if name != assoc_name {
- return None;
+ let error_ty = || Ty::new_error(self.ctx.interner, ErrorGuaranteed);
+ let (assoc_type, trait_args) = match res {
+ Some(TypeNs::GenericParam(param)) => {
+ let AssocTypeShorthandResolution::Resolved(assoc_type) =
+ super::resolve_type_param_assoc_type_shorthand(
+ db,
+ def,
+ param,
+ assoc_name.clone(),
+ )
+ else {
+ return error_ty();
+ };
+ assoc_type
+ .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref()))
+ .skip_binder()
}
-
- // FIXME: `substs_from_path_segment()` pushes `TyKind::Error` for every parent
- // generic params. It's inefficient to splice the `Substitution`s, so we may want
- // that method to optionally take parent `Substitution` as we already know them at
- // this point (`t.substitution`).
- let substs =
- self.substs_from_path_segment(associated_ty.into(), infer_args, None, true);
-
- let substs = GenericArgs::new_from_iter(
- interner,
- t.args.iter().chain(substs.iter().skip(t.args.len())),
- );
-
- Some(Ty::new_alias(
- interner,
- AliasTyKind::Projection,
- AliasTy::new_from_args(interner, associated_ty.into(), substs),
- ))
+ Some(TypeNs::SelfType(impl_)) => {
+ let Some(impl_trait) = db.impl_trait(impl_) else {
+ return error_ty();
+ };
+ let impl_trait = impl_trait.instantiate_identity();
+ // Searching for `Self::Assoc` in `impl Trait for Type` is like searching for `Self::Assoc` in `Trait`.
+ let AssocTypeShorthandResolution::Resolved(assoc_type) =
+ super::resolve_type_param_assoc_type_shorthand(
+ db,
+ impl_trait.def_id.0.into(),
+ TypeParamId::trait_self(impl_trait.def_id.0),
+ assoc_name.clone(),
+ )
+ else {
+ return error_ty();
+ };
+ let (assoc_type, trait_args) = assoc_type
+ .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref()))
+ .skip_binder();
+ (assoc_type, EarlyBinder::bind(trait_args).instantiate(interner, impl_trait.args))
+ }
+ _ => return error_ty(),
};
- named_associated_type_shorthand_candidates(
+
+ // FIXME: `substs_from_path_segment()` pushes `TyKind::Error` for every parent
+ // generic params. It's inefficient to splice the `Substitution`s, so we may want
+ // that method to optionally take parent `Substitution` as we already know them at
+ // this point (`t.substitution`).
+ let substs = self.substs_from_path_segment(assoc_type.into(), infer_args, None, true);
+
+ let substs = GenericArgs::new_from_iter(
interner,
- def,
- res,
- Some(assoc_name.clone()),
- check_alias,
- )
- .unwrap_or_else(|| Ty::new_error(interner, ErrorGuaranteed))
+ trait_args.iter().chain(substs.iter().skip(trait_args.len())),
+ );
+
+ Ty::new_projection_from_args(interner, assoc_type.into(), substs)
}
fn lower_path_inner(&mut self, typeable: TyDefId, infer_args: bool) -> Ty<'db> {
@@ -859,12 +876,12 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
let interner = self.ctx.interner;
self.current_or_prev_segment.args_and_bindings.map(|args_and_bindings| {
args_and_bindings.bindings.iter().enumerate().flat_map(move |(binding_idx, binding)| {
- let found = associated_type_by_name_including_super_traits(
+ let found = associated_type_by_name_including_super_traits_allow_ambiguity(
self.ctx.db,
trait_ref,
- &binding.name,
+ binding.name.clone(),
);
- let (super_trait_ref, associated_ty) = match found {
+ let (associated_ty, super_trait_args) = match found {
None => return SmallVec::new(),
Some(t) => t,
};
@@ -878,7 +895,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
binding.args.as_ref(),
associated_ty.into(),
false, // this is not relevant
- Some(super_trait_ref.self_ty()),
+ Some(super_trait_args.type_at(0)),
PathGenericsSource::AssocType {
segment: this.current_segment_u32(),
assoc_type: binding_idx as u32,
@@ -889,7 +906,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> {
});
let args = GenericArgs::new_from_iter(
interner,
- super_trait_ref.args.iter().chain(args.iter().skip(super_trait_ref.args.len())),
+ super_trait_args.iter().chain(args.iter().skip(super_trait_args.len())),
);
let projection_term =
AliasTerm::new_from_args(interner, associated_ty.into(), args);
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index 5de08313f4..ec0723c3f8 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -1625,7 +1625,7 @@ impl<'db> Evaluator<'db> {
};
match target_ty {
rustc_type_ir::FloatTy::F32 => Owned((value as f32).to_le_bytes().to_vec()),
- rustc_type_ir::FloatTy::F64 => Owned((value as f64).to_le_bytes().to_vec()),
+ rustc_type_ir::FloatTy::F64 => Owned(value.to_le_bytes().to_vec()),
rustc_type_ir::FloatTy::F16 | rustc_type_ir::FloatTy::F128 => {
not_supported!("unstable floating point type f16 and f128");
}
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs
index 199db7a3e7..8d5e5c2e6e 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -1429,7 +1429,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> {
.resolve_path_in_value_ns(self.db, c, HygieneId::ROOT)
.ok_or_else(unresolved_name)?;
match pr {
- ResolveValueResult::ValueNs(v, _) => {
+ ResolveValueResult::ValueNs(v) => {
if let ValueNs::ConstId(c) = v {
self.lower_const_to_operand(
GenericArgs::empty(self.interner()),
@@ -1439,7 +1439,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> {
not_supported!("bad path in range pattern");
}
}
- ResolveValueResult::Partial(_, _, _) => {
+ ResolveValueResult::Partial(_, _) => {
not_supported!("associated constants in range pattern")
}
}
diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs
index a8aacbff16..83139821e3 100644
--- a/crates/hir-ty/src/mir/lower/pattern_matching.rs
+++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs
@@ -373,7 +373,7 @@ impl<'db> MirLowerCtx<'_, 'db> {
if let (
MatchingMode::Assign,
- ResolveValueResult::ValueNs(ValueNs::LocalBinding(binding), _),
+ ResolveValueResult::ValueNs(ValueNs::LocalBinding(binding)),
) = (mode, &pr)
{
let local = self.binding_local(*binding)?;
@@ -398,7 +398,7 @@ impl<'db> MirLowerCtx<'_, 'db> {
{
break 'b (c, x.1);
}
- if let ResolveValueResult::ValueNs(ValueNs::ConstId(c), _) = pr {
+ if let ResolveValueResult::ValueNs(ValueNs::ConstId(c)) = pr {
break 'b (c, GenericArgs::empty(self.interner()));
}
not_supported!("path in pattern position that is not const or variant")
diff --git a/crates/hir-ty/src/next_solver/abi.rs b/crates/hir-ty/src/next_solver/abi.rs
index 80d1ea4aa4..1813abab86 100644
--- a/crates/hir-ty/src/next_solver/abi.rs
+++ b/crates/hir-ty/src/next_solver/abi.rs
@@ -62,7 +62,6 @@ impl<'db> rustc_type_ir::inherent::Abi<DbInterner<'db>> for FnAbi {
}
fn is_rust(self) -> bool {
- // TODO: rustc does not consider `RustCall` to be true here, but Chalk does
- matches!(self, FnAbi::Rust | FnAbi::RustCall)
+ matches!(self, FnAbi::Rust)
}
}
diff --git a/crates/hir-ty/src/next_solver/predicate.rs b/crates/hir-ty/src/next_solver/predicate.rs
index 6f4fae7073..8658d03a9e 100644
--- a/crates/hir-ty/src/next_solver/predicate.rs
+++ b/crates/hir-ty/src/next_solver/predicate.rs
@@ -714,9 +714,9 @@ impl<'db> rustc_type_ir::inherent::Predicate<DbInterner<'db>> for Predicate<'db>
fn allow_normalization(self) -> bool {
// TODO: this should probably live in rustc_type_ir
match self.inner().as_ref().skip_binder() {
- PredicateKind::Clause(ClauseKind::WellFormed(_))
- | PredicateKind::AliasRelate(..)
- | PredicateKind::NormalizesTo(..) => false,
+ PredicateKind::Clause(ClauseKind::WellFormed(_)) | PredicateKind::AliasRelate(..) => {
+ false
+ }
PredicateKind::Clause(ClauseKind::Trait(_))
| PredicateKind::Clause(ClauseKind::RegionOutlives(_))
| PredicateKind::Clause(ClauseKind::TypeOutlives(_))
@@ -729,6 +729,7 @@ impl<'db> rustc_type_ir::inherent::Predicate<DbInterner<'db>> for Predicate<'db>
| PredicateKind::Coerce(_)
| PredicateKind::Clause(ClauseKind::ConstEvaluatable(_))
| PredicateKind::ConstEquate(_, _)
+ | PredicateKind::NormalizesTo(..)
| PredicateKind::Ambiguous => true,
}
}
diff --git a/crates/hir-ty/src/next_solver/util.rs b/crates/hir-ty/src/next_solver/util.rs
index 9a1b476976..c175062bda 100644
--- a/crates/hir-ty/src/next_solver/util.rs
+++ b/crates/hir-ty/src/next_solver/util.rs
@@ -13,13 +13,16 @@ use rustc_type_ir::{
solve::SizedTraitKind,
};
-use crate::next_solver::{
- BoundConst, FxIndexMap, ParamEnv, Placeholder, PlaceholderConst, PlaceholderRegion,
- PolyTraitRef,
- infer::{
- InferCtxt,
- traits::{Obligation, ObligationCause, PredicateObligation},
+use crate::{
+ next_solver::{
+ BoundConst, FxIndexMap, ParamEnv, Placeholder, PlaceholderConst, PlaceholderRegion,
+ PolyTraitRef,
+ infer::{
+ InferCtxt,
+ traits::{Obligation, ObligationCause, PredicateObligation},
+ },
},
+ representability::Representability,
};
use super::{
@@ -419,10 +422,18 @@ pub fn sizedness_constraint_for_ty<'db>(
.next_back()
.and_then(|ty| sizedness_constraint_for_ty(interner, sizedness, ty)),
- Adt(adt, args) => adt.struct_tail_ty(interner).and_then(|tail_ty| {
- let tail_ty = tail_ty.instantiate(interner, args);
- sizedness_constraint_for_ty(interner, sizedness, tail_ty)
- }),
+ Adt(adt, args) => {
+ if crate::representability::representability(interner.db, adt.def_id().0)
+ == Representability::Infinite
+ {
+ return None;
+ }
+
+ adt.struct_tail_ty(interner).and_then(|tail_ty| {
+ let tail_ty = tail_ty.instantiate(interner, args);
+ sizedness_constraint_for_ty(interner, sizedness, tail_ty)
+ })
+ }
Placeholder(..) | Bound(..) | Infer(..) => {
panic!("unexpected type `{ty:?}` in sizedness_constraint_for_ty")
diff --git a/crates/hir-ty/src/representability.rs b/crates/hir-ty/src/representability.rs
new file mode 100644
index 0000000000..7e40f2d7ac
--- /dev/null
+++ b/crates/hir-ty/src/representability.rs
@@ -0,0 +1,131 @@
+//! Detecting whether a type is infinitely-sized.
+
+use hir_def::{AdtId, VariantId};
+use rustc_type_ir::inherent::{AdtDef, IntoKind};
+
+use crate::{
+ db::HirDatabase,
+ next_solver::{GenericArgKind, GenericArgs, Ty, TyKind},
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum Representability {
+ Representable,
+ Infinite,
+}
+
+macro_rules! rtry {
+ ($e:expr) => {
+ match $e {
+ e @ Representability::Infinite => return e,
+ Representability::Representable => {}
+ }
+ };
+}
+
+#[salsa::tracked(cycle_result = representability_cycle)]
+pub(crate) fn representability(db: &dyn HirDatabase, id: AdtId) -> Representability {
+ match id {
+ AdtId::StructId(id) => variant_representability(db, id.into()),
+ AdtId::UnionId(id) => variant_representability(db, id.into()),
+ AdtId::EnumId(id) => {
+ for &(variant, ..) in &id.enum_variants(db).variants {
+ rtry!(variant_representability(db, variant.into()));
+ }
+ Representability::Representable
+ }
+ }
+}
+
+pub(crate) fn representability_cycle(
+ _db: &dyn HirDatabase,
+ _: salsa::Id,
+ _id: AdtId,
+) -> Representability {
+ Representability::Infinite
+}
+
+fn variant_representability(db: &dyn HirDatabase, id: VariantId) -> Representability {
+ for ty in db.field_types(id).values() {
+ rtry!(representability_ty(db, ty.get().instantiate_identity()));
+ }
+ Representability::Representable
+}
+
+fn representability_ty<'db>(db: &'db dyn HirDatabase, ty: Ty<'db>) -> Representability {
+ match ty.kind() {
+ TyKind::Adt(adt_id, args) => representability_adt_ty(db, adt_id.def_id().0, args),
+ // FIXME(#11924) allow zero-length arrays?
+ TyKind::Array(ty, _) => representability_ty(db, ty),
+ TyKind::Tuple(tys) => {
+ for ty in tys {
+ rtry!(representability_ty(db, ty));
+ }
+ Representability::Representable
+ }
+ _ => Representability::Representable,
+ }
+}
+
+fn representability_adt_ty<'db>(
+ db: &'db dyn HirDatabase,
+ def_id: AdtId,
+ args: GenericArgs<'db>,
+) -> Representability {
+ rtry!(representability(db, def_id));
+
+ // At this point, we know that the item of the ADT type is representable;
+ // but the type parameters may cause a cycle with an upstream type
+ let params_in_repr = params_in_repr(db, def_id);
+ for (i, arg) in args.iter().enumerate() {
+ if let GenericArgKind::Type(ty) = arg.kind()
+ && params_in_repr[i]
+ {
+ rtry!(representability_ty(db, ty));
+ }
+ }
+ Representability::Representable
+}
+
+fn params_in_repr(db: &dyn HirDatabase, def_id: AdtId) -> Box<[bool]> {
+ let generics = db.generic_params(def_id.into());
+ let mut params_in_repr = (0..generics.len_lifetimes() + generics.len_type_or_consts())
+ .map(|_| false)
+ .collect::<Box<[bool]>>();
+ let mut handle_variant = |variant| {
+ for field in db.field_types(variant).values() {
+ params_in_repr_ty(db, field.get().instantiate_identity(), &mut params_in_repr);
+ }
+ };
+ match def_id {
+ AdtId::StructId(def_id) => handle_variant(def_id.into()),
+ AdtId::UnionId(def_id) => handle_variant(def_id.into()),
+ AdtId::EnumId(def_id) => {
+ for &(variant, ..) in &def_id.enum_variants(db).variants {
+ handle_variant(variant.into());
+ }
+ }
+ }
+ params_in_repr
+}
+
+fn params_in_repr_ty<'db>(db: &'db dyn HirDatabase, ty: Ty<'db>, params_in_repr: &mut [bool]) {
+ match ty.kind() {
+ TyKind::Adt(adt, args) => {
+ let inner_params_in_repr = self::params_in_repr(db, adt.def_id().0);
+ for (i, arg) in args.iter().enumerate() {
+ if let GenericArgKind::Type(ty) = arg.kind()
+ && inner_params_in_repr[i]
+ {
+ params_in_repr_ty(db, ty, params_in_repr);
+ }
+ }
+ }
+ TyKind::Array(ty, _) => params_in_repr_ty(db, ty, params_in_repr),
+ TyKind::Tuple(tys) => tys.iter().for_each(|ty| params_in_repr_ty(db, ty, params_in_repr)),
+ TyKind::Param(param) => {
+ params_in_repr[param.index as usize] = true;
+ }
+ _ => {}
+ }
+}
diff --git a/crates/hir-ty/src/tests/never_type.rs b/crates/hir-ty/src/tests/never_type.rs
index 4d68179a88..993293bb56 100644
--- a/crates/hir-ty/src/tests/never_type.rs
+++ b/crates/hir-ty/src/tests/never_type.rs
@@ -761,6 +761,79 @@ fn coerce_ref_binding() -> ! {
}
#[test]
+fn diverging_place_match_ref_mut() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: sized
+fn coerce_ref_mut_binding() -> ! {
+ unsafe {
+ let x: *mut ! = 0 as _;
+ let ref mut _x: () = *x;
+ }
+}
+"#,
+ expect![[r#"
+ 33..120 '{ ... } }': !
+ 39..118 'unsafe... }': !
+ 60..61 'x': *mut !
+ 72..73 '0': i32
+ 72..78 '0 as _': *mut !
+ 92..102 'ref mut _x': &'? mut ()
+ 109..111 '*x': !
+ 110..111 'x': *mut !
+ 109..111: expected (), got !
+ "#]],
+ )
+}
+
+#[test]
+fn assign_never_place_no_mismatch() {
+ check_no_mismatches(
+ r#"
+//- minicore: sized
+fn foo() {
+ unsafe {
+ let p: *mut ! = 0 as _;
+ let mut x: () = ();
+ x = *p;
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn binop_rhs_never_place_diverges() {
+ check_no_mismatches(
+ r#"
+//- minicore: sized, add
+fn foo() -> i32 {
+ unsafe {
+ let p: *mut ! = 0 as _;
+ let mut x: i32 = 0;
+ x += *p;
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn binop_lhs_never_place_diverges() {
+ check_no_mismatches(
+ r#"
+//- minicore: sized, add
+fn foo() {
+ unsafe {
+ let p: *mut ! = 0 as _;
+ *p += 1;
+ }
+}
+"#,
+ );
+}
+
+#[test]
fn never_place_isnt_diverging() {
check_infer_with_mismatches(
r#"
@@ -813,3 +886,18 @@ fn foo() {
"#]],
);
}
+
+#[test]
+fn never_coercion_in_struct_update_syntax() {
+ check_no_mismatches(
+ r#"
+struct Struct {
+ field: i32,
+}
+
+fn example() -> Struct {
+ Struct { ..loop {} }
+}
+ "#,
+ );
+}
diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs
index 3b5b4e4fa5..1939db0ef5 100644
--- a/crates/hir-ty/src/tests/regression.rs
+++ b/crates/hir-ty/src/tests/regression.rs
@@ -2363,7 +2363,6 @@ fn test() {
}
"#,
expect![[r#"
- 46..49 'Foo': Foo<N>
93..97 'self': Foo<N>
108..125 '{ ... }': usize
118..119 'N': usize
@@ -2688,3 +2687,131 @@ pub trait FilterT<F: FilterT<F, V = Self::V> = Self> {
"#,
);
}
+
+#[test]
+fn regression_21605() {
+ check_infer(
+ r#"
+//- minicore: fn, coerce_unsized, dispatch_from_dyn, iterator, iterators
+pub struct Filter<'a, 'b, T>
+where
+ T: 'b,
+ 'a: 'b,
+{
+ filter_fn: dyn Fn(&'a T) -> bool,
+ t: Option<T>,
+ b: &'b (),
+}
+
+impl<'a, 'b, T> Filter<'a, 'b, T>
+where
+ T: 'b,
+ 'a: 'b,
+{
+ pub fn new(filter_fn: dyn Fn(&T) -> bool) -> Self {
+ Self {
+ filter_fn: filter_fn,
+ t: None,
+ b: &(),
+ }
+ }
+}
+
+pub trait FilterExt<T> {
+ type Output;
+ fn filter(&self, filter: &Filter<T>) -> Self::Output;
+}
+
+impl<const N: usize, T> FilterExt<T> for [T; N]
+where
+ T: IntoIterator,
+{
+ type Output = T;
+ fn filter(&self, filter: &Filter<T>) -> Self::Output {
+ let _ = self.into_iter().filter(filter.filter_fn);
+ loop {}
+ }
+}
+"#,
+ expect![[r#"
+ 214..223 'filter_fn': dyn Fn(&'? T) -> bool + 'static
+ 253..360 '{ ... }': Filter<'a, 'b, T>
+ 263..354 'Self {... }': Filter<'a, 'b, T>
+ 293..302 'filter_fn': dyn Fn(&'? T) -> bool + 'static
+ 319..323 'None': Option<T>
+ 340..343 '&()': &'? ()
+ 341..343 '()': ()
+ 421..425 'self': &'? Self
+ 427..433 'filter': &'? Filter<'?, '?, T>
+ 580..584 'self': &'? [T; N]
+ 586..592 'filter': &'? Filter<'?, '?, T>
+ 622..704 '{ ... }': T
+ 636..637 '_': Filter<Iter<'?, T>, dyn Fn(&'? T) -> bool + '?>
+ 640..644 'self': &'? [T; N]
+ 640..656 'self.i...iter()': Iter<'?, T>
+ 640..681 'self.i...er_fn)': Filter<Iter<'?, T>, dyn Fn(&'? T) -> bool + '?>
+ 664..670 'filter': &'? Filter<'?, '?, T>
+ 664..680 'filter...ter_fn': dyn Fn(&'? T) -> bool + 'static
+ 691..698 'loop {}': !
+ 696..698 '{}': ()
+ "#]],
+ );
+}
+
+#[test]
+fn extern_fns_cannot_have_param_patterns() {
+ check_no_mismatches(
+ r#"
+pub(crate) struct Builder<'a>(&'a ());
+
+unsafe extern "C" {
+ pub(crate) fn foo<'a>(Builder: &Builder<'a>);
+}
+ "#,
+ );
+}
+
+#[test]
+fn infinitely_sized_type() {
+ check_infer(
+ r#"
+//- minicore: sized
+
+pub struct Recursive {
+ pub content: Recursive,
+}
+
+fn is_sized<T: Sized>() {}
+
+fn foo() {
+ is_sized::<Recursive>();
+}
+ "#,
+ expect![[r#"
+ 79..81 '{}': ()
+ 92..124 '{ ...>(); }': ()
+ 98..119 'is_siz...rsive>': fn is_sized<Recursive>()
+ 98..121 'is_siz...ive>()': ()
+ "#]],
+ );
+}
+
+#[test]
+fn regression_21742() {
+ check_no_mismatches(
+ r#"
+pub trait IntoIterator {
+ type Item;
+}
+
+pub trait Collection: IntoIterator<Item = <Self as Collection>::Item> {
+ type Item;
+ fn contains(&self, item: &<Self as Collection>::Item);
+}
+
+fn contains_0<S: Collection<Item = i32>>(points: &S) {
+ points.contains(&0)
+}
+ "#,
+ );
+}
diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs
index 7d4f04268a..dab8bdb54b 100644
--- a/crates/hir-ty/src/tests/simple.rs
+++ b/crates/hir-ty/src/tests/simple.rs
@@ -4074,3 +4074,38 @@ static S: &[u8; 158] = include_bytes!("/foo/bar/baz.txt");
"#,
);
}
+
+#[test]
+fn proc_macros_are_functions_inside_defining_crate_and_macros_outside() {
+ check_types(
+ r#"
+//- /pm.rs crate:pm
+#![crate_type = "proc-macro"]
+
+#[proc_macro_attribute]
+pub fn proc_macro() {}
+
+fn foo() {
+ proc_macro;
+ // ^^^^^^^^^^ fn proc_macro()
+}
+
+mod bar {
+ use super::proc_macro;
+
+ fn baz() {
+ super::proc_macro;
+ // ^^^^^^^^^^^^^^^^^ fn proc_macro()
+ proc_macro;
+ // ^^^^^^^^^^ fn proc_macro()
+ }
+}
+
+//- /lib.rs crate:lib deps:pm
+fn foo() {
+ pm::proc_macro;
+ // ^^^^^^^^^^^^^^ {unknown}
+}
+ "#,
+ );
+}
diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs
index cdf7b40003..74e9a8dac0 100644
--- a/crates/hir-ty/src/tests/traits.rs
+++ b/crates/hir-ty/src/tests/traits.rs
@@ -2218,6 +2218,40 @@ fn test() {
}
#[test]
+fn tuple_struct_constructor_as_fn_trait() {
+ check_types(
+ r#"
+//- minicore: fn
+struct S(u32, u64);
+
+fn takes_fn<F: Fn(u32, u64) -> S>(f: F) -> S { f(1, 2) }
+
+fn test() {
+ takes_fn(S);
+ //^^^^^^^^^^^ S
+}
+"#,
+ );
+}
+
+#[test]
+fn enum_variant_constructor_as_fn_trait() {
+ check_types(
+ r#"
+//- minicore: fn
+enum E { A(u32) }
+
+fn takes_fn<F: Fn(u32) -> E>(f: F) -> E { f(1) }
+
+fn test() {
+ takes_fn(E::A);
+ //^^^^^^^^^^^^^^ E
+}
+"#,
+ );
+}
+
+#[test]
fn fn_item_fn_trait() {
check_types(
r#"
diff --git a/crates/hir-ty/src/utils.rs b/crates/hir-ty/src/utils.rs
index 148300deb8..be64f55ea5 100644
--- a/crates/hir-ty/src/utils.rs
+++ b/crates/hir-ty/src/utils.rs
@@ -1,28 +1,19 @@
//! Helper functions for working with def, which don't need to be a separate
//! query, but can't be computed directly from `*Data` (ie, which need a `db`).
-use std::cell::LazyCell;
-
use base_db::target::{self, TargetData};
use hir_def::{
- EnumId, EnumVariantId, FunctionId, Lookup, TraitId,
- attrs::AttrFlags,
- db::DefDatabase,
- hir::generics::WherePredicate,
- lang_item::LangItems,
- resolver::{HasResolver, TypeNs},
- type_ref::{TraitBoundModifier, TypeRef},
+ EnumId, EnumVariantId, FunctionId, Lookup, TraitId, attrs::AttrFlags, lang_item::LangItems,
};
use intern::sym;
use rustc_abi::TargetDataLayout;
-use smallvec::{SmallVec, smallvec};
use span::Edition;
use crate::{
TargetFeatures,
db::HirDatabase,
layout::{Layout, TagEncoding},
- lower::all_supertraits_trait_refs,
+ lower::SupertraitsInfo,
mir::pad16,
};
@@ -51,55 +42,13 @@ pub(crate) fn fn_traits(lang_items: &LangItems) -> impl Iterator<Item = TraitId>
}
/// Returns an iterator over the direct super traits (including the trait itself).
-pub fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> {
- let mut result = smallvec![trait_];
- direct_super_traits_cb(db, trait_, |tt| {
- if !result.contains(&tt) {
- result.push(tt);
- }
- });
- result
-}
-
-/// Returns an iterator over the whole super trait hierarchy (including the
-/// trait itself).
-pub fn all_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> {
- let mut supertraits = all_supertraits_trait_refs(db, trait_)
- .map(|trait_ref| trait_ref.skip_binder().def_id.0)
- .collect::<SmallVec<[_; _]>>();
- supertraits.sort_unstable();
- supertraits.dedup();
- supertraits
+pub fn direct_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> &[TraitId] {
+ &SupertraitsInfo::query(db, trait_).direct_supertraits
}
-fn direct_super_traits_cb(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut(TraitId)) {
- let resolver = LazyCell::new(|| trait_.resolver(db));
- let (generic_params, store) = db.generic_params_and_store(trait_.into());
- let trait_self = generic_params.trait_self_param();
- generic_params
- .where_predicates()
- .iter()
- .filter_map(|pred| match pred {
- WherePredicate::ForLifetime { target, bound, .. }
- | WherePredicate::TypeBound { target, bound } => {
- let is_trait = match &store[*target] {
- TypeRef::Path(p) => p.is_self_type(),
- TypeRef::TypeParam(p) => Some(p.local_id()) == trait_self,
- _ => false,
- };
- match is_trait {
- true => bound.as_path(&store),
- false => None,
- }
- }
- WherePredicate::Lifetime { .. } => None,
- })
- .filter(|(_, bound_modifier)| matches!(bound_modifier, TraitBoundModifier::None))
- .filter_map(|(path, _)| match resolver.resolve_path_in_type_ns_fully(db, path) {
- Some(TypeNs::TraitId(t)) => Some(t),
- _ => None,
- })
- .for_each(cb);
+/// Returns the whole super trait hierarchy (including the trait itself).
+pub fn all_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> &[TraitId] {
+ &SupertraitsInfo::query(db, trait_).all_supertraits
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs
index b4440dfa18..91fdcb8e63 100644
--- a/crates/hir/src/display.rs
+++ b/crates/hir/src/display.rs
@@ -172,8 +172,13 @@ fn write_function<'db>(f: &mut HirFormatter<'_, 'db>, func_id: FunctionId) -> Re
write_generic_params(GenericDefId::FunctionId(func_id), f)?;
+ let too_long_param = data.params.len() > 4;
f.write_char('(')?;
+ if too_long_param {
+ f.write_str("\n ")?;
+ }
+
let mut first = true;
let mut skip_self = 0;
if let Some(self_param) = func.self_param(db) {
@@ -182,11 +187,12 @@ fn write_function<'db>(f: &mut HirFormatter<'_, 'db>, func_id: FunctionId) -> Re
skip_self = 1;
}
+ let comma = if too_long_param { ",\n " } else { ", " };
// FIXME: Use resolved `param.ty` once we no longer discard lifetimes
let body = db.body(func_id.into());
for (type_ref, param) in data.params.iter().zip(func.assoc_fn_params(db)).skip(skip_self) {
if !first {
- f.write_str(", ")?;
+ f.write_str(comma)?;
} else {
first = false;
}
@@ -201,11 +207,14 @@ fn write_function<'db>(f: &mut HirFormatter<'_, 'db>, func_id: FunctionId) -> Re
if data.is_varargs() {
if !first {
- f.write_str(", ")?;
+ f.write_str(comma)?;
}
f.write_str("...")?;
}
+ if too_long_param {
+ f.write_char('\n')?;
+ }
f.write_char(')')?;
// `FunctionData::ret_type` will be `::core::future::Future<Output = ...>` for async fns.
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 4b61566516..11d79e2d7b 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2244,6 +2244,39 @@ impl DefWithBody {
acc.push(diag.into())
}
}
+
+ /// Returns an iterator over the inferred types of all expressions in this body.
+ pub fn expression_types<'db>(
+ self,
+ db: &'db dyn HirDatabase,
+ ) -> impl Iterator<Item = Type<'db>> {
+ self.id().into_iter().flat_map(move |def_id| {
+ let infer = InferenceResult::for_body(db, def_id);
+ let resolver = def_id.resolver(db);
+
+ infer.expression_types().map(move |(_, ty)| Type::new_with_resolver(db, &resolver, ty))
+ })
+ }
+
+ /// Returns an iterator over the inferred types of all patterns in this body.
+ pub fn pattern_types<'db>(self, db: &'db dyn HirDatabase) -> impl Iterator<Item = Type<'db>> {
+ self.id().into_iter().flat_map(move |def_id| {
+ let infer = InferenceResult::for_body(db, def_id);
+ let resolver = def_id.resolver(db);
+
+ infer.pattern_types().map(move |(_, ty)| Type::new_with_resolver(db, &resolver, ty))
+ })
+ }
+
+ /// Returns an iterator over the inferred types of all bindings in this body.
+ pub fn binding_types<'db>(self, db: &'db dyn HirDatabase) -> impl Iterator<Item = Type<'db>> {
+ self.id().into_iter().flat_map(move |def_id| {
+ let infer = InferenceResult::for_body(db, def_id);
+ let resolver = def_id.resolver(db);
+
+ infer.binding_types().map(move |(_, ty)| Type::new_with_resolver(db, &resolver, ty))
+ })
+ }
}
fn expr_store_diagnostics<'db>(
@@ -5919,7 +5952,16 @@ impl<'db> Type<'db> {
) -> R {
let module = resolver.module();
let interner = DbInterner::new_with(db, module.krate(db));
- let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis);
+ // Most IDE operations want to operate in PostAnalysis mode, revealing opaques. This makes
+ // for a nicer IDE experience. However, method resolution is always done on real code (either
+ // existing code or code to be inserted), and there using PostAnalysis is dangerous - we may
+ // suggest invalid methods. So we're using the TypingMode of the body we're in.
+ let typing_mode = if let Some(body_owner) = resolver.body_owner() {
+ TypingMode::analysis_in_body(interner, body_owner.into())
+ } else {
+ TypingMode::non_body_analysis()
+ };
+ let infcx = interner.infer_ctxt().build(typing_mode);
let unstable_features =
MethodResolutionUnstableFeatures::from_def_map(resolver.top_level_def_map());
let environment = param_env_from_resolver(db, resolver);
@@ -6068,11 +6110,7 @@ impl<'db> Type<'db> {
match name {
Some(name) => {
- match ctx.probe_for_name(
- method_resolution::Mode::MethodCall,
- name.clone(),
- self_ty,
- ) {
+ match ctx.probe_for_name(method_resolution::Mode::Path, name.clone(), self_ty) {
Ok(candidate)
| Err(method_resolution::MethodError::PrivateMatch(candidate)) => {
let id = candidate.item.into();
@@ -6120,6 +6158,13 @@ impl<'db> Type<'db> {
Some(adt.into())
}
+ /// Holes in the args can come from lifetime/const params.
+ pub fn as_adt_with_args(&self) -> Option<(Adt, Vec<Option<Type<'db>>>)> {
+ let (adt, args) = self.ty.as_adt()?;
+ let args = args.iter().map(|arg| Some(self.derived(arg.ty()?))).collect();
+ Some((adt.into(), args))
+ }
+
pub fn as_builtin(&self) -> Option<BuiltinType> {
self.ty.as_builtin().map(|inner| BuiltinType { inner })
}
@@ -6138,6 +6183,7 @@ impl<'db> Type<'db> {
self.autoderef_(db)
.filter_map(|ty| ty.dyn_trait())
.flat_map(move |dyn_trait_id| hir_ty::all_super_traits(db, dyn_trait_id))
+ .copied()
.map(Trait::from)
}
@@ -6155,6 +6201,7 @@ impl<'db> Type<'db> {
_ => None,
})
.flat_map(|t| hir_ty::all_super_traits(db, t))
+ .copied()
})
.map(Trait::from)
}
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 4bc757da44..1cf3b98160 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -1819,6 +1819,28 @@ impl<'db> SemanticsImpl<'db> {
self.analyze(try_expr.syntax())?.resolve_try_expr(self.db, try_expr)
}
+ /// The type that the associated `try` block, closure or function expects.
+ pub fn try_expr_returned_type(&self, try_expr: &ast::TryExpr) -> Option<Type<'db>> {
+ self.ancestors_with_macros(try_expr.syntax().clone()).find_map(|parent| {
+ if let Some(try_block) = ast::BlockExpr::cast(parent.clone())
+ && try_block.try_block_modifier().is_some()
+ {
+ Some(self.type_of_expr(&try_block.into())?.original)
+ } else if let Some(closure) = ast::ClosureExpr::cast(parent.clone()) {
+ Some(
+ self.type_of_expr(&closure.into())?
+ .original
+ .as_callable(self.db)?
+ .return_type(),
+ )
+ } else if let Some(function) = ast::Fn::cast(parent) {
+ Some(self.to_def(&function)?.ret_type(self.db))
+ } else {
+ None
+ }
+ })
+ }
+
// This does not resolve the method call to the correct trait impl!
// We should probably fix that.
pub fn resolve_method_call_as_callable(
diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs
index 65ca1ceae1..afdced4215 100644
--- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs
@@ -2537,4 +2537,84 @@ impl Test for () {
"#,
);
}
+
+ #[test]
+ fn test_param_name_not_qualified() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod ptr {
+ pub struct NonNull<T>(T);
+}
+mod alloc {
+ use super::ptr::NonNull;
+ pub trait Allocator {
+ unsafe fn deallocate(&self, ptr: NonNull<u8>);
+ }
+}
+
+struct System;
+
+unsafe impl alloc::Allocator for System {
+ $0
+}
+"#,
+ r#"
+mod ptr {
+ pub struct NonNull<T>(T);
+}
+mod alloc {
+ use super::ptr::NonNull;
+ pub trait Allocator {
+ unsafe fn deallocate(&self, ptr: NonNull<u8>);
+ }
+}
+
+struct System;
+
+unsafe impl alloc::Allocator for System {
+ unsafe fn deallocate(&self, ptr: ptr::NonNull<u8>) {
+ ${0:todo!()}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_param_name_shadows_module() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod m { }
+use m as p;
+
+pub trait Allocator {
+ fn deallocate(&self, p: u8);
+}
+
+struct System;
+
+impl Allocator for System {
+ $0
+}
+"#,
+ r#"
+mod m { }
+use m as p;
+
+pub trait Allocator {
+ fn deallocate(&self, p: u8);
+}
+
+struct System;
+
+impl Allocator for System {
+ fn deallocate(&self, p: u8) {
+ ${0:todo!()}
+ }
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
index 10c3ff0e4d..c0ce057d77 100644
--- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs
+++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
@@ -44,8 +44,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
let arm_list_range = ctx.sema.original_range_opt(match_arm_list.syntax())?;
if cursor_at_trivial_match_arm_list(ctx, &match_expr, &match_arm_list).is_none() {
- let arm_list_range = ctx.sema.original_range(match_arm_list.syntax()).range;
- let cursor_in_range = arm_list_range.contains_range(ctx.selection_trimmed());
+ let cursor_in_range = arm_list_range.range.contains_range(ctx.selection_trimmed());
if cursor_in_range {
cov_mark::hit!(not_applicable_outside_of_range_right);
return None;
@@ -348,8 +347,8 @@ fn cursor_at_trivial_match_arm_list(
// $0
// }
if let Some(last_arm) = match_arm_list.arms().last() {
- let last_arm_range = last_arm.syntax().text_range();
- let match_expr_range = match_expr.syntax().text_range();
+ let last_arm_range = ctx.sema.original_range_opt(last_arm.syntax())?.range;
+ let match_expr_range = ctx.sema.original_range_opt(match_expr.syntax())?.range;
if last_arm_range.end() <= ctx.offset() && ctx.offset() < match_expr_range.end() {
cov_mark::hit!(add_missing_match_arms_end_of_last_arm);
return Some(());
@@ -1614,6 +1613,38 @@ fn foo(t: Test) {
});
}"#,
);
+
+ check_assist(
+ add_missing_match_arms,
+ r#"
+macro_rules! m { ($expr:expr) => {$expr}}
+enum Test {
+ A,
+ B,
+ C,
+}
+
+fn foo(t: Test) {
+ m!(match t {
+ Test::A => (),
+ $0});
+}"#,
+ r#"
+macro_rules! m { ($expr:expr) => {$expr}}
+enum Test {
+ A,
+ B,
+ C,
+}
+
+fn foo(t: Test) {
+ m!(match t {
+ Test::A=>(),
+ Test::B => ${1:todo!()},
+ Test::C => ${2:todo!()},$0
+ });
+}"#,
+ );
}
#[test]
diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs
index 2694910aa6..da5c123957 100644
--- a/crates/ide-assists/src/handlers/auto_import.rs
+++ b/crates/ide-assists/src/handlers/auto_import.rs
@@ -352,7 +352,7 @@ mod tests {
let config = TEST_CONFIG;
let ctx = AssistContext::new(sema, &config, frange);
let mut acc = Assists::new(&ctx, AssistResolveStrategy::All);
- auto_import(&mut acc, &ctx);
+ hir::attach_db(&db, || auto_import(&mut acc, &ctx));
let assists = acc.finish();
let labels = assists.iter().map(|assist| assist.label.to_string()).collect::<Vec<_>>();
@@ -1897,4 +1897,35 @@ fn foo(_: S) {}
"#,
);
}
+
+ #[test]
+ fn with_after_segments() {
+ let before = r#"
+mod foo {
+ pub mod wanted {
+ pub fn abc() {}
+ }
+}
+
+mod bar {
+ pub mod wanted {}
+}
+
+mod baz {
+ pub fn wanted() {}
+}
+
+mod quux {
+ pub struct wanted;
+}
+impl quux::wanted {
+ fn abc() {}
+}
+
+fn f() {
+ wanted$0::abc;
+}
+ "#;
+ check_auto_import_order(before, &["Import `foo::wanted`", "Import `quux::wanted`"]);
+ }
}
diff --git a/crates/ide-assists/src/handlers/bind_unused_param.rs b/crates/ide-assists/src/handlers/bind_unused_param.rs
index 771e80bb92..0e85a77822 100644
--- a/crates/ide-assists/src/handlers/bind_unused_param.rs
+++ b/crates/ide-assists/src/handlers/bind_unused_param.rs
@@ -2,7 +2,7 @@ use crate::assist_context::{AssistContext, Assists};
use ide_db::{LineIndexDatabase, assists::AssistId, defs::Definition};
use syntax::{
AstNode,
- ast::{self, HasName, edit_in_place::Indent},
+ ast::{self, HasName, edit::AstNodeEdit},
};
// Assist: bind_unused_param
diff --git a/crates/ide-assists/src/handlers/convert_bool_then.rs b/crates/ide-assists/src/handlers/convert_bool_then.rs
index 91cee59ad8..d2c4ed9b5a 100644
--- a/crates/ide-assists/src/handlers/convert_bool_then.rs
+++ b/crates/ide-assists/src/handlers/convert_bool_then.rs
@@ -102,6 +102,11 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>
ast::Expr::BlockExpr(block) => unwrap_trivial_block(block),
e => e,
};
+ let cond = if invert_cond {
+ invert_boolean_expression(&make, cond)
+ } else {
+ cond.clone_for_update()
+ };
let parenthesize = matches!(
cond,
@@ -123,11 +128,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>
| ast::Expr::WhileExpr(_)
| ast::Expr::YieldExpr(_)
);
- let cond = if invert_cond {
- invert_boolean_expression(&make, cond)
- } else {
- cond.clone_for_update()
- };
+
let cond = if parenthesize { make.expr_paren(cond).into() } else { cond };
let arg_list = make.arg_list(Some(make.expr_closure(None, closure_body).into()));
let mcall = make.expr_method_call(cond, make.name_ref("then"), arg_list);
@@ -591,4 +592,23 @@ fn main() {
",
);
}
+ #[test]
+ fn convert_if_to_bool_then_invert_method_call() {
+ check_assist(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ let test = &[()];
+ let value = if$0 test.is_empty() { None } else { Some(()) };
+}
+",
+ r"
+fn main() {
+ let test = &[()];
+ let value = (!test.is_empty()).then(|| ());
+}
+",
+ );
+ }
}
diff --git a/crates/ide-assists/src/handlers/convert_bool_to_enum.rs b/crates/ide-assists/src/handlers/convert_bool_to_enum.rs
index 1ae5f64917..434fbbae05 100644
--- a/crates/ide-assists/src/handlers/convert_bool_to_enum.rs
+++ b/crates/ide-assists/src/handlers/convert_bool_to_enum.rs
@@ -11,9 +11,10 @@ use ide_db::{
source_change::SourceChangeBuilder,
};
use itertools::Itertools;
+use syntax::ast::edit::AstNodeEdit;
use syntax::{
AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T,
- ast::{self, HasName, edit::IndentLevel, edit_in_place::Indent, make},
+ ast::{self, HasName, edit::IndentLevel, make},
};
use crate::{
@@ -479,10 +480,9 @@ fn add_enum_def(
ctx.sema.scope(name.syntax()).map(|scope| scope.module())
})
.any(|module| module.nearest_non_block_module(ctx.db()) != *target_module);
- let enum_def = make_bool_enum(make_enum_pub);
let indent = IndentLevel::from_node(&insert_before);
- enum_def.reindent_to(indent);
+ let enum_def = make_bool_enum(make_enum_pub).reset_indent().indent(indent);
edit.insert(
insert_before.text_range().start(),
diff --git a/crates/ide-assists/src/handlers/convert_let_else_to_match.rs b/crates/ide-assists/src/handlers/convert_let_else_to_match.rs
index ebfed9f9ca..d2336a4a5d 100644
--- a/crates/ide-assists/src/handlers/convert_let_else_to_match.rs
+++ b/crates/ide-assists/src/handlers/convert_let_else_to_match.rs
@@ -1,7 +1,6 @@
use syntax::T;
use syntax::ast::RangeItem;
-use syntax::ast::edit::IndentLevel;
-use syntax::ast::edit_in_place::Indent;
+use syntax::ast::edit::AstNodeEdit;
use syntax::ast::syntax_factory::SyntaxFactory;
use syntax::ast::{self, AstNode, HasName, LetStmt, Pat};
@@ -93,7 +92,8 @@ pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext<'
);
let else_arm = make.match_arm(make.wildcard_pat().into(), None, else_expr);
let match_ = make.expr_match(init, make.match_arm_list([binding_arm, else_arm]));
- match_.reindent_to(IndentLevel::from_node(let_stmt.syntax()));
+ let match_ = match_.reset_indent();
+ let match_ = match_.indent(let_stmt.indent_level());
if bindings.is_empty() {
editor.replace(let_stmt.syntax(), match_.syntax());
diff --git a/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
index ea5c1637b7..dc51bf4b5b 100644
--- a/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
+++ b/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
@@ -942,6 +942,32 @@ fn main() {
}
#[test]
+ fn convert_let_ref_stmt_inside_fn() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+//- minicore: option
+fn foo() -> &'static Option<i32> {
+ &None
+}
+
+fn main() {
+ let x$0 = foo();
+}
+"#,
+ r#"
+fn foo() -> &'static Option<i32> {
+ &None
+}
+
+fn main() {
+ let Some(x) = foo() else { return };
+}
+"#,
+ );
+ }
+
+ #[test]
fn convert_let_stmt_inside_fn_return_option() {
check_assist(
convert_to_guarded_return,
diff --git a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
index 0e5e6185d0..1740cd024a 100644
--- a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
@@ -5,15 +5,20 @@ use ide_db::{
assists::AssistId,
defs::Definition,
helpers::mod_path_to_ast,
- imports::insert_use::{ImportScope, insert_use},
+ imports::insert_use::{ImportScope, insert_use_with_editor},
search::{FileReference, UsageSearchResult},
source_change::SourceChangeBuilder,
syntax_helpers::node_ext::{for_each_tail_expr, walk_expr},
};
use syntax::{
AstNode, SyntaxNode,
- ast::{self, HasName, edit::IndentLevel, edit_in_place::Indent, make},
- match_ast, ted,
+ ast::{
+ self, HasName,
+ edit::{AstNodeEdit, IndentLevel},
+ syntax_factory::SyntaxFactory,
+ },
+ match_ast,
+ syntax_editor::SyntaxEditor,
};
use crate::assist_context::{AssistContext, Assists};
@@ -67,14 +72,15 @@ pub(crate) fn convert_tuple_return_type_to_struct(
"Convert tuple return type to tuple struct",
target,
move |edit| {
- let ret_type = edit.make_mut(ret_type);
- let fn_ = edit.make_mut(fn_);
+ let mut syntax_editor = edit.make_editor(ret_type.syntax());
+ let syntax_factory = SyntaxFactory::with_mappings();
let usages = Definition::Function(fn_def).usages(&ctx.sema).all();
let struct_name = format!("{}Result", stdx::to_camel_case(&fn_name.to_string()));
let parent = fn_.syntax().ancestors().find_map(<Either<ast::Impl, ast::Trait>>::cast);
add_tuple_struct_def(
edit,
+ &syntax_factory,
ctx,
&usages,
parent.as_ref().map(|it| it.syntax()).unwrap_or(fn_.syntax()),
@@ -83,15 +89,23 @@ pub(crate) fn convert_tuple_return_type_to_struct(
&target_module,
);
- ted::replace(
+ syntax_editor.replace(
ret_type.syntax(),
- make::ret_type(make::ty(&struct_name)).syntax().clone_for_update(),
+ syntax_factory.ret_type(syntax_factory.ty(&struct_name)).syntax(),
);
if let Some(fn_body) = fn_.body() {
- replace_body_return_values(ast::Expr::BlockExpr(fn_body), &struct_name);
+ replace_body_return_values(
+ &mut syntax_editor,
+ &syntax_factory,
+ ast::Expr::BlockExpr(fn_body),
+ &struct_name,
+ );
}
+ syntax_editor.add_mappings(syntax_factory.finish_with_mappings());
+ edit.add_file_edits(ctx.vfs_file_id(), syntax_editor);
+
replace_usages(edit, ctx, &usages, &struct_name, &target_module);
},
)
@@ -106,24 +120,37 @@ fn replace_usages(
target_module: &hir::Module,
) {
for (file_id, references) in usages.iter() {
- edit.edit_file(file_id.file_id(ctx.db()));
+ let Some(first_ref) = references.first() else { continue };
+
+ let mut editor = edit.make_editor(first_ref.name.syntax().as_node().unwrap());
+ let syntax_factory = SyntaxFactory::with_mappings();
- let refs_with_imports =
- augment_references_with_imports(edit, ctx, references, struct_name, target_module);
+ let refs_with_imports = augment_references_with_imports(
+ &syntax_factory,
+ ctx,
+ references,
+ struct_name,
+ target_module,
+ );
refs_with_imports.into_iter().rev().for_each(|(name, import_data)| {
if let Some(fn_) = name.syntax().parent().and_then(ast::Fn::cast) {
cov_mark::hit!(replace_trait_impl_fns);
if let Some(ret_type) = fn_.ret_type() {
- ted::replace(
+ editor.replace(
ret_type.syntax(),
- make::ret_type(make::ty(struct_name)).syntax().clone_for_update(),
+ syntax_factory.ret_type(syntax_factory.ty(struct_name)).syntax(),
);
}
if let Some(fn_body) = fn_.body() {
- replace_body_return_values(ast::Expr::BlockExpr(fn_body), struct_name);
+ replace_body_return_values(
+ &mut editor,
+ &syntax_factory,
+ ast::Expr::BlockExpr(fn_body),
+ struct_name,
+ );
}
} else {
// replace tuple patterns
@@ -143,22 +170,30 @@ fn replace_usages(
_ => None,
});
for tuple_pat in tuple_pats {
- ted::replace(
+ editor.replace(
tuple_pat.syntax(),
- make::tuple_struct_pat(
- make::path_from_text(struct_name),
- tuple_pat.fields(),
- )
- .clone_for_update()
- .syntax(),
+ syntax_factory
+ .tuple_struct_pat(
+ syntax_factory.path_from_text(struct_name),
+ tuple_pat.fields(),
+ )
+ .syntax(),
);
}
}
- // add imports across modules where needed
if let Some((import_scope, path)) = import_data {
- insert_use(&import_scope, path, &ctx.config.insert_use);
+ insert_use_with_editor(
+ &import_scope,
+ path,
+ &ctx.config.insert_use,
+ &mut editor,
+ &syntax_factory,
+ );
}
- })
+ });
+
+ editor.add_mappings(syntax_factory.finish_with_mappings());
+ edit.add_file_edits(file_id.file_id(ctx.db()), editor);
}
}
@@ -176,7 +211,7 @@ fn node_to_pats(node: SyntaxNode) -> Option<Vec<ast::Pat>> {
}
fn augment_references_with_imports(
- edit: &mut SourceChangeBuilder,
+ syntax_factory: &SyntaxFactory,
ctx: &AssistContext<'_>,
references: &[FileReference],
struct_name: &str,
@@ -191,8 +226,6 @@ fn augment_references_with_imports(
ctx.sema.scope(name.syntax()).map(|scope| (name, scope.module()))
})
.map(|(name, ref_module)| {
- let new_name = edit.make_mut(name);
-
// if the referenced module is not the same as the target one and has not been seen before, add an import
let import_data = if ref_module.nearest_non_block_module(ctx.db()) != *target_module
&& !visited_modules.contains(&ref_module)
@@ -201,8 +234,7 @@ fn augment_references_with_imports(
let cfg =
ctx.config.find_path_config(ctx.sema.is_nightly(ref_module.krate(ctx.sema.db)));
- let import_scope =
- ImportScope::find_insert_use_container(new_name.syntax(), &ctx.sema);
+ let import_scope = ImportScope::find_insert_use_container(name.syntax(), &ctx.sema);
let path = ref_module
.find_use_path(
ctx.sema.db,
@@ -211,12 +243,12 @@ fn augment_references_with_imports(
cfg,
)
.map(|mod_path| {
- make::path_concat(
+ syntax_factory.path_concat(
mod_path_to_ast(
&mod_path,
target_module.krate(ctx.db()).edition(ctx.db()),
),
- make::path_from_text(struct_name),
+ syntax_factory.path_from_text(struct_name),
)
});
@@ -225,7 +257,7 @@ fn augment_references_with_imports(
None
};
- (new_name, import_data)
+ (name, import_data)
})
.collect()
}
@@ -233,6 +265,7 @@ fn augment_references_with_imports(
// Adds the definition of the tuple struct before the parent function.
fn add_tuple_struct_def(
edit: &mut SourceChangeBuilder,
+ syntax_factory: &SyntaxFactory,
ctx: &AssistContext<'_>,
usages: &UsageSearchResult,
parent: &SyntaxNode,
@@ -248,22 +281,27 @@ fn add_tuple_struct_def(
ctx.sema.scope(name.syntax()).map(|scope| scope.module())
})
.any(|module| module.nearest_non_block_module(ctx.db()) != *target_module);
- let visibility = if make_struct_pub { Some(make::visibility_pub()) } else { None };
+ let visibility = if make_struct_pub { Some(syntax_factory.visibility_pub()) } else { None };
- let field_list = ast::FieldList::TupleFieldList(make::tuple_field_list(
- tuple_ty.fields().map(|ty| make::tuple_field(visibility.clone(), ty)),
+ let field_list = ast::FieldList::TupleFieldList(syntax_factory.tuple_field_list(
+ tuple_ty.fields().map(|ty| syntax_factory.tuple_field(visibility.clone(), ty)),
));
- let struct_name = make::name(struct_name);
- let struct_def = make::struct_(visibility, struct_name, None, field_list).clone_for_update();
+ let struct_name = syntax_factory.name(struct_name);
+ let struct_def = syntax_factory.struct_(visibility, struct_name, None, field_list);
let indent = IndentLevel::from_node(parent);
- struct_def.reindent_to(indent);
+ let struct_def = struct_def.indent(indent);
edit.insert(parent.text_range().start(), format!("{struct_def}\n\n{indent}"));
}
/// Replaces each returned tuple in `body` with the constructor of the tuple struct named `struct_name`.
-fn replace_body_return_values(body: ast::Expr, struct_name: &str) {
+fn replace_body_return_values(
+ syntax_editor: &mut SyntaxEditor,
+ syntax_factory: &SyntaxFactory,
+ body: ast::Expr,
+ struct_name: &str,
+) {
let mut exprs_to_wrap = Vec::new();
let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_wrap, e);
@@ -278,12 +316,11 @@ fn replace_body_return_values(body: ast::Expr, struct_name: &str) {
for ret_expr in exprs_to_wrap {
if let ast::Expr::TupleExpr(tuple_expr) = &ret_expr {
- let struct_constructor = make::expr_call(
- make::expr_path(make::ext::ident_path(struct_name)),
- make::arg_list(tuple_expr.fields()),
- )
- .clone_for_update();
- ted::replace(ret_expr.syntax(), struct_constructor.syntax());
+ let struct_constructor = syntax_factory.expr_call(
+ syntax_factory.expr_path(syntax_factory.ident_path(struct_name)),
+ syntax_factory.arg_list(tuple_expr.fields()),
+ );
+ syntax_editor.replace(ret_expr.syntax(), struct_constructor.syntax());
}
}
}
diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
index bb5d112210..4c4cee1d78 100644
--- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
@@ -1,15 +1,19 @@
-use hir::HasVisibility;
+use hir::{HasVisibility, Semantics};
use ide_db::{
- FxHashMap, FxHashSet,
+ FxHashMap, FxHashSet, RootDatabase,
assists::AssistId,
defs::Definition,
helpers::mod_path_to_ast,
search::{FileReference, SearchScope},
};
use itertools::Itertools;
-use syntax::ast::{HasName, syntax_factory::SyntaxFactory};
use syntax::syntax_editor::SyntaxEditor;
use syntax::{AstNode, Edition, SmolStr, SyntaxNode, ToSmolStr, ast};
+use syntax::{
+ SyntaxToken,
+ ast::{HasName, edit::IndentLevel, syntax_factory::SyntaxFactory},
+ syntax_editor::Position,
+};
use crate::{
assist_context::{AssistContext, Assists, SourceChangeBuilder},
@@ -44,33 +48,90 @@ use crate::{
// }
// ```
pub(crate) fn destructure_struct_binding(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
- let ident_pat = ctx.find_node_at_offset::<ast::IdentPat>()?;
- let data = collect_data(ident_pat, ctx)?;
+ let target = ctx.find_node_at_offset::<Target>()?;
+ let data = collect_data(target, ctx)?;
acc.add(
AssistId::refactor_rewrite("destructure_struct_binding"),
"Destructure struct binding",
- data.ident_pat.syntax().text_range(),
+ data.target.syntax().text_range(),
|edit| destructure_struct_binding_impl(ctx, edit, &data),
);
Some(())
}
+enum Target {
+ IdentPat(ast::IdentPat),
+ SelfParam { param: ast::SelfParam, insert_after: SyntaxToken },
+}
+
+impl Target {
+ fn ty<'db>(&self, sema: &Semantics<'db, RootDatabase>) -> Option<hir::Type<'db>> {
+ match self {
+ Target::IdentPat(pat) => sema.type_of_binding_in_pat(pat),
+ Target::SelfParam { param, .. } => sema.type_of_self(param),
+ }
+ }
+
+ fn is_ref(&self) -> bool {
+ match self {
+ Target::IdentPat(ident_pat) => ident_pat.ref_token().is_some(),
+ Target::SelfParam { .. } => false,
+ }
+ }
+
+ fn is_mut(&self) -> bool {
+ match self {
+ Target::IdentPat(ident_pat) => ident_pat.mut_token().is_some(),
+ Target::SelfParam { param, .. } => {
+ param.mut_token().is_some() && param.amp_token().is_none()
+ }
+ }
+ }
+}
+
+impl HasName for Target {}
+
+impl AstNode for Target {
+ fn cast(node: SyntaxNode) -> Option<Self> {
+ if ast::IdentPat::can_cast(node.kind()) {
+ ast::IdentPat::cast(node).map(Self::IdentPat)
+ } else {
+ let param = ast::SelfParam::cast(node)?;
+ let param_list = param.syntax().parent().and_then(ast::ParamList::cast)?;
+ let block = param_list.syntax().parent()?.children().find_map(ast::BlockExpr::cast)?;
+ let insert_after = block.stmt_list()?.l_curly_token()?;
+ Some(Self::SelfParam { param, insert_after })
+ }
+ }
+
+ fn can_cast(kind: syntax::SyntaxKind) -> bool {
+ ast::IdentPat::can_cast(kind) || ast::SelfParam::can_cast(kind)
+ }
+
+ fn syntax(&self) -> &SyntaxNode {
+ match self {
+ Target::IdentPat(ident_pat) => ident_pat.syntax(),
+ Target::SelfParam { param, .. } => param.syntax(),
+ }
+ }
+}
+
fn destructure_struct_binding_impl(
ctx: &AssistContext<'_>,
builder: &mut SourceChangeBuilder,
data: &StructEditData,
) {
let field_names = generate_field_names(ctx, data);
- let mut editor = builder.make_editor(data.ident_pat.syntax());
+ let mut editor = builder.make_editor(data.target.syntax());
destructure_pat(ctx, &mut editor, data, &field_names);
update_usages(ctx, &mut editor, data, &field_names.into_iter().collect());
builder.add_file_edits(ctx.vfs_file_id(), editor);
}
struct StructEditData {
- ident_pat: ast::IdentPat,
+ target: Target,
name: ast::Name,
kind: hir::StructKind,
struct_def_path: hir::ModPath,
@@ -83,11 +144,44 @@ struct StructEditData {
edition: Edition,
}
-fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<StructEditData> {
- let ty = ctx.sema.type_of_binding_in_pat(&ident_pat)?;
+impl StructEditData {
+ fn apply_to_destruct(
+ &self,
+ new_pat: ast::Pat,
+ editor: &mut SyntaxEditor,
+ make: &SyntaxFactory,
+ ) {
+ match &self.target {
+ Target::IdentPat(pat) => {
+ // If the binding is nested inside a record, we need to wrap the new
+ // destructured pattern in a non-shorthand record field
+ if self.need_record_field_name {
+ let new_pat =
+ make.record_pat_field(make.name_ref(&self.name.to_string()), new_pat);
+ editor.replace(pat.syntax(), new_pat.syntax())
+ } else {
+ editor.replace(pat.syntax(), new_pat.syntax())
+ }
+ }
+ Target::SelfParam { insert_after, .. } => {
+ let indent = IndentLevel::from_token(insert_after) + 1;
+ let newline = make.whitespace(&format!("\n{indent}"));
+ let initializer = make.expr_path(make.ident_path("self"));
+ let let_stmt = make.let_stmt(new_pat, None, Some(initializer));
+ editor.insert_all(
+ Position::after(insert_after),
+ vec![newline.into(), let_stmt.syntax().clone().into()],
+ );
+ }
+ }
+ }
+}
+
+fn collect_data(target: Target, ctx: &AssistContext<'_>) -> Option<StructEditData> {
+ let ty = target.ty(&ctx.sema)?;
let hir::Adt::Struct(struct_type) = ty.strip_references().as_adt()? else { return None };
- let module = ctx.sema.scope(ident_pat.syntax())?.module();
+ let module = ctx.sema.scope(target.syntax())?.module();
let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.db())));
let struct_def = hir::ModuleDef::from(struct_type);
let kind = struct_type.kind(ctx.db());
@@ -116,15 +210,17 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
}
let is_ref = ty.is_reference();
- let need_record_field_name = ident_pat
+ let need_record_field_name = target
.syntax()
.parent()
.and_then(ast::RecordPatField::cast)
.is_some_and(|field| field.colon_token().is_none());
- let usages = ctx
- .sema
- .to_def(&ident_pat)
+ let def = match &target {
+ Target::IdentPat(pat) => ctx.sema.to_def(pat),
+ Target::SelfParam { param, .. } => ctx.sema.to_def(param),
+ };
+ let usages = def
.and_then(|def| {
Definition::Local(def)
.usages(&ctx.sema)
@@ -136,11 +232,11 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
})
.unwrap_or_default();
- let names_in_scope = get_names_in_scope(ctx, &ident_pat, &usages).unwrap_or_default();
+ let names_in_scope = get_names_in_scope(ctx, &target, &usages).unwrap_or_default();
Some(StructEditData {
- name: ident_pat.name()?,
- ident_pat,
+ name: target.name()?,
+ target,
kind,
struct_def_path,
usages,
@@ -155,7 +251,7 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
fn get_names_in_scope(
ctx: &AssistContext<'_>,
- ident_pat: &ast::IdentPat,
+ target: &Target,
usages: &[FileReference],
) -> Option<FxHashSet<SmolStr>> {
fn last_usage(usages: &[FileReference]) -> Option<SyntaxNode> {
@@ -165,7 +261,7 @@ fn get_names_in_scope(
// If available, find names visible to the last usage of the binding
// else, find names visible to the binding itself
let last_usage = last_usage(usages);
- let node = last_usage.as_ref().unwrap_or(ident_pat.syntax());
+ let node = last_usage.as_ref().unwrap_or(target.syntax());
let scope = ctx.sema.scope(node)?;
let mut names = FxHashSet::default();
@@ -183,12 +279,9 @@ fn destructure_pat(
data: &StructEditData,
field_names: &[(SmolStr, SmolStr)],
) {
- let ident_pat = &data.ident_pat;
- let name = &data.name;
-
let struct_path = mod_path_to_ast(&data.struct_def_path, data.edition);
- let is_ref = ident_pat.ref_token().is_some();
- let is_mut = ident_pat.mut_token().is_some();
+ let is_ref = data.target.is_ref();
+ let is_mut = data.target.is_mut();
let make = SyntaxFactory::with_mappings();
let new_pat = match data.kind {
@@ -221,16 +314,8 @@ fn destructure_pat(
hir::StructKind::Unit => make.path_pat(struct_path),
};
- // If the binding is nested inside a record, we need to wrap the new
- // destructured pattern in a non-shorthand record field
- let destructured_pat = if data.need_record_field_name {
- make.record_pat_field(make.name_ref(&name.to_string()), new_pat).syntax().clone()
- } else {
- new_pat.syntax().clone()
- };
-
+ data.apply_to_destruct(new_pat, editor, &make);
editor.add_mappings(make.finish_with_mappings());
- editor.replace(data.ident_pat.syntax(), destructured_pat);
}
fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(SmolStr, SmolStr)> {
@@ -699,6 +784,84 @@ mod tests {
}
#[test]
+ fn mut_self_param() {
+ check_assist(
+ destructure_struct_binding,
+ r#"
+ struct Foo { bar: i32, baz: i32 }
+
+ impl Foo {
+ fn foo(mut $0self) {
+ self.bar = 5;
+ }
+ }
+ "#,
+ r#"
+ struct Foo { bar: i32, baz: i32 }
+
+ impl Foo {
+ fn foo(mut self) {
+ let Foo { mut bar, mut baz } = self;
+ bar = 5;
+ }
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn ref_mut_self_param() {
+ check_assist(
+ destructure_struct_binding,
+ r#"
+ struct Foo { bar: i32, baz: i32 }
+
+ impl Foo {
+ fn foo(&mut $0self) {
+ self.bar = 5;
+ }
+ }
+ "#,
+ r#"
+ struct Foo { bar: i32, baz: i32 }
+
+ impl Foo {
+ fn foo(&mut self) {
+ let Foo { bar, baz } = self;
+ *bar = 5;
+ }
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn ref_self_param() {
+ check_assist(
+ destructure_struct_binding,
+ r#"
+ struct Foo { bar: i32, baz: i32 }
+
+ impl Foo {
+ fn foo(&$0self) -> &i32 {
+ &self.bar
+ }
+ }
+ "#,
+ r#"
+ struct Foo { bar: i32, baz: i32 }
+
+ impl Foo {
+ fn foo(&self) -> &i32 {
+ let Foo { bar, baz } = self;
+ bar
+ }
+ }
+ "#,
+ )
+ }
+
+ #[test]
fn ref_not_add_parenthesis_and_deref_record() {
check_assist(
destructure_struct_binding,
diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
index e2afc0bf13..d51a3a26a3 100644
--- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
@@ -8,8 +8,8 @@ use ide_db::{
use itertools::Itertools;
use syntax::{
T,
- ast::{self, AstNode, FieldExpr, HasName, IdentPat, make},
- ted,
+ ast::{self, AstNode, FieldExpr, HasName, IdentPat, syntax_factory::SyntaxFactory},
+ syntax_editor::{Position, SyntaxEditor},
};
use crate::{
@@ -89,13 +89,20 @@ fn destructure_tuple_edit_impl(
data: &TupleData,
in_sub_pattern: bool,
) {
- let assignment_edit = edit_tuple_assignment(ctx, edit, data, in_sub_pattern);
- let current_file_usages_edit = edit_tuple_usages(data, edit, ctx, in_sub_pattern);
+ let mut syntax_editor = edit.make_editor(data.ident_pat.syntax());
+ let syntax_factory = SyntaxFactory::with_mappings();
- assignment_edit.apply();
+ let assignment_edit =
+ edit_tuple_assignment(ctx, edit, &mut syntax_editor, &syntax_factory, data, in_sub_pattern);
+ let current_file_usages_edit = edit_tuple_usages(data, ctx, &syntax_factory, in_sub_pattern);
+
+ assignment_edit.apply(&mut syntax_editor, &syntax_factory);
if let Some(usages_edit) = current_file_usages_edit {
- usages_edit.into_iter().for_each(|usage_edit| usage_edit.apply(edit))
+ usages_edit.into_iter().for_each(|usage_edit| usage_edit.apply(edit, &mut syntax_editor))
}
+
+ syntax_editor.add_mappings(syntax_factory.finish_with_mappings());
+ edit.add_file_edits(ctx.vfs_file_id(), syntax_editor);
}
fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleData> {
@@ -157,6 +164,7 @@ enum RefType {
Mutable,
}
struct TupleData {
+ // FIXME: After removing ted, it may be possible to reuse destructure_struct_binding::Target
ident_pat: IdentPat,
ref_type: Option<RefType>,
field_names: Vec<String>,
@@ -165,11 +173,11 @@ struct TupleData {
fn edit_tuple_assignment(
ctx: &AssistContext<'_>,
edit: &mut SourceChangeBuilder,
+ editor: &mut SyntaxEditor,
+ make: &SyntaxFactory,
data: &TupleData,
in_sub_pattern: bool,
) -> AssignmentEdit {
- let ident_pat = edit.make_mut(data.ident_pat.clone());
-
let tuple_pat = {
let original = &data.ident_pat;
let is_ref = original.ref_token().is_some();
@@ -177,10 +185,11 @@ fn edit_tuple_assignment(
let fields = data
.field_names
.iter()
- .map(|name| ast::Pat::from(make::ident_pat(is_ref, is_mut, make::name(name))));
- make::tuple_pat(fields).clone_for_update()
+ .map(|name| ast::Pat::from(make.ident_pat(is_ref, is_mut, make.name(name))));
+ make.tuple_pat(fields)
};
- let is_shorthand_field = ident_pat
+ let is_shorthand_field = data
+ .ident_pat
.name()
.as_ref()
.and_then(ast::RecordPatField::for_field_name)
@@ -189,14 +198,20 @@ fn edit_tuple_assignment(
if let Some(cap) = ctx.config.snippet_cap {
// place cursor on first tuple name
if let Some(ast::Pat::IdentPat(first_pat)) = tuple_pat.fields().next() {
- edit.add_tabstop_before(
- cap,
- first_pat.name().expect("first ident pattern should have a name"),
- )
+ let annotation = edit.make_tabstop_before(cap);
+ editor.add_annotation(
+ first_pat.name().expect("first ident pattern should have a name").syntax(),
+ annotation,
+ );
}
}
- AssignmentEdit { ident_pat, tuple_pat, in_sub_pattern, is_shorthand_field }
+ AssignmentEdit {
+ ident_pat: data.ident_pat.clone(),
+ tuple_pat,
+ in_sub_pattern,
+ is_shorthand_field,
+ }
}
struct AssignmentEdit {
ident_pat: ast::IdentPat,
@@ -206,23 +221,30 @@ struct AssignmentEdit {
}
impl AssignmentEdit {
- fn apply(self) {
+ fn apply(self, syntax_editor: &mut SyntaxEditor, syntax_mapping: &SyntaxFactory) {
// with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
if self.in_sub_pattern {
- self.ident_pat.set_pat(Some(self.tuple_pat.into()))
+ self.ident_pat.set_pat_with_editor(
+ Some(self.tuple_pat.into()),
+ syntax_editor,
+ syntax_mapping,
+ )
} else if self.is_shorthand_field {
- ted::insert(ted::Position::after(self.ident_pat.syntax()), self.tuple_pat.syntax());
- ted::insert_raw(ted::Position::after(self.ident_pat.syntax()), make::token(T![:]));
+ syntax_editor.insert(Position::after(self.ident_pat.syntax()), self.tuple_pat.syntax());
+ syntax_editor
+ .insert(Position::after(self.ident_pat.syntax()), syntax_mapping.whitespace(" "));
+ syntax_editor
+ .insert(Position::after(self.ident_pat.syntax()), syntax_mapping.token(T![:]));
} else {
- ted::replace(self.ident_pat.syntax(), self.tuple_pat.syntax())
+ syntax_editor.replace(self.ident_pat.syntax(), self.tuple_pat.syntax())
}
}
}
fn edit_tuple_usages(
data: &TupleData,
- edit: &mut SourceChangeBuilder,
ctx: &AssistContext<'_>,
+ make: &SyntaxFactory,
in_sub_pattern: bool,
) -> Option<Vec<EditTupleUsage>> {
// We need to collect edits first before actually applying them
@@ -238,20 +260,20 @@ fn edit_tuple_usages(
.as_ref()?
.as_slice()
.iter()
- .filter_map(|r| edit_tuple_usage(ctx, edit, r, data, in_sub_pattern))
+ .filter_map(|r| edit_tuple_usage(ctx, make, r, data, in_sub_pattern))
.collect_vec();
Some(edits)
}
fn edit_tuple_usage(
ctx: &AssistContext<'_>,
- builder: &mut SourceChangeBuilder,
+ make: &SyntaxFactory,
usage: &FileReference,
data: &TupleData,
in_sub_pattern: bool,
) -> Option<EditTupleUsage> {
match detect_tuple_index(usage, data) {
- Some(index) => Some(edit_tuple_field_usage(ctx, builder, data, index)),
+ Some(index) => Some(edit_tuple_field_usage(ctx, make, data, index)),
None if in_sub_pattern => {
cov_mark::hit!(destructure_tuple_call_with_subpattern);
None
@@ -262,20 +284,18 @@ fn edit_tuple_usage(
fn edit_tuple_field_usage(
ctx: &AssistContext<'_>,
- builder: &mut SourceChangeBuilder,
+ make: &SyntaxFactory,
data: &TupleData,
index: TupleIndex,
) -> EditTupleUsage {
let field_name = &data.field_names[index.index];
- let field_name = make::expr_path(make::ext::ident_path(field_name));
+ let field_name = make.expr_path(make.ident_path(field_name));
if data.ref_type.is_some() {
let (replace_expr, ref_data) = determine_ref_and_parens(ctx, &index.field_expr);
- let replace_expr = builder.make_mut(replace_expr);
- EditTupleUsage::ReplaceExpr(replace_expr, ref_data.wrap_expr(field_name))
+ EditTupleUsage::ReplaceExpr(replace_expr, ref_data.wrap_expr_with_factory(field_name, make))
} else {
- let field_expr = builder.make_mut(index.field_expr);
- EditTupleUsage::ReplaceExpr(field_expr.into(), field_name)
+ EditTupleUsage::ReplaceExpr(index.field_expr.into(), field_name)
}
}
enum EditTupleUsage {
@@ -291,14 +311,14 @@ enum EditTupleUsage {
}
impl EditTupleUsage {
- fn apply(self, edit: &mut SourceChangeBuilder) {
+ fn apply(self, edit: &mut SourceChangeBuilder, syntax_editor: &mut SyntaxEditor) {
match self {
EditTupleUsage::NoIndex(range) => {
edit.insert(range.start(), "/*");
edit.insert(range.end(), "*/");
}
EditTupleUsage::ReplaceExpr(target_expr, replace_with) => {
- ted::replace(target_expr.syntax(), replace_with.clone_for_update().syntax())
+ syntax_editor.replace(target_expr.syntax(), replace_with.syntax())
}
}
}
diff --git a/crates/ide-assists/src/handlers/fix_visibility.rs b/crates/ide-assists/src/handlers/fix_visibility.rs
index 0fd8057a39..5134b98f1b 100644
--- a/crates/ide-assists/src/handlers/fix_visibility.rs
+++ b/crates/ide-assists/src/handlers/fix_visibility.rs
@@ -2,7 +2,7 @@ use hir::{HasSource, HasVisibility, ModuleDef, PathResolution, ScopeDef, db::Hir
use ide_db::FileId;
use syntax::{
AstNode, TextRange,
- ast::{self, HasVisibility as _, edit_in_place::HasVisibilityEdit, make},
+ ast::{self, HasVisibility as _, syntax_factory::SyntaxFactory},
};
use crate::{AssistContext, AssistId, Assists};
@@ -59,10 +59,12 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
let (vis_owner, target, target_file, target_name) = target_data_for_def(ctx.db(), def)?;
+ let make = SyntaxFactory::without_mappings();
+
let missing_visibility = if current_module.krate(ctx.db()) == target_module.krate(ctx.db()) {
- make::visibility_pub_crate()
+ make.visibility_pub_crate()
} else {
- make::visibility_pub()
+ make.visibility_pub()
};
let assist_label = match target_name {
@@ -75,15 +77,36 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
}
};
- acc.add(AssistId::quick_fix("fix_visibility"), assist_label, target, |edit| {
- edit.edit_file(target_file);
-
- let vis_owner = edit.make_mut(vis_owner);
- vis_owner.set_visibility(Some(missing_visibility.clone_for_update()));
+ acc.add(AssistId::quick_fix("fix_visibility"), assist_label, target, |builder| {
+ let mut editor = builder.make_editor(vis_owner.syntax());
+
+ if let Some(current_visibility) = vis_owner.visibility() {
+ editor.replace(current_visibility.syntax(), missing_visibility.syntax());
+ } else {
+ let vis_before = vis_owner
+ .syntax()
+ .children_with_tokens()
+ .find(|it| {
+ !matches!(
+ it.kind(),
+ syntax::SyntaxKind::WHITESPACE
+ | syntax::SyntaxKind::COMMENT
+ | syntax::SyntaxKind::ATTR
+ )
+ })
+ .unwrap_or_else(|| vis_owner.syntax().first_child_or_token().unwrap());
+
+ editor.insert_all(
+ syntax::syntax_editor::Position::before(vis_before),
+ vec![missing_visibility.syntax().clone().into(), make.whitespace(" ").into()],
+ );
+ }
- if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
- edit.add_tabstop_before(cap, vis);
+ if let Some(cap) = ctx.config.snippet_cap {
+ editor.add_annotation(missing_visibility.syntax(), builder.make_tabstop_before(cap));
}
+
+ builder.add_file_edits(target_file, editor);
})
}
diff --git a/crates/ide-assists/src/handlers/generate_derive.rs b/crates/ide-assists/src/handlers/generate_derive.rs
index 06fef4af22..3ef68f06e4 100644
--- a/crates/ide-assists/src/handlers/generate_derive.rs
+++ b/crates/ide-assists/src/handlers/generate_derive.rs
@@ -1,7 +1,7 @@
use syntax::{
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
T,
- ast::{self, AstNode, HasAttrs, edit::IndentLevel, make},
+ ast::{self, AstNode, HasAttrs, edit::IndentLevel, syntax_factory::SyntaxFactory},
syntax_editor::{Element, Position},
};
@@ -42,13 +42,15 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
};
acc.add(AssistId::generate("generate_derive"), "Add `#[derive]`", target, |edit| {
+ let make = SyntaxFactory::without_mappings();
+
match derive_attr {
None => {
- let derive = make::attr_outer(make::meta_token_tree(
- make::ext::ident_path("derive"),
- make::token_tree(T!['('], vec![]).clone_for_update(),
- ))
- .clone_for_update();
+ let derive =
+ make.attr_outer(make.meta_token_tree(
+ make.ident_path("derive"),
+ make.token_tree(T!['('], vec![]),
+ ));
let mut editor = edit.make_editor(nominal.syntax());
let indent = IndentLevel::from_node(nominal.syntax());
@@ -57,11 +59,12 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
.children_with_tokens()
.find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
.map_or(Position::first_child_of(nominal.syntax()), Position::before);
+
editor.insert_all(
after_attrs_and_comments,
vec![
derive.syntax().syntax_element(),
- make::tokens::whitespace(&format!("\n{indent}")).syntax_element(),
+ make.whitespace(&format!("\n{indent}")).syntax_element(),
],
);
@@ -72,7 +75,9 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
.expect("failed to get token tree out of Meta")
.r_paren_token()
.expect("make::attr_outer was expected to have a R_PAREN");
+
let tabstop_before = edit.make_tabstop_before(cap);
+
editor.add_annotation(delimiter, tabstop_before);
edit.add_file_edits(ctx.vfs_file_id(), editor);
}
diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs
index ded3b0f5ac..f62eccaf19 100644
--- a/crates/ide-assists/src/handlers/generate_function.rs
+++ b/crates/ide-assists/src/handlers/generate_function.rs
@@ -147,7 +147,16 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
return None;
}
- let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?;
+ let enclosing_impl = ctx.find_node_at_offset::<ast::Impl>();
+ let cursor_impl = enclosing_impl.filter(|impl_| {
+ ctx.sema.to_def(impl_).map_or(false, |def| def.self_ty(ctx.sema.db).as_adt() == Some(adt))
+ });
+
+ let (impl_, file) = if let Some(impl_) = cursor_impl {
+ (Some(impl_), ctx.vfs_file_id())
+ } else {
+ get_adt_source(ctx, &adt, fn_name.text().as_str())?
+ };
let target = get_method_target(ctx, &impl_, &adt)?;
let function_builder = FunctionBuilder::from_method_call(
@@ -3206,4 +3215,44 @@ fn bar(arg: impl Fn(_) -> bool) {
"#,
);
}
+ #[test]
+ fn generate_method_uses_current_impl_block() {
+ check_assist(
+ generate_function,
+ r"
+struct Foo;
+
+impl Foo {
+ fn new() -> Self {
+ Foo
+ }
+}
+
+impl Foo {
+ fn method1(&self) {
+ self.method2$0(42)
+ }
+}
+",
+ r"
+struct Foo;
+
+impl Foo {
+ fn new() -> Self {
+ Foo
+ }
+}
+
+impl Foo {
+ fn method1(&self) {
+ self.method2(42)
+ }
+
+ fn method2(&self, arg: i32) {
+ ${0:todo!()}
+ }
+}
+",
+ )
+ }
}
diff --git a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs
index 73e93a3fbf..92a654743b 100644
--- a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs
+++ b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs
@@ -2,13 +2,16 @@ use ide_db::{famous_defs::FamousDefs, source_change::SourceChangeBuilder};
use stdx::{format_to, to_lower_snake_case};
use syntax::{
TextRange,
- ast::{self, AstNode, HasName, HasVisibility, edit_in_place::Indent, make},
- ted,
+ ast::{
+ self, AstNode, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit,
+ syntax_factory::SyntaxFactory,
+ },
+ syntax_editor::Position,
};
use crate::{
AssistContext, AssistId, Assists, GroupLabel,
- utils::{convert_reference_type, find_struct_impl, generate_impl},
+ utils::{convert_reference_type, find_struct_impl, is_selected},
};
// Assist: generate_setter
@@ -215,12 +218,14 @@ fn generate_getter_from_info(
ctx: &AssistContext<'_>,
info: &AssistInfo,
record_field_info: &RecordFieldInfo,
+ syntax_factory: &SyntaxFactory,
) -> ast::Fn {
let (ty, body) = if matches!(info.assist_type, AssistType::MutGet) {
+ let self_expr = syntax_factory.expr_path(syntax_factory.ident_path("self"));
(
- make::ty_ref(record_field_info.field_ty.clone(), true),
- make::expr_ref(
- make::expr_field(make::ext::expr_self(), &record_field_info.field_name.text()),
+ syntax_factory.ty_ref(record_field_info.field_ty.clone(), true),
+ syntax_factory.expr_ref(
+ syntax_factory.expr_field(self_expr, &record_field_info.field_name.text()).into(),
true,
),
)
@@ -241,9 +246,14 @@ fn generate_getter_from_info(
})()
.unwrap_or_else(|| {
(
- make::ty_ref(record_field_info.field_ty.clone(), false),
- make::expr_ref(
- make::expr_field(make::ext::expr_self(), &record_field_info.field_name.text()),
+ syntax_factory.ty_ref(record_field_info.field_ty.clone(), false),
+ syntax_factory.expr_ref(
+ syntax_factory
+ .expr_field(
+ syntax_factory.expr_path(syntax_factory.ident_path("self")),
+ &record_field_info.field_name.text(),
+ )
+ .into(),
false,
),
)
@@ -251,18 +261,18 @@ fn generate_getter_from_info(
};
let self_param = if matches!(info.assist_type, AssistType::MutGet) {
- make::mut_self_param()
+ syntax_factory.mut_self_param()
} else {
- make::self_param()
+ syntax_factory.self_param()
};
let strukt = &info.strukt;
- let fn_name = make::name(&record_field_info.fn_name);
- let params = make::param_list(Some(self_param), []);
- let ret_type = Some(make::ret_type(ty));
- let body = make::block_expr([], Some(body));
+ let fn_name = syntax_factory.name(&record_field_info.fn_name);
+ let params = syntax_factory.param_list(Some(self_param), []);
+ let ret_type = Some(syntax_factory.ret_type(ty));
+ let body = syntax_factory.block_expr([], Some(body));
- make::fn_(
+ syntax_factory.fn_(
None,
strukt.visibility(),
fn_name,
@@ -278,28 +288,35 @@ fn generate_getter_from_info(
)
}
-fn generate_setter_from_info(info: &AssistInfo, record_field_info: &RecordFieldInfo) -> ast::Fn {
+fn generate_setter_from_info(
+ info: &AssistInfo,
+ record_field_info: &RecordFieldInfo,
+ syntax_factory: &SyntaxFactory,
+) -> ast::Fn {
let strukt = &info.strukt;
let field_name = &record_field_info.fn_name;
- let fn_name = make::name(&format!("set_{field_name}"));
+ let fn_name = syntax_factory.name(&format!("set_{field_name}"));
let field_ty = &record_field_info.field_ty;
// Make the param list
// `(&mut self, $field_name: $field_ty)`
- let field_param =
- make::param(make::ident_pat(false, false, make::name(field_name)).into(), field_ty.clone());
- let params = make::param_list(Some(make::mut_self_param()), [field_param]);
+ let field_param = syntax_factory.param(
+ syntax_factory.ident_pat(false, false, syntax_factory.name(field_name)).into(),
+ field_ty.clone(),
+ );
+ let params = syntax_factory.param_list(Some(syntax_factory.mut_self_param()), [field_param]);
// Make the assignment body
// `self.$field_name = $field_name`
- let self_expr = make::ext::expr_self();
- let lhs = make::expr_field(self_expr, field_name);
- let rhs = make::expr_path(make::ext::ident_path(field_name));
- let assign_stmt = make::expr_stmt(make::expr_assignment(lhs, rhs).into());
- let body = make::block_expr([assign_stmt.into()], None);
+ let self_expr = syntax_factory.expr_path(syntax_factory.ident_path("self"));
+ let lhs = syntax_factory.expr_field(self_expr, field_name);
+ let rhs = syntax_factory.expr_path(syntax_factory.ident_path(field_name));
+ let assign_stmt =
+ syntax_factory.expr_stmt(syntax_factory.expr_assignment(lhs.into(), rhs).into());
+ let body = syntax_factory.block_expr([assign_stmt.into()], None);
// Make the setter fn
- make::fn_(
+ syntax_factory.fn_(
None,
strukt.visibility(),
fn_name,
@@ -360,7 +377,7 @@ fn extract_and_parse_record_fields(
let info_of_record_fields_in_selection = ele
.fields()
.filter_map(|record_field| {
- if selection_range.contains_range(record_field.syntax().text_range()) {
+ if is_selected(&record_field, selection_range, false) {
let record_field_info = parse_record_field(record_field, assist_type)?;
field_names.push(record_field_info.fn_name.clone());
return Some(record_field_info);
@@ -403,47 +420,69 @@ fn build_source_change(
info_of_record_fields: Vec<RecordFieldInfo>,
assist_info: AssistInfo,
) {
- let record_fields_count = info_of_record_fields.len();
-
- let impl_def = if let Some(impl_def) = &assist_info.impl_def {
- // We have an existing impl to add to
- builder.make_mut(impl_def.clone())
- } else {
- // Generate a new impl to add the methods to
- let impl_def = generate_impl(&ast::Adt::Struct(assist_info.strukt.clone()));
+ let syntax_factory = SyntaxFactory::without_mappings();
- // Insert it after the adt
- let strukt = builder.make_mut(assist_info.strukt.clone());
-
- ted::insert_all_raw(
- ted::Position::after(strukt.syntax()),
- vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
- );
-
- impl_def
- };
-
- let assoc_item_list = impl_def.get_or_create_assoc_item_list();
+ let items: Vec<ast::AssocItem> = info_of_record_fields
+ .iter()
+ .map(|record_field_info| {
+ let method = match assist_info.assist_type {
+ AssistType::Set => {
+ generate_setter_from_info(&assist_info, record_field_info, &syntax_factory)
+ }
+ _ => {
+ generate_getter_from_info(ctx, &assist_info, record_field_info, &syntax_factory)
+ }
+ };
+ let new_fn = method.clone_for_update();
+ let new_fn = new_fn.indent(1.into());
+ new_fn.into()
+ })
+ .collect();
- for (i, record_field_info) in info_of_record_fields.iter().enumerate() {
- // Make the new getter or setter fn
- let new_fn = match assist_info.assist_type {
- AssistType::Set => generate_setter_from_info(&assist_info, record_field_info),
- _ => generate_getter_from_info(ctx, &assist_info, record_field_info),
- }
- .clone_for_update();
- new_fn.indent(1.into());
+ if let Some(impl_def) = &assist_info.impl_def {
+ // We have an existing impl to add to
+ let mut editor = builder.make_editor(impl_def.syntax());
+ impl_def.assoc_item_list().unwrap().add_items(&mut editor, items.clone());
- // Insert a tabstop only for last method we generate
- if i == record_fields_count - 1
- && let Some(cap) = ctx.config.snippet_cap
- && let Some(name) = new_fn.name()
+ if let Some(cap) = ctx.config.snippet_cap
+ && let Some(ast::AssocItem::Fn(fn_)) = items.last()
+ && let Some(name) = fn_.name()
{
- builder.add_tabstop_before(cap, name);
+ let tabstop = builder.make_tabstop_before(cap);
+ editor.add_annotation(name.syntax(), tabstop);
}
- assoc_item_list.add_item(new_fn.clone().into());
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
+ return;
+ }
+ let ty_params = assist_info.strukt.generic_param_list();
+ let ty_args = ty_params.as_ref().map(|it| it.to_generic_args());
+ let impl_def = syntax_factory.impl_(
+ None,
+ ty_params,
+ ty_args,
+ syntax_factory
+ .ty_path(syntax_factory.ident_path(&assist_info.strukt.name().unwrap().to_string()))
+ .into(),
+ None,
+ Some(syntax_factory.assoc_item_list(items)),
+ );
+ let mut editor = builder.make_editor(assist_info.strukt.syntax());
+ editor.insert_all(
+ Position::after(assist_info.strukt.syntax()),
+ vec![syntax_factory.whitespace("\n\n").into(), impl_def.syntax().clone().into()],
+ );
+
+ if let Some(cap) = ctx.config.snippet_cap
+ && let Some(assoc_list) = impl_def.assoc_item_list()
+ && let Some(ast::AssocItem::Fn(fn_)) = assoc_list.assoc_items().last()
+ && let Some(name) = fn_.name()
+ {
+ let tabstop = builder.make_tabstop_before(cap);
+ editor.add_annotation(name.syntax().clone(), tabstop);
}
+
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
}
#[cfg(test)]
@@ -909,6 +948,37 @@ impl Context {
}
#[test]
+ fn test_generate_multiple_getters_from_partial_selection() {
+ check_assist(
+ generate_getter,
+ r#"
+struct Context {
+ data$0: Data,
+ count$0: usize,
+ other: usize,
+}
+ "#,
+ r#"
+struct Context {
+ data: Data,
+ count: usize,
+ other: usize,
+}
+
+impl Context {
+ fn data(&self) -> &Data {
+ &self.data
+ }
+
+ fn $0count(&self) -> &usize {
+ &self.count
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
fn test_generate_multiple_getters_from_selection_one_already_exists() {
// As impl for one of the fields already exist, skip it
check_assist_not_applicable(
diff --git a/crates/ide-assists/src/handlers/generate_impl.rs b/crates/ide-assists/src/handlers/generate_impl.rs
index 77eb8efc6f..bbd42481ef 100644
--- a/crates/ide-assists/src/handlers/generate_impl.rs
+++ b/crates/ide-assists/src/handlers/generate_impl.rs
@@ -1,5 +1,5 @@
use syntax::{
- ast::{self, AstNode, HasGenericParams, HasName, edit_in_place::Indent, make},
+ ast::{self, AstNode, HasGenericParams, HasName, edit::AstNodeEdit, make},
syntax_editor::{Position, SyntaxEditor},
};
@@ -8,10 +8,14 @@ use crate::{
utils::{self, DefaultMethods, IgnoreAssocItems},
};
-fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &impl Indent) {
+fn insert_impl(
+ editor: &mut SyntaxEditor,
+ impl_: &ast::Impl,
+ nominal: &impl AstNodeEdit,
+) -> ast::Impl {
let indent = nominal.indent_level();
- impl_.indent(indent);
+ let impl_ = impl_.indent(indent);
editor.insert_all(
Position::after(nominal.syntax()),
vec![
@@ -20,6 +24,8 @@ fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &impl Inde
impl_.syntax().clone().into(),
],
);
+
+ impl_
}
// Assist: generate_impl
@@ -57,6 +63,8 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
let impl_ = utils::generate_impl(&nominal);
let mut editor = edit.make_editor(nominal.syntax());
+
+ let impl_ = insert_impl(&mut editor, &impl_, &nominal);
// Add a tabstop after the left curly brace
if let Some(cap) = ctx.config.snippet_cap
&& let Some(l_curly) = impl_.assoc_item_list().and_then(|it| it.l_curly_token())
@@ -65,7 +73,6 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
editor.add_annotation(l_curly, tabstop);
}
- insert_impl(&mut editor, &impl_, &nominal);
edit.add_file_edits(ctx.vfs_file_id(), editor);
},
)
@@ -106,6 +113,8 @@ pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let impl_ = utils::generate_trait_impl_intransitive(&nominal, make::ty_placeholder());
let mut editor = edit.make_editor(nominal.syntax());
+
+ let impl_ = insert_impl(&mut editor, &impl_, &nominal);
// Make the trait type a placeholder snippet
if let Some(cap) = ctx.config.snippet_cap {
if let Some(trait_) = impl_.trait_() {
@@ -119,7 +128,6 @@ pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
}
}
- insert_impl(&mut editor, &impl_, &nominal);
edit.add_file_edits(ctx.vfs_file_id(), editor);
},
)
@@ -206,6 +214,8 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) ->
make_impl_(Some(assoc_item_list))
};
+ let impl_ = insert_impl(&mut editor, &impl_, &trait_);
+
if let Some(cap) = ctx.config.snippet_cap {
if let Some(generics) = impl_.trait_().and_then(|it| it.generic_arg_list()) {
for generic in generics.generic_args() {
@@ -232,7 +242,6 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) ->
}
}
- insert_impl(&mut editor, &impl_, &trait_);
edit.add_file_edits(ctx.vfs_file_id(), editor);
},
)
diff --git a/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs b/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs
index 53f6f4883f..3a62a8853e 100644
--- a/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs
+++ b/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs
@@ -1,7 +1,7 @@
use ide_db::{famous_defs::FamousDefs, traits::resolve_target_trait};
use syntax::{
AstNode, SyntaxElement, SyntaxNode, T,
- ast::{self, edit::AstNodeEdit, edit_in_place::Indent, syntax_factory::SyntaxFactory},
+ ast::{self, edit::AstNodeEdit, syntax_factory::SyntaxFactory},
syntax_editor::{Element, Position, SyntaxEditor},
};
@@ -46,7 +46,7 @@ use crate::{AssistContext, AssistId, Assists};
// ```
pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
- let indent = Indent::indent_level(&impl_def);
+ let indent = impl_def.indent_level();
let ast::Type::PathType(path) = impl_def.trait_()? else {
return None;
@@ -78,7 +78,7 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>
let new_impl = ast::Impl::cast(new_root.clone()).unwrap();
- Indent::indent(&new_impl, indent);
+ let new_impl = new_impl.indent(indent);
let mut editor = edit.make_editor(impl_def.syntax());
editor.insert_all(
diff --git a/crates/ide-assists/src/handlers/generate_new.rs b/crates/ide-assists/src/handlers/generate_new.rs
index 4b923ab556..793211a27b 100644
--- a/crates/ide-assists/src/handlers/generate_new.rs
+++ b/crates/ide-assists/src/handlers/generate_new.rs
@@ -3,7 +3,7 @@ use ide_db::{
use_trivial_constructor::use_trivial_constructor,
};
use syntax::{
- ast::{self, AstNode, HasName, HasVisibility, StructKind, edit_in_place::Indent, make},
+ ast::{self, AstNode, HasName, HasVisibility, StructKind, edit::AstNodeEdit, make},
syntax_editor::Position,
};
@@ -150,14 +150,14 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
false,
false,
)
- .clone_for_update();
- fn_.indent(1.into());
+ .clone_for_update()
+ .indent(1.into());
let mut editor = builder.make_editor(strukt.syntax());
// Get the node for set annotation
let contain_fn = if let Some(impl_def) = impl_def {
- fn_.indent(impl_def.indent_level());
+ let fn_ = fn_.indent(impl_def.indent_level());
if let Some(l_curly) = impl_def.assoc_item_list().and_then(|list| list.l_curly_token())
{
@@ -182,9 +182,8 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
let indent_level = strukt.indent_level();
let body = vec![ast::AssocItem::Fn(fn_)];
let list = make::assoc_item_list(Some(body));
- let impl_def = generate_impl_with_item(&ast::Adt::Struct(strukt.clone()), Some(list));
-
- impl_def.indent(strukt.indent_level());
+ let impl_def = generate_impl_with_item(&ast::Adt::Struct(strukt.clone()), Some(list))
+ .indent(strukt.indent_level());
// Insert it after the adt
editor.insert_all(
diff --git a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs
index 56500cf068..8bc4d50cf6 100644
--- a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs
+++ b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs
@@ -2,7 +2,7 @@ use crate::assist_context::{AssistContext, Assists};
use ide_db::assists::AssistId;
use syntax::{
AstNode, SyntaxKind, T,
- ast::{self, HasGenericParams, HasName, HasVisibility, edit_in_place::Indent, make},
+ ast::{self, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, make},
syntax_editor::{Position, SyntaxEditor},
};
diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs
index fa4f2a78c8..21f2249a19 100644
--- a/crates/ide-assists/src/handlers/inline_call.rs
+++ b/crates/ide-assists/src/handlers/inline_call.rs
@@ -403,6 +403,12 @@ fn inline(
.find(|tok| tok.kind() == SyntaxKind::SELF_TYPE_KW)
{
let replace_with = t.clone_subtree().syntax().clone_for_update();
+ if !is_in_type_path(&self_tok)
+ && let Some(ty) = ast::Type::cast(replace_with.clone())
+ && let Some(generic_arg_list) = ty.generic_arg_list()
+ {
+ ted::remove(generic_arg_list.syntax());
+ }
ted::replace(self_tok, replace_with);
}
}
@@ -588,6 +594,17 @@ fn inline(
}
}
+fn is_in_type_path(self_tok: &syntax::SyntaxToken) -> bool {
+ self_tok
+ .parent_ancestors()
+ .skip_while(|it| !ast::Path::can_cast(it.kind()))
+ .map_while(ast::Path::cast)
+ .last()
+ .and_then(|it| it.syntax().parent())
+ .and_then(ast::PathType::cast)
+ .is_some()
+}
+
fn path_expr_as_record_field(usage: &PathExpr) -> Option<ast::RecordExprField> {
let path = usage.path()?;
let name_ref = path.as_single_name_ref()?;
@@ -1695,6 +1712,41 @@ fn main() {
}
#[test]
+ fn inline_trait_method_call_with_lifetimes() {
+ check_assist(
+ inline_call,
+ r#"
+trait Trait {
+ fn f() -> Self;
+}
+struct Foo<'a>(&'a ());
+impl<'a> Trait for Foo<'a> {
+ fn f() -> Self { Self(&()) }
+}
+impl Foo<'_> {
+ fn new() -> Self {
+ Self::$0f()
+ }
+}
+"#,
+ r#"
+trait Trait {
+ fn f() -> Self;
+}
+struct Foo<'a>(&'a ());
+impl<'a> Trait for Foo<'a> {
+ fn f() -> Self { Self(&()) }
+}
+impl Foo<'_> {
+ fn new() -> Self {
+ Foo(&())
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
fn method_by_reborrow() {
check_assist(
inline_call,
diff --git a/crates/ide-assists/src/handlers/introduce_named_lifetime.rs b/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
index 264e3767a2..854e9561d2 100644
--- a/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
@@ -1,11 +1,12 @@
-use ide_db::FxHashSet;
+use ide_db::{FileId, FxHashSet};
use syntax::{
- AstNode, TextRange,
- ast::{self, HasGenericParams, edit_in_place::GenericParamsOwnerEdit, make},
- ted::{self, Position},
+ AstNode, SmolStr, T, TextRange, ToSmolStr,
+ ast::{self, HasGenericParams, HasName, syntax_factory::SyntaxFactory},
+ format_smolstr,
+ syntax_editor::{Element, Position, SyntaxEditor},
};
-use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder};
+use crate::{AssistContext, AssistId, Assists};
static ASSIST_NAME: &str = "introduce_named_lifetime";
static ASSIST_LABEL: &str = "Introduce named lifetime";
@@ -38,100 +39,108 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext<'_
// FIXME: should also add support for the case fun(f: &Foo) -> &$0Foo
let lifetime =
ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?;
+ let file_id = ctx.vfs_file_id();
let lifetime_loc = lifetime.lifetime_ident_token()?.text_range();
if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) {
- generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime)
+ generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime, file_id)
} else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) {
- generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime)
+ generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime, file_id)
} else {
None
}
}
-/// Generate the assist for the fn def case
+/// Given a type parameter list, generate a unique lifetime parameter name
+/// which is not in the list
+fn generate_unique_lifetime_param_name(
+ existing_params: Option<ast::GenericParamList>,
+) -> Option<SmolStr> {
+ let used_lifetime_param: FxHashSet<SmolStr> = existing_params
+ .iter()
+ .flat_map(|params| params.lifetime_params())
+ .map(|p| p.syntax().text().to_smolstr())
+ .collect();
+ ('a'..='z').map(|c| format_smolstr!("'{c}")).find(|lt| !used_lifetime_param.contains(lt))
+}
+
fn generate_fn_def_assist(
acc: &mut Assists,
fn_def: ast::Fn,
lifetime_loc: TextRange,
lifetime: ast::Lifetime,
+ file_id: FileId,
) -> Option<()> {
- let param_list: ast::ParamList = fn_def.param_list()?;
- let new_lifetime_param = generate_unique_lifetime_param_name(fn_def.generic_param_list())?;
+ let param_list = fn_def.param_list()?;
+ let new_lifetime_name = generate_unique_lifetime_param_name(fn_def.generic_param_list())?;
let self_param =
- // use the self if it's a reference and has no explicit lifetime
param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some());
- // compute the location which implicitly has the same lifetime as the anonymous lifetime
+
let loc_needing_lifetime = if let Some(self_param) = self_param {
- // if we have a self reference, use that
Some(NeedsLifetime::SelfParam(self_param))
} else {
- // otherwise, if there's a single reference parameter without a named lifetime, use that
- let fn_params_without_lifetime: Vec<_> = param_list
+ let unnamed_refs: Vec<_> = param_list
.params()
.filter_map(|param| match param.ty() {
- Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => {
- Some(NeedsLifetime::RefType(ascribed_type))
+ Some(ast::Type::RefType(ref_type)) if ref_type.lifetime().is_none() => {
+ Some(NeedsLifetime::RefType(ref_type))
}
_ => None,
})
.collect();
- match fn_params_without_lifetime.len() {
- 1 => Some(fn_params_without_lifetime.into_iter().next()?),
+
+ match unnamed_refs.len() {
+ 1 => Some(unnamed_refs.into_iter().next()?),
0 => None,
- // multiple unnamed is invalid. assist is not applicable
_ => return None,
}
};
- acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| {
- let fn_def = builder.make_mut(fn_def);
- let lifetime = builder.make_mut(lifetime);
- let loc_needing_lifetime =
- loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position());
-
- fn_def.get_or_create_generic_param_list().add_generic_param(
- make::lifetime_param(new_lifetime_param.clone()).clone_for_update().into(),
- );
- ted::replace(lifetime.syntax(), new_lifetime_param.clone_for_update().syntax());
- if let Some(position) = loc_needing_lifetime {
- ted::insert(position, new_lifetime_param.clone_for_update().syntax());
+
+ acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |edit| {
+ let root = fn_def.syntax().ancestors().last().unwrap().clone();
+ let mut editor = SyntaxEditor::new(root);
+ let factory = SyntaxFactory::with_mappings();
+
+ if let Some(generic_list) = fn_def.generic_param_list() {
+ insert_lifetime_param(&mut editor, &factory, &generic_list, &new_lifetime_name);
+ } else {
+ insert_new_generic_param_list_fn(&mut editor, &factory, &fn_def, &new_lifetime_name);
}
+
+ editor.replace(lifetime.syntax(), factory.lifetime(&new_lifetime_name).syntax());
+
+ if let Some(pos) = loc_needing_lifetime.and_then(|l| l.to_position()) {
+ editor.insert_all(
+ pos,
+ vec![
+ factory.lifetime(&new_lifetime_name).syntax().clone().into(),
+ factory.whitespace(" ").into(),
+ ],
+ );
+ }
+
+ edit.add_file_edits(file_id, editor);
})
}
-/// Generate the assist for the impl def case
-fn generate_impl_def_assist(
- acc: &mut Assists,
- impl_def: ast::Impl,
- lifetime_loc: TextRange,
- lifetime: ast::Lifetime,
+fn insert_new_generic_param_list_fn(
+ editor: &mut SyntaxEditor,
+ factory: &SyntaxFactory,
+ fn_def: &ast::Fn,
+ lifetime_name: &str,
) -> Option<()> {
- let new_lifetime_param = generate_unique_lifetime_param_name(impl_def.generic_param_list())?;
- acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| {
- let impl_def = builder.make_mut(impl_def);
- let lifetime = builder.make_mut(lifetime);
+ let name = fn_def.name()?;
- impl_def.get_or_create_generic_param_list().add_generic_param(
- make::lifetime_param(new_lifetime_param.clone()).clone_for_update().into(),
- );
- ted::replace(lifetime.syntax(), new_lifetime_param.clone_for_update().syntax());
- })
-}
+ editor.insert_all(
+ Position::after(name.syntax()),
+ vec![
+ factory.token(T![<]).syntax_element(),
+ factory.lifetime(lifetime_name).syntax().syntax_element(),
+ factory.token(T![>]).syntax_element(),
+ ],
+ );
-/// Given a type parameter list, generate a unique lifetime parameter name
-/// which is not in the list
-fn generate_unique_lifetime_param_name(
- existing_type_param_list: Option<ast::GenericParamList>,
-) -> Option<ast::Lifetime> {
- match existing_type_param_list {
- Some(type_params) => {
- let used_lifetime_params: FxHashSet<_> =
- type_params.lifetime_params().map(|p| p.syntax().text().to_string()).collect();
- ('a'..='z').map(|it| format!("'{it}")).find(|it| !used_lifetime_params.contains(it))
- }
- None => Some("'a".to_owned()),
- }
- .map(|it| make::lifetime(&it))
+ Some(())
}
enum NeedsLifetime {
@@ -140,13 +149,6 @@ enum NeedsLifetime {
}
impl NeedsLifetime {
- fn make_mut(self, builder: &mut SourceChangeBuilder) -> Self {
- match self {
- Self::SelfParam(it) => Self::SelfParam(builder.make_mut(it)),
- Self::RefType(it) => Self::RefType(builder.make_mut(it)),
- }
- }
-
fn to_position(self) -> Option<Position> {
match self {
Self::SelfParam(it) => Some(Position::after(it.amp_token()?)),
@@ -155,6 +157,75 @@ impl NeedsLifetime {
}
}
+fn generate_impl_def_assist(
+ acc: &mut Assists,
+ impl_def: ast::Impl,
+ lifetime_loc: TextRange,
+ lifetime: ast::Lifetime,
+ file_id: FileId,
+) -> Option<()> {
+ let new_lifetime_name = generate_unique_lifetime_param_name(impl_def.generic_param_list())?;
+
+ acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |edit| {
+ let root = impl_def.syntax().ancestors().last().unwrap().clone();
+ let mut editor = SyntaxEditor::new(root);
+ let factory = SyntaxFactory::without_mappings();
+
+ if let Some(generic_list) = impl_def.generic_param_list() {
+ insert_lifetime_param(&mut editor, &factory, &generic_list, &new_lifetime_name);
+ } else {
+ insert_new_generic_param_list_imp(&mut editor, &factory, &impl_def, &new_lifetime_name);
+ }
+
+ editor.replace(lifetime.syntax(), factory.lifetime(&new_lifetime_name).syntax());
+
+ edit.add_file_edits(file_id, editor);
+ })
+}
+
+fn insert_new_generic_param_list_imp(
+ editor: &mut SyntaxEditor,
+ factory: &SyntaxFactory,
+ impl_: &ast::Impl,
+ lifetime_name: &str,
+) -> Option<()> {
+ let impl_kw = impl_.impl_token()?;
+
+ editor.insert_all(
+ Position::after(impl_kw),
+ vec![
+ factory.token(T![<]).syntax_element(),
+ factory.lifetime(lifetime_name).syntax().syntax_element(),
+ factory.token(T![>]).syntax_element(),
+ ],
+ );
+
+ Some(())
+}
+
+fn insert_lifetime_param(
+ editor: &mut SyntaxEditor,
+ factory: &SyntaxFactory,
+ generic_list: &ast::GenericParamList,
+ lifetime_name: &str,
+) -> Option<()> {
+ let r_angle = generic_list.r_angle_token()?;
+ let needs_comma = generic_list.generic_params().next().is_some();
+
+ let mut elements = Vec::new();
+
+ if needs_comma {
+ elements.push(factory.token(T![,]).syntax_element());
+ elements.push(factory.whitespace(" ").syntax_element());
+ }
+
+ let lifetime = factory.lifetime(lifetime_name);
+ elements.push(lifetime.syntax().clone().into());
+
+ editor.insert_all(Position::before(r_angle), elements);
+ Some(())
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/crates/ide-assists/src/handlers/move_const_to_impl.rs b/crates/ide-assists/src/handlers/move_const_to_impl.rs
index 102d7e6d53..b3e79e4663 100644
--- a/crates/ide-assists/src/handlers/move_const_to_impl.rs
+++ b/crates/ide-assists/src/handlers/move_const_to_impl.rs
@@ -2,7 +2,10 @@ use hir::{AsAssocItem, AssocItemContainer, FileRange, HasSource};
use ide_db::{assists::AssistId, defs::Definition, search::SearchScope};
use syntax::{
SyntaxKind,
- ast::{self, AstNode, edit::IndentLevel, edit_in_place::Indent},
+ ast::{
+ self, AstNode,
+ edit::{AstNodeEdit, IndentLevel},
+ },
};
use crate::assist_context::{AssistContext, Assists};
@@ -136,7 +139,8 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let indent = IndentLevel::from_node(parent_fn.syntax());
let const_ = const_.clone_for_update();
- const_.reindent_to(indent);
+ let const_ = const_.reset_indent();
+ let const_ = const_.indent(indent);
builder.insert(insert_offset, format!("\n{indent}{const_}{fixup}"));
},
)
diff --git a/crates/ide-assists/src/handlers/move_guard.rs b/crates/ide-assists/src/handlers/move_guard.rs
index fdc5a0fbda..b4c347ff36 100644
--- a/crates/ide-assists/src/handlers/move_guard.rs
+++ b/crates/ide-assists/src/handlers/move_guard.rs
@@ -567,7 +567,8 @@ fn main() {
match 92 {
x => if true
&& true
- && true {
+ && true
+ {
{
false
}
diff --git a/crates/ide-assists/src/handlers/promote_local_to_const.rs b/crates/ide-assists/src/handlers/promote_local_to_const.rs
index 547d3686e3..483c90d103 100644
--- a/crates/ide-assists/src/handlers/promote_local_to_const.rs
+++ b/crates/ide-assists/src/handlers/promote_local_to_const.rs
@@ -8,7 +8,7 @@ use syntax::{
use crate::{
assist_context::{AssistContext, Assists},
- utils::{self},
+ utils,
};
// Assist: promote_local_to_const
diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index 11b3fd22fa..7c024263b4 100644
--- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -1308,6 +1308,29 @@ impl<T: Clone> Clone for Foo<T> {
}
#[test]
+ fn add_custom_impl_clone_generic_tuple_struct_with_associated() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: clone, derive, deref
+#[derive(Clo$0ne)]
+struct Foo<T: core::ops::Deref>(T::Target);
+"#,
+ r#"
+struct Foo<T: core::ops::Deref>(T::Target);
+
+impl<T: core::ops::Deref + Clone> Clone for Foo<T>
+where T::Target: Clone
+{
+ $0fn clone(&self) -> Self {
+ Self(self.0.clone())
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
fn test_ignore_derive_macro_without_input() {
check_assist_not_applicable(
replace_derive_with_manual_impl,
diff --git a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
index b7e5344712..8ff30fce5b 100644
--- a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
@@ -3,7 +3,11 @@ use std::iter::successors;
use ide_db::{RootDatabase, defs::NameClass, ty_filter::TryEnum};
use syntax::{
AstNode, Edition, SyntaxKind, T, TextRange,
- ast::{self, HasName, edit::IndentLevel, edit_in_place::Indent, syntax_factory::SyntaxFactory},
+ ast::{
+ self, HasName,
+ edit::{AstNodeEdit, IndentLevel},
+ syntax_factory::SyntaxFactory,
+ },
syntax_editor::SyntaxEditor,
};
@@ -53,9 +57,8 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<'
let if_exprs = successors(Some(if_expr.clone()), |expr| match expr.else_branch()? {
ast::ElseBranch::IfExpr(expr) => Some(expr),
ast::ElseBranch::Block(block) => {
- let block = unwrap_trivial_block(block).clone_for_update();
- block.reindent_to(IndentLevel(1));
- else_block = Some(block);
+ let block = unwrap_trivial_block(block);
+ else_block = Some(block.reset_indent().indent(IndentLevel(1)));
None
}
});
@@ -82,12 +85,13 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<'
(Some(pat), guard)
}
};
- if let Some(guard) = &guard {
- guard.dedent(indent);
- guard.indent(IndentLevel(1));
- }
- let body = if_expr.then_branch()?.clone_for_update();
- body.indent(IndentLevel(1));
+ let guard = if let Some(guard) = &guard {
+ Some(guard.dedent(indent).indent(IndentLevel(1)))
+ } else {
+ guard
+ };
+
+ let body = if_expr.then_branch()?.indent(IndentLevel(1));
cond_bodies.push((cond, guard, body));
}
@@ -109,7 +113,8 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<'
let else_arm = make_else_arm(ctx, &make, else_block, &cond_bodies);
let make_match_arm =
|(pat, guard, body): (_, Option<ast::Expr>, ast::BlockExpr)| {
- body.reindent_to(IndentLevel::single());
+ // Dedent from original position, then indent for match arm
+ let body = body.dedent(indent);
let body = unwrap_trivial_block(body);
match (pat, guard.map(|it| make.match_guard(it))) {
(Some(pat), guard) => make.match_arm(pat, guard, body),
@@ -122,8 +127,8 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<'
}
};
let arms = cond_bodies.into_iter().map(make_match_arm).chain([else_arm]);
- let match_expr = make.expr_match(scrutinee_to_be_expr, make.match_arm_list(arms));
- match_expr.indent(indent);
+ let expr = scrutinee_to_be_expr.reset_indent();
+ let match_expr = make.expr_match(expr, make.match_arm_list(arms)).indent(indent);
match_expr.into()
};
@@ -131,10 +136,9 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<'
if_expr.syntax().parent().is_some_and(|it| ast::IfExpr::can_cast(it.kind()));
let expr = if has_preceding_if_expr {
// make sure we replace the `else if let ...` with a block so we don't end up with `else expr`
- match_expr.dedent(indent);
- match_expr.indent(IndentLevel(1));
- let block_expr = make.block_expr([], Some(match_expr));
- block_expr.indent(indent);
+ let block_expr = make
+ .block_expr([], Some(match_expr.dedent(indent).indent(IndentLevel(1))))
+ .indent(indent);
block_expr.into()
} else {
match_expr
@@ -242,7 +246,7 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<'
first_arm.guard(),
second_arm.guard(),
)?;
- let scrutinee = match_expr.expr()?;
+ let scrutinee = match_expr.expr()?.reset_indent();
let guard = guard.and_then(|it| it.condition());
let let_ = match &if_let_pat {
@@ -267,10 +271,7 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<'
// wrap them in another BlockExpr.
match expr {
ast::Expr::BlockExpr(block) if block.modifier().is_none() => block,
- expr => {
- expr.indent(IndentLevel(1));
- make.block_expr([], Some(expr))
- }
+ expr => make.block_expr([], Some(expr.indent(IndentLevel(1)))),
}
};
@@ -292,18 +293,17 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<'
} else {
condition
};
- let then_expr = then_expr.clone_for_update();
- let else_expr = else_expr.clone_for_update();
- then_expr.reindent_to(IndentLevel::single());
- else_expr.reindent_to(IndentLevel::single());
+ let then_expr = then_expr.reset_indent();
+ let else_expr = else_expr.reset_indent();
let then_block = make_block_expr(then_expr);
let else_expr = if is_empty_expr(&else_expr) { None } else { Some(else_expr) };
- let if_let_expr = make.expr_if(
- condition,
- then_block,
- else_expr.map(make_block_expr).map(ast::ElseBranch::Block),
- );
- if_let_expr.indent(IndentLevel::from_node(match_expr.syntax()));
+ let if_let_expr = make
+ .expr_if(
+ condition,
+ then_block,
+ else_expr.map(make_block_expr).map(ast::ElseBranch::Block),
+ )
+ .indent(IndentLevel::from_node(match_expr.syntax()));
let mut editor = builder.make_editor(match_expr.syntax());
editor.replace(match_expr.syntax(), if_let_expr.syntax());
@@ -848,6 +848,31 @@ fn foo(x: Option<i32>) {
}
#[test]
+ fn special_case_option_ref() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+//- minicore: option
+fn foo(x: &Option<i32>) {
+ $0if let Some(x) = x {
+ println!("{}", x)
+ } else {
+ println!("none")
+ }
+}
+"#,
+ r#"
+fn foo(x: &Option<i32>) {
+ match x {
+ Some(x) => println!("{}", x),
+ None => println!("none"),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
fn special_case_inverted_option() {
check_assist(
replace_if_let_with_match,
@@ -929,7 +954,9 @@ fn foo(x: Result<i32, ()>) {
r#"
fn main() {
if true {
- $0if let Ok(rel_path) = path.strip_prefix(root_path) {
+ $0if let Ok(rel_path) = path.strip_prefix(root_path)
+ .and(x)
+ {
let rel_path = RelativePathBuf::from_path(rel_path)
.ok()?;
Some((*id, rel_path))
@@ -944,7 +971,9 @@ fn main() {
r#"
fn main() {
if true {
- match path.strip_prefix(root_path) {
+ match path.strip_prefix(root_path)
+ .and(x)
+ {
Ok(rel_path) => {
let rel_path = RelativePathBuf::from_path(rel_path)
.ok()?;
@@ -966,7 +995,9 @@ fn main() {
r#"
fn main() {
if true {
- $0if let Ok(rel_path) = path.strip_prefix(root_path) {
+ $0if let Ok(rel_path) = path.strip_prefix(root_path)
+ .and(x)
+ {
Foo {
x: 1
}
@@ -981,7 +1012,9 @@ fn main() {
r#"
fn main() {
if true {
- match path.strip_prefix(root_path) {
+ match path.strip_prefix(root_path)
+ .and(x)
+ {
Ok(rel_path) => {
Foo {
x: 1
@@ -996,7 +1029,34 @@ fn main() {
}
}
"#,
- )
+ );
+
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+fn main() {
+ if true {
+ $0if true
+ && false
+ {
+ foo()
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ if true {
+ match true
+ && false
+ {
+ true => foo(),
+ false => (),
+ }
+ }
+}
+"#,
+ );
}
#[test]
@@ -1851,7 +1911,9 @@ fn foo(x: Result<i32, ()>) {
r#"
fn main() {
if true {
- $0match path.strip_prefix(root_path) {
+ $0match path.strip_prefix(root_path)
+ .and(x)
+ {
Ok(rel_path) => Foo {
x: 2
}
@@ -1865,7 +1927,9 @@ fn main() {
r#"
fn main() {
if true {
- if let Ok(rel_path) = path.strip_prefix(root_path) {
+ if let Ok(rel_path) = path.strip_prefix(root_path)
+ .and(x)
+ {
Foo {
x: 2
}
@@ -1884,7 +1948,9 @@ fn main() {
r#"
fn main() {
if true {
- $0match path.strip_prefix(root_path) {
+ $0match path.strip_prefix(root_path)
+ .and(x)
+ {
Ok(rel_path) => {
let rel_path = RelativePathBuf::from_path(rel_path)
.ok()?;
@@ -1902,7 +1968,9 @@ fn main() {
r#"
fn main() {
if true {
- if let Ok(rel_path) = path.strip_prefix(root_path) {
+ if let Ok(rel_path) = path.strip_prefix(root_path)
+ .and(x)
+ {
let rel_path = RelativePathBuf::from_path(rel_path)
.ok()?;
Some((*id, rel_path))
diff --git a/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs b/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs
index 5a2307739c..d22e951b5d 100644
--- a/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs
+++ b/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs
@@ -1,6 +1,6 @@
use either::Either;
use ide_db::syntax_helpers::suggest_name;
-use syntax::ast::{self, AstNode, syntax_factory::SyntaxFactory};
+use syntax::ast::{self, AstNode, HasArgList, syntax_factory::SyntaxFactory};
use crate::{AssistContext, AssistId, Assists, utils::cover_let_chain};
@@ -34,8 +34,9 @@ pub(crate) fn replace_is_method_with_if_let_method(
_ => return None,
};
- let name_ref = call_expr.name_ref()?;
- match name_ref.text().as_str() {
+ let token = call_expr.name_ref()?.ident_token()?;
+ let method_kind = token.text().strip_suffix("_and").unwrap_or(token.text());
+ match method_kind {
"is_some" | "is_ok" => {
let receiver = call_expr.receiver()?;
@@ -47,8 +48,9 @@ pub(crate) fn replace_is_method_with_if_let_method(
} else {
name_generator.for_variable(&receiver, &ctx.sema)
};
+ let (pat, predicate) = method_predicate(&call_expr).unzip();
- let (assist_id, message, text) = if name_ref.text() == "is_some" {
+ let (assist_id, message, text) = if method_kind == "is_some" {
("replace_is_some_with_if_let_some", "Replace `is_some` with `let Some`", "Some")
} else {
("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `let Ok`", "Ok")
@@ -62,19 +64,29 @@ pub(crate) fn replace_is_method_with_if_let_method(
let make = SyntaxFactory::with_mappings();
let mut editor = edit.make_editor(call_expr.syntax());
- let var_pat = make.ident_pat(false, false, make.name(&var_name));
- let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat.into()]);
- let let_expr = make.expr_let(pat.into(), receiver);
+ let var_pat = pat.unwrap_or_else(|| {
+ make.ident_pat(false, false, make.name(&var_name)).into()
+ });
+ let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat]).into();
+ let let_expr = make.expr_let(pat, receiver);
if let Some(cap) = ctx.config.snippet_cap
&& let Some(ast::Pat::TupleStructPat(pat)) = let_expr.pat()
&& let Some(first_var) = pat.fields().next()
+ && predicate.is_none()
{
let placeholder = edit.make_placeholder_snippet(cap);
editor.add_annotation(first_var.syntax(), placeholder);
}
- editor.replace(call_expr.syntax(), let_expr.syntax());
+ let new_expr = if let Some(predicate) = predicate {
+ let op = ast::BinaryOp::LogicOp(ast::LogicOp::And);
+ make.expr_bin(let_expr.into(), op, predicate).into()
+ } else {
+ ast::Expr::from(let_expr)
+ };
+ editor.replace(call_expr.syntax(), new_expr.syntax());
+
editor.add_mappings(make.finish_with_mappings());
edit.add_file_edits(ctx.vfs_file_id(), editor);
},
@@ -84,6 +96,17 @@ pub(crate) fn replace_is_method_with_if_let_method(
}
}
+fn method_predicate(call_expr: &ast::MethodCallExpr) -> Option<(ast::Pat, ast::Expr)> {
+ let argument = call_expr.arg_list()?.args().next()?;
+ match argument {
+ ast::Expr::ClosureExpr(it) => {
+ let pat = it.param_list()?.params().next()?.pat()?;
+ Some((pat, it.body()?))
+ }
+ _ => None,
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -195,6 +218,25 @@ fn main() {
}
#[test]
+ fn replace_is_some_and_with_if_let_chain_some_works() {
+ check_assist(
+ replace_is_method_with_if_let_method,
+ r#"
+fn main() {
+ let x = Some(1);
+ if x.is_som$0e_and(|it| it != 3) {}
+}
+"#,
+ r#"
+fn main() {
+ let x = Some(1);
+ if let Some(it) = x && it != 3 {}
+}
+"#,
+ );
+ }
+
+ #[test]
fn replace_is_some_with_if_let_some_in_let_chain() {
check_assist(
replace_is_method_with_if_let_method,
diff --git a/crates/ide-assists/src/handlers/replace_let_with_if_let.rs b/crates/ide-assists/src/handlers/replace_let_with_if_let.rs
index b95e9b52b0..5587f1b59c 100644
--- a/crates/ide-assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ide-assists/src/handlers/replace_let_with_if_let.rs
@@ -1,7 +1,11 @@
use ide_db::ty_filter::TryEnum;
use syntax::{
AstNode, T,
- ast::{self, edit::IndentLevel, edit_in_place::Indent, syntax_factory::SyntaxFactory},
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ syntax_factory::SyntaxFactory,
+ },
};
use crate::{AssistContext, AssistId, Assists};
@@ -64,7 +68,7 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>
if let_expr_needs_paren(&init) { make.expr_paren(init).into() } else { init };
let block = make.block_expr([], None);
- block.indent(IndentLevel::from_node(let_stmt.syntax()));
+ let block = block.indent(IndentLevel::from_node(let_stmt.syntax()));
let if_expr = make.expr_if(
make.expr_let(pat, init_expr).into(),
block,
@@ -98,6 +102,29 @@ mod tests {
use super::*;
#[test]
+ fn replace_let_try_enum_ref() {
+ check_assist(
+ replace_let_with_if_let,
+ r"
+//- minicore: option
+fn main(action: Action) {
+ $0let x = compute();
+}
+
+fn compute() -> &'static Option<i32> { &None }
+ ",
+ r"
+fn main(action: Action) {
+ if let Some(x) = compute() {
+ }
+}
+
+fn compute() -> &'static Option<i32> { &None }
+ ",
+ )
+ }
+
+ #[test]
fn replace_let_unknown_enum() {
check_assist(
replace_let_with_if_let,
diff --git a/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs
index 60b0797f02..15143575e7 100644
--- a/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs
+++ b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs
@@ -1,6 +1,7 @@
use ide_db::assists::AssistId;
use syntax::{
- AstNode, SyntaxToken, T,
+ AstNode, SyntaxKind, SyntaxToken, T,
+ algo::{previous_non_trivia_token, skip_trivia_token},
ast::{self, syntax_factory::SyntaxFactory},
};
@@ -36,15 +37,18 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>)
RCur,
}
- let makro = ctx.find_node_at_offset::<ast::MacroCall>()?;
+ let token_tree = ctx.find_node_at_offset::<ast::TokenTree>()?;
let cursor_offset = ctx.offset();
- let semicolon = macro_semicolon(&makro);
- let token_tree = makro.token_tree()?;
+ let semicolon = macro_semicolon(&token_tree);
let ltoken = token_tree.left_delimiter_token()?;
let rtoken = token_tree.right_delimiter_token()?;
+ if !is_macro_call(&token_tree)? {
+ return None;
+ }
+
if !ltoken.text_range().contains(cursor_offset) && !rtoken.text_range().contains(cursor_offset)
{
return None;
@@ -70,7 +74,7 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>)
token_tree.syntax().text_range(),
|builder| {
let make = SyntaxFactory::with_mappings();
- let mut editor = builder.make_editor(makro.syntax());
+ let mut editor = builder.make_editor(token_tree.syntax());
match token {
MacroDelims::LPar | MacroDelims::RPar => {
@@ -102,12 +106,21 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>)
)
}
-fn macro_semicolon(makro: &ast::MacroCall) -> Option<SyntaxToken> {
- makro.semicolon_token().or_else(|| {
- let macro_expr = ast::MacroExpr::cast(makro.syntax().parent()?)?;
- let expr_stmt = ast::ExprStmt::cast(macro_expr.syntax().parent()?)?;
- expr_stmt.semicolon_token()
- })
+fn is_macro_call(token_tree: &ast::TokenTree) -> Option<bool> {
+ let parent = token_tree.syntax().parent()?;
+ if ast::MacroCall::can_cast(parent.kind()) {
+ return Some(true);
+ }
+
+ let token_tree = ast::TokenTree::cast(parent)?;
+ let prev = previous_non_trivia_token(token_tree.syntax().clone())?;
+ let prev_prev = previous_non_trivia_token(prev.clone())?;
+ Some(prev.kind() == T![!] && prev_prev.kind() == SyntaxKind::IDENT)
+}
+
+fn macro_semicolon(token_tree: &ast::TokenTree) -> Option<SyntaxToken> {
+ let next_token = token_tree.syntax().last_token()?.next_token()?;
+ skip_trivia_token(next_token, syntax::Direction::Next).filter(|it| it.kind() == T![;])
}
fn needs_semicolon(tt: ast::TokenTree) -> bool {
@@ -402,10 +415,9 @@ prt!{(3 + 5)}
)
}
- // FIXME @alibektas : Inner macro_call is not seen as such. So this doesn't work.
#[test]
fn test_nested_macros() {
- check_assist_not_applicable(
+ check_assist(
toggle_macro_delimiter,
r#"
macro_rules! prt {
@@ -420,7 +432,22 @@ macro_rules! abc {
}};
}
-prt!{abc!($03 + 5)};
+prt!{abc!$0(3 + 5)};
+"#,
+ r#"
+macro_rules! prt {
+ ($e:expr) => {{
+ println!("{}", stringify!{$e});
+ }};
+}
+
+macro_rules! abc {
+ ($e:expr) => {{
+ println!("{}", stringify!{$e});
+ }};
+}
+
+prt!{abc!{3 + 5}};
"#,
)
}
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index 5e08cba8e2..b4055e77cc 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/crates/ide-assists/src/utils.rs
@@ -14,6 +14,7 @@ use ide_db::{
path_transform::PathTransform,
syntax_helpers::{node_ext::preorder_expr, prettify_macro_expansion},
};
+use itertools::Itertools;
use stdx::format_to;
use syntax::{
AstNode, AstToken, Direction, NodeOrToken, SourceFile,
@@ -765,6 +766,11 @@ fn generate_impl_inner(
});
let generic_args =
generic_params.as_ref().map(|params| params.to_generic_args().clone_for_update());
+ let adt_assoc_bounds = trait_
+ .as_ref()
+ .zip(generic_params.as_ref())
+ .and_then(|(trait_, params)| generic_param_associated_bounds(adt, trait_, params));
+
let ty = make::ty_path(make::ext::ident_path(&adt.name().unwrap().text()));
let cfg_attrs =
@@ -780,7 +786,7 @@ fn generate_impl_inner(
false,
trait_,
ty,
- None,
+ adt_assoc_bounds,
adt.where_clause(),
body,
),
@@ -789,6 +795,51 @@ fn generate_impl_inner(
.clone_for_update()
}
+fn generic_param_associated_bounds(
+ adt: &ast::Adt,
+ trait_: &ast::Type,
+ generic_params: &ast::GenericParamList,
+) -> Option<ast::WhereClause> {
+ let in_type_params = |name: &ast::NameRef| {
+ generic_params
+ .generic_params()
+ .filter_map(|param| match param {
+ ast::GenericParam::TypeParam(type_param) => type_param.name(),
+ _ => None,
+ })
+ .any(|param| param.text() == name.text())
+ };
+ let adt_body = match adt {
+ ast::Adt::Enum(e) => e.variant_list().map(|it| it.syntax().clone()),
+ ast::Adt::Struct(s) => s.field_list().map(|it| it.syntax().clone()),
+ ast::Adt::Union(u) => u.record_field_list().map(|it| it.syntax().clone()),
+ };
+ let mut trait_where_clause = adt_body
+ .into_iter()
+ .flat_map(|it| it.descendants())
+ .filter_map(ast::Path::cast)
+ .filter_map(|path| {
+ let qualifier = path.qualifier()?.as_single_segment()?;
+ let qualifier = qualifier
+ .name_ref()
+ .or_else(|| match qualifier.type_anchor()?.ty()? {
+ ast::Type::PathType(path_type) => path_type.path()?.as_single_name_ref(),
+ _ => None,
+ })
+ .filter(in_type_params)?;
+ Some((qualifier, path.segment()?.name_ref()?))
+ })
+ .map(|(qualifier, assoc_name)| {
+ let segments = [qualifier, assoc_name].map(make::path_segment);
+ let path = make::path_from_segments(segments, false);
+ let bounds = Some(make::type_bound(trait_.clone()));
+ make::where_pred(either::Either::Right(make::ty_path(path)), bounds)
+ })
+ .unique_by(|it| it.syntax().to_string())
+ .peekable();
+ trait_where_clause.peek().is_some().then(|| make::where_clause(trait_where_clause))
+}
+
pub(crate) fn add_method_to_adt(
builder: &mut SourceChangeBuilder,
adt: &ast::Adt,
diff --git a/crates/ide-assists/src/utils/ref_field_expr.rs b/crates/ide-assists/src/utils/ref_field_expr.rs
index 840b26a7ad..df8ad41112 100644
--- a/crates/ide-assists/src/utils/ref_field_expr.rs
+++ b/crates/ide-assists/src/utils/ref_field_expr.rs
@@ -5,7 +5,7 @@
//! based on the parent of the existing expression.
use syntax::{
AstNode, T,
- ast::{self, FieldExpr, MethodCallExpr, make},
+ ast::{self, FieldExpr, MethodCallExpr, make, syntax_factory::SyntaxFactory},
};
use crate::AssistContext;
@@ -130,4 +130,20 @@ impl RefData {
expr
}
+
+ pub(crate) fn wrap_expr_with_factory(
+ &self,
+ mut expr: ast::Expr,
+ syntax_factory: &SyntaxFactory,
+ ) -> ast::Expr {
+ if self.needs_deref {
+ expr = syntax_factory.expr_prefix(T![*], expr).into();
+ }
+
+ if self.needs_parentheses {
+ expr = syntax_factory.expr_paren(expr).into();
+ }
+
+ expr
+ }
}
diff --git a/crates/ide-completion/src/completions/fn_param.rs b/crates/ide-completion/src/completions/fn_param.rs
index 34d25c9c67..96dac66b8a 100644
--- a/crates/ide-completion/src/completions/fn_param.rs
+++ b/crates/ide-completion/src/completions/fn_param.rs
@@ -4,9 +4,9 @@ use hir::HirDisplay;
use ide_db::FxHashMap;
use itertools::Either;
use syntax::{
- AstNode, Direction, SyntaxKind, TextRange, TextSize, algo,
+ AstNode, Direction, SmolStr, SyntaxKind, TextRange, TextSize, ToSmolStr, algo,
ast::{self, HasModuleItem},
- match_ast,
+ format_smolstr, match_ast,
};
use crate::{
@@ -25,7 +25,7 @@ pub(crate) fn complete_fn_param(
ctx: &CompletionContext<'_>,
pattern_ctx: &PatternContext,
) -> Option<()> {
- let (ParamContext { param_list, kind, .. }, impl_or_trait) = match pattern_ctx {
+ let (ParamContext { param_list, kind, param, .. }, impl_or_trait) = match pattern_ctx {
PatternContext { param_ctx: Some(kind), impl_or_trait, .. } => (kind, impl_or_trait),
_ => return None,
};
@@ -46,13 +46,18 @@ pub(crate) fn complete_fn_param(
match kind {
ParamKind::Function(function) => {
- fill_fn_params(ctx, function, param_list, impl_or_trait, add_new_item_to_acc);
+ fill_fn_params(ctx, function, param_list, param, impl_or_trait, add_new_item_to_acc);
}
ParamKind::Closure(closure) => {
- let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?;
- params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
- add_new_item_to_acc(&format!("{}: {ty}", name.display(ctx.db, ctx.edition)));
- });
+ if is_simple_param(param) {
+ let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?;
+ params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
+ add_new_item_to_acc(&format_smolstr!(
+ "{}: {ty}",
+ name.display(ctx.db, ctx.edition)
+ ));
+ });
+ }
}
}
@@ -63,17 +68,20 @@ fn fill_fn_params(
ctx: &CompletionContext<'_>,
function: &ast::Fn,
param_list: &ast::ParamList,
+ current_param: &ast::Param,
impl_or_trait: &Option<Either<ast::Impl, ast::Trait>>,
mut add_new_item_to_acc: impl FnMut(&str),
) {
let mut file_params = FxHashMap::default();
let mut extract_params = |f: ast::Fn| {
+ if !is_simple_param(current_param) {
+ return;
+ }
f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
if let Some(pat) = param.pat() {
- // FIXME: We should be able to turn these into SmolStr without having to allocate a String
- let whole_param = param.syntax().text().to_string();
- let binding = pat.syntax().text().to_string();
+ let whole_param = param.to_smolstr();
+ let binding = pat.to_smolstr();
file_params.entry(whole_param).or_insert(binding);
}
});
@@ -99,11 +107,13 @@ fn fill_fn_params(
};
}
- if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) {
+ if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast)
+ && is_simple_param(current_param)
+ {
params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
file_params
- .entry(format!("{}: {ty}", name.display(ctx.db, ctx.edition)))
- .or_insert(name.display(ctx.db, ctx.edition).to_string());
+ .entry(format_smolstr!("{}: {ty}", name.display(ctx.db, ctx.edition)))
+ .or_insert(name.display(ctx.db, ctx.edition).to_smolstr());
});
}
remove_duplicated(&mut file_params, param_list.params());
@@ -139,11 +149,11 @@ fn params_from_stmt_list_scope(
}
fn remove_duplicated(
- file_params: &mut FxHashMap<String, String>,
+ file_params: &mut FxHashMap<SmolStr, SmolStr>,
fn_params: ast::AstChildren<ast::Param>,
) {
fn_params.for_each(|param| {
- let whole_param = param.syntax().text().to_string();
+ let whole_param = param.to_smolstr();
file_params.remove(&whole_param);
match param.pat() {
@@ -151,7 +161,7 @@ fn remove_duplicated(
// if the type is missing we are checking the current param to be completed
// in which case this would find itself removing the suggestions due to itself
Some(pattern) if param.ty().is_some() => {
- let binding = pattern.syntax().text().to_string();
+ let binding = pattern.to_smolstr();
file_params.retain(|_, v| v != &binding);
}
_ => (),
@@ -173,7 +183,7 @@ fn should_add_self_completions(
}
}
-fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> String, TextRange)> {
+fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> SmolStr, TextRange)> {
let param =
ctx.original_token.parent_ancestors().find(|node| node.kind() == SyntaxKind::PARAM)?;
@@ -196,5 +206,11 @@ fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> String
matches!(prev_token_kind, SyntaxKind::COMMA | SyntaxKind::L_PAREN | SyntaxKind::PIPE);
let leading = if has_leading_comma { "" } else { ", " };
- Some((move |label: &_| format!("{leading}{label}{trailing}"), param.text_range()))
+ Some((move |label: &_| format_smolstr!("{leading}{label}{trailing}"), param.text_range()))
+}
+
+fn is_simple_param(param: &ast::Param) -> bool {
+ param
+ .pat()
+ .is_none_or(|pat| matches!(pat, ast::Pat::IdentPat(ident_pat) if ident_pat.pat().is_none()))
}
diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs
index eeb2c65e48..6e9328165d 100644
--- a/crates/ide-completion/src/completions/pattern.rs
+++ b/crates/ide-completion/src/completions/pattern.rs
@@ -95,7 +95,7 @@ pub(crate) fn complete_pattern(
if refutable || single_variant_enum(variant.parent_enum(ctx.db)) =>
{
acc.add_variant_pat(ctx, pattern_ctx, None, variant, Some(name.clone()));
- true
+ false
}
hir::ModuleDef::Adt(hir::Adt::Enum(e)) => refutable || single_variant_enum(e),
hir::ModuleDef::Const(..) => refutable,
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index cffc44f8af..ea53aef40c 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -52,6 +52,7 @@ pub(crate) fn complete_postfix(
_ => return,
};
let expr_ctx = &dot_access.ctx;
+ let receiver_accessor = receiver_accessor(dot_receiver);
let receiver_text =
get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
@@ -90,9 +91,8 @@ pub(crate) fn complete_postfix(
// The rest of the postfix completions create an expression that moves an argument,
// so it's better to consider references now to avoid breaking the compilation
- let (dot_receiver_including_refs, prefix) = include_references(dot_receiver);
- let mut receiver_text =
- get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
+ let (dot_receiver_including_refs, prefix) = include_references(&receiver_accessor);
+ let mut receiver_text = receiver_text;
receiver_text.insert_str(0, &prefix);
let postfix_snippet =
match build_postfix_snippet_builder(ctx, cap, &dot_receiver_including_refs) {
@@ -111,14 +111,9 @@ pub(crate) fn complete_postfix(
postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
.add_to(acc, ctx.db);
- let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
- let mut is_in_cond = false;
- if let Some(parent) = dot_receiver_including_refs.syntax().parent()
- && let Some(second_ancestor) = parent.parent()
- {
- if let Some(parent_expr) = ast::Expr::cast(parent) {
- is_in_cond = is_in_condition(&parent_expr);
- }
+ let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty);
+ let is_in_cond = is_in_condition(&dot_receiver_including_refs);
+ if let Some(parent) = dot_receiver_including_refs.syntax().parent() {
let placeholder = suggest_receiver_name(dot_receiver, "0", &ctx.sema);
match &try_enum {
Some(try_enum) if is_in_cond => match try_enum {
@@ -155,12 +150,26 @@ pub(crate) fn complete_postfix(
postfix_snippet("let", "let", &format!("let $1 = {receiver_text}"))
.add_to(acc, ctx.db);
}
- _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT) => {
+ _ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) => {
postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
.add_to(acc, ctx.db);
postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
.add_to(acc, ctx.db);
}
+ _ if ast::MatchArm::can_cast(parent.kind()) => {
+ postfix_snippet(
+ "let",
+ "let",
+ &format!("{{\n let $1 = {receiver_text};\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+ postfix_snippet(
+ "letm",
+ "let mut",
+ &format!("{{\n let mut $1 = {receiver_text};\n $0\n}}"),
+ )
+ .add_to(acc, ctx.db);
+ }
_ => (),
}
}
@@ -292,7 +301,7 @@ pub(crate) fn complete_postfix(
postfix_snippet("const", "const {}", &const_completion_string).add_to(acc, ctx.db);
}
- if let ast::Expr::Literal(literal) = dot_receiver_including_refs.clone()
+ if let ast::Expr::Literal(literal) = dot_receiver.clone()
&& let Some(literal_text) = ast::String::cast(literal.token())
{
add_format_like_completions(acc, ctx, &dot_receiver_including_refs, cap, &literal_text);
@@ -379,14 +388,22 @@ fn escape_snippet_bits(text: &mut String) {
stdx::replace(text, '$', "\\$");
}
+fn receiver_accessor(receiver: &ast::Expr) -> ast::Expr {
+ receiver
+ .syntax()
+ .parent()
+ .and_then(ast::Expr::cast)
+ .filter(|it| {
+ matches!(
+ it,
+ ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_)
+ )
+ })
+ .unwrap_or_else(|| receiver.clone())
+}
+
fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {
let mut resulting_element = initial_element.clone();
-
- while let Some(field_expr) = resulting_element.syntax().parent().and_then(ast::FieldExpr::cast)
- {
- resulting_element = ast::Expr::from(field_expr);
- }
-
let mut prefix = String::new();
let mut found_ref_or_deref = false;
@@ -672,6 +689,87 @@ fn main() {
sn while while expr {}
"#]],
);
+ check(
+ r#"
+fn main() {
+ &baz.l$0
+ res
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn let_tail_block() {
+ check(
+ r#"
+fn main() {
+ baz.l$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ sn while while expr {}
+ "#]],
+ );
+
+ check(
+ r#"
+fn main() {
+ &baz.l$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ sn while while expr {}
+ "#]],
+ );
}
#[test]
@@ -796,6 +894,78 @@ fn main() {
}
#[test]
+ fn match_arm_let_block() {
+ check(
+ r#"
+fn main() {
+ match 2 {
+ bar => bar.$0
+ }
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ "#]],
+ );
+ check(
+ r#"
+fn main() {
+ match 2 {
+ bar => &bar.l$0
+ }
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ "#]],
+ );
+ check_edit(
+ "let",
+ r#"
+fn main() {
+ match 2 {
+ bar => bar.$0
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 2 {
+ bar => {
+ let $1 = bar;
+ $0
+}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
fn option_letelse() {
check_edit(
"lete",
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index cab8bced88..97afd07b00 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -821,7 +821,10 @@ impl<'db> CompletionContext<'db> {
CompleteSemicolon::DoNotComplete
} else if let Some(term_node) =
sema.token_ancestors_with_macros(token.clone()).find(|node| {
- matches!(node.kind(), BLOCK_EXPR | MATCH_ARM | CLOSURE_EXPR | ARG_LIST | PAREN_EXPR)
+ matches!(
+ node.kind(),
+ BLOCK_EXPR | MATCH_ARM | CLOSURE_EXPR | ARG_LIST | PAREN_EXPR | ARRAY_EXPR
+ )
})
{
let next_token = iter::successors(token.next_token(), |it| it.next_token())
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 1c8bc656ca..4b0cc0c7cd 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -1501,7 +1501,7 @@ fn classify_name_ref<'db>(
| SyntaxKind::RECORD_FIELD
)
})
- .and_then(|_| nameref.as_ref()?.syntax().ancestors().find_map(ast::Adt::cast))
+ .and_then(|_| find_node_at_offset::<ast::Adt>(original_file, original_offset))
.and_then(|adt| sema.derive_helpers_in_scope(&adt))
.unwrap_or_default();
Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind, derive_helpers } })
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs
index 4713b1f1af..475e00dfcf 100644
--- a/crates/ide-completion/src/render/function.rs
+++ b/crates/ide-completion/src/render/function.rs
@@ -908,4 +908,23 @@ fn bar() {
"#,
);
}
+
+ #[test]
+ fn no_semicolon_in_array() {
+ check_edit(
+ r#"foo"#,
+ r#"
+fn foo() {}
+fn bar() {
+ let _ = [fo$0];
+}
+"#,
+ r#"
+fn foo() {}
+fn bar() {
+ let _ = [foo()$0];
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index 3701416dfc..131911be91 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -68,7 +68,71 @@ pub struct Foo(#[m$0] i32);
kw crate::
kw self::
"#]],
- )
+ );
+ check(
+ r#"
+//- /mac.rs crate:mac
+#![crate_type = "proc-macro"]
+
+#[proc_macro_derive(MyDerive, attributes(my_cool_helper_attribute))]
+pub fn my_derive() {}
+
+//- /lib.rs crate:lib deps:mac
+#[rustc_builtin_macro]
+pub macro derive($item:item) {}
+
+#[derive(mac::MyDerive)]
+pub struct Foo(#[$0] i32);
+"#,
+ expect![[r#"
+ at allow(…)
+ at automatically_derived
+ at cfg(…)
+ at cfg_attr(…)
+ at cold
+ at deny(…)
+ at deprecated
+ at derive macro derive
+ at derive(…)
+ at diagnostic::do_not_recommend
+ at diagnostic::on_unimplemented
+ at doc = "…"
+ at doc = include_str!("…")
+ at doc(alias = "…")
+ at doc(hidden)
+ at expect(…)
+ at export_name = "…"
+ at forbid(…)
+ at global_allocator
+ at ignore = "…"
+ at inline
+ at link
+ at link_name = "…"
+ at link_section = "…"
+ at macro_export
+ at macro_use
+ at must_use
+ at my_cool_helper_attribute derive helper of `MyDerive`
+ at no_mangle
+ at non_exhaustive
+ at panic_handler
+ at path = "…"
+ at proc_macro
+ at proc_macro_attribute
+ at proc_macro_derive(…)
+ at repr(…)
+ at should_panic
+ at target_feature(enable = "…")
+ at test
+ at track_caller
+ at unsafe(…)
+ at used
+ at warn(…)
+ md mac
+ kw crate::
+ kw self::
+ "#]],
+ );
}
#[test]
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index df39591a33..8e50ef10ec 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -3268,6 +3268,8 @@ fn foo() {
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn deref *expr
+ sn let let
+ sn letm let mut
sn match match expr {}
sn ref &expr
sn refm &mut expr
@@ -3657,3 +3659,38 @@ fn main() {
"#]],
);
}
+
+#[test]
+fn rpitit_with_reference() {
+ check(
+ r#"
+trait Foo {
+ fn foo(&self);
+}
+
+trait Bar {
+ fn bar(&self) -> &impl Foo;
+}
+
+fn baz(v: impl Bar) {
+ v.bar().$0
+}
+ "#,
+ expect![[r#"
+ me foo() (as Foo) fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn const const {}
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn deref *expr
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ sn return return expr
+ sn unsafe unsafe {}
+ "#]],
+ );
+}
diff --git a/crates/ide-completion/src/tests/fn_param.rs b/crates/ide-completion/src/tests/fn_param.rs
index 02cba6b646..d6d73da3f1 100644
--- a/crates/ide-completion/src/tests/fn_param.rs
+++ b/crates/ide-completion/src/tests/fn_param.rs
@@ -293,6 +293,60 @@ fn bar(bar$0) {}
}
#[test]
+fn not_shows_fully_equal_inside_pattern_params() {
+ check(
+ r#"
+fn foo(bar: u32) {}
+fn bar((a, bar$0)) {}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn not_shows_locals_inside_pattern_params() {
+ check(
+ r#"
+fn outer() {
+ let foo = 3;
+ {
+ let bar = 3;
+ |($0)| {};
+ let baz = 3;
+ let qux = 3;
+ }
+ let fez = 3;
+}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ );
+ check(
+ r#"
+fn outer() {
+ let foo = 3;
+ {
+ let bar = 3;
+ fn inner(($0)) {}
+ let baz = 3;
+ let qux = 3;
+ }
+ let fez = 3;
+}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
fn completes_for_params_with_attributes() {
check(
r#"
diff --git a/crates/ide-completion/src/tests/pattern.rs b/crates/ide-completion/src/tests/pattern.rs
index b8728028bb..0d85f2e9ad 100644
--- a/crates/ide-completion/src/tests/pattern.rs
+++ b/crates/ide-completion/src/tests/pattern.rs
@@ -122,7 +122,6 @@ fn foo() {
st Record
st Tuple
st Unit
- ev TupleV
bn Record {…} Record { field$1 }$0
bn Tuple(…) Tuple($1)$0
bn TupleV(…) TupleV($1)$0
@@ -159,8 +158,6 @@ fn foo(foo: Foo) { match foo { Foo { x: $0 } } }
expect![[r#"
en Bar
st Foo
- ev Nil
- ev Value
bn Foo {…} Foo { x$1 }$0
bn Nil Nil$0
bn Value Value$0
@@ -189,7 +186,6 @@ fn foo() {
st Record
st Tuple
st Unit
- ev Variant
bn Record {…} Record { field$1 }$0
bn Tuple(…) Tuple($1)$0
bn Variant Variant$0
@@ -355,6 +351,34 @@ fn func() {
}
#[test]
+fn enum_unqualified() {
+ check_with_base_items(
+ r#"
+use Enum::*;
+fn func() {
+ if let $0 = unknown {}
+}
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ bn Record {…} Record { field$1 }$0
+ bn RecordV {…} RecordV { field$1 }$0
+ bn Tuple(…) Tuple($1)$0
+ bn TupleV(…) TupleV($1)$0
+ bn UnitV UnitV$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
fn completes_in_record_field_pat() {
check(
r#"
diff --git a/crates/ide-completion/src/tests/record.rs b/crates/ide-completion/src/tests/record.rs
index 045b2d03b0..c1274f6640 100644
--- a/crates/ide-completion/src/tests/record.rs
+++ b/crates/ide-completion/src/tests/record.rs
@@ -61,8 +61,6 @@ fn foo(baz: Baz) {
en Baz
en Result
md core
- ev Err
- ev Ok
bn Baz::Bar Baz::Bar$0
bn Baz::Foo Baz::Foo$0
bn Err(…) Err($1)$0
@@ -89,10 +87,6 @@ fn foo(baz: Baz) {
en Baz
en Result
md core
- ev Bar
- ev Err
- ev Foo
- ev Ok
bn Bar Bar$0
bn Err(…) Err($1)$0
bn Foo Foo$0
diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs
index 35579eb259..1c48527027 100644
--- a/crates/ide-db/src/imports/import_assets.rs
+++ b/crates/ide-db/src/imports/import_assets.rs
@@ -9,7 +9,7 @@ use hir::{
};
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
-use smallvec::SmallVec;
+use smallvec::{SmallVec, smallvec};
use syntax::{
AstNode, SyntaxNode,
ast::{self, HasName, make},
@@ -68,6 +68,8 @@ pub struct PathImportCandidate {
pub qualifier: Vec<Name>,
/// The name the item (struct, trait, enum, etc.) should have.
pub name: NameToImport,
+ /// Potentially more segments that should resolve in the candidate.
+ pub after: Vec<Name>,
}
/// A name that will be used during item lookups.
@@ -376,7 +378,7 @@ fn path_applicable_imports(
) -> FxIndexSet<LocatedImport> {
let _p = tracing::info_span!("ImportAssets::path_applicable_imports").entered();
- match &*path_candidate.qualifier {
+ let mut result = match &*path_candidate.qualifier {
[] => {
items_locator::items_with_name(
db,
@@ -433,6 +435,75 @@ fn path_applicable_imports(
})
.take(DEFAULT_QUERY_SEARCH_LIMIT)
.collect(),
+ };
+
+ filter_candidates_by_after_path(db, scope, path_candidate, &mut result);
+
+ result
+}
+
+fn filter_candidates_by_after_path(
+ db: &RootDatabase,
+ scope: &SemanticsScope<'_>,
+ path_candidate: &PathImportCandidate,
+ imports: &mut FxIndexSet<LocatedImport>,
+) {
+ if imports.len() <= 1 {
+ // Short-circuit, as even if it doesn't match fully we want it.
+ return;
+ }
+
+ let Some((last_after, after_except_last)) = path_candidate.after.split_last() else {
+ return;
+ };
+
+ let original_imports = imports.clone();
+
+ let traits_in_scope = scope.visible_traits();
+ imports.retain(|import| {
+ let items = if after_except_last.is_empty() {
+ smallvec![import.original_item]
+ } else {
+ let ItemInNs::Types(ModuleDef::Module(item)) = import.original_item else {
+ return false;
+ };
+ // FIXME: This doesn't consider visibilities.
+ item.resolve_mod_path(db, after_except_last.iter().cloned())
+ .into_iter()
+ .flatten()
+ .collect::<SmallVec<[_; 3]>>()
+ };
+ items.into_iter().any(|item| {
+ let has_last_method = |ty: hir::Type<'_>| {
+ ty.iterate_path_candidates(db, scope, &traits_in_scope, Some(last_after), |_| {
+ Some(())
+ })
+ .is_some()
+ };
+ // FIXME: A trait can have an assoc type that has a function/const, that's two segments before last.
+ match item {
+ // A module? Can we resolve one more segment?
+ ItemInNs::Types(ModuleDef::Module(module)) => module
+ .resolve_mod_path(db, [last_after.clone()])
+ .is_some_and(|mut it| it.any(|_| true)),
+ // And ADT/Type Alias? That might be a method.
+ ItemInNs::Types(ModuleDef::Adt(it)) => has_last_method(it.ty(db)),
+ ItemInNs::Types(ModuleDef::BuiltinType(it)) => has_last_method(it.ty(db)),
+ ItemInNs::Types(ModuleDef::TypeAlias(it)) => has_last_method(it.ty(db)),
+ // A trait? Might have an associated item.
+ ItemInNs::Types(ModuleDef::Trait(it)) => it
+ .items(db)
+ .into_iter()
+ .any(|assoc_item| assoc_item.name(db) == Some(last_after.clone())),
+ // Other items? can't resolve one more segment.
+ _ => false,
+ }
+ })
+ });
+
+ if imports.is_empty() {
+ // Better one half-match than zero full matches.
+ *imports = original_imports;
}
}
@@ -759,10 +830,14 @@ impl<'db> ImportCandidate<'db> {
if sema.resolve_path(path).is_some() {
return None;
}
+ let after = std::iter::successors(path.parent_path(), |it| it.parent_path())
+ .map(|seg| seg.segment()?.name_ref().map(|name| Name::new_root(&name.text())))
+ .collect::<Option<_>>()?;
path_import_candidate(
sema,
path.qualifier(),
NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()),
+ after,
)
}
@@ -777,6 +852,7 @@ impl<'db> ImportCandidate<'db> {
Some(ImportCandidate::Path(PathImportCandidate {
qualifier: vec![],
name: NameToImport::exact_case_sensitive(name.to_string()),
+ after: vec![],
}))
}
@@ -785,7 +861,8 @@ impl<'db> ImportCandidate<'db> {
fuzzy_name: String,
sema: &Semantics<'db, RootDatabase>,
) -> Option<Self> {
- path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name))
+ // Assume a fuzzy match does not want the segments after. Because... I guess why not?
+ path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name), Vec::new())
}
}
@@ -793,6 +870,7 @@ fn path_import_candidate<'db>(
sema: &Semantics<'db, RootDatabase>,
qualifier: Option<ast::Path>,
name: NameToImport,
+ after: Vec<Name>,
) -> Option<ImportCandidate<'db>> {
Some(match qualifier {
Some(qualifier) => match sema.resolve_path(&qualifier) {
@@ -802,7 +880,7 @@ fn path_import_candidate<'db>(
.segments()
.map(|seg| seg.name_ref().map(|name| Name::new_root(&name.text())))
.collect::<Option<Vec<_>>>()?;
- ImportCandidate::Path(PathImportCandidate { qualifier, name })
+ ImportCandidate::Path(PathImportCandidate { qualifier, name, after })
} else {
return None;
}
@@ -826,7 +904,7 @@ fn path_import_candidate<'db>(
}
Some(_) => return None,
},
- None => ImportCandidate::Path(PathImportCandidate { qualifier: vec![], name }),
+ None => ImportCandidate::Path(PathImportCandidate { qualifier: vec![], name, after }),
})
}
diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs
index db1d599d55..da8525d1fb 100644
--- a/crates/ide-db/src/imports/insert_use.rs
+++ b/crates/ide-db/src/imports/insert_use.rs
@@ -9,8 +9,9 @@ use syntax::{
Direction, NodeOrToken, SyntaxKind, SyntaxNode, algo,
ast::{
self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind,
- edit_in_place::Removable, make,
+ edit_in_place::Removable, make, syntax_factory::SyntaxFactory,
},
+ syntax_editor::{Position, SyntaxEditor},
ted,
};
@@ -93,7 +94,7 @@ impl ImportScope {
.item_list()
.map(ImportScopeKind::Module)
.map(|kind| ImportScope { kind, required_cfgs });
- } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax) {
+ } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax.clone()) {
if block.is_none()
&& let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone())
&& let Some(b) = sema.original_ast_node(b)
@@ -104,11 +105,34 @@ impl ImportScope {
.attrs()
.any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg"))
{
- if let Some(b) = block {
- return Some(ImportScope {
- kind: ImportScopeKind::Block(b),
- required_cfgs,
+ if let Some(b) = block.clone() {
+ let current_cfgs = has_attrs.attrs().filter(|attr| {
+ attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
});
+
+ let total_cfgs: Vec<_> =
+ required_cfgs.iter().cloned().chain(current_cfgs).collect();
+
+ let parent = syntax.parent();
+ let mut can_merge = false;
+ if let Some(parent) = parent {
+ can_merge = parent.children().filter_map(ast::Use::cast).any(|u| {
+ let u_attrs = u.attrs().filter(|attr| {
+ attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
+ });
+ crate::imports::merge_imports::eq_attrs(
+ u_attrs,
+ total_cfgs.iter().cloned(),
+ )
+ });
+ }
+
+ if !can_merge {
+ return Some(ImportScope {
+ kind: ImportScopeKind::Block(b),
+ required_cfgs,
+ });
+ }
}
required_cfgs.extend(has_attrs.attrs().filter(|attr| {
attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")
@@ -146,6 +170,17 @@ pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
insert_use_with_alias_option(scope, path, cfg, None);
}
+/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
+pub fn insert_use_with_editor(
+ scope: &ImportScope,
+ path: ast::Path,
+ cfg: &InsertUseConfig,
+ syntax_editor: &mut SyntaxEditor,
+ syntax_factory: &SyntaxFactory,
+) {
+ insert_use_with_alias_option_with_editor(scope, path, cfg, None, syntax_editor, syntax_factory);
+}
+
pub fn insert_use_as_alias(
scope: &ImportScope,
path: ast::Path,
@@ -229,6 +264,71 @@ fn insert_use_with_alias_option(
insert_use_(scope, use_item, cfg.group);
}
+fn insert_use_with_alias_option_with_editor(
+ scope: &ImportScope,
+ path: ast::Path,
+ cfg: &InsertUseConfig,
+ alias: Option<ast::Rename>,
+ syntax_editor: &mut SyntaxEditor,
+ syntax_factory: &SyntaxFactory,
+) {
+ let _p = tracing::info_span!("insert_use_with_alias_option").entered();
+ let mut mb = match cfg.granularity {
+ ImportGranularity::Crate => Some(MergeBehavior::Crate),
+ ImportGranularity::Module => Some(MergeBehavior::Module),
+ ImportGranularity::One => Some(MergeBehavior::One),
+ ImportGranularity::Item => None,
+ };
+ if !cfg.enforce_granularity {
+ let file_granularity = guess_granularity_from_scope(scope);
+ mb = match file_granularity {
+ ImportGranularityGuess::Unknown => mb,
+ ImportGranularityGuess::Item => None,
+ ImportGranularityGuess::Module => Some(MergeBehavior::Module),
+ // We use the user's setting to infer if this is module or item.
+ ImportGranularityGuess::ModuleOrItem => match mb {
+ Some(MergeBehavior::Module) | None => mb,
+ // There isn't really a way to decide between module or item here, so we just pick one.
+ // FIXME: Maybe it is possible to infer based on semantic analysis?
+ Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
+ },
+ ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
+ ImportGranularityGuess::CrateOrModule => match mb {
+ Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
+ Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
+ },
+ ImportGranularityGuess::One => Some(MergeBehavior::One),
+ };
+ }
+
+ let use_tree = syntax_factory.use_tree(path, None, alias, false);
+ if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
+ use_tree.wrap_in_tree_list();
+ }
+ let use_item = make::use_(None, None, use_tree).clone_for_update();
+ for attr in
+ scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
+ {
+ syntax_editor.insert(Position::first_child_of(use_item.syntax()), attr);
+ }
+
+ // merge into existing imports if possible
+ if let Some(mb) = mb {
+ let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
+ for existing_use in
+ scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
+ {
+ if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
+ syntax_editor.replace(existing_use.syntax(), merged.syntax());
+ return;
+ }
+ }
+ }
+ // either we weren't allowed to merge or there is no import that fits the merge conditions
+ // so look for the place we have to insert to
+ insert_use_with_editor_(scope, use_item, cfg.group, syntax_editor, syntax_factory);
+}
+
pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> {
// FIXME: improve this
if path.parent_path().is_some() {
@@ -469,7 +569,9 @@ fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
// skip the curly brace
.skip(l_curly.is_some() as usize)
.take_while(|child| match child {
- NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
+ NodeOrToken::Node(node) => {
+ is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
+ }
NodeOrToken::Token(token) => {
[SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
.contains(&token.kind())
@@ -500,6 +602,129 @@ fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
}
}
+fn insert_use_with_editor_(
+ scope: &ImportScope,
+ use_item: ast::Use,
+ group_imports: bool,
+ syntax_editor: &mut SyntaxEditor,
+ syntax_factory: &SyntaxFactory,
+) {
+ let scope_syntax = scope.as_syntax_node();
+ let insert_use_tree =
+ use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
+ let group = ImportGroup::new(&insert_use_tree);
+ let path_node_iter = scope_syntax
+ .children()
+ .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
+ .flat_map(|(use_, node)| {
+ let tree = use_.use_tree()?;
+ Some((tree, node))
+ });
+
+ if group_imports {
+ // Iterator that discards anything that's not in the required grouping
+ // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
+ let group_iter = path_node_iter
+ .clone()
+ .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
+ .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
+
+ // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
+ let mut last = None;
+ // find the element that would come directly after our new import
+ let post_insert: Option<(_, SyntaxNode)> = group_iter
+ .inspect(|(.., node)| last = Some(node.clone()))
+ .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
+
+ if let Some((.., node)) = post_insert {
+ cov_mark::hit!(insert_group);
+ // insert our import before that element
+ return syntax_editor.insert(Position::before(node), use_item.syntax());
+ }
+ if let Some(node) = last {
+ cov_mark::hit!(insert_group_last);
+ // there is no element after our new import, so append it to the end of the group
+ return syntax_editor.insert(Position::after(node), use_item.syntax());
+ }
+
+ // the group we were looking for actually doesn't exist, so insert
+
+ let mut last = None;
+ // find the group that comes after where we want to insert
+ let post_group = path_node_iter
+ .inspect(|(.., node)| last = Some(node.clone()))
+ .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
+ if let Some((.., node)) = post_group {
+ cov_mark::hit!(insert_group_new_group);
+ syntax_editor.insert(Position::before(&node), use_item.syntax());
+ if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
+ syntax_editor.insert(Position::after(node), syntax_factory.whitespace("\n"));
+ }
+ return;
+ }
+ // there is no such group, so append after the last one
+ if let Some(node) = last {
+ cov_mark::hit!(insert_group_no_group);
+ syntax_editor.insert(Position::after(&node), use_item.syntax());
+ syntax_editor.insert(Position::after(node), syntax_factory.whitespace("\n"));
+ return;
+ }
+ } else {
+ // There exists a group, so append to the end of it
+ if let Some((_, node)) = path_node_iter.last() {
+ cov_mark::hit!(insert_no_grouping_last);
+ syntax_editor.insert(Position::after(node), use_item.syntax());
+ return;
+ }
+ }
+
+ let l_curly = match &scope.kind {
+ ImportScopeKind::File(_) => None,
+ // don't insert the imports before the item list/block expr's opening curly brace
+ ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
+ // don't insert the imports before the item list's opening curly brace
+ ImportScopeKind::Block(block) => block.l_curly_token(),
+ };
+ // there are no imports in this file at all
+ // so put the import after all inner module attributes and possible license header comments
+ if let Some(last_inner_element) = scope_syntax
+ .children_with_tokens()
+ // skip the curly brace
+ .skip(l_curly.is_some() as usize)
+ .take_while(|child| match child {
+ NodeOrToken::Node(node) => {
+ is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
+ }
+ NodeOrToken::Token(token) => {
+ [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
+ .contains(&token.kind())
+ }
+ })
+ .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
+ .last()
+ {
+ cov_mark::hit!(insert_empty_inner_attr);
+ syntax_editor.insert(Position::after(&last_inner_element), use_item.syntax());
+ syntax_editor.insert(Position::after(last_inner_element), syntax_factory.whitespace("\n"));
+ } else {
+ match l_curly {
+ Some(b) => {
+ cov_mark::hit!(insert_empty_module);
+ syntax_editor.insert(Position::after(&b), syntax_factory.whitespace("\n"));
+ syntax_editor.insert(Position::after(&b), use_item.syntax());
+ }
+ None => {
+ cov_mark::hit!(insert_empty_file);
+ syntax_editor.insert(
+ Position::first_child_of(scope_syntax),
+ syntax_factory.whitespace("\n\n"),
+ );
+ syntax_editor.insert(Position::first_child_of(scope_syntax), use_item.syntax());
+ }
+ }
+ }
+}
+
fn is_inner_attribute(node: SyntaxNode) -> bool {
ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
}
diff --git a/crates/ide-db/src/imports/insert_use/tests.rs b/crates/ide-db/src/imports/insert_use/tests.rs
index 3350e1c3d2..6c7b97458d 100644
--- a/crates/ide-db/src/imports/insert_use/tests.rs
+++ b/crates/ide-db/src/imports/insert_use/tests.rs
@@ -1438,3 +1438,156 @@ fn check_guess(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: Import
let file = ImportScope { kind: ImportScopeKind::File(syntax), required_cfgs: vec![] };
assert_eq!(super::guess_granularity_from_scope(&file), expected);
}
+
+#[test]
+fn insert_with_existing_imports_and_cfg_module() {
+ check(
+ "std::fmt",
+ r#"
+use foo::bar;
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ r#"
+use std::fmt;
+
+use foo::bar;
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ ImportGranularity::Crate,
+ );
+}
+
+#[test]
+fn insert_before_cfg_module() {
+ check(
+ "std::fmt",
+ r#"
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ r#"
+use std::fmt;
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ ImportGranularity::Crate,
+ );
+}
+
+fn check_merge(ra_fixture0: &str, ra_fixture1: &str, last: &str, mb: MergeBehavior) {
+ let use0 = ast::SourceFile::parse(ra_fixture0, span::Edition::CURRENT)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let use1 = ast::SourceFile::parse(ra_fixture1, span::Edition::CURRENT)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let result = try_merge_imports(&use0, &use1, mb);
+ assert_eq!(result.map(|u| u.to_string().trim().to_owned()), Some(last.trim().to_owned()));
+}
+
+#[test]
+fn merge_gated_imports() {
+ check_merge(
+ r#"#[cfg(test)] use foo::bar;"#,
+ r#"#[cfg(test)] use foo::baz;"#,
+ r#"#[cfg(test)] use foo::{bar, baz};"#,
+ MergeBehavior::Crate,
+ );
+}
+
+#[test]
+fn merge_gated_imports_with_different_values() {
+ let use0 = ast::SourceFile::parse(r#"#[cfg(a)] use foo::bar;"#, span::Edition::CURRENT)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let use1 = ast::SourceFile::parse(r#"#[cfg(b)] use foo::baz;"#, span::Edition::CURRENT)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let result = try_merge_imports(&use0, &use1, MergeBehavior::Crate);
+ assert_eq!(result, None);
+}
+
+#[test]
+fn merge_gated_imports_different_order() {
+ check_merge(
+ r#"#[cfg(a)] #[cfg(b)] use foo::bar;"#,
+ r#"#[cfg(b)] #[cfg(a)] use foo::baz;"#,
+ r#"#[cfg(a)] #[cfg(b)] use foo::{bar, baz};"#,
+ MergeBehavior::Crate,
+ );
+}
+
+#[test]
+fn merge_into_existing_cfg_import() {
+ check(
+ r#"foo::Foo"#,
+ r#"
+#[cfg(target_os = "windows")]
+use bar::Baz;
+
+#[cfg(target_os = "windows")]
+fn buzz() {
+ Foo$0;
+}
+"#,
+ r#"
+#[cfg(target_os = "windows")]
+use bar::Baz;
+#[cfg(target_os = "windows")]
+use foo::Foo;
+
+#[cfg(target_os = "windows")]
+fn buzz() {
+ Foo;
+}
+"#,
+ ImportGranularity::Crate,
+ );
+}
+
+#[test]
+fn reproduce_user_issue_missing_semicolon() {
+ check(
+ "std::fmt",
+ r#"
+use {
+ foo
+}
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ r#"
+use std::fmt;
+
+use {
+ foo
+}
+
+#[cfg(target_arch = "x86_64")]
+pub mod api;
+"#,
+ ImportGranularity::Crate,
+ );
+}
diff --git a/crates/ide-db/src/imports/merge_imports.rs b/crates/ide-db/src/imports/merge_imports.rs
index 635ed7368c..3301719f5c 100644
--- a/crates/ide-db/src/imports/merge_imports.rs
+++ b/crates/ide-db/src/imports/merge_imports.rs
@@ -4,7 +4,7 @@ use std::cmp::Ordering;
use itertools::{EitherOrBoth, Itertools};
use parser::T;
use syntax::{
- Direction, SyntaxElement, algo,
+ Direction, SyntaxElement, ToSmolStr, algo,
ast::{
self, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind, edit_in_place::Removable,
make,
@@ -691,14 +691,12 @@ pub fn eq_attrs(
attrs0: impl Iterator<Item = ast::Attr>,
attrs1: impl Iterator<Item = ast::Attr>,
) -> bool {
- // FIXME order of attributes should not matter
- let attrs0 = attrs0
- .flat_map(|attr| attr.syntax().descendants_with_tokens())
- .flat_map(|it| it.into_token());
- let attrs1 = attrs1
- .flat_map(|attr| attr.syntax().descendants_with_tokens())
- .flat_map(|it| it.into_token());
- stdx::iter_eq_by(attrs0, attrs1, |tok, tok2| tok.text() == tok2.text())
+ let mut attrs0: Vec<_> = attrs0.map(|attr| attr.syntax().text().to_smolstr()).collect();
+ let mut attrs1: Vec<_> = attrs1.map(|attr| attr.syntax().text().to_smolstr()).collect();
+ attrs0.sort_unstable();
+ attrs1.sort_unstable();
+
+ attrs0 == attrs1
}
fn path_is_self(path: &ast::Path) -> bool {
diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs
index 48305c2082..01a326a0dc 100644
--- a/crates/ide-db/src/path_transform.rs
+++ b/crates/ide-db/src/path_transform.rs
@@ -553,6 +553,39 @@ impl Ctx<'_> {
return None;
}
+ // Similarly, modules cannot be used in pattern position.
+ if matches!(def, hir::ModuleDef::Module(_)) {
+ return None;
+ }
+
+ if matches!(
+ def,
+ hir::ModuleDef::Function(_)
+ | hir::ModuleDef::Trait(_)
+ | hir::ModuleDef::TypeAlias(_)
+ ) {
+ return None;
+ }
+
+ if let hir::ModuleDef::Adt(adt) = def {
+ match adt {
+ hir::Adt::Struct(s)
+ if s.kind(self.source_scope.db) != hir::StructKind::Unit =>
+ {
+ return None;
+ }
+ hir::Adt::Union(_) => return None,
+ hir::Adt::Enum(_) => return None,
+ _ => (),
+ }
+ }
+
+ if let hir::ModuleDef::Variant(v) = def
+ && v.kind(self.source_scope.db) != hir::StructKind::Unit
+ {
+ return None;
+ }
+
let cfg = FindPathConfig {
prefer_no_std: false,
prefer_prelude: true,
@@ -632,3 +665,87 @@ fn find_trait_for_assoc_item(
None
}
+
+#[cfg(test)]
+mod tests {
+ use crate::RootDatabase;
+ use crate::path_transform::PathTransform;
+ use hir::Semantics;
+ use syntax::{AstNode, ast::HasName};
+ use test_fixture::WithFixture;
+ use test_utils::assert_eq_text;
+
+ #[test]
+ fn test_transform_ident_pat() {
+ let (db, file_id) = RootDatabase::with_single_file(
+ r#"
+mod foo {
+ pub struct UnitStruct;
+ pub struct RecordStruct {}
+ pub enum Enum { UnitVariant, RecordVariant {} }
+ pub fn function() {}
+ pub const CONST: i32 = 0;
+ pub static STATIC: i32 = 0;
+ pub type Alias = i32;
+ pub union Union { f: i32 }
+}
+
+mod bar {
+ fn anchor() {}
+}
+
+fn main() {
+ use foo::*;
+ use foo::Enum::*;
+ let UnitStruct = ();
+ let RecordStruct = ();
+ let Enum = ();
+ let UnitVariant = ();
+ let RecordVariant = ();
+ let function = ();
+ let CONST = ();
+ let STATIC = ();
+ let Alias = ();
+ let Union = ();
+}
+"#,
+ );
+ let sema = Semantics::new(&db);
+ let source_file = sema.parse(file_id);
+
+ let function = source_file
+ .syntax()
+ .descendants()
+ .filter_map(syntax::ast::Fn::cast)
+ .find(|it| it.name().unwrap().text() == "main")
+ .unwrap();
+ let source_scope = sema.scope(function.body().unwrap().syntax()).unwrap();
+
+ let anchor = source_file
+ .syntax()
+ .descendants()
+ .filter_map(syntax::ast::Fn::cast)
+ .find(|it| it.name().unwrap().text() == "anchor")
+ .unwrap();
+ let target_scope = sema.scope(anchor.body().unwrap().syntax()).unwrap();
+
+ let transform = PathTransform::generic_transformation(&target_scope, &source_scope);
+ let transformed = transform.apply(function.body().unwrap().syntax());
+
+ let expected = r#"{
+ use crate::foo::*;
+ use crate::foo::Enum::*;
+ let crate::foo::UnitStruct = ();
+ let RecordStruct = ();
+ let Enum = ();
+ let crate::foo::Enum::UnitVariant = ();
+ let RecordVariant = ();
+ let function = ();
+ let crate::foo::CONST = ();
+ let crate::foo::STATIC = ();
+ let Alias = ();
+ let Union = ();
+}"#;
+ assert_eq_text!(expected, &transformed.to_string());
+ }
+}
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index 1d865892a2..4196a13aa3 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -1370,7 +1370,7 @@ fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool {
}
fn is_name_ref_in_test(sema: &Semantics<'_, RootDatabase>, name_ref: &ast::NameRef) -> bool {
- name_ref.syntax().ancestors().any(|node| match ast::Fn::cast(node) {
+ sema.ancestors_with_macros(name_ref.syntax().clone()).any(|node| match ast::Fn::cast(node) {
Some(it) => sema.to_def(&it).is_some_and(|func| func.is_test(sema.db)),
None => false,
})
diff --git a/crates/ide-db/src/ty_filter.rs b/crates/ide-db/src/ty_filter.rs
index 095256d829..b5c43b3b36 100644
--- a/crates/ide-db/src/ty_filter.rs
+++ b/crates/ide-db/src/ty_filter.rs
@@ -19,8 +19,16 @@ pub enum TryEnum {
impl TryEnum {
const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result];
- /// Returns `Some(..)` if the provided type is an enum that implements `std::ops::Try`.
+ /// Returns `Some(..)` if the provided `ty.strip_references()` is an enum that implements `std::ops::Try`.
pub fn from_ty(sema: &Semantics<'_, RootDatabase>, ty: &hir::Type<'_>) -> Option<TryEnum> {
+ Self::from_ty_without_strip(sema, &ty.strip_references())
+ }
+
+ /// Returns `Some(..)` if the provided type is an enum that implements `std::ops::Try`.
+ pub fn from_ty_without_strip(
+ sema: &Semantics<'_, RootDatabase>,
+ ty: &hir::Type<'_>,
+ ) -> Option<TryEnum> {
let enum_ = match ty.as_adt() {
Some(hir::Adt::Enum(it)) => it,
_ => return None,
diff --git a/crates/ide-diagnostics/src/handlers/invalid_cast.rs b/crates/ide-diagnostics/src/handlers/invalid_cast.rs
index 7479f8147d..405d8df685 100644
--- a/crates/ide-diagnostics/src/handlers/invalid_cast.rs
+++ b/crates/ide-diagnostics/src/handlers/invalid_cast.rs
@@ -517,11 +517,13 @@ trait Trait<'a> {}
fn add_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send) {
x as _
+ //^^^^^^ error: cannot add auto trait to dyn bound via pointer cast
}
// (to test diagnostic list formatting)
fn add_multiple_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send + Sync + Unpin) {
x as _
+ //^^^^^^ error: cannot add auto trait to dyn bound via pointer cast
}
"#,
);
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 3c3ac9d3bb..3890bcad7f 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -95,6 +95,13 @@ pub(crate) fn goto_definition(
continue;
}
+ let parent = token.value.parent()?;
+
+ if let Some(question_mark_conversion) = goto_question_mark_conversions(sema, &parent) {
+ navs.extend(def_to_nav(sema, question_mark_conversion.into()));
+ continue;
+ }
+
if let Some(token) = ast::String::cast(token.value.clone())
&& let Some(original_token) = ast::String::cast(original_token.clone())
&& let Some((analysis, fixture_analysis)) =
@@ -113,8 +120,6 @@ pub(crate) fn goto_definition(
});
}
- let parent = token.value.parent()?;
-
let token_file_id = token.file_id;
if let Some(token) = ast::String::cast(token.value.clone())
&& let Some(x) =
@@ -149,6 +154,45 @@ pub(crate) fn goto_definition(
Some(RangeInfo::new(original_token.text_range(), navs))
}
+/// When the `?` operator is used on `Result`, go to the `From` impl if it exists as this provides more value.
+fn goto_question_mark_conversions(
+ sema: &Semantics<'_, RootDatabase>,
+ node: &SyntaxNode,
+) -> Option<hir::Function> {
+ let node = ast::TryExpr::cast(node.clone())?;
+ let try_expr_ty = sema.type_of_expr(&node.expr()?)?.adjusted();
+
+ let fd = FamousDefs(sema, try_expr_ty.krate(sema.db));
+ let result_enum = fd.core_result_Result()?.into();
+
+ let (try_expr_ty_adt, try_expr_ty_args) = try_expr_ty.as_adt_with_args()?;
+ if try_expr_ty_adt != result_enum {
+ // FIXME: Support `Poll<Result>`.
+ return None;
+ }
+ let original_err_ty = try_expr_ty_args.get(1)?.clone()?;
+
+ let returned_ty = sema.try_expr_returned_type(&node)?;
+ let (returned_adt, returned_ty_args) = returned_ty.as_adt_with_args()?;
+ if returned_adt != result_enum {
+ return None;
+ }
+ let returned_err_ty = returned_ty_args.get(1)?.clone()?;
+
+ if returned_err_ty.could_unify_with_deeply(sema.db, &original_err_ty) {
+ return None;
+ }
+
+ let from_trait = fd.core_convert_From()?;
+ let from_fn = from_trait.function(sema.db, sym::from)?;
+ sema.resolve_trait_impl_method(
+ returned_err_ty.clone(),
+ from_trait,
+ from_fn,
+ [returned_err_ty, original_err_ty],
+ )
+}
+
// If the token is into(), try_into(), search the definition of From, TryFrom.
fn find_definition_for_known_blanket_dual_impls(
sema: &Semantics<'_, RootDatabase>,
@@ -4034,4 +4078,25 @@ where
"#,
)
}
+
+ #[test]
+ fn question_mark_on_result_goes_to_conversion() {
+ check(
+ r#"
+//- minicore: try, result, from
+
+struct Foo;
+struct Bar;
+impl From<Foo> for Bar {
+ fn from(_: Foo) -> Bar { Bar }
+ // ^^^^
+}
+
+fn foo() -> Result<(), Bar> {
+ Err(Foo)?$0;
+ Ok(())
+}
+ "#,
+ );
+ }
}
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 7900a0dc99..7fbbc576dd 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -9720,6 +9720,99 @@ fn test_hover_function_with_pat_param() {
}
#[test]
+fn test_hover_function_with_too_long_param() {
+ check(
+ r#"
+fn fn_$0(
+ attrs: impl IntoIterator<Item = ast::Attr>,
+ visibility: Option<ast::Visibility>,
+ fn_name: ast::Name,
+ type_params: Option<ast::GenericParamList>,
+ where_clause: Option<ast::WhereClause>,
+ params: ast::ParamList,
+ body: ast::BlockExpr,
+ ret_type: Option<ast::RetType>,
+ is_async: bool,
+ is_const: bool,
+ is_unsafe: bool,
+ is_gen: bool,
+) -> ast::Fn {}
+ "#,
+ expect![[r#"
+ *fn_*
+
+ ```rust
+ ra_test_fixture
+ ```
+
+ ```rust
+ fn fn_(
+ attrs: impl IntoIterator<Item = ast::Attr>,
+ visibility: Option<ast::Visibility>,
+ fn_name: ast::Name,
+ type_params: Option<ast::GenericParamList>,
+ where_clause: Option<ast::WhereClause>,
+ params: ast::ParamList,
+ body: ast::BlockExpr,
+ ret_type: Option<ast::RetType>,
+ is_async: bool,
+ is_const: bool,
+ is_unsafe: bool,
+ is_gen: bool
+ ) -> ast::Fn
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+fn fn_$0(
+ &self,
+ attrs: impl IntoIterator<Item = ast::Attr>,
+ visibility: Option<ast::Visibility>,
+ fn_name: ast::Name,
+ type_params: Option<ast::GenericParamList>,
+ where_clause: Option<ast::WhereClause>,
+ params: ast::ParamList,
+ body: ast::BlockExpr,
+ ret_type: Option<ast::RetType>,
+ is_async: bool,
+ is_const: bool,
+ is_unsafe: bool,
+ is_gen: bool,
+ ...
+) -> ast::Fn {}
+ "#,
+ expect![[r#"
+ *fn_*
+
+ ```rust
+ ra_test_fixture
+ ```
+
+ ```rust
+ fn fn_(
+ &self,
+ attrs: impl IntoIterator<Item = ast::Attr>,
+ visibility: Option<ast::Visibility>,
+ fn_name: ast::Name,
+ type_params: Option<ast::GenericParamList>,
+ where_clause: Option<ast::WhereClause>,
+ params: ast::ParamList,
+ body: ast::BlockExpr,
+ ret_type: Option<ast::RetType>,
+ is_async: bool,
+ is_const: bool,
+ is_unsafe: bool,
+ is_gen: bool,
+ ...
+ ) -> ast::Fn
+ ```
+ "#]],
+ );
+}
+
+#[test]
fn hover_path_inside_block_scope() {
check(
r#"
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 2e618550f9..930eaf2262 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -67,7 +67,7 @@ use ide_db::{
FxHashMap, FxIndexSet, LineIndexDatabase,
base_db::{
CrateOrigin, CrateWorkspaceData, Env, FileSet, RootQueryDb, SourceDatabase, VfsPath,
- salsa::{CancellationToken, Cancelled, Database},
+ salsa::{Cancelled, Database},
},
prime_caches, symbol_index,
};
@@ -947,10 +947,6 @@ impl Analysis {
// We use `attach_db_allow_change()` and not `attach_db()` because fixture injection can change the database.
hir::attach_db_allow_change(&self.db, || Cancelled::catch(|| f(&self.db)))
}
-
- pub fn cancellation_token(&self) -> CancellationToken {
- self.db.cancellation_token()
- }
}
#[test]
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 5443021988..102eb91b74 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -513,26 +513,32 @@ fn test() {
}
#[test]
- fn test_access() {
+ fn exclude_tests_macro_refs() {
check(
r#"
-struct S { f$0: u32 }
+macro_rules! my_macro {
+ ($e:expr) => { $e };
+}
+
+fn foo$0() -> i32 { 42 }
+
+fn bar() {
+ foo();
+}
#[test]
-fn test() {
- let mut x = S { f: 92 };
- x.f = 92;
+fn t2() {
+ my_macro!(foo());
}
"#,
expect![[r#"
- f Field FileId(0) 11..17 11..12
+ foo Function FileId(0) 52..74 55..58
- FileId(0) 61..62 read test
- FileId(0) 76..77 write test
+ FileId(0) 91..94
+ FileId(0) 133..136 test
"#]],
);
}
-
#[test]
fn test_struct_literal_after_space() {
check(
@@ -2073,6 +2079,7 @@ fn func() {}
expect![[r#"
identity Attribute FileId(1) 1..107 32..40
+ FileId(0) 17..25 import
FileId(0) 43..51
"#]],
);
@@ -2103,6 +2110,7 @@ mirror$0! {}
expect![[r#"
mirror ProcMacro FileId(1) 1..77 22..28
+ FileId(0) 17..23 import
FileId(0) 26..32
"#]],
)
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index f86974b4ec..9ab07565e9 100644
--- a/crates/ide/src/signature_help.rs
+++ b/crates/ide/src/signature_help.rs
@@ -1975,8 +1975,8 @@ trait Sub: Super + Super {
fn f() -> impl Sub<$0
"#,
expect![[r#"
- trait Sub<SuperTy = …, SubTy = …>
- ^^^^^^^^^^^ ---------
+ trait Sub<SubTy = …, SuperTy = …>
+ ^^^^^^^^^ -----------
"#]],
);
}
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
index 59612634fd..740a6272a7 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
@@ -41,7 +41,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
</style>
-<pre><code><span class="keyword">use</span> <span class="crate_root library">proc_macros</span><span class="operator">::</span><span class="brace">{</span><span class="function library">mirror</span><span class="comma">,</span> <span class="function library">identity</span><span class="comma">,</span> <span class="derive library">DeriveIdentity</span><span class="brace">}</span><span class="semicolon">;</span>
+<pre><code><span class="keyword">use</span> <span class="crate_root library">proc_macros</span><span class="operator">::</span><span class="brace">{</span><span class="proc_macro library">mirror</span><span class="comma">,</span> <span class="attribute library">identity</span><span class="comma">,</span> <span class="derive library">DeriveIdentity</span><span class="brace">}</span><span class="semicolon">;</span>
+<span class="keyword">use</span> <span class="crate_root library">pm</span><span class="operator">::</span><span class="attribute library">proc_macro</span><span class="semicolon">;</span>
<span class="proc_macro library">mirror</span><span class="macro_bang">!</span> <span class="brace">{</span>
<span class="brace macro proc_macro">{</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 8b529cf10f..c6aebd0b0c 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -55,8 +55,9 @@ fn macros() {
r#"
//- proc_macros: mirror, identity, derive_identity
//- minicore: fmt, include, concat
-//- /lib.rs crate:lib
+//- /lib.rs crate:lib deps:pm
use proc_macros::{mirror, identity, DeriveIdentity};
+use pm::proc_macro;
mirror! {
{
@@ -126,6 +127,11 @@ fn main() {
//- /foo/foo.rs crate:foo
mod foo {}
use self::foo as bar;
+//- /pm.rs crate:pm
+#![crate_type = "proc-macro"]
+
+#[proc_macro_attribute]
+pub fn proc_macro() {}
"#,
expect_file!["./test_data/highlight_macros.html"],
false,
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index c2935d94a8..b8ce3a8da4 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -26,10 +26,7 @@ use ide_db::{
use itertools::Itertools;
use proc_macro_api::{
MacroDylib, ProcMacroClient,
- bidirectional_protocol::{
- msg::{SubRequest, SubResponse},
- reject_subrequests,
- },
+ bidirectional_protocol::msg::{ParentSpan, SubRequest, SubResponse},
};
use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace};
use span::{Span, SpanAnchor, SyntaxContext};
@@ -446,7 +443,7 @@ pub fn load_proc_macro(
) -> ProcMacroLoadResult {
let res: Result<Vec<_>, _> = (|| {
let dylib = MacroDylib::new(path.to_path_buf());
- let vec = server.load_dylib(dylib, Some(&reject_subrequests)).map_err(|e| {
+ let vec = server.load_dylib(dylib).map_err(|e| {
ProcMacroLoadingError::ProcMacroSrvError(format!("{e}").into_boxed_str())
})?;
if vec.is_empty() {
@@ -615,6 +612,91 @@ impl ProcMacroExpander for Expander {
Ok(SubResponse::ByteRangeResult { range: range.range.into() })
}
+ SubRequest::SpanSource { file_id, ast_id, start, end, ctx } => {
+ let span = Span {
+ range: TextRange::new(TextSize::from(start), TextSize::from(end)),
+ anchor: SpanAnchor {
+ file_id: span::EditionedFileId::from_raw(file_id),
+ ast_id: span::ErasedFileAstId::from_raw(ast_id),
+ },
+ // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen,
+ // but that will be their problem.
+ ctx: unsafe { SyntaxContext::from_u32(ctx) },
+ };
+
+ let mut current_span = span;
+ let mut current_ctx = span.ctx;
+
+ while let Some(macro_call_id) = current_ctx.outer_expn(db) {
+ let macro_call_loc = db.lookup_intern_macro_call(macro_call_id.into());
+
+ let call_site_file = macro_call_loc.kind.file_id();
+
+ let resolved = db.resolve_span(current_span);
+
+ current_ctx = macro_call_loc.ctxt;
+ current_span = Span {
+ range: resolved.range,
+ anchor: SpanAnchor {
+ file_id: resolved.file_id.editioned_file_id(db),
+ ast_id: span::ROOT_ERASED_FILE_AST_ID,
+ },
+ ctx: current_ctx,
+ };
+
+ if call_site_file.file_id().is_some() {
+ break;
+ }
+ }
+
+ let resolved = db.resolve_span(current_span);
+
+ Ok(SubResponse::SpanSourceResult {
+ file_id: resolved.file_id.editioned_file_id(db).as_u32(),
+ ast_id: span::ROOT_ERASED_FILE_AST_ID.into_raw(),
+ start: u32::from(resolved.range.start()),
+ end: u32::from(resolved.range.end()),
+ ctx: current_span.ctx.into_u32(),
+ })
+ }
+ SubRequest::SpanParent { file_id, ast_id, start, end, ctx } => {
+ let span = Span {
+ range: TextRange::new(TextSize::from(start), TextSize::from(end)),
+ anchor: SpanAnchor {
+ file_id: span::EditionedFileId::from_raw(file_id),
+ ast_id: span::ErasedFileAstId::from_raw(ast_id),
+ },
+ // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen,
+ // but that will be their problem.
+ ctx: unsafe { SyntaxContext::from_u32(ctx) },
+ };
+
+ if let Some(macro_call_id) = span.ctx.outer_expn(db) {
+ let macro_call_loc = db.lookup_intern_macro_call(macro_call_id.into());
+
+ let call_site_file = macro_call_loc.kind.file_id();
+ let call_site_ast_id = macro_call_loc.kind.erased_ast_id();
+
+ if let Some(editioned_file_id) = call_site_file.file_id() {
+ let range = db
+ .ast_id_map(editioned_file_id.into())
+ .get_erased(call_site_ast_id)
+ .text_range();
+
+ let parent_span = Some(ParentSpan {
+ file_id: editioned_file_id.editioned_file_id(db).as_u32(),
+ ast_id: span::ROOT_ERASED_FILE_AST_ID.into_raw(),
+ start: u32::from(range.start()),
+ end: u32::from(range.end()),
+ ctx: macro_call_loc.ctxt.into_u32(),
+ });
+
+ return Ok(SubResponse::SpanParentResult { parent_span });
+ }
+ }
+
+ Ok(SubResponse::SpanParentResult { parent_span: None })
+ }
};
match self.0.expand(
subtree.view(),
diff --git a/crates/proc-macro-api/Cargo.toml b/crates/proc-macro-api/Cargo.toml
index 4de1a3e5dd..a135a469e8 100644
--- a/crates/proc-macro-api/Cargo.toml
+++ b/crates/proc-macro-api/Cargo.toml
@@ -31,6 +31,7 @@ span = { path = "../span", version = "0.0.0", default-features = false}
intern.workspace = true
postcard.workspace = true
semver.workspace = true
+rayon.workspace = true
[features]
sysroot-abi = ["proc-macro-srv", "proc-macro-srv/sysroot-abi"]
diff --git a/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/crates/proc-macro-api/src/bidirectional_protocol/msg.rs
index 3f0422dc5b..ab4bed81e6 100644
--- a/crates/proc-macro-api/src/bidirectional_protocol/msg.rs
+++ b/crates/proc-macro-api/src/bidirectional_protocol/msg.rs
@@ -21,6 +21,8 @@ pub enum SubRequest {
LocalFilePath { file_id: u32 },
LineColumn { file_id: u32, ast_id: u32, offset: u32 },
ByteRange { file_id: u32, ast_id: u32, start: u32, end: u32 },
+ SpanSource { file_id: u32, ast_id: u32, start: u32, end: u32, ctx: u32 },
+ SpanParent { file_id: u32, ast_id: u32, start: u32, end: u32, ctx: u32 },
}
#[derive(Debug, Serialize, Deserialize)]
@@ -42,12 +44,31 @@ pub enum SubResponse {
ByteRangeResult {
range: Range<usize>,
},
+ SpanSourceResult {
+ file_id: u32,
+ ast_id: u32,
+ start: u32,
+ end: u32,
+ ctx: u32,
+ },
+ SpanParentResult {
+ parent_span: Option<ParentSpan>,
+ },
Cancel {
reason: String,
},
}
#[derive(Debug, Serialize, Deserialize)]
+pub struct ParentSpan {
+ pub file_id: u32,
+ pub ast_id: u32,
+ pub start: u32,
+ pub end: u32,
+ pub ctx: u32,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
pub enum BidirectionalMessage {
Request(Request),
Response(Response),
diff --git a/crates/proc-macro-api/src/lib.rs b/crates/proc-macro-api/src/lib.rs
index e4b121b033..e83ddb8594 100644
--- a/crates/proc-macro-api/src/lib.rs
+++ b/crates/proc-macro-api/src/lib.rs
@@ -10,7 +10,7 @@
feature = "sysroot-abi",
feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)
)]
-#![allow(internal_features)]
+#![allow(internal_features, unused_features)]
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
#[cfg(feature = "in-rust-tree")]
@@ -198,12 +198,8 @@ impl ProcMacroClient {
}
/// Loads a proc-macro dylib into the server process returning a list of `ProcMacro`s loaded.
- pub fn load_dylib(
- &self,
- dylib: MacroDylib,
- callback: Option<SubCallback<'_>>,
- ) -> Result<Vec<ProcMacro>, ServerError> {
- self.pool.load_dylib(&dylib, callback)
+ pub fn load_dylib(&self, dylib: MacroDylib) -> Result<Vec<ProcMacro>, ServerError> {
+ self.pool.load_dylib(&dylib)
}
/// Checks if the proc-macro server has exited.
diff --git a/crates/proc-macro-api/src/pool.rs b/crates/proc-macro-api/src/pool.rs
index a637bc0e48..e6541823da 100644
--- a/crates/proc-macro-api/src/pool.rs
+++ b/crates/proc-macro-api/src/pool.rs
@@ -1,10 +1,9 @@
//! A pool of proc-macro server processes
use std::sync::Arc;
-use crate::{
- MacroDylib, ProcMacro, ServerError, bidirectional_protocol::SubCallback,
- process::ProcMacroServerProcess,
-};
+use rayon::iter::{IntoParallelIterator, ParallelIterator};
+
+use crate::{MacroDylib, ProcMacro, ServerError, process::ProcMacroServerProcess};
#[derive(Debug, Clone)]
pub(crate) struct ProcMacroServerPool {
@@ -50,11 +49,7 @@ impl ProcMacroServerPool {
})
}
- pub(crate) fn load_dylib(
- &self,
- dylib: &MacroDylib,
- callback: Option<SubCallback<'_>>,
- ) -> Result<Vec<ProcMacro>, ServerError> {
+ pub(crate) fn load_dylib(&self, dylib: &MacroDylib) -> Result<Vec<ProcMacro>, ServerError> {
let _span = tracing::info_span!("ProcMacroServer::load_dylib").entered();
let dylib_path = Arc::new(dylib.path.clone());
@@ -64,14 +59,17 @@ impl ProcMacroServerPool {
let (first, rest) = self.workers.split_first().expect("worker pool must not be empty");
let macros = first
- .find_proc_macros(&dylib.path, callback)?
+ .find_proc_macros(&dylib.path)?
.map_err(|e| ServerError { message: e, io: None })?;
- for worker in rest {
- worker
- .find_proc_macros(&dylib.path, callback)?
- .map_err(|e| ServerError { message: e, io: None })?;
- }
+ rest.into_par_iter()
+ .map(|worker| {
+ worker
+ .find_proc_macros(&dylib.path)?
+ .map(|_| ())
+ .map_err(|e| ServerError { message: e, io: None })
+ })
+ .collect::<Result<(), _>>()?;
Ok(macros
.into_iter()
diff --git a/crates/proc-macro-api/src/process.rs b/crates/proc-macro-api/src/process.rs
index 9f80880965..80e4ed05c3 100644
--- a/crates/proc-macro-api/src/process.rs
+++ b/crates/proc-macro-api/src/process.rs
@@ -18,7 +18,11 @@ use stdx::JodChild;
use crate::{
ProcMacro, ProcMacroKind, ProtocolFormat, ServerError,
- bidirectional_protocol::{self, SubCallback, msg::BidirectionalMessage, reject_subrequests},
+ bidirectional_protocol::{
+ self, SubCallback,
+ msg::{BidirectionalMessage, SubResponse},
+ reject_subrequests,
+ },
legacy_protocol::{self, SpanMode},
version,
};
@@ -207,14 +211,18 @@ impl ProcMacroServerProcess {
pub(crate) fn find_proc_macros(
&self,
dylib_path: &AbsPath,
- callback: Option<SubCallback<'_>>,
) -> Result<Result<Vec<(String, ProcMacroKind)>, String>, ServerError> {
match self.protocol {
Protocol::LegacyJson { .. } => legacy_protocol::find_proc_macros(self, dylib_path),
Protocol::BidirectionalPostcardPrototype { .. } => {
- let cb = callback.expect("callback required for bidirectional protocol");
- bidirectional_protocol::find_proc_macros(self, dylib_path, cb)
+ bidirectional_protocol::find_proc_macros(self, dylib_path, &|_| {
+ Ok(SubResponse::Cancel {
+ reason: String::from(
+ "Server should not do a sub request when loading proc-macros",
+ ),
+ })
+ })
}
}
}
diff --git a/crates/proc-macro-srv-cli/src/main_loop.rs b/crates/proc-macro-srv-cli/src/main_loop.rs
index 9be3199a38..c525ed848b 100644
--- a/crates/proc-macro-srv-cli/src/main_loop.rs
+++ b/crates/proc-macro-srv-cli/src/main_loop.rs
@@ -273,6 +273,76 @@ impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle<'_> {
other => handle_failure(other),
}
}
+
+ fn span_source(
+ &mut self,
+ proc_macro_srv::span::Span { range, anchor, ctx }: proc_macro_srv::span::Span,
+ ) -> proc_macro_srv::span::Span {
+ match self.roundtrip(bidirectional::SubRequest::SpanSource {
+ file_id: anchor.file_id.as_u32(),
+ ast_id: anchor.ast_id.into_raw(),
+ start: range.start().into(),
+ end: range.end().into(),
+ ctx: ctx.into_u32(),
+ }) {
+ Ok(bidirectional::SubResponse::SpanSourceResult {
+ file_id,
+ ast_id,
+ start,
+ end,
+ ctx,
+ }) => {
+ proc_macro_srv::span::Span {
+ range: proc_macro_srv::span::TextRange::new(
+ proc_macro_srv::span::TextSize::new(start),
+ proc_macro_srv::span::TextSize::new(end),
+ ),
+ anchor: proc_macro_srv::span::SpanAnchor {
+ file_id: proc_macro_srv::span::EditionedFileId::from_raw(file_id),
+ ast_id: proc_macro_srv::span::ErasedFileAstId::from_raw(ast_id),
+ },
+ // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen,
+ // but that will be their problem.
+ ctx: unsafe { proc_macro_srv::span::SyntaxContext::from_u32(ctx) },
+ }
+ }
+ other => handle_failure(other),
+ }
+ }
+
+ fn span_parent(
+ &mut self,
+ proc_macro_srv::span::Span { range, anchor, ctx }: proc_macro_srv::span::Span,
+ ) -> Option<proc_macro_srv::span::Span> {
+ let response = self.roundtrip(bidirectional::SubRequest::SpanParent {
+ file_id: anchor.file_id.as_u32(),
+ ast_id: anchor.ast_id.into_raw(),
+ start: range.start().into(),
+ end: range.end().into(),
+ ctx: ctx.into_u32(),
+ });
+
+ match response {
+ Ok(bidirectional::SubResponse::SpanParentResult { parent_span }) => {
+ parent_span.map(|bidirectional::ParentSpan { file_id, ast_id, start, end, ctx }| {
+ proc_macro_srv::span::Span {
+ range: proc_macro_srv::span::TextRange::new(
+ proc_macro_srv::span::TextSize::new(start),
+ proc_macro_srv::span::TextSize::new(end),
+ ),
+ anchor: proc_macro_srv::span::SpanAnchor {
+ file_id: proc_macro_srv::span::EditionedFileId::from_raw(file_id),
+ ast_id: proc_macro_srv::span::ErasedFileAstId::from_raw(ast_id),
+ },
+ // SAFETY: spans originate from the server. If the protocol is violated,
+ // undefined behavior is the caller’s responsibility.
+ ctx: unsafe { proc_macro_srv::span::SyntaxContext::from_u32(ctx) },
+ }
+ })
+ }
+ other => handle_failure(other),
+ }
+ }
}
fn handle_expand_ra(
diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs
index c548dc620a..734cb4ecc1 100644
--- a/crates/proc-macro-srv/src/lib.rs
+++ b/crates/proc-macro-srv/src/lib.rs
@@ -18,7 +18,8 @@
internal_features,
clippy::disallowed_types,
clippy::print_stderr,
- unused_crate_dependencies
+ unused_crate_dependencies,
+ unused_features
)]
#![deny(deprecated_safe, clippy::undocumented_unsafe_blocks)]
@@ -120,6 +121,8 @@ pub trait ProcMacroClientInterface {
fn line_column(&mut self, span: Span) -> Option<(u32, u32)>;
fn byte_range(&mut self, span: Span) -> Range<usize>;
+ fn span_source(&mut self, span: Span) -> Span;
+ fn span_parent(&mut self, span: Span) -> Option<Span>;
}
const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
diff --git a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
index c114d52ec3..6b6bfcc934 100644
--- a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
+++ b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
@@ -62,8 +62,9 @@ impl server::Server for RaSpanServer<'_> {
self.tracked_paths.insert(path.into());
}
- fn literal_from_str(&mut self, s: &str) -> Result<Literal<Self::Span>, ()> {
+ fn literal_from_str(&mut self, s: &str) -> Result<Literal<Self::Span>, String> {
literal_from_str(s, self.call_site)
+ .map_err(|()| "cannot parse string into literal".to_string())
}
fn emit_diagnostic(&mut self, _: Diagnostic<Self::Span>) {
@@ -81,14 +82,9 @@ impl server::Server for RaSpanServer<'_> {
fn ts_is_empty(&mut self, stream: &Self::TokenStream) -> bool {
stream.is_empty()
}
- fn ts_from_str(&mut self, src: &str) -> Self::TokenStream {
- Self::TokenStream::from_str(src, self.call_site).unwrap_or_else(|e| {
- Self::TokenStream::from_str(
- &format!("compile_error!(\"failed to parse str to token stream: {e}\")"),
- self.call_site,
- )
- .unwrap()
- })
+ fn ts_from_str(&mut self, src: &str) -> Result<Self::TokenStream, String> {
+ Self::TokenStream::from_str(src, self.call_site)
+ .map_err(|e| format!("failed to parse str to token stream: {e}"))
}
fn ts_to_string(&mut self, stream: &Self::TokenStream) -> String {
stream.to_string()
@@ -168,12 +164,16 @@ impl server::Server for RaSpanServer<'_> {
self.callback.as_mut()?.source_text(span)
}
- fn span_parent(&mut self, _span: Self::Span) -> Option<Self::Span> {
- // FIXME requires db, looks up the parent call site
+ fn span_parent(&mut self, span: Self::Span) -> Option<Self::Span> {
+ if let Some(ref mut callback) = self.callback {
+ return callback.span_parent(span);
+ }
None
}
fn span_source(&mut self, span: Self::Span) -> Self::Span {
- // FIXME requires db, returns the top level call site
+ if let Some(ref mut callback) = self.callback {
+ return callback.span_source(span);
+ }
span
}
fn span_byte_range(&mut self, span: Self::Span) -> Range<usize> {
diff --git a/crates/proc-macro-srv/src/server_impl/token_id.rs b/crates/proc-macro-srv/src/server_impl/token_id.rs
index 70484c4dc2..e1c96095c8 100644
--- a/crates/proc-macro-srv/src/server_impl/token_id.rs
+++ b/crates/proc-macro-srv/src/server_impl/token_id.rs
@@ -67,8 +67,9 @@ impl server::Server for SpanIdServer<'_> {
self.tracked_paths.insert(path.into());
}
- fn literal_from_str(&mut self, s: &str) -> Result<Literal<Self::Span>, ()> {
+ fn literal_from_str(&mut self, s: &str) -> Result<Literal<Self::Span>, String> {
literal_from_str(s, self.call_site)
+ .map_err(|()| "cannot parse string into literal".to_string())
}
fn emit_diagnostic(&mut self, _: Diagnostic<Self::Span>) {}
@@ -84,14 +85,9 @@ impl server::Server for SpanIdServer<'_> {
fn ts_is_empty(&mut self, stream: &Self::TokenStream) -> bool {
stream.is_empty()
}
- fn ts_from_str(&mut self, src: &str) -> Self::TokenStream {
- Self::TokenStream::from_str(src, self.call_site).unwrap_or_else(|e| {
- Self::TokenStream::from_str(
- &format!("compile_error!(\"failed to parse str to token stream: {e}\")"),
- self.call_site,
- )
- .unwrap()
- })
+ fn ts_from_str(&mut self, src: &str) -> Result<Self::TokenStream, String> {
+ Self::TokenStream::from_str(src, self.call_site)
+ .map_err(|e| format!("failed to parse str to token stream: {e}"))
}
fn ts_to_string(&mut self, stream: &Self::TokenStream) -> String {
stream.to_string()
diff --git a/crates/proc-macro-srv/src/tests/utils.rs b/crates/proc-macro-srv/src/tests/utils.rs
index b7c5c4fdd2..31beca20d6 100644
--- a/crates/proc-macro-srv/src/tests/utils.rs
+++ b/crates/proc-macro-srv/src/tests/utils.rs
@@ -142,6 +142,14 @@ impl ProcMacroClientInterface for MockCallback<'_> {
fn byte_range(&mut self, span: Span) -> Range<usize> {
Range { start: span.range.start().into(), end: span.range.end().into() }
}
+
+ fn span_source(&mut self, span: Span) -> Span {
+ span
+ }
+
+ fn span_parent(&mut self, _span: Span) -> Option<Span> {
+ None
+ }
}
pub fn assert_expand_with_callback(
diff --git a/crates/profile/src/google_cpu_profiler.rs b/crates/profile/src/google_cpu_profiler.rs
index cae6caeaa6..d77c945f26 100644
--- a/crates/profile/src/google_cpu_profiler.rs
+++ b/crates/profile/src/google_cpu_profiler.rs
@@ -9,7 +9,7 @@ use std::{
#[link(name = "profiler")]
#[allow(non_snake_case)]
-extern "C" {
+unsafe extern "C" {
fn ProfilerStart(fname: *const c_char) -> i32;
fn ProfilerStop();
}
diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs
index fedc6944f5..aff5391697 100644
--- a/crates/project-model/src/build_dependencies.rs
+++ b/crates/project-model/src/build_dependencies.rs
@@ -22,8 +22,9 @@ use triomphe::Arc;
use crate::{
CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot,
- TargetKind, cargo_config_file::make_lockfile_copy,
- cargo_workspace::MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH, utf8_stdout,
+ TargetKind,
+ cargo_config_file::{LockfileCopy, LockfileUsage, make_lockfile_copy},
+ utf8_stdout,
};
/// Output of the build script and proc-macro building steps for a workspace.
@@ -436,7 +437,7 @@ impl WorkspaceBuildScripts {
current_dir: &AbsPath,
sysroot: &Sysroot,
toolchain: Option<&semver::Version>,
- ) -> io::Result<(Option<temp_dir::TempDir>, Command)> {
+ ) -> io::Result<(Option<LockfileCopy>, Command)> {
match config.run_build_script_command.as_deref() {
Some([program, args @ ..]) => {
let mut cmd = toolchain::command(program, current_dir, &config.extra_env);
@@ -461,17 +462,26 @@ impl WorkspaceBuildScripts {
if let Some(target) = &config.target {
cmd.args(["--target", target]);
}
- let mut temp_dir_guard = None;
- if toolchain
- .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
- {
+ let mut lockfile_copy = None;
+ if let Some(toolchain) = toolchain {
let lockfile_path =
<_ as AsRef<Utf8Path>>::as_ref(manifest_path).with_extension("lock");
- if let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile_path) {
+ lockfile_copy = make_lockfile_copy(toolchain, &lockfile_path);
+ if let Some(lockfile_copy) = &lockfile_copy {
requires_unstable_options = true;
- temp_dir_guard = Some(temp_dir);
- cmd.arg("--lockfile-path");
- cmd.arg(target_lockfile.as_str());
+ match lockfile_copy.usage {
+ LockfileUsage::WithFlag => {
+ cmd.arg("--lockfile-path");
+ cmd.arg(lockfile_copy.path.as_str());
+ }
+ LockfileUsage::WithEnvVar => {
+ cmd.arg("-Zlockfile-path");
+ cmd.env(
+ "CARGO_RESOLVER_LOCKFILE_PATH",
+ lockfile_copy.path.as_os_str(),
+ );
+ }
+ }
}
}
match &config.features {
@@ -542,7 +552,7 @@ impl WorkspaceBuildScripts {
cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
cmd.arg("-Zunstable-options");
}
- Ok((temp_dir_guard, cmd))
+ Ok((lockfile_copy, cmd))
}
}
}
diff --git a/crates/project-model/src/cargo_config_file.rs b/crates/project-model/src/cargo_config_file.rs
index 5d6e5fd648..9ce88484f7 100644
--- a/crates/project-model/src/cargo_config_file.rs
+++ b/crates/project-model/src/cargo_config_file.rs
@@ -132,25 +132,66 @@ impl<'a> CargoConfigFileReader<'a> {
}
}
+pub(crate) struct LockfileCopy {
+ pub(crate) path: Utf8PathBuf,
+ pub(crate) usage: LockfileUsage,
+ _temp_dir: temp_dir::TempDir,
+}
+
+pub(crate) enum LockfileUsage {
+ /// Rust [1.82.0, 1.95.0). `cargo <subcmd> --lockfile-path <lockfile path>`
+ WithFlag,
+ /// Rust >= 1.95.0. `CARGO_RESOLVER_LOCKFILE_PATH=<lockfile path> cargo <subcmd>`
+ WithEnvVar,
+}
+
pub(crate) fn make_lockfile_copy(
+ toolchain_version: &semver::Version,
lockfile_path: &Utf8Path,
-) -> Option<(temp_dir::TempDir, Utf8PathBuf)> {
+) -> Option<LockfileCopy> {
+ const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_FLAG: semver::Version =
+ semver::Version {
+ major: 1,
+ minor: 82,
+ patch: 0,
+ pre: semver::Prerelease::EMPTY,
+ build: semver::BuildMetadata::EMPTY,
+ };
+
+ // TODO: turn this into a const and remove pre once 1.95 is stable
+ #[allow(non_snake_case)]
+ let MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV: semver::Version = semver::Version {
+ major: 1,
+ minor: 95,
+ patch: 0,
+ pre: semver::Prerelease::new("beta").unwrap(),
+ build: semver::BuildMetadata::EMPTY,
+ };
+
+ let usage = if *toolchain_version >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV {
+ LockfileUsage::WithEnvVar
+ } else if *toolchain_version >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_FLAG {
+ LockfileUsage::WithFlag
+ } else {
+ return None;
+ };
+
let temp_dir = temp_dir::TempDir::with_prefix("rust-analyzer").ok()?;
- let target_lockfile = temp_dir.path().join("Cargo.lock").try_into().ok()?;
- match std::fs::copy(lockfile_path, &target_lockfile) {
+ let path: Utf8PathBuf = temp_dir.path().join("Cargo.lock").try_into().ok()?;
+ let path = match std::fs::copy(lockfile_path, &path) {
Ok(_) => {
- tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, target_lockfile);
- Some((temp_dir, target_lockfile))
+ tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, path);
+ path
}
// lockfile does not yet exist, so we can just create a new one in the temp dir
- Err(e) if e.kind() == std::io::ErrorKind::NotFound => Some((temp_dir, target_lockfile)),
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => path,
Err(e) => {
- tracing::warn!(
- "Failed to copy lock file from `{lockfile_path}` to `{target_lockfile}`: {e}",
- );
- None
+ tracing::warn!("Failed to copy lock file from `{lockfile_path}` to `{path}`: {e}",);
+ return None;
}
- }
+ };
+
+ Some(LockfileCopy { path, usage, _temp_dir: temp_dir })
}
#[test]
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index 483ab28450..792206b74f 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -16,18 +16,10 @@ use toolchain::{NO_RUSTUP_AUTO_INSTALL_ENV, Tool};
use triomphe::Arc;
use crate::{
- CfgOverrides, InvocationStrategy, ManifestPath, Sysroot, cargo_config_file::make_lockfile_copy,
+ CfgOverrides, InvocationStrategy, ManifestPath, Sysroot,
+ cargo_config_file::{LockfileCopy, LockfileUsage, make_lockfile_copy},
};
-pub(crate) const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version =
- semver::Version {
- major: 1,
- minor: 82,
- patch: 0,
- pre: semver::Prerelease::EMPTY,
- build: semver::BuildMetadata::EMPTY,
- };
-
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output.
///
@@ -628,7 +620,7 @@ pub(crate) struct FetchMetadata {
command: cargo_metadata::MetadataCommand,
#[expect(dead_code)]
manifest_path: ManifestPath,
- lockfile_path: Option<Utf8PathBuf>,
+ lockfile_copy: Option<LockfileCopy>,
#[expect(dead_code)]
kind: &'static str,
no_deps: bool,
@@ -688,15 +680,14 @@ impl FetchMetadata {
}
}
- let mut lockfile_path = None;
+ let mut lockfile_copy = None;
if cargo_toml.is_rust_manifest() {
other_options.push("-Zscript".to_owned());
- } else if config
- .toolchain_version
- .as_ref()
- .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
- {
- lockfile_path = Some(<_ as AsRef<Utf8Path>>::as_ref(cargo_toml).with_extension("lock"));
+ } else if let Some(v) = config.toolchain_version.as_ref() {
+ lockfile_copy = make_lockfile_copy(
+ v,
+ &<_ as AsRef<Utf8Path>>::as_ref(cargo_toml).with_extension("lock"),
+ );
}
if !config.targets.is_empty() {
@@ -729,7 +720,7 @@ impl FetchMetadata {
Self {
manifest_path: cargo_toml.clone(),
command,
- lockfile_path,
+ lockfile_copy,
kind: config.kind,
no_deps,
no_deps_result,
@@ -749,7 +740,7 @@ impl FetchMetadata {
let Self {
mut command,
manifest_path: _,
- lockfile_path,
+ lockfile_copy,
kind: _,
no_deps,
no_deps_result,
@@ -761,13 +752,17 @@ impl FetchMetadata {
}
let mut using_lockfile_copy = false;
- let mut _temp_dir_guard;
- if let Some(lockfile) = lockfile_path
- && let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile)
- {
- _temp_dir_guard = temp_dir;
- other_options.push("--lockfile-path".to_owned());
- other_options.push(target_lockfile.to_string());
+ if let Some(lockfile_copy) = &lockfile_copy {
+ match lockfile_copy.usage {
+ LockfileUsage::WithFlag => {
+ other_options.push("--lockfile-path".to_owned());
+ other_options.push(lockfile_copy.path.to_string());
+ }
+ LockfileUsage::WithEnvVar => {
+ other_options.push("-Zlockfile-path".to_owned());
+ command.env("CARGO_RESOLVER_LOCKFILE_PATH", lockfile_copy.path.as_os_str());
+ }
+ }
using_lockfile_copy = true;
}
if using_lockfile_copy || other_options.iter().any(|it| it.starts_with("-Z")) {
diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs
index 6938010cbd..9b9111012b 100644
--- a/crates/project-model/src/project_json.rs
+++ b/crates/project-model/src/project_json.rs
@@ -391,7 +391,6 @@ struct CrateData {
display_name: Option<String>,
root_module: Utf8PathBuf,
edition: EditionData,
- #[serde(default)]
version: Option<semver::Version>,
deps: Vec<Dep>,
#[serde(default)]
@@ -408,11 +407,8 @@ struct CrateData {
source: Option<CrateSource>,
#[serde(default)]
is_proc_macro: bool,
- #[serde(default)]
repository: Option<String>,
- #[serde(default)]
build: Option<BuildData>,
- #[serde(default)]
proc_macro_cwd: Option<Utf8PathBuf>,
}
diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs
index c74f4550fd..cdaf944bba 100644
--- a/crates/rust-analyzer/src/flycheck.rs
+++ b/crates/rust-analyzer/src/flycheck.rs
@@ -382,6 +382,7 @@ enum FlycheckCommandOrigin {
ProjectJsonRunnable,
}
+#[derive(Debug)]
enum StateChange {
Restart {
generation: DiagnosticsGeneration,
@@ -435,6 +436,7 @@ enum DiagnosticsReceived {
}
#[allow(clippy::large_enum_variant)]
+#[derive(Debug)]
enum Event {
RequestStateChange(StateChange),
CheckEvent(Option<CheckMessage>),
@@ -445,6 +447,7 @@ const SAVED_FILE_PLACEHOLDER_DOLLAR: &str = "$saved_file";
const LABEL_INLINE: &str = "{label}";
const SAVED_FILE_INLINE: &str = "{saved_file}";
+#[derive(Debug)]
struct Substitutions<'a> {
label: Option<&'a str>,
saved_file: Option<&'a str>,
@@ -556,17 +559,37 @@ impl FlycheckActor {
self.cancel_check_process();
}
Event::RequestStateChange(StateChange::Restart {
- generation,
- scope,
- saved_file,
- target,
+ mut generation,
+ mut scope,
+ mut saved_file,
+ mut target,
}) => {
// Cancel the previously spawned process
self.cancel_check_process();
+
+ // Debounce by briefly waiting for other state changes.
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
- // restart chained with a stop, so just cancel
- if let StateChange::Cancel = restart {
- continue 'event;
+ match restart {
+ StateChange::Cancel => {
+ // We got a cancel straight after this restart request, so
+ // don't do anything.
+ continue 'event;
+ }
+ StateChange::Restart {
+ generation: g,
+ scope: s,
+ saved_file: sf,
+ target: t,
+ } => {
+ // We got another restart request. Take the parameters
+ // from the last restart request in this time window,
+ // because the most recent request is probably the most
+ // relevant to the user.
+ generation = g;
+ scope = s;
+ saved_file = sf;
+ target = t;
+ }
}
}
@@ -741,70 +764,42 @@ impl FlycheckActor {
flycheck_id = self.id,
message = diagnostic.message,
package_id = package_id.as_ref().map(|it| it.as_str()),
- scope = ?self.scope,
"diagnostic received"
);
-
- match &self.scope {
- FlycheckScope::Workspace => {
- if self.diagnostics_received == DiagnosticsReceived::NotYet {
- self.send(FlycheckMessage::ClearDiagnostics {
- id: self.id,
- kind: ClearDiagnosticsKind::All(ClearScope::Workspace),
- });
-
- self.diagnostics_received =
- DiagnosticsReceived::AtLeastOneAndClearedWorkspace;
- }
-
- if let Some(package_id) = package_id {
- tracing::warn!(
- "Ignoring package label {:?} and applying diagnostics to the whole workspace",
- package_id
- );
- }
-
- self.send(FlycheckMessage::AddDiagnostic {
- id: self.id,
- generation: self.generation,
- package_id: None,
- workspace_root: self.root.clone(),
- diagnostic,
- });
- }
- FlycheckScope::Package { package: flycheck_package, .. } => {
- if self.diagnostics_received == DiagnosticsReceived::NotYet {
- self.diagnostics_received = DiagnosticsReceived::AtLeastOne;
- }
-
- // If the package has been set in the diagnostic JSON, respect that. Otherwise, use the
- // package that the current flycheck is scoped to. This is useful when a project is
- // directly using rustc for its checks (e.g. custom check commands in rust-project.json).
- let package_id = package_id.unwrap_or(flycheck_package.clone());
-
- if self.diagnostics_cleared_for.insert(package_id.clone()) {
- tracing::trace!(
- flycheck_id = self.id,
- package_id = package_id.as_str(),
- "clearing diagnostics"
- );
- self.send(FlycheckMessage::ClearDiagnostics {
- id: self.id,
- kind: ClearDiagnosticsKind::All(ClearScope::Package(
- package_id.clone(),
- )),
- });
- }
-
- self.send(FlycheckMessage::AddDiagnostic {
+ if self.diagnostics_received == DiagnosticsReceived::NotYet {
+ self.diagnostics_received = DiagnosticsReceived::AtLeastOne;
+ }
+ if let Some(package_id) = &package_id {
+ if self.diagnostics_cleared_for.insert(package_id.clone()) {
+ tracing::trace!(
+ flycheck_id = self.id,
+ package_id = package_id.as_str(),
+ "clearing diagnostics"
+ );
+ self.send(FlycheckMessage::ClearDiagnostics {
id: self.id,
- generation: self.generation,
- package_id: Some(package_id),
- workspace_root: self.root.clone(),
- diagnostic,
+ kind: ClearDiagnosticsKind::All(ClearScope::Package(
+ package_id.clone(),
+ )),
});
}
+ } else if self.diagnostics_received
+ != DiagnosticsReceived::AtLeastOneAndClearedWorkspace
+ {
+ self.diagnostics_received =
+ DiagnosticsReceived::AtLeastOneAndClearedWorkspace;
+ self.send(FlycheckMessage::ClearDiagnostics {
+ id: self.id,
+ kind: ClearDiagnosticsKind::All(ClearScope::Workspace),
+ });
}
+ self.send(FlycheckMessage::AddDiagnostic {
+ id: self.id,
+ generation: self.generation,
+ package_id,
+ workspace_root: self.root.clone(),
+ diagnostic,
+ });
}
},
}
@@ -978,6 +973,7 @@ impl FlycheckActor {
}
#[allow(clippy::large_enum_variant)]
+#[derive(Debug)]
enum CheckMessage {
/// A message from `cargo check`, including details like the path
/// to the relevant `Cargo.toml`.
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 1462727df4..afd4162de6 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -14,7 +14,7 @@ use hir::ChangeWithProcMacros;
use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
use ide_db::{
MiniCore,
- base_db::{Crate, ProcMacroPaths, SourceDatabase, salsa::CancellationToken, salsa::Revision},
+ base_db::{Crate, ProcMacroPaths, SourceDatabase, salsa::Revision},
};
use itertools::Itertools;
use load_cargo::SourceRootConfig;
@@ -88,7 +88,6 @@ pub(crate) struct GlobalState {
pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>,
pub(crate) fmt_pool: Handle<TaskPool<Task>, Receiver<Task>>,
pub(crate) cancellation_pool: thread::Pool,
- pub(crate) cancellation_tokens: FxHashMap<lsp_server::RequestId, CancellationToken>,
pub(crate) config: Arc<Config>,
pub(crate) config_errors: Option<ConfigErrors>,
@@ -266,7 +265,6 @@ impl GlobalState {
task_pool,
fmt_pool,
cancellation_pool,
- cancellation_tokens: Default::default(),
loader,
config: Arc::new(config.clone()),
analysis_host,
@@ -619,7 +617,6 @@ impl GlobalState {
}
pub(crate) fn respond(&mut self, response: lsp_server::Response) {
- self.cancellation_tokens.remove(&response.id);
if let Some((method, start)) = self.req_queue.incoming.complete(&response.id) {
if let Some(err) = &response.error
&& err.message.starts_with("server panicked")
@@ -634,9 +631,6 @@ impl GlobalState {
}
pub(crate) fn cancel(&mut self, request_id: lsp_server::RequestId) {
- if let Some(token) = self.cancellation_tokens.remove(&request_id) {
- token.cancel();
- }
if let Some(response) = self.req_queue.incoming.cancel(request_id) {
self.send(response.into());
}
diff --git a/crates/rust-analyzer/src/handlers/dispatch.rs b/crates/rust-analyzer/src/handlers/dispatch.rs
index 63b4e6430c..67bd643fce 100644
--- a/crates/rust-analyzer/src/handlers/dispatch.rs
+++ b/crates/rust-analyzer/src/handlers/dispatch.rs
@@ -253,9 +253,6 @@ impl RequestDispatcher<'_> {
tracing::debug!(?params);
let world = self.global_state.snapshot();
- self.global_state
- .cancellation_tokens
- .insert(req.id.clone(), world.analysis.cancellation_token());
if RUSTFMT {
&mut self.global_state.fmt_pool.handle
} else {
@@ -268,19 +265,7 @@ impl RequestDispatcher<'_> {
});
match thread_result_to_response::<R>(req.id.clone(), result) {
Ok(response) => Task::Response(response),
- Err(HandlerCancelledError::Inner(
- Cancelled::PendingWrite | Cancelled::PropagatedPanic,
- )) if ALLOW_RETRYING => Task::Retry(req),
- // Note: Technically the return value here does not matter as we have already responded to the client with this error.
- Err(HandlerCancelledError::Inner(Cancelled::Local)) => Task::Response(Response {
- id: req.id,
- result: None,
- error: Some(ResponseError {
- code: lsp_server::ErrorCode::RequestCanceled as i32,
- message: "canceled by client".to_owned(),
- data: None,
- }),
- }),
+ Err(_cancelled) if ALLOW_RETRYING => Task::Retry(req),
Err(_cancelled) => {
let error = on_cancelled();
Task::Response(Response { id: req.id, result: None, error: Some(error) })
@@ -429,7 +414,8 @@ impl NotificationDispatcher<'_> {
let params = match not.extract::<N::Params>(N::METHOD) {
Ok(it) => it,
Err(ExtractError::JsonError { method, error }) => {
- panic!("Invalid request\nMethod: {method}\n error: {error}",)
+ tracing::error!(method = %method, error = %error, "invalid notification");
+ return self;
}
Err(ExtractError::MethodMismatch(not)) => {
self.not = Some(not);
diff --git a/crates/rust-analyzer/src/tracing/config.rs b/crates/rust-analyzer/src/tracing/config.rs
index ca897aeb3e..2bc9f3c34a 100644
--- a/crates/rust-analyzer/src/tracing/config.rs
+++ b/crates/rust-analyzer/src/tracing/config.rs
@@ -1,7 +1,7 @@
//! Simple logger that logs either to stderr or to a file, using `tracing_subscriber`
//! filter syntax and `tracing_appender` for non blocking output.
-use std::io::{self};
+use std::io;
use anyhow::Context;
use tracing::level_filters::LevelFilter;
diff --git a/crates/rust-analyzer/tests/slow-tests/flycheck.rs b/crates/rust-analyzer/tests/slow-tests/flycheck.rs
new file mode 100644
index 0000000000..c1d53fb33a
--- /dev/null
+++ b/crates/rust-analyzer/tests/slow-tests/flycheck.rs
@@ -0,0 +1,112 @@
+use test_utils::skip_slow_tests;
+
+use crate::support::Project;
+
+#[test]
+fn test_flycheck_diagnostics_for_unused_variable() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/main.rs
+fn main() {
+ let x = 1;
+}
+"#,
+ )
+ .with_config(serde_json::json!({
+ "checkOnSave": true,
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ let diagnostics = server.wait_for_diagnostics();
+ assert!(
+ diagnostics.diagnostics.iter().any(|d| d.message.contains("unused variable")),
+ "expected unused variable diagnostic, got: {:?}",
+ diagnostics.diagnostics,
+ );
+}
+
+#[test]
+fn test_flycheck_diagnostic_cleared_after_fix() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/main.rs
+fn main() {
+ let x = 1;
+}
+"#,
+ )
+ .with_config(serde_json::json!({
+ "checkOnSave": true,
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ // Wait for the unused variable diagnostic to appear.
+ let diagnostics = server.wait_for_diagnostics();
+ assert!(
+ diagnostics.diagnostics.iter().any(|d| d.message.contains("unused variable")),
+ "expected unused variable diagnostic, got: {:?}",
+ diagnostics.diagnostics,
+ );
+
+ // Fix the code by removing the unused variable.
+ server.write_file_and_save("src/main.rs", "fn main() {}\n".to_owned());
+
+ // Wait for diagnostics to be cleared.
+ server.wait_for_diagnostics_cleared();
+}
+
+#[test]
+fn test_flycheck_diagnostic_with_override_command() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/main.rs
+fn main() {}
+"#,
+ )
+ .with_config(serde_json::json!({
+ "checkOnSave": true,
+ "check": {
+ "overrideCommand": ["rustc", "--error-format=json", "$saved_file"]
+ }
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ server.write_file_and_save("src/main.rs", "fn main() {\n let x = 1;\n}\n".to_owned());
+
+ let diagnostics = server.wait_for_diagnostics();
+ assert!(
+ diagnostics.diagnostics.iter().any(|d| d.message.contains("unused variable")),
+ "expected unused variable diagnostic, got: {:?}",
+ diagnostics.diagnostics,
+ );
+}
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index b4a7b44d16..fcdc8bb7cd 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -15,12 +15,14 @@
extern crate rustc_driver as _;
mod cli;
+mod flycheck;
mod ratoml;
mod support;
mod testdir;
use std::{collections::HashMap, path::PathBuf, time::Instant};
+use ide_db::FxHashMap;
use lsp_types::{
CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
DocumentFormattingParams, DocumentRangeFormattingParams, FileRename, FormattingOptions,
@@ -672,6 +674,17 @@ fn test_format_document_range() {
return;
}
+ // This test requires a nightly toolchain, so skip if it's not available.
+ let cwd = std::env::current_dir().unwrap_or_default();
+ let has_nightly_rustfmt = toolchain::command("rustfmt", cwd, &FxHashMap::default())
+ .args(["+nightly", "--version"])
+ .output()
+ .is_ok_and(|out| out.status.success());
+ if !has_nightly_rustfmt {
+ tracing::warn!("skipping test_format_document_range: nightly rustfmt not available");
+ return;
+ }
+
let server = Project::with_fixture(
r#"
//- /Cargo.toml
diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs
index 195ad226ae..7ee31f3d53 100644
--- a/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -8,7 +8,9 @@ use std::{
use crossbeam_channel::{Receiver, after, select};
use itertools::Itertools;
use lsp_server::{Connection, Message, Notification, Request};
-use lsp_types::{TextDocumentIdentifier, Url, notification::Exit, request::Shutdown};
+use lsp_types::{
+ PublishDiagnosticsParams, TextDocumentIdentifier, Url, notification::Exit, request::Shutdown,
+};
use parking_lot::{Mutex, MutexGuard};
use paths::{Utf8Path, Utf8PathBuf};
use rust_analyzer::{
@@ -407,6 +409,53 @@ impl Server {
.unwrap_or_else(|Timeout| panic!("timeout while waiting for ws to load"));
self
}
+ pub(crate) fn wait_for_diagnostics(&self) -> PublishDiagnosticsParams {
+ for msg in self.messages.borrow().iter() {
+ if let Message::Notification(n) = msg
+ && n.method == "textDocument/publishDiagnostics"
+ {
+ let params: PublishDiagnosticsParams =
+ serde_json::from_value(n.params.clone()).unwrap();
+ if !params.diagnostics.is_empty() {
+ return params;
+ }
+ }
+ }
+ loop {
+ let msg = self
+ .recv()
+ .unwrap_or_else(|Timeout| panic!("timeout while waiting for diagnostics"))
+ .expect("connection closed while waiting for diagnostics");
+ if let Message::Notification(n) = &msg
+ && n.method == "textDocument/publishDiagnostics"
+ {
+ let params: PublishDiagnosticsParams =
+ serde_json::from_value(n.params.clone()).unwrap();
+ if !params.diagnostics.is_empty() {
+ return params;
+ }
+ }
+ }
+ }
+
+ pub(crate) fn wait_for_diagnostics_cleared(&self) {
+ loop {
+ let msg = self
+ .recv()
+ .unwrap_or_else(|Timeout| panic!("timeout while waiting for diagnostics to clear"))
+ .expect("connection closed while waiting for diagnostics to clear");
+ if let Message::Notification(n) = &msg
+ && n.method == "textDocument/publishDiagnostics"
+ {
+ let params: PublishDiagnosticsParams =
+ serde_json::from_value(n.params.clone()).unwrap();
+ if params.diagnostics.is_empty() {
+ return;
+ }
+ }
+ }
+ }
+
fn wait_for_message_cond(
&self,
n: usize,
diff --git a/crates/span/src/ast_id.rs b/crates/span/src/ast_id.rs
index 599b3c7175..f52604e139 100644
--- a/crates/span/src/ast_id.rs
+++ b/crates/span/src/ast_id.rs
@@ -88,7 +88,6 @@ impl fmt::Debug for ErasedFileAstId {
Module,
Static,
Trait,
- TraitAlias,
Variant,
Const,
Fn,
@@ -129,7 +128,6 @@ enum ErasedFileAstIdKind {
Module,
Static,
Trait,
- TraitAlias,
// Until here associated with `ErasedHasNameFileAstId`.
// The following are associated with `ErasedAssocItemFileAstId`.
Variant,
diff --git a/crates/span/src/hygiene.rs b/crates/span/src/hygiene.rs
index 0a81cef52e..fe05ef9465 100644
--- a/crates/span/src/hygiene.rs
+++ b/crates/span/src/hygiene.rs
@@ -81,24 +81,25 @@ const _: () = {
#[derive(Hash)]
struct StructKey<'db, T0, T1, T2, T3>(T0, T1, T2, T3, std::marker::PhantomData<&'db ()>);
- impl<'db, T0, T1, T2, T3> zalsa_::HashEqLike<StructKey<'db, T0, T1, T2, T3>> for SyntaxContextData
+ impl<'db, T0, T1, T2, T3> zalsa_::interned::HashEqLike<StructKey<'db, T0, T1, T2, T3>>
+ for SyntaxContextData
where
- Option<MacroCallId>: zalsa_::HashEqLike<T0>,
- Transparency: zalsa_::HashEqLike<T1>,
- Edition: zalsa_::HashEqLike<T2>,
- SyntaxContext: zalsa_::HashEqLike<T3>,
+ Option<MacroCallId>: zalsa_::interned::HashEqLike<T0>,
+ Transparency: zalsa_::interned::HashEqLike<T1>,
+ Edition: zalsa_::interned::HashEqLike<T2>,
+ SyntaxContext: zalsa_::interned::HashEqLike<T3>,
{
fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
- zalsa_::HashEqLike::<T0>::hash(&self.outer_expn, &mut *h);
- zalsa_::HashEqLike::<T1>::hash(&self.outer_transparency, &mut *h);
- zalsa_::HashEqLike::<T2>::hash(&self.edition, &mut *h);
- zalsa_::HashEqLike::<T3>::hash(&self.parent, &mut *h);
+ zalsa_::interned::HashEqLike::<T0>::hash(&self.outer_expn, &mut *h);
+ zalsa_::interned::HashEqLike::<T1>::hash(&self.outer_transparency, &mut *h);
+ zalsa_::interned::HashEqLike::<T2>::hash(&self.edition, &mut *h);
+ zalsa_::interned::HashEqLike::<T3>::hash(&self.parent, &mut *h);
}
fn eq(&self, data: &StructKey<'db, T0, T1, T2, T3>) -> bool {
- zalsa_::HashEqLike::<T0>::eq(&self.outer_expn, &data.0)
- && zalsa_::HashEqLike::<T1>::eq(&self.outer_transparency, &data.1)
- && zalsa_::HashEqLike::<T2>::eq(&self.edition, &data.2)
- && zalsa_::HashEqLike::<T3>::eq(&self.parent, &data.3)
+ zalsa_::interned::HashEqLike::<T0>::eq(&self.outer_expn, &data.0)
+ && zalsa_::interned::HashEqLike::<T1>::eq(&self.outer_transparency, &data.1)
+ && zalsa_::interned::HashEqLike::<T2>::eq(&self.edition, &data.2)
+ && zalsa_::interned::HashEqLike::<T3>::eq(&self.parent, &data.3)
}
}
impl zalsa_struct_::Configuration for SyntaxContext {
@@ -202,10 +203,10 @@ const _: () = {
impl<'db> SyntaxContext {
pub fn new<
Db,
- T0: zalsa_::Lookup<Option<MacroCallId>> + std::hash::Hash,
- T1: zalsa_::Lookup<Transparency> + std::hash::Hash,
- T2: zalsa_::Lookup<Edition> + std::hash::Hash,
- T3: zalsa_::Lookup<SyntaxContext> + std::hash::Hash,
+ T0: zalsa_::interned::Lookup<Option<MacroCallId>> + std::hash::Hash,
+ T1: zalsa_::interned::Lookup<Transparency> + std::hash::Hash,
+ T2: zalsa_::interned::Lookup<Edition> + std::hash::Hash,
+ T3: zalsa_::interned::Lookup<SyntaxContext> + std::hash::Hash,
>(
db: &'db Db,
outer_expn: T0,
@@ -217,10 +218,10 @@ const _: () = {
) -> Self
where
Db: ?Sized + salsa::Database,
- Option<MacroCallId>: zalsa_::HashEqLike<T0>,
- Transparency: zalsa_::HashEqLike<T1>,
- Edition: zalsa_::HashEqLike<T2>,
- SyntaxContext: zalsa_::HashEqLike<T3>,
+ Option<MacroCallId>: zalsa_::interned::HashEqLike<T0>,
+ Transparency: zalsa_::interned::HashEqLike<T1>,
+ Edition: zalsa_::interned::HashEqLike<T2>,
+ SyntaxContext: zalsa_::interned::HashEqLike<T3>,
{
let (zalsa, zalsa_local) = db.zalsas();
@@ -235,10 +236,10 @@ const _: () = {
std::marker::PhantomData,
),
|id, data| SyntaxContextData {
- outer_expn: zalsa_::Lookup::into_owned(data.0),
- outer_transparency: zalsa_::Lookup::into_owned(data.1),
- edition: zalsa_::Lookup::into_owned(data.2),
- parent: zalsa_::Lookup::into_owned(data.3),
+ outer_expn: zalsa_::interned::Lookup::into_owned(data.0),
+ outer_transparency: zalsa_::interned::Lookup::into_owned(data.1),
+ edition: zalsa_::interned::Lookup::into_owned(data.2),
+ parent: zalsa_::interned::Lookup::into_owned(data.3),
opaque: opaque(zalsa_::FromId::from_id(id)),
opaque_and_semiopaque: opaque_and_semiopaque(zalsa_::FromId::from_id(id)),
},
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 1cd8146f68..2b7dc5cd76 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -9,8 +9,9 @@ use crate::{
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
SyntaxNode, SyntaxToken,
algo::{self, neighbor},
- ast::{self, HasGenericParams, edit::IndentLevel, make},
- ted::{self, Position},
+ ast::{self, HasGenericParams, edit::IndentLevel, make, syntax_factory::SyntaxFactory},
+ syntax_editor::{Position, SyntaxEditor},
+ ted,
};
use super::{GenericParam, HasName};
@@ -26,13 +27,13 @@ impl GenericParamsOwnerEdit for ast::Fn {
Some(it) => it,
None => {
let position = if let Some(name) = self.name() {
- Position::after(name.syntax)
+ ted::Position::after(name.syntax)
} else if let Some(fn_token) = self.fn_token() {
- Position::after(fn_token)
+ ted::Position::after(fn_token)
} else if let Some(param_list) = self.param_list() {
- Position::before(param_list.syntax)
+ ted::Position::before(param_list.syntax)
} else {
- Position::last_child_of(self.syntax())
+ ted::Position::last_child_of(self.syntax())
};
create_generic_param_list(position)
}
@@ -42,11 +43,11 @@ impl GenericParamsOwnerEdit for ast::Fn {
fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() {
let position = if let Some(ty) = self.ret_type() {
- Position::after(ty.syntax())
+ ted::Position::after(ty.syntax())
} else if let Some(param_list) = self.param_list() {
- Position::after(param_list.syntax())
+ ted::Position::after(param_list.syntax())
} else {
- Position::last_child_of(self.syntax())
+ ted::Position::last_child_of(self.syntax())
};
create_where_clause(position);
}
@@ -60,8 +61,8 @@ impl GenericParamsOwnerEdit for ast::Impl {
Some(it) => it,
None => {
let position = match self.impl_token() {
- Some(imp_token) => Position::after(imp_token),
- None => Position::last_child_of(self.syntax()),
+ Some(imp_token) => ted::Position::after(imp_token),
+ None => ted::Position::last_child_of(self.syntax()),
};
create_generic_param_list(position)
}
@@ -71,8 +72,8 @@ impl GenericParamsOwnerEdit for ast::Impl {
fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() {
let position = match self.assoc_item_list() {
- Some(items) => Position::before(items.syntax()),
- None => Position::last_child_of(self.syntax()),
+ Some(items) => ted::Position::before(items.syntax()),
+ None => ted::Position::last_child_of(self.syntax()),
};
create_where_clause(position);
}
@@ -86,11 +87,11 @@ impl GenericParamsOwnerEdit for ast::Trait {
Some(it) => it,
None => {
let position = if let Some(name) = self.name() {
- Position::after(name.syntax)
+ ted::Position::after(name.syntax)
} else if let Some(trait_token) = self.trait_token() {
- Position::after(trait_token)
+ ted::Position::after(trait_token)
} else {
- Position::last_child_of(self.syntax())
+ ted::Position::last_child_of(self.syntax())
};
create_generic_param_list(position)
}
@@ -100,9 +101,9 @@ impl GenericParamsOwnerEdit for ast::Trait {
fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() {
let position = match (self.assoc_item_list(), self.semicolon_token()) {
- (Some(items), _) => Position::before(items.syntax()),
- (_, Some(tok)) => Position::before(tok),
- (None, None) => Position::last_child_of(self.syntax()),
+ (Some(items), _) => ted::Position::before(items.syntax()),
+ (_, Some(tok)) => ted::Position::before(tok),
+ (None, None) => ted::Position::last_child_of(self.syntax()),
};
create_where_clause(position);
}
@@ -116,11 +117,11 @@ impl GenericParamsOwnerEdit for ast::TypeAlias {
Some(it) => it,
None => {
let position = if let Some(name) = self.name() {
- Position::after(name.syntax)
+ ted::Position::after(name.syntax)
} else if let Some(trait_token) = self.type_token() {
- Position::after(trait_token)
+ ted::Position::after(trait_token)
} else {
- Position::last_child_of(self.syntax())
+ ted::Position::last_child_of(self.syntax())
};
create_generic_param_list(position)
}
@@ -130,10 +131,10 @@ impl GenericParamsOwnerEdit for ast::TypeAlias {
fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() {
let position = match self.eq_token() {
- Some(tok) => Position::before(tok),
+ Some(tok) => ted::Position::before(tok),
None => match self.semicolon_token() {
- Some(tok) => Position::before(tok),
- None => Position::last_child_of(self.syntax()),
+ Some(tok) => ted::Position::before(tok),
+ None => ted::Position::last_child_of(self.syntax()),
},
};
create_where_clause(position);
@@ -148,11 +149,11 @@ impl GenericParamsOwnerEdit for ast::Struct {
Some(it) => it,
None => {
let position = if let Some(name) = self.name() {
- Position::after(name.syntax)
+ ted::Position::after(name.syntax)
} else if let Some(struct_token) = self.struct_token() {
- Position::after(struct_token)
+ ted::Position::after(struct_token)
} else {
- Position::last_child_of(self.syntax())
+ ted::Position::last_child_of(self.syntax())
};
create_generic_param_list(position)
}
@@ -166,13 +167,13 @@ impl GenericParamsOwnerEdit for ast::Struct {
ast::FieldList::TupleFieldList(it) => Some(it),
});
let position = if let Some(tfl) = tfl {
- Position::after(tfl.syntax())
+ ted::Position::after(tfl.syntax())
} else if let Some(gpl) = self.generic_param_list() {
- Position::after(gpl.syntax())
+ ted::Position::after(gpl.syntax())
} else if let Some(name) = self.name() {
- Position::after(name.syntax())
+ ted::Position::after(name.syntax())
} else {
- Position::last_child_of(self.syntax())
+ ted::Position::last_child_of(self.syntax())
};
create_where_clause(position);
}
@@ -186,11 +187,11 @@ impl GenericParamsOwnerEdit for ast::Enum {
Some(it) => it,
None => {
let position = if let Some(name) = self.name() {
- Position::after(name.syntax)
+ ted::Position::after(name.syntax)
} else if let Some(enum_token) = self.enum_token() {
- Position::after(enum_token)
+ ted::Position::after(enum_token)
} else {
- Position::last_child_of(self.syntax())
+ ted::Position::last_child_of(self.syntax())
};
create_generic_param_list(position)
}
@@ -200,11 +201,11 @@ impl GenericParamsOwnerEdit for ast::Enum {
fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() {
let position = if let Some(gpl) = self.generic_param_list() {
- Position::after(gpl.syntax())
+ ted::Position::after(gpl.syntax())
} else if let Some(name) = self.name() {
- Position::after(name.syntax())
+ ted::Position::after(name.syntax())
} else {
- Position::last_child_of(self.syntax())
+ ted::Position::last_child_of(self.syntax())
};
create_where_clause(position);
}
@@ -212,12 +213,12 @@ impl GenericParamsOwnerEdit for ast::Enum {
}
}
-fn create_where_clause(position: Position) {
+fn create_where_clause(position: ted::Position) {
let where_clause = make::where_clause(empty()).clone_for_update();
ted::insert(position, where_clause.syntax());
}
-fn create_generic_param_list(position: Position) -> ast::GenericParamList {
+fn create_generic_param_list(position: ted::Position) -> ast::GenericParamList {
let gpl = make::generic_param_list(empty()).clone_for_update();
ted::insert_raw(position, gpl.syntax());
gpl
@@ -253,7 +254,7 @@ impl ast::GenericParamList {
pub fn add_generic_param(&self, generic_param: ast::GenericParam) {
match self.generic_params().last() {
Some(last_param) => {
- let position = Position::after(last_param.syntax());
+ let position = ted::Position::after(last_param.syntax());
let elements = vec![
make::token(T![,]).into(),
make::tokens::single_space().into(),
@@ -262,7 +263,7 @@ impl ast::GenericParamList {
ted::insert_all(position, elements);
}
None => {
- let after_l_angle = Position::after(self.l_angle_token().unwrap());
+ let after_l_angle = ted::Position::after(self.l_angle_token().unwrap());
ted::insert(after_l_angle, generic_param.syntax());
}
}
@@ -412,7 +413,7 @@ impl ast::UseTree {
match self.use_tree_list() {
Some(it) => it,
None => {
- let position = Position::last_child_of(self.syntax());
+ let position = ted::Position::last_child_of(self.syntax());
let use_tree_list = make::use_tree_list(empty()).clone_for_update();
let mut elements = Vec::with_capacity(2);
if self.coloncolon_token().is_none() {
@@ -458,7 +459,7 @@ impl ast::UseTree {
// Next, transform 'suffix' use tree into 'prefix::{suffix}'
let subtree = self.clone_subtree().clone_for_update();
ted::remove_all_iter(self.syntax().children_with_tokens());
- ted::insert(Position::first_child_of(self.syntax()), prefix.syntax());
+ ted::insert(ted::Position::first_child_of(self.syntax()), prefix.syntax());
self.get_or_create_use_tree_list().add_use_tree(subtree);
fn split_path_prefix(prefix: &ast::Path) -> Option<()> {
@@ -507,7 +508,7 @@ impl ast::UseTreeList {
pub fn add_use_tree(&self, use_tree: ast::UseTree) {
let (position, elements) = match self.use_trees().last() {
Some(last_tree) => (
- Position::after(last_tree.syntax()),
+ ted::Position::after(last_tree.syntax()),
vec![
make::token(T![,]).into(),
make::tokens::single_space().into(),
@@ -516,8 +517,8 @@ impl ast::UseTreeList {
),
None => {
let position = match self.l_curly_token() {
- Some(l_curly) => Position::after(l_curly),
- None => Position::last_child_of(self.syntax()),
+ Some(l_curly) => ted::Position::after(l_curly),
+ None => ted::Position::last_child_of(self.syntax()),
};
(position, vec![use_tree.syntax.into()])
}
@@ -582,15 +583,15 @@ impl ast::AssocItemList {
let (indent, position, whitespace) = match self.assoc_items().last() {
Some(last_item) => (
IndentLevel::from_node(last_item.syntax()),
- Position::after(last_item.syntax()),
+ ted::Position::after(last_item.syntax()),
"\n\n",
),
None => match self.l_curly_token() {
Some(l_curly) => {
normalize_ws_between_braces(self.syntax());
- (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n")
+ (IndentLevel::from_token(&l_curly) + 1, ted::Position::after(&l_curly), "\n")
}
- None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
+ None => (IndentLevel::single(), ted::Position::last_child_of(self.syntax()), "\n"),
},
};
let elements: Vec<SyntaxElement> = vec![
@@ -618,17 +619,17 @@ impl ast::RecordExprFieldList {
let position = match self.fields().last() {
Some(last_field) => {
let comma = get_or_insert_comma_after(last_field.syntax());
- Position::after(comma)
+ ted::Position::after(comma)
}
None => match self.l_curly_token() {
- Some(it) => Position::after(it),
- None => Position::last_child_of(self.syntax()),
+ Some(it) => ted::Position::after(it),
+ None => ted::Position::last_child_of(self.syntax()),
},
};
ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]);
if is_multiline {
- ted::insert(Position::after(field.syntax()), ast::make::token(T![,]));
+ ted::insert(ted::Position::after(field.syntax()), ast::make::token(T![,]));
}
}
}
@@ -656,7 +657,7 @@ impl ast::RecordExprField {
ast::make::tokens::single_space().into(),
expr.syntax().clone().into(),
];
- ted::insert_all_raw(Position::last_child_of(self.syntax()), children);
+ ted::insert_all_raw(ted::Position::last_child_of(self.syntax()), children);
}
}
}
@@ -679,17 +680,17 @@ impl ast::RecordPatFieldList {
Some(last_field) => {
let syntax = last_field.syntax();
let comma = get_or_insert_comma_after(syntax);
- Position::after(comma)
+ ted::Position::after(comma)
}
None => match self.l_curly_token() {
- Some(it) => Position::after(it),
- None => Position::last_child_of(self.syntax()),
+ Some(it) => ted::Position::after(it),
+ None => ted::Position::last_child_of(self.syntax()),
},
};
ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]);
if is_multiline {
- ted::insert(Position::after(field.syntax()), ast::make::token(T![,]));
+ ted::insert(ted::Position::after(field.syntax()), ast::make::token(T![,]));
}
}
}
@@ -703,7 +704,7 @@ fn get_or_insert_comma_after(syntax: &SyntaxNode) -> SyntaxToken {
Some(it) => it,
None => {
let comma = ast::make::token(T![,]);
- ted::insert(Position::after(syntax), &comma);
+ ted::insert(ted::Position::after(syntax), &comma);
comma
}
}
@@ -728,7 +729,7 @@ fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> {
}
}
Some(ws) if ws.kind() == T!['}'] => {
- ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{indent}")));
+ ted::insert(ted::Position::after(l), make::tokens::whitespace(&format!("\n{indent}")));
}
_ => (),
}
@@ -780,6 +781,56 @@ impl ast::IdentPat {
}
}
}
+
+ pub fn set_pat_with_editor(
+ &self,
+ pat: Option<ast::Pat>,
+ syntax_editor: &mut SyntaxEditor,
+ syntax_factory: &SyntaxFactory,
+ ) {
+ match pat {
+ None => {
+ if let Some(at_token) = self.at_token() {
+ // Remove `@ Pat`
+ let start = at_token.clone().into();
+ let end = self
+ .pat()
+ .map(|it| it.syntax().clone().into())
+ .unwrap_or_else(|| at_token.into());
+ syntax_editor.delete_all(start..=end);
+
+ // Remove any trailing ws
+ if let Some(last) =
+ self.syntax().last_token().filter(|it| it.kind() == WHITESPACE)
+ {
+ last.detach();
+ }
+ }
+ }
+ Some(pat) => {
+ if let Some(old_pat) = self.pat() {
+ // Replace existing pattern
+ syntax_editor.replace(old_pat.syntax(), pat.syntax())
+ } else if let Some(at_token) = self.at_token() {
+ // Have an `@` token but not a pattern yet
+ syntax_editor.insert(Position::after(at_token), pat.syntax());
+ } else {
+ // Don't have an `@`, should have a name
+ let name = self.name().unwrap();
+
+ syntax_editor.insert_all(
+ Position::after(name.syntax()),
+ vec![
+ syntax_factory.whitespace(" ").into(),
+ syntax_factory.token(T![@]).into(),
+ syntax_factory.whitespace(" ").into(),
+ pat.syntax().clone().into(),
+ ],
+ )
+ }
+ }
+ }
+ }
}
pub trait HasVisibilityEdit: ast::HasVisibility {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 98d759aef2..f5d1d009a5 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -644,7 +644,8 @@ pub fn expr_await(expr: ast::Expr) -> ast::Expr {
expr_from_text(&format!("{expr}.await"))
}
pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::MatchExpr {
- expr_from_text(&format!("match {expr} {match_arm_list}"))
+ let ws = block_whitespace(&expr);
+ expr_from_text(&format!("match {expr}{ws}{match_arm_list}"))
}
pub fn expr_if(
condition: ast::Expr,
@@ -656,14 +657,17 @@ pub fn expr_if(
Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {if_expr}"),
None => String::new(),
};
- expr_from_text(&format!("if {condition} {then_branch} {else_branch}"))
+ let ws = block_whitespace(&condition);
+ expr_from_text(&format!("if {condition}{ws}{then_branch} {else_branch}"))
}
pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::ForExpr {
- expr_from_text(&format!("for {pat} in {expr} {block}"))
+ let ws = block_whitespace(&expr);
+ expr_from_text(&format!("for {pat} in {expr}{ws}{block}"))
}
pub fn expr_while_loop(condition: ast::Expr, block: ast::BlockExpr) -> ast::WhileExpr {
- expr_from_text(&format!("while {condition} {block}"))
+ let ws = block_whitespace(&condition);
+ expr_from_text(&format!("while {condition}{ws}{block}"))
}
pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr {
@@ -723,6 +727,9 @@ pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::BinExpr {
fn expr_from_text<E: Into<ast::Expr> + AstNode>(text: &str) -> E {
ast_from_text(&format!("const C: () = {text};"))
}
+fn block_whitespace(after: &impl AstNode) -> &'static str {
+ if after.syntax().text().contains_char('\n') { "\n" } else { " " }
+}
pub fn expr_let(pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr {
ast_from_text(&format!("const _: () = while let {pattern} = {expr} {{}};"))
}
@@ -880,8 +887,9 @@ pub fn ref_pat(pat: ast::Pat) -> ast::RefPat {
pub fn match_arm(pat: ast::Pat, guard: Option<ast::MatchGuard>, expr: ast::Expr) -> ast::MatchArm {
let comma_str = if expr.is_block_like() { "" } else { "," };
+ let ws = guard.as_ref().filter(|_| expr.is_block_like()).map_or(" ", block_whitespace);
return match guard {
- Some(guard) => from_text(&format!("{pat} {guard} => {expr}{comma_str}")),
+ Some(guard) => from_text(&format!("{pat} {guard} =>{ws}{expr}{comma_str}")),
None => from_text(&format!("{pat} => {expr}{comma_str}")),
};
@@ -890,19 +898,6 @@ pub fn match_arm(pat: ast::Pat, guard: Option<ast::MatchGuard>, expr: ast::Expr)
}
}
-pub fn match_arm_with_guard(
- pats: impl IntoIterator<Item = ast::Pat>,
- guard: ast::Expr,
- expr: ast::Expr,
-) -> ast::MatchArm {
- let pats_str = pats.into_iter().join(" | ");
- return from_text(&format!("{pats_str} if {guard} => {expr}"));
-
- fn from_text(text: &str) -> ast::MatchArm {
- ast_from_text(&format!("fn f() {{ match () {{{text}}} }}"))
- }
-}
-
pub fn match_guard(condition: ast::Expr) -> ast::MatchGuard {
return from_text(&format!("if {condition}"));
diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs
index 5fe419ad4e..27182191c3 100644
--- a/crates/syntax/src/ast/syntax_factory/constructors.rs
+++ b/crates/syntax/src/ast/syntax_factory/constructors.rs
@@ -75,6 +75,28 @@ impl SyntaxFactory {
make::path_from_text(text).clone_for_update()
}
+ pub fn path_concat(&self, first: ast::Path, second: ast::Path) -> ast::Path {
+ make::path_concat(first, second).clone_for_update()
+ }
+
+ pub fn visibility_pub_crate(&self) -> ast::Visibility {
+ make::visibility_pub_crate().clone_for_update()
+ }
+
+ pub fn visibility_pub(&self) -> ast::Visibility {
+ make::visibility_pub().clone_for_update()
+ }
+
+ pub fn struct_(
+ &self,
+ visibility: Option<ast::Visibility>,
+ strukt_name: ast::Name,
+ generic_param_list: Option<ast::GenericParamList>,
+ field_list: ast::FieldList,
+ ) -> ast::Struct {
+ make::struct_(visibility, strukt_name, generic_param_list, field_list).clone_for_update()
+ }
+
pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr {
let ast::Expr::FieldExpr(ast) =
make::expr_field(receiver.clone(), field).clone_for_update()
@@ -1590,6 +1612,65 @@ impl SyntaxFactory {
ast
}
+ pub fn self_param(&self) -> ast::SelfParam {
+ let ast = make::self_param().clone_for_update();
+
+ if let Some(mut mapping) = self.mappings() {
+ let builder = SyntaxMappingBuilder::new(ast.syntax().clone());
+ builder.finish(&mut mapping);
+ }
+
+ ast
+ }
+
+ pub fn impl_(
+ &self,
+ attrs: impl IntoIterator<Item = ast::Attr>,
+ generic_params: Option<ast::GenericParamList>,
+ generic_args: Option<ast::GenericArgList>,
+ path_type: ast::Type,
+ where_clause: Option<ast::WhereClause>,
+ body: Option<ast::AssocItemList>,
+ ) -> ast::Impl {
+ let (attrs, attrs_input) = iterator_input(attrs);
+ let ast = make::impl_(
+ attrs,
+ generic_params.clone(),
+ generic_args.clone(),
+ path_type.clone(),
+ where_clause.clone(),
+ body.clone(),
+ )
+ .clone_for_update();
+
+ if let Some(mut mapping) = self.mappings() {
+ let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
+ builder.map_children(attrs_input, ast.attrs().map(|attr| attr.syntax().clone()));
+ if let Some(generic_params) = generic_params {
+ builder.map_node(
+ generic_params.syntax().clone(),
+ ast.generic_param_list().unwrap().syntax().clone(),
+ );
+ }
+ builder.map_node(path_type.syntax().clone(), ast.self_ty().unwrap().syntax().clone());
+ if let Some(where_clause) = where_clause {
+ builder.map_node(
+ where_clause.syntax().clone(),
+ ast.where_clause().unwrap().syntax().clone(),
+ );
+ }
+ if let Some(body) = body {
+ builder.map_node(
+ body.syntax().clone(),
+ ast.assoc_item_list().unwrap().syntax().clone(),
+ );
+ }
+ builder.finish(&mut mapping);
+ }
+
+ ast
+ }
+
pub fn ret_type(&self, ty: ast::Type) -> ast::RetType {
let ast = make::ret_type(ty.clone()).clone_for_update();
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 7d95043867..c34475bbdf 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -1689,6 +1689,21 @@ pub mod iter {
}
}
+ pub struct Filter<I, P> {
+ iter: I,
+ predicate: P,
+ }
+ impl<I: Iterator, P> Iterator for Filter<I, P>
+ where
+ P: FnMut(&I::Item) -> bool,
+ {
+ type Item = I::Item;
+
+ fn next(&mut self) -> Option<I::Item> {
+ loop {}
+ }
+ }
+
pub struct FilterMap<I, F> {
iter: I,
f: F,
@@ -1705,7 +1720,7 @@ pub mod iter {
}
}
}
- pub use self::adapters::{FilterMap, Take};
+ pub use self::adapters::{Filter, FilterMap, Take};
mod sources {
mod repeat {
@@ -1756,6 +1771,13 @@ pub mod iter {
{
loop {}
}
+ fn filter<P>(self, predicate: P) -> crate::iter::Filter<Self, P>
+ where
+ Self: Sized,
+ P: FnMut(&Self::Item) -> bool,
+ {
+ loop {}
+ }
fn filter_map<B, F>(self, _f: F) -> crate::iter::FilterMap<Self, F>
where
Self: Sized,
diff --git a/crates/tt/src/storage.rs b/crates/tt/src/storage.rs
index 4dd02d875a..50a1106175 100644
--- a/crates/tt/src/storage.rs
+++ b/crates/tt/src/storage.rs
@@ -488,7 +488,7 @@ impl TopSubtree {
unreachable!()
};
*open_span = S::new(span.open.range, 0);
- *close_span = S::new(span.close.range, 0);
+ *close_span = S::new(span.close.range, 1);
}
dispatch! {
match &mut self.repr => tt => do_it(tt, span)
diff --git a/docs/book/src/non_cargo_based_projects.md b/docs/book/src/non_cargo_based_projects.md
index f1f10ae336..9cc3292444 100644
--- a/docs/book/src/non_cargo_based_projects.md
+++ b/docs/book/src/non_cargo_based_projects.md
@@ -135,7 +135,7 @@ interface Crate {
cfg_groups?: string[];
/// The set of cfgs activated for a given crate, like
/// `["unix", "feature=\"foo\"", "feature=\"bar\""]`.
- cfg: string[];
+ cfg?: string[];
/// Target tuple for this Crate.
///
/// Used when running `rustc --print cfg`
@@ -143,7 +143,7 @@ interface Crate {
target?: string;
/// Environment variables, used for
/// the `env!` macro
- env: { [key: string]: string; };
+ env?: { [key: string]: string; };
/// Extra crate-level attributes applied to this crate.
///
/// rust-analyzer will behave as if these attributes
@@ -155,7 +155,8 @@ interface Crate {
crate_attrs?: string[];
/// Whether the crate is a proc-macro crate.
- is_proc_macro: boolean;
+ /// Defaults to `false` if unspecified.
+ is_proc_macro?: boolean;
/// For proc-macro crates, path to compiled
/// proc-macro (.so file).
proc_macro_dylib_path?: string;
diff --git a/docs/book/src/vs_code.md b/docs/book/src/vs_code.md
index 75f940e69a..69a96156b8 100644
--- a/docs/book/src/vs_code.md
+++ b/docs/book/src/vs_code.md
@@ -98,12 +98,12 @@ some directories, `/usr/bin` will always be mounted under
system-wide installation of Rust, or any other libraries you might want
to link to. Some compilers and libraries can be acquired as Flatpak
SDKs, such as `org.freedesktop.Sdk.Extension.rust-stable` or
-`org.freedesktop.Sdk.Extension.llvm15`.
+`org.freedesktop.Sdk.Extension.llvm21`.
If you use a Flatpak SDK for Rust, it must be in your `PATH`:
- * install the SDK extensions with `flatpak install org.freedesktop.Sdk.Extension.{llvm15,rust-stable}//23.08`
- * enable SDK extensions in the editor with the environment variable `FLATPAK_ENABLE_SDK_EXT=llvm15,rust-stable` (this can be done using flatseal or `flatpak override`)
+ * install the SDK extensions with `flatpak install org.freedesktop.Sdk.Extension.{llvm21,rust-stable}//25.08`
+ * enable SDK extensions in the editor with the environment variable `FLATPAK_ENABLE_SDK_EXT=llvm21,rust-stable` (this can be done using flatseal or `flatpak override`)
If you want to use Flatpak in combination with `rustup`, the following
steps might help:
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 57f6bf69be..be0bdc8d55 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -749,9 +749,9 @@
}
},
"node_modules/@eslint/config-array/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -810,9 +810,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -934,29 +934,6 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@isaacs/balanced-match": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
- "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/@isaacs/brace-expansion": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
- "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@isaacs/balanced-match": "^4.0.1"
- },
- "engines": {
- "node": "20 || >=22"
- }
- },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -1486,7 +1463,6 @@
"integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.25.0",
"@typescript-eslint/types": "8.25.0",
@@ -1852,9 +1828,9 @@
}
},
"node_modules/@vscode/vsce/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -1870,7 +1846,6 @@
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2840,7 +2815,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
- "peer": true,
"engines": {
"node": ">=12"
}
@@ -3322,7 +3296,6 @@
"integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3467,9 +3440,9 @@
}
},
"node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -3911,17 +3884,40 @@
"node": ">=10.13.0"
}
},
+ "node_modules/glob/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+ "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
"node_modules/glob/node_modules/minimatch": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
- "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "@isaacs/brace-expansion": "^5.0.0"
+ "brace-expansion": "^5.0.2"
},
"engines": {
- "node": "20 || >=22"
+ "node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -4410,7 +4406,6 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
"license": "MIT",
- "peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@@ -4655,9 +4650,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"dev": true,
"license": "MIT"
},
@@ -4900,13 +4895,13 @@
}
},
"node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -5584,9 +5579,9 @@
}
},
"node_modules/qs": {
- "version": "6.14.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
- "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -6678,7 +6673,6 @@
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6846,9 +6840,9 @@
}
},
"node_modules/vscode-languageclient/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
diff --git a/lib/lsp-server/src/req_queue.rs b/lib/lsp-server/src/req_queue.rs
index c216864bee..84748bbca8 100644
--- a/lib/lsp-server/src/req_queue.rs
+++ b/lib/lsp-server/src/req_queue.rs
@@ -18,6 +18,12 @@ impl<I, O> Default for ReqQueue<I, O> {
}
}
+impl<I, O> ReqQueue<I, O> {
+ pub fn has_pending(&self) -> bool {
+ self.incoming.has_pending() || self.outgoing.has_pending()
+ }
+}
+
#[derive(Debug)]
pub struct Incoming<I> {
pending: HashMap<RequestId, I>,
@@ -51,6 +57,10 @@ impl<I> Incoming<I> {
pub fn is_completed(&self, id: &RequestId) -> bool {
!self.pending.contains_key(id)
}
+
+ pub fn has_pending(&self) -> bool {
+ !self.pending.is_empty()
+ }
}
impl<O> Outgoing<O> {
@@ -64,4 +74,8 @@ impl<O> Outgoing<O> {
pub fn complete(&mut self, id: RequestId) -> Option<O> {
self.pending.remove(&id)
}
+
+ pub fn has_pending(&self) -> bool {
+ !self.pending.is_empty()
+ }
}
diff --git a/lib/smol_str/CHANGELOG.md b/lib/smol_str/CHANGELOG.md
index 4aa25fa134..6327275d07 100644
--- a/lib/smol_str/CHANGELOG.md
+++ b/lib/smol_str/CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog
+## 0.3.6 - 2026-03-04
+- Fix the `borsh` feature.
+
## 0.3.5 - 2026-01-08
- Optimise `SmolStr::clone` 4-5x speedup inline, 0.5x heap (slow down).
diff --git a/lib/smol_str/Cargo.toml b/lib/smol_str/Cargo.toml
index 4e7844b49e..22068fe841 100644
--- a/lib/smol_str/Cargo.toml
+++ b/lib/smol_str/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "smol_str"
-version = "0.3.5"
+version = "0.3.6"
description = "small-string optimized string type with O(1) clone"
license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/smol_str"
diff --git a/lib/smol_str/src/borsh.rs b/lib/smol_str/src/borsh.rs
index 527ce85a17..b684a4910c 100644
--- a/lib/smol_str/src/borsh.rs
+++ b/lib/smol_str/src/borsh.rs
@@ -29,8 +29,9 @@ impl BorshDeserialize for SmolStr {
}))
} else {
// u8::vec_from_reader always returns Some on success in current implementation
- let vec = u8::vec_from_reader(len, reader)?
- .ok_or_else(|| Error::other("u8::vec_from_reader unexpectedly returned None"))?;
+ let vec = u8::vec_from_reader(len, reader)?.ok_or_else(|| {
+ Error::new(ErrorKind::Other, "u8::vec_from_reader unexpectedly returned None")
+ })?;
Ok(SmolStr::from(String::from_utf8(vec).map_err(|err| {
let msg = err.to_string();
Error::new(ErrorKind::InvalidData, msg)
diff --git a/lib/smol_str/tests/test.rs b/lib/smol_str/tests/test.rs
index 640e7df681..00fab2ee1c 100644
--- a/lib/smol_str/tests/test.rs
+++ b/lib/smol_str/tests/test.rs
@@ -393,7 +393,7 @@ mod test_str_ext {
}
}
-#[cfg(feature = "borsh")]
+#[cfg(all(feature = "borsh", feature = "std"))]
mod borsh_tests {
use borsh::BorshDeserialize;
use smol_str::{SmolStr, ToSmolStr};
diff --git a/rust-version b/rust-version
index a1011c4a0a..7c89bcb9ab 100644
--- a/rust-version
+++ b/rust-version
@@ -1 +1 @@
-ba284f468cd2cda48420251efc991758ec13d450
+f8704be04fe1150527fc2cf21dd44327f0fe87fb
diff --git a/triagebot.toml b/triagebot.toml
index ac4efd0a24..5fd97f52d8 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -24,4 +24,5 @@ labels = ["has-merge-commits", "S-waiting-on-author"]
[transfer]
# Canonicalize issue numbers to avoid closing the wrong issue when upstreaming this subtree
-[canonicalize-issue-links]
+[issue-links]
+check-commits = "uncanonicalized"
diff --git a/xtask/src/codegen/grammar/ast_src.rs b/xtask/src/codegen/grammar/ast_src.rs
index 205072a6ce..564d9cc24e 100644
--- a/xtask/src/codegen/grammar/ast_src.rs
+++ b/xtask/src/codegen/grammar/ast_src.rs
@@ -189,9 +189,9 @@ pub(crate) fn generate_kind_src(
}
}
});
- PUNCT.iter().zip(used_puncts).filter(|(_, used)| !used).for_each(|((punct, _), _)| {
+ if let Some(punct) = PUNCT.iter().zip(used_puncts).find(|(_, used)| !used) {
panic!("Punctuation {punct:?} is not used in grammar");
- });
+ }
keywords.extend(RESERVED.iter().copied());
keywords.sort();
keywords.dedup();