Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #137176 - matthiaskrgr:rollup-eht05gr, r=matthiaskrgr
Rollup of 9 pull requests Successful merges: - #136959 (Simplify switch sources) - #137020 (Pass vendored sources from bootstrap to generate-copyright) - #137073 (boostrap: skip no_std targets in Std doc step) - #137165 (Use `tell` for `<File as Seek>::stream_position`) - #137166 (Update default loongarch code model in docs) - #137168 (correct comment) - #137169 (CI: rfl: move job forward to Linux v6.14-rc3) - #137170 (Allow configuring jemalloc per target) - #137173 (Subtree update of `rust-analyzer`) r? `@ghost` `@rustbot` modify labels: rollup
bors 2025-02-18
parent e2795eb · parent 447b347 · commit f9a1c5f
-rw-r--r--.github/workflows/ci.yaml6
-rw-r--r--Cargo.lock24
-rw-r--r--Cargo.toml10
-rw-r--r--crates/hir-def/src/data.rs4
-rw-r--r--crates/hir-def/src/dyn_map.rs7
-rw-r--r--crates/hir-def/src/expr_store.rs10
-rw-r--r--crates/hir-def/src/expr_store/lower.rs53
-rw-r--r--crates/hir-def/src/expr_store/pretty.rs16
-rw-r--r--crates/hir-def/src/expr_store/tests.rs45
-rw-r--r--crates/hir-def/src/hir.rs12
-rw-r--r--crates/hir-def/src/import_map.rs20
-rw-r--r--crates/hir-def/src/item_scope.rs18
-rw-r--r--crates/hir-def/src/macro_expansion_tests/proc_macros.rs20
-rw-r--r--crates/hir-def/src/nameres/collector.rs24
-rw-r--r--crates/hir-def/src/path.rs10
-rw-r--r--crates/hir-def/src/resolver.rs3
-rw-r--r--crates/hir-expand/src/cfg_process.rs56
-rw-r--r--crates/hir-expand/src/name.rs4
-rw-r--r--crates/hir-ty/src/chalk_db.rs20
-rw-r--r--crates/hir-ty/src/diagnostics/expr.rs4
-rw-r--r--crates/hir-ty/src/infer.rs79
-rw-r--r--crates/hir-ty/src/infer/coerce.rs38
-rw-r--r--crates/hir-ty/src/infer/diagnostics.rs91
-rw-r--r--crates/hir-ty/src/infer/expr.rs263
-rw-r--r--crates/hir-ty/src/infer/pat.rs7
-rw-r--r--crates/hir-ty/src/infer/path.rs168
-rw-r--r--crates/hir-ty/src/lower.rs983
-rw-r--r--crates/hir-ty/src/lower/diagnostics.rs2
-rw-r--r--crates/hir-ty/src/lower/path.rs911
-rw-r--r--crates/hir-ty/src/mir.rs11
-rw-r--r--crates/hir-ty/src/mir/eval/tests.rs33
-rw-r--r--crates/hir-ty/src/mir/lower.rs25
-rw-r--r--crates/hir-ty/src/mir/lower/pattern_matching.rs4
-rw-r--r--crates/hir-ty/src/tests.rs2
-rw-r--r--crates/hir-ty/src/tests/coercion.rs28
-rw-r--r--crates/hir-ty/src/tests/diagnostics.rs50
-rw-r--r--crates/hir-ty/src/tests/method_resolution.rs2
-rw-r--r--crates/hir-ty/src/tests/traits.rs12
-rw-r--r--crates/hir/src/diagnostics.rs71
-rw-r--r--crates/hir/src/from_id.rs1
-rw-r--r--crates/hir/src/has_source.rs4
-rw-r--r--crates/hir/src/lib.rs48
-rw-r--r--crates/hir/src/semantics.rs1
-rw-r--r--crates/hir/src/semantics/child_by_source.rs3
-rw-r--r--crates/hir/src/semantics/source_to_def.rs16
-rw-r--r--crates/hir/src/source_analyzer.rs97
-rw-r--r--crates/ide-assists/src/handlers/add_missing_match_arms.rs136
-rw-r--r--crates/ide-assists/src/handlers/expand_glob_import.rs365
-rw-r--r--crates/ide-assists/src/lib.rs1
-rw-r--r--crates/ide-assists/src/tests/generated.rs23
-rw-r--r--crates/ide-completion/src/completions/postfix.rs91
-rw-r--r--crates/ide-db/src/defs.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/parenthesized_generic_args_without_fn_trait.rs59
-rw-r--r--crates/ide-diagnostics/src/handlers/type_mismatch.rs21
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_field.rs7
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_method.rs53
-rw-r--r--crates/ide-diagnostics/src/handlers/unused_variables.rs13
-rw-r--r--crates/ide-diagnostics/src/lib.rs8
-rw-r--r--crates/ide/src/annotations.rs67
-rw-r--r--crates/ide/src/goto_definition.rs34
-rw-r--r--crates/ide/src/inlay_hints/extern_block.rs19
-rw-r--r--crates/ide/src/inlay_hints/implicit_drop.rs38
-rw-r--r--crates/ide/src/runnables.rs67
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html9
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_general.html15
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs27
-rw-r--r--crates/intern/src/symbol/symbols.rs33
-rw-r--r--crates/load-cargo/src/lib.rs4
-rw-r--r--crates/project-model/src/cargo_workspace.rs3
-rw-r--r--crates/project-model/src/lib.rs10
-rw-r--r--crates/project-model/src/sysroot.rs174
-rw-r--r--crates/project-model/src/tests.rs23
-rw-r--r--crates/project-model/src/workspace.rs405
-rw-r--r--crates/project-model/test_data/output/rust_project_cfg_groups.txt2
-rw-r--r--crates/project-model/test_data/output/rust_project_hello_world_project_model.txt1
-rw-r--r--crates/rust-analyzer/src/cli/rustc_tests.rs12
-rw-r--r--crates/rust-analyzer/src/config.rs16
-rw-r--r--crates/rust-analyzer/src/global_state.rs25
-rw-r--r--crates/rust-analyzer/src/handlers/notification.rs16
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs173
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs14
-rw-r--r--crates/rust-analyzer/src/lib.rs11
-rw-r--r--crates/rust-analyzer/src/lsp/from_proto.rs30
-rw-r--r--crates/rust-analyzer/src/main_loop.rs31
-rw-r--r--crates/rust-analyzer/src/reload.rs5
-rw-r--r--crates/rust-analyzer/tests/slow-tests/main.rs116
-rw-r--r--crates/stdx/src/panic_context.rs40
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs46
-rw-r--r--crates/syntax/src/ast/make.rs3
-rw-r--r--crates/test-fixture/src/lib.rs44
-rw-r--r--crates/test-utils/src/lib.rs9
-rw-r--r--crates/test-utils/src/minicore.rs6
-rw-r--r--crates/vfs-notify/src/lib.rs5
-rw-r--r--crates/vfs/src/lib.rs27
-rw-r--r--docs/book/README.md2
-rw-r--r--docs/book/book.toml11
-rw-r--r--docs/book/src/SUMMARY.md3
-rw-r--r--docs/book/src/assists_generated.md3820
-rw-r--r--docs/book/src/configuration_generated.md4
-rw-r--r--docs/book/src/diagnostics_generated.md516
-rw-r--r--docs/book/src/features_generated.md940
-rw-r--r--docs/book/src/installation.md632
-rw-r--r--docs/book/src/other_editors.md423
-rw-r--r--docs/book/src/rust_analyzer_binary.md74
-rw-r--r--docs/book/src/vs_code.md121
-rw-r--r--editors/code/package-lock.json307
-rw-r--r--editors/code/package.json6
-rw-r--r--editors/code/walkthrough-setup-tips.md1
-rw-r--r--rust-version2
-rw-r--r--xtask/src/codegen.rs2
112 files changed, 9573 insertions, 2949 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index ec33009239..81b55712d7 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -64,7 +64,11 @@ jobs:
run: |
rustup update --no-self-update ${{ env.RUST_CHANNEL }}
rustup default ${{ env.RUST_CHANNEL }}
- rustup component add --toolchain ${{ env.RUST_CHANNEL }} rustfmt rust-src
+ rustup component add --toolchain ${{ env.RUST_CHANNEL }} rust-src
+ # We always use a nightly rustfmt, regardless of channel, because we need
+ # --file-lines.
+ rustup toolchain add nightly --profile minimal
+ rustup component add --toolchain nightly rustfmt
# https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/rust.json
- name: Install Rust Problem Matcher
if: matrix.os == 'ubuntu-latest'
diff --git a/Cargo.lock b/Cargo.lock
index 98268d8f84..6d7f6042de 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1514,9 +1514,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_abi"
-version = "0.94.0"
+version = "0.95.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fa4333df7b71217edb44a36702cafd2bcfc9850677bdf78b32c9f2c98e5df40"
+checksum = "b40c4e339b71a8f075a829b1acaf32f870a11b466d9b8623d50b0ce33e65af95"
dependencies = [
"bitflags 2.7.0",
"ra-ap-rustc_index",
@@ -1525,9 +1525,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_index"
-version = "0.94.0"
+version = "0.95.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d200275ff3d952cc11108f4dc6a692473659758623d63f2bdcea6104a7f1cec8"
+checksum = "872072e2ba11d11147ebe9fde1608fe7f7d9b5c51dac524af28ee07c6dade468"
dependencies = [
"ra-ap-rustc_index_macros",
"smallvec",
@@ -1535,9 +1535,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_index_macros"
-version = "0.94.0"
+version = "0.95.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06eb63df8c1ce2dcb07647305bed811c9c5ebd157def01a81c1b9479b8592b3b"
+checksum = "ffcd77debcaf2ad690a57c2d041c11eb33fe66869754b2c5f35c52954b46af0c"
dependencies = [
"proc-macro2",
"quote",
@@ -1546,9 +1546,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_lexer"
-version = "0.94.0"
+version = "0.95.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7a4d402b2f85650e8c1f78e2e2defc241b03948d6e30d9f5254c9b82755cc4d"
+checksum = "49265cdf8823f8d246e476c79c60bd6e5b551c81ae76e1c8d6a5e0dc73df0bca"
dependencies = [
"memchr",
"unicode-properties",
@@ -1557,9 +1557,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_parse_format"
-version = "0.94.0"
+version = "0.95.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a23a382dbe392beb26360c1a8ce9193155ef74eeac59bcda0fa0a233e047323a"
+checksum = "b3da239fdc971176de0db45cb631d71475b52033a3d0027d91964da7be89eee6"
dependencies = [
"ra-ap-rustc_index",
"ra-ap-rustc_lexer",
@@ -1567,9 +1567,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_pattern_analysis"
-version = "0.94.0"
+version = "0.95.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d746955d67f315ab79767f1d0bbc17fee4f0970d4a00b9ad76bf09cc7d3cd17e"
+checksum = "56057d08fdfa0d95494e461bbdd5d4b3fdb349cca6be05ad7759bc964be1b8d4"
dependencies = [
"ra-ap-rustc_index",
"rustc-hash 2.0.0",
diff --git a/Cargo.toml b/Cargo.toml
index 44c628e829..924ffb8dd2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -87,11 +87,11 @@ vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
vfs = { path = "./crates/vfs", version = "0.0.0" }
edition = { path = "./crates/edition", version = "0.0.0" }
-ra-ap-rustc_lexer = { version = "0.94", default-features = false }
-ra-ap-rustc_parse_format = { version = "0.94", default-features = false }
-ra-ap-rustc_index = { version = "0.94", default-features = false }
-ra-ap-rustc_abi = { version = "0.94", default-features = false }
-ra-ap-rustc_pattern_analysis = { version = "0.94", default-features = false }
+ra-ap-rustc_lexer = { version = "0.95", default-features = false }
+ra-ap-rustc_parse_format = { version = "0.95", default-features = false }
+ra-ap-rustc_index = { version = "0.95", default-features = false }
+ra-ap-rustc_abi = { version = "0.95", default-features = false }
+ra-ap-rustc_pattern_analysis = { version = "0.95", default-features = false }
# local crates that aren't published to crates.io. These should not have versions.
diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs
index c5bbd4edba..bec6627877 100644
--- a/crates/hir-def/src/data.rs
+++ b/crates/hir-def/src/data.rs
@@ -250,6 +250,7 @@ bitflags::bitflags! {
const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS = 1 << 3;
const SKIP_ARRAY_DURING_METHOD_DISPATCH = 1 << 4;
const SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH = 1 << 5;
+ const RUSTC_PAREN_SUGAR = 1 << 6;
}
}
@@ -294,6 +295,9 @@ impl TraitData {
if attrs.by_key(&sym::rustc_has_incoherent_inherent_impls).exists() {
flags |= TraitFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS;
}
+ if attrs.by_key(&sym::rustc_paren_sugar).exists() {
+ flags |= TraitFlags::RUSTC_PAREN_SUGAR;
+ }
let mut skip_array_during_method_dispatch =
attrs.by_key(&sym::rustc_skip_array_during_method_dispatch).exists();
diff --git a/crates/hir-def/src/dyn_map.rs b/crates/hir-def/src/dyn_map.rs
index 0f73595347..e9318d146d 100644
--- a/crates/hir-def/src/dyn_map.rs
+++ b/crates/hir-def/src/dyn_map.rs
@@ -31,9 +31,9 @@ pub mod keys {
use crate::{
dyn_map::{DynMap, Policy},
- BlockId, ConstId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, ImplId,
- LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId,
- TraitId, TypeAliasId, TypeOrConstParamId, UnionId, UseId,
+ BlockId, ConstId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId, FieldId, FunctionId,
+ ImplId, LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId,
+ TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, UnionId, UseId,
};
pub type Key<K, V> = crate::dyn_map::Key<AstPtr<K>, V, AstPtrPolicy<K, V>>;
@@ -44,6 +44,7 @@ pub mod keys {
pub const STATIC: Key<ast::Static, StaticId> = Key::new();
pub const TYPE_ALIAS: Key<ast::TypeAlias, TypeAliasId> = Key::new();
pub const IMPL: Key<ast::Impl, ImplId> = Key::new();
+ pub const EXTERN_BLOCK: Key<ast::ExternBlock, ExternBlockId> = Key::new();
pub const TRAIT: Key<ast::Trait, TraitId> = Key::new();
pub const TRAIT_ALIAS: Key<ast::TraitAlias, TraitAliasId> = Key::new();
pub const STRUCT: Key<ast::Struct, StructId> = Key::new();
diff --git a/crates/hir-def/src/expr_store.rs b/crates/hir-def/src/expr_store.rs
index 9df6eaade7..5ff6a7ffe5 100644
--- a/crates/hir-def/src/expr_store.rs
+++ b/crates/hir-def/src/expr_store.rs
@@ -112,9 +112,9 @@ pub struct ExpressionStoreSourceMap {
// AST expressions can create patterns in destructuring assignments. Therefore, `ExprSource` can also map
// to `PatId`, and `PatId` can also map to `ExprSource` (the other way around is unaffected).
expr_map: FxHashMap<ExprSource, ExprOrPatId>,
- expr_map_back: ArenaMap<ExprId, ExprSource>,
+ expr_map_back: ArenaMap<ExprId, ExprOrPatSource>,
- pat_map: FxHashMap<PatSource, PatId>,
+ pat_map: FxHashMap<PatSource, ExprOrPatId>,
pat_map_back: ArenaMap<PatId, ExprOrPatSource>,
label_map: FxHashMap<LabelSource, LabelId>,
@@ -606,12 +606,12 @@ impl Index<TypeRefId> for ExpressionStore {
impl ExpressionStoreSourceMap {
pub fn expr_or_pat_syntax(&self, id: ExprOrPatId) -> Result<ExprOrPatSource, SyntheticSyntax> {
match id {
- ExprOrPatId::ExprId(id) => self.expr_syntax(id).map(|it| it.map(AstPtr::wrap_left)),
+ ExprOrPatId::ExprId(id) => self.expr_syntax(id),
ExprOrPatId::PatId(id) => self.pat_syntax(id),
}
}
- pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprSource, SyntheticSyntax> {
+ pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprOrPatSource, SyntheticSyntax> {
self.expr_map_back.get(expr).cloned().ok_or(SyntheticSyntax)
}
@@ -633,7 +633,7 @@ impl ExpressionStoreSourceMap {
self.pat_map_back.get(pat).cloned().ok_or(SyntheticSyntax)
}
- pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<PatId> {
+ pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<ExprOrPatId> {
self.pat_map.get(&node.map(AstPtr::new)).cloned()
}
diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs
index 88f770da02..6e505a6b11 100644
--- a/crates/hir-def/src/expr_store/lower.rs
+++ b/crates/hir-def/src/expr_store/lower.rs
@@ -44,8 +44,8 @@ use crate::{
FormatPlaceholder, FormatSign, FormatTrait,
},
Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind,
- Expr, ExprId, Item, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability,
- OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
+ Expr, ExprId, Item, Label, LabelId, Literal, MatchArm, Movability, OffsetOf, Pat, PatId,
+ RecordFieldPat, RecordLitField, Statement,
},
item_scope::BuiltinShadowMode,
lang_item::LangItem,
@@ -1784,23 +1784,33 @@ impl ExprCollector<'_> {
self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| {
this.collect_pat_opt(expanded_pat, binding_list)
});
- self.source_map.pat_map.insert(src, pat);
+ self.source_map.pat_map.insert(src, pat.into());
return pat;
}
None => Pat::Missing,
},
- // FIXME: implement in a way that also builds source map and calculates assoc resolutions in type inference.
ast::Pat::RangePat(p) => {
- let mut range_part_lower = |p: Option<ast::Pat>| {
- p.and_then(|it| match &it {
- ast::Pat::LiteralPat(it) => {
- Some(Box::new(LiteralOrConst::Literal(pat_literal_to_hir(it)?.0)))
+ let mut range_part_lower = |p: Option<ast::Pat>| -> Option<ExprId> {
+ p.and_then(|it| {
+ let ptr = PatPtr::new(&it);
+ match &it {
+ ast::Pat::LiteralPat(it) => Some(self.alloc_expr_from_pat(
+ Expr::Literal(pat_literal_to_hir(it)?.0),
+ ptr,
+ )),
+ ast::Pat::IdentPat(ident) if ident.is_simple_ident() => ident
+ .name()
+ .map(|name| name.as_name())
+ .map(Path::from)
+ .map(|path| self.alloc_expr_from_pat(Expr::Path(path), ptr)),
+ ast::Pat::PathPat(p) => p
+ .path()
+ .and_then(|path| self.parse_path(path))
+ .map(|parsed| self.alloc_expr_from_pat(Expr::Path(parsed), ptr)),
+ // We only need to handle literal, ident (if bare) and path patterns here,
+ // as any other pattern as a range pattern operand is semantically invalid.
+ _ => None,
}
- pat @ (ast::Pat::IdentPat(_) | ast::Pat::PathPat(_)) => {
- let subpat = self.collect_pat(pat.clone(), binding_list);
- Some(Box::new(LiteralOrConst::Const(subpat)))
- }
- _ => None,
})
};
let start = range_part_lower(p.start());
@@ -1863,7 +1873,7 @@ impl ExprCollector<'_> {
}
});
if let Some(pat) = pat.left() {
- self.source_map.pat_map.insert(src, pat);
+ self.source_map.pat_map.insert(src, pat.into());
}
pat
}
@@ -2490,7 +2500,7 @@ impl ExprCollector<'_> {
fn alloc_expr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId {
let src = self.expander.in_file(ptr);
let id = self.store.exprs.alloc(expr);
- self.source_map.expr_map_back.insert(id, src);
+ self.source_map.expr_map_back.insert(id, src.map(AstPtr::wrap_left));
self.source_map.expr_map.insert(src, id.into());
id
}
@@ -2502,7 +2512,7 @@ impl ExprCollector<'_> {
fn alloc_expr_desugared_with_ptr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId {
let src = self.expander.in_file(ptr);
let id = self.store.exprs.alloc(expr);
- self.source_map.expr_map_back.insert(id, src);
+ self.source_map.expr_map_back.insert(id, src.map(AstPtr::wrap_left));
// We intentionally don't fill this as it could overwrite a non-desugared entry
// self.source_map.expr_map.insert(src, id);
id
@@ -2526,11 +2536,20 @@ impl ExprCollector<'_> {
self.source_map.pat_map_back.insert(id, src.map(AstPtr::wrap_left));
id
}
+
+ fn alloc_expr_from_pat(&mut self, expr: Expr, ptr: PatPtr) -> ExprId {
+ let src = self.expander.in_file(ptr);
+ let id = self.store.exprs.alloc(expr);
+ self.source_map.pat_map.insert(src, id.into());
+ self.source_map.expr_map_back.insert(id, src.map(AstPtr::wrap_right));
+ id
+ }
+
fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId {
let src = self.expander.in_file(ptr);
let id = self.store.pats.alloc(pat);
self.source_map.pat_map_back.insert(id, src.map(AstPtr::wrap_right));
- self.source_map.pat_map.insert(src, id);
+ self.source_map.pat_map.insert(src, id.into());
id
}
// FIXME: desugared pats don't have ptr, that's wrong and should be fixed somehow.
diff --git a/crates/hir-def/src/expr_store/pretty.rs b/crates/hir-def/src/expr_store/pretty.rs
index 6a0b1e5197..82ad756dc2 100644
--- a/crates/hir-def/src/expr_store/pretty.rs
+++ b/crates/hir-def/src/expr_store/pretty.rs
@@ -6,10 +6,7 @@ use itertools::Itertools;
use span::Edition;
use crate::{
- hir::{
- Array, BindingAnnotation, CaptureBy, ClosureKind, Literal, LiteralOrConst, Movability,
- Statement,
- },
+ hir::{Array, BindingAnnotation, CaptureBy, ClosureKind, Literal, Movability, Statement},
pretty::{print_generic_args, print_path, print_type_ref},
};
@@ -656,11 +653,11 @@ impl Printer<'_> {
}
Pat::Range { start, end } => {
if let Some(start) = start {
- self.print_literal_or_const(start);
+ self.print_expr(*start);
}
w!(self, "..=");
if let Some(end) = end {
- self.print_literal_or_const(end);
+ self.print_expr(*end);
}
}
Pat::Slice { prefix, slice, suffix } => {
@@ -757,13 +754,6 @@ impl Printer<'_> {
}
}
- fn print_literal_or_const(&mut self, literal_or_const: &LiteralOrConst) {
- match literal_or_const {
- LiteralOrConst::Literal(l) => self.print_literal(l),
- LiteralOrConst::Const(c) => self.print_pat(*c),
- }
- }
-
fn print_literal(&mut self, literal: &Literal) {
match literal {
Literal::String(it) => w!(self, "{:?}", it),
diff --git a/crates/hir-def/src/expr_store/tests.rs b/crates/hir-def/src/expr_store/tests.rs
index 9bf1ddb479..16bf46d3e3 100644
--- a/crates/hir-def/src/expr_store/tests.rs
+++ b/crates/hir-def/src/expr_store/tests.rs
@@ -1,11 +1,10 @@
mod block;
+use crate::{hir::MatchArm, test_db::TestDB, ModuleDefId};
use expect_test::{expect, Expect};
use la_arena::RawIdx;
use test_fixture::WithFixture;
-use crate::{test_db::TestDB, ModuleDefId};
-
use super::*;
fn lower(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (TestDB, Arc<Body>, DefWithBodyId) {
@@ -460,3 +459,45 @@ async fn foo(a: (), b: i32) -> u32 {
expect!["fn foo(�: (), �: i32) -> impl ::core::future::Future::<Output = u32> �"]
.assert_eq(&printed);
}
+
+#[test]
+fn range_bounds_are_hir_exprs() {
+ let (_, body, _) = lower(
+ r#"
+pub const L: i32 = 6;
+mod x {
+ pub const R: i32 = 100;
+}
+const fn f(x: i32) -> i32 {
+ match x {
+ -1..=5 => x * 10,
+ L..=x::R => x * 100,
+ _ => x,
+ }
+}"#,
+ );
+
+ let mtch_arms = body
+ .exprs
+ .iter()
+ .find_map(|(_, expr)| {
+ if let Expr::Match { arms, .. } = expr {
+ return Some(arms);
+ }
+
+ None
+ })
+ .unwrap();
+
+ let MatchArm { pat, .. } = mtch_arms[1];
+ match body.pats[pat] {
+ Pat::Range { start, end } => {
+ let hir_start = &body.exprs[start.unwrap()];
+ let hir_end = &body.exprs[end.unwrap()];
+
+ assert!(matches!(hir_start, Expr::Path { .. }));
+ assert!(matches!(hir_end, Expr::Path { .. }));
+ }
+ _ => {}
+ }
+}
diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs
index 0dcddf162b..494644d8ef 100644
--- a/crates/hir-def/src/hir.rs
+++ b/crates/hir-def/src/hir.rs
@@ -55,12 +55,20 @@ impl ExprOrPatId {
}
}
+ pub fn is_expr(&self) -> bool {
+ matches!(self, Self::ExprId(_))
+ }
+
pub fn as_pat(self) -> Option<PatId> {
match self {
Self::PatId(v) => Some(v),
_ => None,
}
}
+
+ pub fn is_pat(&self) -> bool {
+ matches!(self, Self::PatId(_))
+ }
}
stdx::impl_from!(ExprId, PatId for ExprOrPatId);
@@ -571,8 +579,8 @@ pub enum Pat {
ellipsis: bool,
},
Range {
- start: Option<Box<LiteralOrConst>>,
- end: Option<Box<LiteralOrConst>>,
+ start: Option<ExprId>,
+ end: Option<ExprId>,
},
Slice {
prefix: Box<[PatId]>,
diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs
index 6137bd34d6..d43776b8a6 100644
--- a/crates/hir-def/src/import_map.rs
+++ b/crates/hir-def/src/import_map.rs
@@ -320,7 +320,7 @@ impl SearchMode {
};
match m {
Some((index, _)) => {
- name = &name[index + 1..];
+ name = name[index..].strip_prefix(|_: char| true).unwrap_or_default();
true
}
None => false,
@@ -1039,4 +1039,22 @@ pub mod fmt {
"#]],
);
}
+
+ #[test]
+ fn unicode_fn_name() {
+ let ra_fixture = r#"
+ //- /main.rs crate:main deps:dep
+ //- /dep.rs crate:dep
+ pub fn あい() {}
+ "#;
+
+ check_search(
+ ra_fixture,
+ "main",
+ Query::new("あ".to_owned()).fuzzy(),
+ expect![[r#"
+ dep::あい (f)
+ "#]],
+ );
+ }
}
diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs
index 65a39c5656..0ca1eb9bcf 100644
--- a/crates/hir-def/src/item_scope.rs
+++ b/crates/hir-def/src/item_scope.rs
@@ -18,8 +18,8 @@ use crate::{
db::DefDatabase,
per_ns::{Item, MacrosItem, PerNs, TypesItem, ValuesItem},
visibility::{Visibility, VisibilityExplicitness},
- AdtId, BuiltinType, ConstId, ExternCrateId, FxIndexMap, HasModule, ImplId, LocalModuleId,
- Lookup, MacroId, ModuleDefId, ModuleId, TraitId, UseId,
+ AdtId, BuiltinType, ConstId, ExternBlockId, ExternCrateId, FxIndexMap, HasModule, ImplId,
+ LocalModuleId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId, UseId,
};
#[derive(Debug, Default)]
@@ -158,6 +158,8 @@ pub struct ItemScope {
declarations: Vec<ModuleDefId>,
impls: Vec<ImplId>,
+ #[allow(clippy::box_collection)]
+ extern_blocks: Option<Box<Vec<ExternBlockId>>>,
unnamed_consts: Vec<ConstId>,
/// Traits imported via `use Trait as _;`.
unnamed_trait_imports: FxHashMap<TraitId, Item<()>>,
@@ -319,6 +321,10 @@ impl ItemScope {
self.extern_crate_decls.iter().copied()
}
+ pub fn extern_blocks(&self) -> impl Iterator<Item = ExternBlockId> + '_ {
+ self.extern_blocks.iter().flat_map(|it| it.iter()).copied()
+ }
+
pub fn use_decls(&self) -> impl ExactSizeIterator<Item = UseId> + '_ {
self.use_decls.iter().copied()
}
@@ -469,6 +475,10 @@ impl ItemScope {
self.impls.push(imp);
}
+ pub(crate) fn define_extern_block(&mut self, extern_block: ExternBlockId) {
+ self.extern_blocks.get_or_insert_default().push(extern_block);
+ }
+
pub(crate) fn define_extern_crate_decl(&mut self, extern_crate: ExternCrateId) {
self.extern_crate_decls.push(extern_crate);
}
@@ -806,7 +816,11 @@ impl ItemScope {
use_imports_types,
use_imports_macros,
macro_invocations,
+ extern_blocks,
} = self;
+ if let Some(it) = extern_blocks {
+ it.shrink_to_fit();
+ }
types.shrink_to_fit();
values.shrink_to_fit();
macros.shrink_to_fit();
diff --git a/crates/hir-def/src/macro_expansion_tests/proc_macros.rs b/crates/hir-def/src/macro_expansion_tests/proc_macros.rs
index 70e3e1ed4e..a43c0eb9d7 100644
--- a/crates/hir-def/src/macro_expansion_tests/proc_macros.rs
+++ b/crates/hir-def/src/macro_expansion_tests/proc_macros.rs
@@ -5,7 +5,7 @@
//! in-memory macros.
use expect_test::expect;
-use crate::macro_expansion_tests::check;
+use crate::macro_expansion_tests::{check, check_errors};
#[test]
fn attribute_macro_attr_censoring() {
@@ -216,3 +216,21 @@ struct S;
#[doc = "doc attr"] struct S;"##]],
);
}
+
+#[test]
+fn cfg_evaluated_before_attr_macros() {
+ check_errors(
+ r#"
+//- proc_macros: disallow_cfg
+
+use proc_macros::disallow_cfg;
+
+#[disallow_cfg] #[cfg(false)] fn foo() {}
+// True cfg are kept.
+// #[disallow_cfg] #[cfg(true)] fn bar() {}
+#[disallow_cfg] #[cfg_attr(false, inline)] fn baz() {}
+#[disallow_cfg] #[cfg_attr(true, inline)] fn qux() {}
+ "#,
+ expect![[r#""#]],
+ );
+}
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index 06276335b7..254c137991 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -1759,16 +1759,20 @@ impl ModCollector<'_, '_> {
);
}
}
- ModItem::ExternBlock(block) => self.collect(
- &self.item_tree[block].children,
- ItemContainerId::ExternBlockId(
- ExternBlockLoc {
- container: module,
- id: ItemTreeId::new(self.tree_id, block),
- }
- .intern(db),
- ),
- ),
+ ModItem::ExternBlock(block) => {
+ let extern_block_id = ExternBlockLoc {
+ container: module,
+ id: ItemTreeId::new(self.tree_id, block),
+ }
+ .intern(db);
+ self.def_collector.def_map.modules[self.module_id]
+ .scope
+ .define_extern_block(extern_block_id);
+ self.collect(
+ &self.item_tree[block].children,
+ ItemContainerId::ExternBlockId(extern_block_id),
+ )
+ }
ModItem::MacroCall(mac) => self.collect_macro_call(&self.item_tree[mac], container),
ModItem::MacroRules(id) => self.collect_macro_rules(id, module),
ModItem::Macro2(id) => self.collect_macro_def(id, module),
diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs
index e59c37104d..e6c2504d07 100644
--- a/crates/hir-def/src/path.rs
+++ b/crates/hir-def/src/path.rs
@@ -173,10 +173,7 @@ impl Path {
segments: path.mod_path().segments(),
generic_args: Some(path.generic_args()),
},
- Path::LangItem(_, seg) => PathSegments {
- segments: seg.as_ref().map_or(&[], |seg| std::slice::from_ref(seg)),
- generic_args: None,
- },
+ Path::LangItem(_, seg) => PathSegments { segments: seg.as_slice(), generic_args: None },
}
}
@@ -240,6 +237,11 @@ pub struct PathSegment<'a> {
pub args_and_bindings: Option<&'a GenericArgs>,
}
+impl PathSegment<'_> {
+ pub const MISSING: PathSegment<'static> =
+ PathSegment { name: &Name::missing(), args_and_bindings: None };
+}
+
#[derive(Debug, Clone, Copy)]
pub struct PathSegments<'a> {
segments: &'a [Name],
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index 7e13ae2f7a..9dfb6e3cc4 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -327,8 +327,9 @@ impl Resolver {
| LangItemTarget::ImplDef(_)
| LangItemTarget::Static(_) => return None,
};
+ // Remaining segments start from 0 because lang paths have no segments other than the remaining.
return Some((
- ResolveValueResult::Partial(type_ns, 1, None),
+ ResolveValueResult::Partial(type_ns, 0, None),
ResolvePathResultPrefixInfo::default(),
));
}
diff --git a/crates/hir-expand/src/cfg_process.rs b/crates/hir-expand/src/cfg_process.rs
index 01a3103af8..626a82ae08 100644
--- a/crates/hir-expand/src/cfg_process.rs
+++ b/crates/hir-expand/src/cfg_process.rs
@@ -201,9 +201,6 @@ pub(crate) fn process_cfg_attrs(
MacroDefKind::BuiltInAttr(_, expander) => expander.is_derive(),
_ => false,
};
- if !is_derive {
- return None;
- }
let mut remove = FxHashSet::default();
let item = ast::Item::cast(node.clone())?;
@@ -220,28 +217,43 @@ pub(crate) fn process_cfg_attrs(
}
}
}
- match item {
- ast::Item::Struct(it) => match it.field_list()? {
- ast::FieldList::RecordFieldList(fields) => {
- process_has_attrs_with_possible_comma(db, fields.fields(), loc.krate, &mut remove)?;
+
+ if is_derive {
+ // Only derives get their code cfg-clean, normal attribute macros process only the cfg at their level
+ // (cfg_attr is handled above, cfg is handled in the def map).
+ match item {
+ ast::Item::Struct(it) => match it.field_list()? {
+ ast::FieldList::RecordFieldList(fields) => {
+ process_has_attrs_with_possible_comma(
+ db,
+ fields.fields(),
+ loc.krate,
+ &mut remove,
+ )?;
+ }
+ ast::FieldList::TupleFieldList(fields) => {
+ process_has_attrs_with_possible_comma(
+ db,
+ fields.fields(),
+ loc.krate,
+ &mut remove,
+ )?;
+ }
+ },
+ ast::Item::Enum(it) => {
+ process_enum(db, it.variant_list()?, loc.krate, &mut remove)?;
}
- ast::FieldList::TupleFieldList(fields) => {
- process_has_attrs_with_possible_comma(db, fields.fields(), loc.krate, &mut remove)?;
+ ast::Item::Union(it) => {
+ process_has_attrs_with_possible_comma(
+ db,
+ it.record_field_list()?.fields(),
+ loc.krate,
+ &mut remove,
+ )?;
}
- },
- ast::Item::Enum(it) => {
- process_enum(db, it.variant_list()?, loc.krate, &mut remove)?;
- }
- ast::Item::Union(it) => {
- process_has_attrs_with_possible_comma(
- db,
- it.record_field_list()?.fields(),
- loc.krate,
- &mut remove,
- )?;
+ // FIXME: Implement for other items if necessary. As we do not support #[cfg_eval] yet, we do not need to implement it for now
+ _ => {}
}
- // FIXME: Implement for other items if necessary. As we do not support #[cfg_eval] yet, we do not need to implement it for now
- _ => {}
}
Some(remove)
}
diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs
index 21e5fb5ef9..0758bd4515 100644
--- a/crates/hir-expand/src/name.rs
+++ b/crates/hir-expand/src/name.rs
@@ -142,8 +142,8 @@ impl Name {
/// Ideally, we want a `gensym` semantics for missing names -- each missing
/// name is equal only to itself. It's not clear how to implement this in
/// salsa though, so we punt on that bit for a moment.
- pub fn missing() -> Name {
- Name { symbol: sym::MISSING_NAME.clone(), ctx: () }
+ pub const fn missing() -> Name {
+ Name { symbol: sym::consts::MISSING_NAME, ctx: () }
}
/// Returns true if this is a fake name for things missing in the source code. See
diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs
index c8ff6cba3d..6d4753ea38 100644
--- a/crates/hir-ty/src/chalk_db.rs
+++ b/crates/hir-ty/src/chalk_db.rs
@@ -768,23 +768,21 @@ pub(crate) fn adt_datum_query(
phantom_data,
};
- // this slows down rust-analyzer by quite a bit unfortunately, so enabling this is currently not worth it
- let _variant_id_to_fields = |id: VariantId| {
+ let variant_id_to_fields = |id: VariantId| {
let variant_data = &id.variant_data(db.upcast());
- let fields = if variant_data.fields().is_empty() {
+ let fields = if variant_data.fields().is_empty() || bound_vars_subst.is_empty(Interner) {
vec![]
} else {
- let field_types = db.field_types(id);
- variant_data
- .fields()
- .iter()
- .map(|(idx, _)| field_types[idx].clone().substitute(Interner, &bound_vars_subst))
- .filter(|it| !it.contains_unknown())
- .collect()
+ // HACK: provide full struct type info slows down rust-analyzer by quite a bit unfortunately,
+ // so we trick chalk into thinking that our struct impl Unsize
+ if let Some(ty) = bound_vars_subst.at(Interner, 0).ty(Interner) {
+ vec![ty.clone()]
+ } else {
+ vec![]
+ }
};
rust_ir::AdtVariantDatum { fields }
};
- let variant_id_to_fields = |_: VariantId| rust_ir::AdtVariantDatum { fields: vec![] };
let (kind, variants) = match adt_id {
hir_def::AdtId::StructId(id) => {
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index 0b5f131924..59aaf85164 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -440,7 +440,9 @@ impl ExprValidator {
return;
};
let root = source_ptr.file_syntax(db.upcast());
- let ast::Expr::IfExpr(if_expr) = source_ptr.value.to_node(&root) else {
+ let either::Left(ast::Expr::IfExpr(if_expr)) =
+ source_ptr.value.to_node(&root)
+ else {
return;
};
let mut top_if_expr = if_expr;
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 617ebba881..0cb7002f44 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -16,7 +16,7 @@
pub(crate) mod cast;
pub(crate) mod closure;
mod coerce;
-mod diagnostics;
+pub(crate) mod diagnostics;
mod expr;
mod mutability;
mod pat;
@@ -236,7 +236,7 @@ pub enum InferenceDiagnostic {
name: Name,
/// Contains the type the field resolves to
field_with_same_name: Option<Ty>,
- assoc_func_with_same_name: Option<AssocItemId>,
+ assoc_func_with_same_name: Option<FunctionId>,
},
UnresolvedAssocItem {
id: ExprOrPatId,
@@ -1239,7 +1239,29 @@ impl<'a> InferenceContext<'a> {
}
fn write_expr_adj(&mut self, expr: ExprId, adjustments: Vec<Adjustment>) {
- self.result.expr_adjustments.insert(expr, adjustments);
+ if adjustments.is_empty() {
+ return;
+ }
+ match self.result.expr_adjustments.entry(expr) {
+ std::collections::hash_map::Entry::Occupied(mut entry) => {
+ match (&mut entry.get_mut()[..], &adjustments[..]) {
+ (
+ [Adjustment { kind: Adjust::NeverToAny, target }],
+ [.., Adjustment { target: new_target, .. }],
+ ) => {
+ // NeverToAny coercion can target any type, so instead of adding a new
+ // adjustment on top we can change the target.
+ *target = new_target.clone();
+ }
+ _ => {
+ *entry.get_mut() = adjustments;
+ }
+ }
+ }
+ std::collections::hash_map::Entry::Vacant(entry) => {
+ entry.insert(adjustments);
+ }
+ }
}
fn write_method_resolution(&mut self, expr: ExprId, func: FunctionId, subst: Substitution) {
@@ -1480,21 +1502,22 @@ impl<'a> InferenceContext<'a> {
&self.diagnostics,
InferenceTyDiagnosticSource::Body,
);
+ let mut path_ctx = ctx.at_path(path, node);
let (resolution, unresolved) = if value_ns {
- let Some(res) = ctx.resolve_path_in_value_ns(path, node, HygieneId::ROOT) else {
+ let Some(res) = path_ctx.resolve_path_in_value_ns(HygieneId::ROOT) else {
return (self.err_ty(), None);
};
match res {
ResolveValueResult::ValueNs(value, _) => match value {
ValueNs::EnumVariantId(var) => {
- let substs = ctx.substs_from_path(path, var.into(), true);
+ let substs = path_ctx.substs_from_path(var.into(), true);
drop(ctx);
let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
return (ty, Some(var.into()));
}
ValueNs::StructId(strukt) => {
- let substs = ctx.substs_from_path(path, strukt.into(), true);
+ let substs = path_ctx.substs_from_path(strukt.into(), true);
drop(ctx);
let ty = self.db.ty(strukt.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
@@ -1509,7 +1532,7 @@ impl<'a> InferenceContext<'a> {
ResolveValueResult::Partial(typens, unresolved, _) => (typens, Some(unresolved)),
}
} else {
- match ctx.resolve_path_in_type_ns(path, node) {
+ match path_ctx.resolve_path_in_type_ns() {
Some((it, idx)) => (it, idx),
None => return (self.err_ty(), None),
}
@@ -1520,21 +1543,21 @@ impl<'a> InferenceContext<'a> {
};
return match resolution {
TypeNs::AdtId(AdtId::StructId(strukt)) => {
- let substs = ctx.substs_from_path(path, strukt.into(), true);
+ let substs = path_ctx.substs_from_path(strukt.into(), true);
drop(ctx);
let ty = self.db.ty(strukt.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
forbid_unresolved_segments((ty, Some(strukt.into())), unresolved)
}
TypeNs::AdtId(AdtId::UnionId(u)) => {
- let substs = ctx.substs_from_path(path, u.into(), true);
+ let substs = path_ctx.substs_from_path(u.into(), true);
drop(ctx);
let ty = self.db.ty(u.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
forbid_unresolved_segments((ty, Some(u.into())), unresolved)
}
TypeNs::EnumVariantId(var) => {
- let substs = ctx.substs_from_path(path, var.into(), true);
+ let substs = path_ctx.substs_from_path(var.into(), true);
drop(ctx);
let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
@@ -1545,31 +1568,32 @@ impl<'a> InferenceContext<'a> {
let substs = generics.placeholder_subst(self.db);
let mut ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs);
- let Some(mut remaining_idx) = unresolved else {
+ let Some(remaining_idx) = unresolved else {
drop(ctx);
return self.resolve_variant_on_alias(ty, None, mod_path);
};
let mut remaining_segments = path.segments().skip(remaining_idx);
+ if remaining_segments.len() >= 2 {
+ path_ctx.ignore_last_segment();
+ }
+
// We need to try resolving unresolved segments one by one because each may resolve
// to a projection, which `TyLoweringContext` cannot handle on its own.
let mut tried_resolving_once = false;
- while !remaining_segments.is_empty() {
- let resolved_segment = path.segments().get(remaining_idx - 1).unwrap();
- let current_segment = remaining_segments.take(1);
-
+ while let Some(current_segment) = remaining_segments.first() {
// If we can resolve to an enum variant, it takes priority over associated type
// of the same name.
if let Some((AdtId::EnumId(id), _)) = ty.as_adt() {
let enum_data = self.db.enum_data(id);
- let name = current_segment.first().unwrap().name;
- if let Some(variant) = enum_data.variant(name) {
+ if let Some(variant) = enum_data.variant(current_segment.name) {
return if remaining_segments.len() == 1 {
(ty, Some(variant.into()))
} else {
// We still have unresolved paths, but enum variants never have
// associated types!
+ // FIXME: Report an error.
(self.err_ty(), None)
};
}
@@ -1578,23 +1602,13 @@ impl<'a> InferenceContext<'a> {
if tried_resolving_once {
// FIXME: with `inherent_associated_types` this is allowed, but our `lower_partly_resolved_path()`
// will need to be updated to err at the correct segment.
- //
- // We need to stop here because otherwise the segment index passed to `lower_partly_resolved_path()`
- // will be incorrect, and that can mess up error reporting.
break;
}
// `lower_partly_resolved_path()` returns `None` as type namespace unless
// `remaining_segments` is empty, which is never the case here. We don't know
// which namespace the new `ty` is in until normalized anyway.
- (ty, _) = ctx.lower_partly_resolved_path(
- node,
- resolution,
- resolved_segment,
- current_segment,
- (remaining_idx - 1) as u32,
- false,
- );
+ (ty, _) = path_ctx.lower_partly_resolved_path(resolution, false);
tried_resolving_once = true;
ty = self.table.insert_type_vars(ty);
@@ -1604,8 +1618,6 @@ impl<'a> InferenceContext<'a> {
return (self.err_ty(), None);
}
- // FIXME(inherent_associated_types): update `resolution` based on `ty` here.
- remaining_idx += 1;
remaining_segments = remaining_segments.skip(1);
}
drop(ctx);
@@ -1621,12 +1633,7 @@ impl<'a> InferenceContext<'a> {
(ty, variant)
}
TypeNs::TypeAliasId(it) => {
- let resolved_seg = match unresolved {
- None => path.segments().last().unwrap(),
- Some(n) => path.segments().get(path.segments().len() - n - 1).unwrap(),
- };
- let substs =
- ctx.substs_from_path_segment(resolved_seg, Some(it.into()), true, None);
+ let substs = path_ctx.substs_from_path_segment(it.into(), true, None);
drop(ctx);
let ty = self.db.ty(it.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs
index 956d4de071..acd86b1f3e 100644
--- a/crates/hir-ty/src/infer/coerce.rs
+++ b/crates/hir-ty/src/infer/coerce.rs
@@ -163,10 +163,27 @@ impl CoerceMany {
// type is a type variable and the new one is `!`, trying it the other
// way around first would mean we make the type variable `!`, instead of
// just marking it as possibly diverging.
- if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty(), CoerceNever::Yes) {
- self.final_ty = Some(res);
- } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty, CoerceNever::Yes) {
+ //
+ // - [Comment from rustc](https://github.com/rust-lang/rust/blob/5ff18d0eaefd1bd9ab8ec33dab2404a44e7631ed/compiler/rustc_hir_typeck/src/coercion.rs#L1334-L1335)
+ // First try to coerce the new expression to the type of the previous ones,
+ // but only if the new expression has no coercion already applied to it.
+ if expr.is_none_or(|expr| !ctx.result.expr_adjustments.contains_key(&expr)) {
+ if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty(), CoerceNever::Yes) {
+ self.final_ty = Some(res);
+ if let Some(expr) = expr {
+ self.expressions.push(expr);
+ }
+ return;
+ }
+ }
+
+ if let Ok((adjustments, res)) =
+ ctx.coerce_inner(&self.merged_ty(), &expr_ty, CoerceNever::Yes)
+ {
self.final_ty = Some(res);
+ for &e in &self.expressions {
+ ctx.write_expr_adj(e, adjustments.clone());
+ }
} else {
match cause {
CoercionCause::Expr(id) => {
@@ -244,14 +261,23 @@ impl InferenceContext<'_> {
// between places and values.
coerce_never: CoerceNever,
) -> Result<Ty, TypeError> {
- let from_ty = self.resolve_ty_shallow(from_ty);
- let to_ty = self.resolve_ty_shallow(to_ty);
- let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty, coerce_never)?;
+ let (adjustments, ty) = self.coerce_inner(from_ty, to_ty, coerce_never)?;
if let Some(expr) = expr {
self.write_expr_adj(expr, adjustments);
}
Ok(ty)
}
+
+ fn coerce_inner(
+ &mut self,
+ from_ty: &Ty,
+ to_ty: &Ty,
+ coerce_never: CoerceNever,
+ ) -> Result<(Vec<Adjustment>, Ty), TypeError> {
+ let from_ty = self.resolve_ty_shallow(from_ty);
+ let to_ty = self.resolve_ty_shallow(to_ty);
+ self.table.coerce(&from_ty, &to_ty, coerce_never)
+ }
}
impl InferenceTable<'_> {
diff --git a/crates/hir-ty/src/infer/diagnostics.rs b/crates/hir-ty/src/infer/diagnostics.rs
index b85378531a..e4f5b5ed37 100644
--- a/crates/hir-ty/src/infer/diagnostics.rs
+++ b/crates/hir-ty/src/infer/diagnostics.rs
@@ -5,16 +5,14 @@
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
-use hir_def::expr_store::HygieneId;
-use hir_def::hir::ExprOrPatId;
-use hir_def::path::{Path, PathSegment, PathSegments};
-use hir_def::resolver::{ResolveValueResult, Resolver, TypeNs};
-use hir_def::type_ref::TypesMap;
-use hir_def::TypeOwnerId;
-
-use crate::db::HirDatabase;
+use either::Either;
+use hir_def::{hir::ExprOrPatId, path::Path, resolver::Resolver, type_ref::TypesMap, TypeOwnerId};
+use la_arena::{Idx, RawIdx};
+
use crate::{
- InferenceDiagnostic, InferenceTyDiagnosticSource, Ty, TyLoweringContext, TyLoweringDiagnostic,
+ db::HirDatabase,
+ lower::path::{PathDiagnosticCallback, PathLoweringContext},
+ InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringContext, TyLoweringDiagnostic,
};
// Unfortunately, this struct needs to use interior mutability (but we encapsulate it)
@@ -44,6 +42,11 @@ impl Diagnostics {
}
}
+pub(crate) struct PathDiagnosticCallbackData<'a> {
+ node: ExprOrPatId,
+ diagnostics: &'a Diagnostics,
+}
+
pub(super) struct InferenceTyLoweringContext<'a> {
ctx: TyLoweringContext<'a>,
diagnostics: &'a Diagnostics,
@@ -51,6 +54,7 @@ pub(super) struct InferenceTyLoweringContext<'a> {
}
impl<'a> InferenceTyLoweringContext<'a> {
+ #[inline]
pub(super) fn new(
db: &'a dyn HirDatabase,
resolver: &'a Resolver,
@@ -62,65 +66,62 @@ impl<'a> InferenceTyLoweringContext<'a> {
Self { ctx: TyLoweringContext::new(db, resolver, types_map, owner), diagnostics, source }
}
- pub(super) fn resolve_path_in_type_ns(
- &mut self,
- path: &Path,
+ #[inline]
+ pub(super) fn at_path<'b>(
+ &'b mut self,
+ path: &'b Path,
node: ExprOrPatId,
- ) -> Option<(TypeNs, Option<usize>)> {
- let diagnostics = self.diagnostics;
- self.ctx.resolve_path_in_type_ns(path, &mut |_, diag| {
- diagnostics.push(InferenceDiagnostic::PathDiagnostic { node, diag })
- })
+ ) -> PathLoweringContext<'b, 'a> {
+ let on_diagnostic = PathDiagnosticCallback {
+ data: Either::Right(PathDiagnosticCallbackData { diagnostics: self.diagnostics, node }),
+ callback: |data, _, diag| {
+ let data = data.as_ref().right().unwrap();
+ data.diagnostics
+ .push(InferenceDiagnostic::PathDiagnostic { node: data.node, diag });
+ },
+ };
+ PathLoweringContext::new(&mut self.ctx, on_diagnostic, path)
}
- pub(super) fn resolve_path_in_value_ns(
- &mut self,
- path: &Path,
- node: ExprOrPatId,
- hygiene_id: HygieneId,
- ) -> Option<ResolveValueResult> {
- let diagnostics = self.diagnostics;
- self.ctx.resolve_path_in_value_ns(path, hygiene_id, &mut |_, diag| {
- diagnostics.push(InferenceDiagnostic::PathDiagnostic { node, diag })
- })
+ #[inline]
+ pub(super) fn at_path_forget_diagnostics<'b>(
+ &'b mut self,
+ path: &'b Path,
+ ) -> PathLoweringContext<'b, 'a> {
+ let on_diagnostic = PathDiagnosticCallback {
+ data: Either::Right(PathDiagnosticCallbackData {
+ diagnostics: self.diagnostics,
+ node: ExprOrPatId::ExprId(Idx::from_raw(RawIdx::from_u32(0))),
+ }),
+ callback: |_data, _, _diag| {},
+ };
+ PathLoweringContext::new(&mut self.ctx, on_diagnostic, path)
}
- pub(super) fn lower_partly_resolved_path(
- &mut self,
- node: ExprOrPatId,
- resolution: TypeNs,
- resolved_segment: PathSegment<'_>,
- remaining_segments: PathSegments<'_>,
- resolved_segment_idx: u32,
- infer_args: bool,
- ) -> (Ty, Option<TypeNs>) {
- let diagnostics = self.diagnostics;
- self.ctx.lower_partly_resolved_path(
- resolution,
- resolved_segment,
- remaining_segments,
- resolved_segment_idx,
- infer_args,
- &mut |_, diag| diagnostics.push(InferenceDiagnostic::PathDiagnostic { node, diag }),
- )
+ #[inline]
+ pub(super) fn forget_diagnostics(&mut self) {
+ self.ctx.diagnostics.clear();
}
}
impl<'a> Deref for InferenceTyLoweringContext<'a> {
type Target = TyLoweringContext<'a>;
+ #[inline]
fn deref(&self) -> &Self::Target {
&self.ctx
}
}
impl DerefMut for InferenceTyLoweringContext<'_> {
+ #[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.ctx
}
}
impl Drop for InferenceTyLoweringContext<'_> {
+ #[inline]
fn drop(&mut self) {
self.diagnostics
.push_ty_diagnostics(self.source, std::mem::take(&mut self.ctx.diagnostics));
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 86e5afdb50..80e3ca1fa2 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -489,78 +489,7 @@ impl InferenceContext<'_> {
ty
}
- Expr::Call { callee, args, .. } => {
- let callee_ty = self.infer_expr(*callee, &Expectation::none(), ExprIsRead::Yes);
- let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false, true);
- let (res, derefed_callee) = loop {
- let Some((callee_deref_ty, _)) = derefs.next() else {
- break (None, callee_ty.clone());
- };
- if let Some(res) = derefs.table.callable_sig(&callee_deref_ty, args.len()) {
- break (Some(res), callee_deref_ty);
- }
- };
- // if the function is unresolved, we use is_varargs=true to
- // suppress the arg count diagnostic here
- let is_varargs =
- derefed_callee.callable_sig(self.db).is_some_and(|sig| sig.is_varargs)
- || res.is_none();
- let (param_tys, ret_ty) = match res {
- Some((func, params, ret_ty)) => {
- let mut adjustments = auto_deref_adjust_steps(&derefs);
- if let TyKind::Closure(c, _) =
- self.table.resolve_completely(callee_ty.clone()).kind(Interner)
- {
- if let Some(par) = self.current_closure {
- self.closure_dependencies.entry(par).or_default().push(*c);
- }
- self.deferred_closures.entry(*c).or_default().push((
- derefed_callee.clone(),
- callee_ty.clone(),
- params.clone(),
- tgt_expr,
- ));
- }
- if let Some(fn_x) = func {
- self.write_fn_trait_method_resolution(
- fn_x,
- &derefed_callee,
- &mut adjustments,
- &callee_ty,
- &params,
- tgt_expr,
- );
- }
- self.write_expr_adj(*callee, adjustments);
- (params, ret_ty)
- }
- None => {
- self.push_diagnostic(InferenceDiagnostic::ExpectedFunction {
- call_expr: tgt_expr,
- found: callee_ty.clone(),
- });
- (Vec::new(), self.err_ty())
- }
- };
- let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args);
- self.register_obligations_for_call(&callee_ty);
-
- let expected_inputs = self.expected_inputs_for_expected_output(
- expected,
- ret_ty.clone(),
- param_tys.clone(),
- );
-
- self.check_call_arguments(
- tgt_expr,
- args,
- &expected_inputs,
- &param_tys,
- &indices_to_skip,
- is_varargs,
- );
- self.normalize_associated_types_in(ret_ty)
- }
+ Expr::Call { callee, args, .. } => self.infer_call(tgt_expr, *callee, args, expected),
Expr::MethodCall { receiver, args, method_name, generic_args } => self
.infer_method_call(
tgt_expr,
@@ -1872,6 +1801,107 @@ impl InferenceContext<'_> {
}
}
+ fn infer_call(
+ &mut self,
+ tgt_expr: ExprId,
+ callee: ExprId,
+ args: &[ExprId],
+ expected: &Expectation,
+ ) -> Ty {
+ let callee_ty = self.infer_expr(callee, &Expectation::none(), ExprIsRead::Yes);
+ let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false, true);
+ let (res, derefed_callee) = loop {
+ let Some((callee_deref_ty, _)) = derefs.next() else {
+ break (None, callee_ty.clone());
+ };
+ if let Some(res) = derefs.table.callable_sig(&callee_deref_ty, args.len()) {
+ break (Some(res), callee_deref_ty);
+ }
+ };
+ // if the function is unresolved, we use is_varargs=true to
+ // suppress the arg count diagnostic here
+ let is_varargs =
+ derefed_callee.callable_sig(self.db).is_some_and(|sig| sig.is_varargs) || res.is_none();
+ let (param_tys, ret_ty) = match res {
+ Some((func, params, ret_ty)) => {
+ let mut adjustments = auto_deref_adjust_steps(&derefs);
+ if let TyKind::Closure(c, _) =
+ self.table.resolve_completely(callee_ty.clone()).kind(Interner)
+ {
+ if let Some(par) = self.current_closure {
+ self.closure_dependencies.entry(par).or_default().push(*c);
+ }
+ self.deferred_closures.entry(*c).or_default().push((
+ derefed_callee.clone(),
+ callee_ty.clone(),
+ params.clone(),
+ tgt_expr,
+ ));
+ }
+ if let Some(fn_x) = func {
+ self.write_fn_trait_method_resolution(
+ fn_x,
+ &derefed_callee,
+ &mut adjustments,
+ &callee_ty,
+ &params,
+ tgt_expr,
+ );
+ }
+ self.write_expr_adj(callee, adjustments);
+ (params, ret_ty)
+ }
+ None => {
+ self.push_diagnostic(InferenceDiagnostic::ExpectedFunction {
+ call_expr: tgt_expr,
+ found: callee_ty.clone(),
+ });
+ (Vec::new(), self.err_ty())
+ }
+ };
+ let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args);
+ self.check_call(
+ tgt_expr,
+ args,
+ callee_ty,
+ &param_tys,
+ ret_ty,
+ &indices_to_skip,
+ is_varargs,
+ expected,
+ )
+ }
+
+ fn check_call(
+ &mut self,
+ tgt_expr: ExprId,
+ args: &[ExprId],
+ callee_ty: Ty,
+ param_tys: &[Ty],
+ ret_ty: Ty,
+ indices_to_skip: &[u32],
+ is_varargs: bool,
+ expected: &Expectation,
+ ) -> Ty {
+ self.register_obligations_for_call(&callee_ty);
+
+ let expected_inputs = self.expected_inputs_for_expected_output(
+ expected,
+ ret_ty.clone(),
+ param_tys.to_owned(),
+ );
+
+ self.check_call_arguments(
+ tgt_expr,
+ args,
+ &expected_inputs,
+ param_tys,
+ indices_to_skip,
+ is_varargs,
+ );
+ self.normalize_associated_types_in(ret_ty)
+ }
+
fn infer_method_call(
&mut self,
tgt_expr: ExprId,
@@ -1892,21 +1922,32 @@ impl InferenceContext<'_> {
VisibleFromModule::Filter(self.resolver.module()),
method_name,
);
- let (receiver_ty, method_ty, substs) = match resolved {
+ match resolved {
Some((adjust, func, visible)) => {
- let (ty, adjustments) = adjust.apply(&mut self.table, receiver_ty);
- let generics = generics(self.db.upcast(), func.into());
- let substs = self.substs_for_method_call(generics, generic_args);
- self.write_expr_adj(receiver, adjustments);
- self.write_method_resolution(tgt_expr, func, substs.clone());
if !visible {
self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem {
id: tgt_expr.into(),
item: func.into(),
})
}
- (ty, self.db.value_ty(func.into()).unwrap(), substs)
+
+ let (ty, adjustments) = adjust.apply(&mut self.table, receiver_ty);
+ self.write_expr_adj(receiver, adjustments);
+
+ let generics = generics(self.db.upcast(), func.into());
+ let substs = self.substs_for_method_call(generics, generic_args);
+ self.write_method_resolution(tgt_expr, func, substs.clone());
+ self.check_method_call(
+ tgt_expr,
+ args,
+ self.db.value_ty(func.into()).expect("we have a function def"),
+ substs,
+ ty,
+ expected,
+ )
}
+ // Failed to resolve, report diagnostic and try to resolve as call to field access or
+ // assoc function
None => {
let field_with_same_name_exists = match self.lookup_field(&receiver_ty, method_name)
{
@@ -1926,12 +1967,11 @@ impl InferenceContext<'_> {
VisibleFromModule::Filter(self.resolver.module()),
Some(method_name),
method_resolution::LookupMode::Path,
- |_ty, item, visible| {
- if visible {
- Some(item)
- } else {
- None
+ |_ty, item, visible| match item {
+ hir_def::AssocItemId::FunctionId(function_id) if visible => {
+ Some(function_id)
}
+ _ => None,
},
);
@@ -1939,17 +1979,45 @@ impl InferenceContext<'_> {
expr: tgt_expr,
receiver: receiver_ty.clone(),
name: method_name.clone(),
- field_with_same_name: field_with_same_name_exists,
+ field_with_same_name: field_with_same_name_exists.clone(),
assoc_func_with_same_name,
});
- (
- receiver_ty,
- Binders::empty(Interner, self.err_ty()),
- Substitution::empty(Interner),
- )
+
+ let recovered = match assoc_func_with_same_name {
+ Some(f) => {
+ let generics = generics(self.db.upcast(), f.into());
+ let substs = self.substs_for_method_call(generics, generic_args);
+ let f = self
+ .db
+ .value_ty(f.into())
+ .expect("we have a function def")
+ .substitute(Interner, &substs);
+ let sig = f.callable_sig(self.db).expect("we have a function def");
+ Some((f, sig, true))
+ }
+ None => field_with_same_name_exists.and_then(|field_ty| {
+ let callable_sig = field_ty.callable_sig(self.db)?;
+ Some((field_ty, callable_sig, false))
+ }),
+ };
+ match recovered {
+ Some((callee_ty, sig, strip_first)) => self.check_call(
+ tgt_expr,
+ args,
+ callee_ty,
+ sig.params().get(strip_first as usize..).unwrap_or(&[]),
+ sig.ret().clone(),
+ &[],
+ true,
+ expected,
+ ),
+ None => {
+ self.check_call_arguments(tgt_expr, args, &[], &[], &[], true);
+ self.err_ty()
+ }
+ }
}
- };
- self.check_method_call(tgt_expr, args, method_ty, substs, receiver_ty, expected)
+ }
}
fn check_method_call(
@@ -2019,9 +2087,10 @@ impl InferenceContext<'_> {
expected_inputs: &[Ty],
param_tys: &[Ty],
skip_indices: &[u32],
- is_varargs: bool,
+ ignore_arg_param_mismatch: bool,
) {
- let arg_count_mismatch = args.len() != param_tys.len() + skip_indices.len() && !is_varargs;
+ let arg_count_mismatch =
+ !ignore_arg_param_mismatch && args.len() != param_tys.len() + skip_indices.len();
if arg_count_mismatch {
self.push_diagnostic(InferenceDiagnostic::MismatchedArgCount {
call_expr: expr,
@@ -2050,7 +2119,7 @@ impl InferenceContext<'_> {
continue;
}
- while skip_indices.peek().is_some_and(|i| *i < idx as u32) {
+ while skip_indices.peek().is_some_and(|&i| i < idx as u32) {
skip_indices.next();
}
if skip_indices.peek().copied() == Some(idx as u32) {
@@ -2132,8 +2201,8 @@ impl InferenceContext<'_> {
for kind_id in def_generics.iter_self_id().take(self_params) {
let arg = args.peek();
let arg = match (kind_id, arg) {
- // Lifetimes can be elided.
- // Once we have implemented lifetime elision correctly,
+ // Lifetimes can be inferred.
+ // Once we have implemented lifetime inference correctly,
// this should be handled in a proper way.
(
GenericParamId::LifetimeParamId(_),
diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs
index 5ff22bea34..db93116f10 100644
--- a/crates/hir-ty/src/infer/pat.rs
+++ b/crates/hir-ty/src/infer/pat.rs
@@ -564,9 +564,10 @@ impl InferenceContext<'_> {
| Pat::Range { .. }
| Pat::Slice { .. } => true,
Pat::Or(pats) => pats.iter().all(|p| self.is_non_ref_pat(body, *p)),
- Pat::Path(p) => {
- let v = self.resolve_value_path_inner(p, pat.into());
- v.is_some_and(|x| !matches!(x.0, hir_def::resolver::ValueNs::ConstId(_)))
+ Pat::Path(path) => {
+ // A const is a reference pattern, but other value ns things aren't (see #16131).
+ let resolved = self.resolve_value_path_inner(path, pat.into(), true);
+ resolved.is_some_and(|it| !matches!(it.0, hir_def::resolver::ValueNs::ConstId(_)))
}
Pat::ConstBlock(..) => false,
Pat::Lit(expr) => !matches!(
diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs
index 36ec60a7a2..6254bc1239 100644
--- a/crates/hir-ty/src/infer/path.rs
+++ b/crates/hir-ty/src/infer/path.rs
@@ -7,7 +7,6 @@ use hir_def::{
AdtId, AssocItemId, GenericDefId, ItemContainerId, Lookup,
};
use hir_expand::name::Name;
-use intern::sym;
use stdx::never;
use crate::{
@@ -41,7 +40,7 @@ impl InferenceContext<'_> {
}
fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<ValuePathResolution> {
- let (value, self_subst) = self.resolve_value_path_inner(path, id)?;
+ let (value, self_subst) = self.resolve_value_path_inner(path, id, false)?;
let value_def: ValueTyDefId = match value {
ValueNs::FunctionId(it) => it.into(),
@@ -94,7 +93,14 @@ impl InferenceContext<'_> {
return Some(ValuePathResolution::NonGeneric(ty));
};
- let substs = self.with_body_ty_lowering(|ctx| ctx.substs_from_path(path, value_def, true));
+ let substs = self.with_body_ty_lowering(|ctx| {
+ let mut path_ctx = ctx.at_path(path, id);
+ let last_segment = path.segments().len().checked_sub(1);
+ if let Some(last_segment) = last_segment {
+ path_ctx.set_current_segment(last_segment)
+ }
+ path_ctx.substs_from_path(value_def, true)
+ });
let substs = substs.as_slice(Interner);
if let ValueNs::EnumVariantId(_) = value {
@@ -146,6 +152,7 @@ impl InferenceContext<'_> {
&mut self,
path: &Path,
id: ExprOrPatId,
+ no_diagnostics: bool,
) -> Option<(ValueNs, Option<chalk_ir::Substitution<Interner>>)> {
// Don't use `self.make_ty()` here as we need `orig_ns`.
let mut ctx = TyLoweringContext::new(
@@ -156,33 +163,83 @@ impl InferenceContext<'_> {
&self.diagnostics,
InferenceTyDiagnosticSource::Body,
);
+ let mut path_ctx = if no_diagnostics {
+ ctx.at_path_forget_diagnostics(path)
+ } else {
+ ctx.at_path(path, id)
+ };
let (value, self_subst) = if let Some(type_ref) = path.type_anchor() {
let last = path.segments().last()?;
- let (ty, orig_ns) = ctx.lower_ty_ext(type_ref);
+ let (ty, orig_ns) = path_ctx.ty_ctx().lower_ty_ext(type_ref);
let ty = self.table.insert_type_vars(ty);
let ty = self.table.normalize_associated_types_in(ty);
- let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
- let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty);
- drop(ctx);
+ path_ctx.ignore_last_segment();
+ let (ty, _) = path_ctx.lower_ty_relative_path(ty, orig_ns);
+ drop_ctx(ctx, no_diagnostics);
let ty = self.table.insert_type_vars(ty);
let ty = self.table.normalize_associated_types_in(ty);
self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))?
} else {
let hygiene = self.body.expr_or_pat_path_hygiene(id);
// FIXME: report error, unresolved first path segment
- let value_or_partial = ctx.resolve_path_in_value_ns(path, id, hygiene)?;
- drop(ctx);
+ let value_or_partial = path_ctx.resolve_path_in_value_ns(hygiene)?;
match value_or_partial {
- ResolveValueResult::ValueNs(it, _) => (it, None),
- ResolveValueResult::Partial(def, remaining_index, _) => self
- .resolve_assoc_item(id, def, path, remaining_index, id)
- .map(|(it, substs)| (it, Some(substs)))?,
+ ResolveValueResult::ValueNs(it, _) => {
+ drop_ctx(ctx, no_diagnostics);
+ (it, None)
+ }
+ 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.
+
+ let remaining_segments = path.segments().skip(remaining_index);
+ let is_before_last = remaining_segments.len() == 1;
+ let last_segment = remaining_segments
+ .last()
+ .expect("there should be at least one segment here");
+
+ let (resolution, substs) = match (def, is_before_last) {
+ (TypeNs::TraitId(trait_), true) => {
+ let self_ty = self.table.new_type_var();
+ let trait_ref =
+ path_ctx.lower_trait_ref_from_resolved_path(trait_, self_ty);
+ drop_ctx(ctx, no_diagnostics);
+ self.resolve_trait_assoc_item(trait_ref, last_segment, id)
+ }
+ (def, _) => {
+ // Either we already have a type (e.g. `Vec::new`), or we have a
+ // trait but it's not the last segment, so the next segment
+ // should resolve to an associated type of that trait (e.g. `<T
+ // as Iterator>::Item::default`)
+ path_ctx.ignore_last_segment();
+ let (ty, _) = path_ctx.lower_partly_resolved_path(def, true);
+ drop_ctx(ctx, no_diagnostics);
+ if ty.is_unknown() {
+ return None;
+ }
+
+ let ty = self.insert_type_vars(ty);
+ let ty = self.normalize_associated_types_in(ty);
+
+ self.resolve_ty_assoc_item(ty, last_segment.name, id)
+ }
+ }?;
+ (resolution, Some(substs))
+ }
}
};
- Some((value, self_subst))
+ return Some((value, self_subst));
+
+ #[inline]
+ fn drop_ctx(mut ctx: TyLoweringContext<'_>, no_diagnostics: bool) {
+ if no_diagnostics {
+ ctx.forget_diagnostics();
+ }
+ }
}
fn add_required_obligations_for_value_path(&mut self, def: GenericDefId, subst: &Substitution) {
@@ -212,89 +269,6 @@ impl InferenceContext<'_> {
}
}
- fn resolve_assoc_item(
- &mut self,
- node: ExprOrPatId,
- def: TypeNs,
- path: &Path,
- remaining_index: usize,
- id: ExprOrPatId,
- ) -> Option<(ValueNs, Substitution)> {
- // 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.
-
- let _d;
- let (resolved_segment, remaining_segments) = match path {
- Path::Normal { .. } | Path::BarePath(_) => {
- assert!(remaining_index < path.segments().len());
- (
- path.segments().get(remaining_index - 1).unwrap(),
- path.segments().skip(remaining_index),
- )
- }
- Path::LangItem(..) => (
- PathSegment {
- name: {
- _d = Name::new_symbol_root(sym::Unknown.clone());
- &_d
- },
- args_and_bindings: None,
- },
- path.segments(),
- ),
- };
- let is_before_last = remaining_segments.len() == 1;
-
- match (def, is_before_last) {
- (TypeNs::TraitId(trait_), true) => {
- let segment =
- remaining_segments.last().expect("there should be at least one segment here");
- let self_ty = self.table.new_type_var();
- let trait_ref = self.with_body_ty_lowering(|ctx| {
- ctx.lower_trait_ref_from_resolved_path(trait_, resolved_segment, self_ty)
- });
- self.resolve_trait_assoc_item(trait_ref, segment, id)
- }
- (def, _) => {
- // Either we already have a type (e.g. `Vec::new`), or we have a
- // trait but it's not the last segment, so the next segment
- // should resolve to an associated type of that trait (e.g. `<T
- // as Iterator>::Item::default`)
- let remaining_segments_for_ty =
- remaining_segments.take(remaining_segments.len() - 1);
- let mut ctx = TyLoweringContext::new(
- self.db,
- &self.resolver,
- &self.body.types,
- self.owner.into(),
- &self.diagnostics,
- InferenceTyDiagnosticSource::Body,
- );
- let (ty, _) = ctx.lower_partly_resolved_path(
- node,
- def,
- resolved_segment,
- remaining_segments_for_ty,
- (remaining_index - 1) as u32,
- true,
- );
- drop(ctx);
- if ty.is_unknown() {
- return None;
- }
-
- let ty = self.insert_type_vars(ty);
- let ty = self.normalize_associated_types_in(ty);
-
- let segment =
- remaining_segments.last().expect("there should be at least one segment here");
-
- self.resolve_ty_assoc_item(ty, segment.name, id)
- }
- }
- }
-
fn resolve_trait_assoc_item(
&mut self,
trait_ref: TraitRef,
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index db13e1fd35..af73b5ed9a 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -6,6 +6,7 @@
//!
//! This usually involves resolving names, collecting generic arguments etc.
pub(crate) mod diagnostics;
+pub(crate) mod path;
use std::{
cell::OnceCell,
@@ -26,29 +27,26 @@ use hir_def::{
builtin_type::BuiltinType,
data::{adt::StructKind, TraitFlags},
expander::Expander,
- expr_store::HygieneId,
generics::{
GenericParamDataRef, TypeOrConstParamData, TypeParamProvenance, WherePredicate,
WherePredicateTypeTarget,
},
lang_item::LangItem,
nameres::MacroSubNs,
- path::{GenericArg, GenericArgs, ModPath, Path, PathKind, PathSegment, PathSegments},
- resolver::{HasResolver, LifetimeNs, ResolveValueResult, Resolver, TypeNs, ValueNs},
+ path::{GenericArg, ModPath, Path, PathKind},
+ resolver::{HasResolver, LifetimeNs, Resolver, TypeNs},
type_ref::{
ConstRef, LifetimeRef, PathId, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound,
TypeRef, TypeRefId, TypesMap, TypesSourceMap,
},
AdtId, AssocItemId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId,
- FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstLoc, ItemContainerId,
- LocalFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId,
- TypeOwnerId, UnionId, VariantId,
+ FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstLoc, LocalFieldId,
+ Lookup, StaticId, StructId, TypeAliasId, TypeOrConstParamId, TypeOwnerId, UnionId, VariantId,
};
use hir_expand::{name::Name, ExpandResult};
use la_arena::{Arena, ArenaMap};
use rustc_hash::FxHashSet;
use rustc_pattern_analysis::Captures;
-use smallvec::SmallVec;
use stdx::{impl_from, never};
use syntax::ast;
use triomphe::{Arc, ThinArc};
@@ -62,18 +60,19 @@ use crate::{
db::HirDatabase,
error_lifetime,
generics::{generics, trait_self_param_idx, Generics},
- lower::diagnostics::*,
+ lower::{
+ diagnostics::*,
+ path::{PathDiagnosticCallback, PathLoweringContext},
+ },
make_binders,
mapping::{from_chalk_trait_id, lt_to_placeholder_idx, ToChalk},
- static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx,
- utils::{
- all_super_trait_refs, associated_type_by_name_including_super_traits, InTypeConstIdMetadata,
- },
- AliasEq, AliasTy, Binders, BoundVar, CallableSig, Const, ConstScalar, DebruijnIndex, DynTy,
- FnAbi, FnPointer, FnSig, FnSubst, ImplTrait, ImplTraitId, ImplTraits, Interner, Lifetime,
- LifetimeData, LifetimeOutlives, ParamKind, PolyFnSig, ProgramClause, ProjectionTy,
- QuantifiedWhereClause, QuantifiedWhereClauses, Substitution, TraitEnvironment, TraitRef,
- TraitRefExt, Ty, TyBuilder, TyKind, WhereClause,
+ static_lifetime, to_chalk_trait_id, to_placeholder_idx,
+ utils::{all_super_trait_refs, InTypeConstIdMetadata},
+ AliasTy, Binders, BoundVar, CallableSig, Const, ConstScalar, DebruijnIndex, DynTy, FnAbi,
+ FnPointer, FnSig, FnSubst, ImplTrait, ImplTraitId, ImplTraits, Interner, Lifetime,
+ LifetimeData, LifetimeOutlives, ParamKind, PolyFnSig, ProgramClause, QuantifiedWhereClause,
+ QuantifiedWhereClauses, Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder,
+ TyKind, WhereClause,
};
#[derive(Debug, Default)]
@@ -106,6 +105,8 @@ impl ImplTraitLoweringState {
}
}
+pub(crate) struct PathDiagnosticCallbackData(TypeRefId);
+
#[derive(Debug)]
pub struct TyLoweringContext<'a> {
pub db: &'a dyn HirDatabase,
@@ -527,9 +528,8 @@ impl<'a> TyLoweringContext<'a> {
if path.segments().len() > 1 {
return None;
}
- let resolution = match self
- .resolve_path_in_type_ns(path, &mut Self::on_path_diagnostic_callback(type_ref_id))
- {
+ let mut ctx = self.at_path(PathId::from_type_ref_unchecked(type_ref_id));
+ let resolution = match ctx.resolve_path_in_type_ns() {
Some((it, None)) => it,
_ => return None,
};
@@ -539,409 +539,36 @@ impl<'a> TyLoweringContext<'a> {
}
}
- pub(crate) fn lower_ty_relative_path(
- &mut self,
- ty: Ty,
- // We need the original resolution to lower `Self::AssocTy` correctly
- res: Option<TypeNs>,
- remaining_segments: PathSegments<'_>,
- ) -> (Ty, Option<TypeNs>) {
- match remaining_segments.len() {
- 0 => (ty, res),
- 1 => {
- // resolve unselected assoc types
- let segment = remaining_segments.first().unwrap();
- (self.select_associated_type(res, segment), None)
- }
- _ => {
- // FIXME report error (ambiguous associated type)
- (TyKind::Error.intern(Interner), None)
- }
- }
- }
-
- pub(crate) fn lower_partly_resolved_path(
- &mut self,
- resolution: TypeNs,
- resolved_segment: PathSegment<'_>,
- remaining_segments: PathSegments<'_>,
- _resolved_segment_idx: u32,
- infer_args: bool,
- _on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic),
- ) -> (Ty, Option<TypeNs>) {
- let ty = match resolution {
- TypeNs::TraitId(trait_) => {
- let ty = match remaining_segments.len() {
- 1 => {
- let trait_ref = self.lower_trait_ref_from_resolved_path(
- trait_,
- resolved_segment,
- TyKind::Error.intern(Interner),
- );
- let segment = remaining_segments.first().unwrap();
- let found = self
- .db
- .trait_data(trait_ref.hir_trait_id())
- .associated_type_by_name(segment.name);
-
- match found {
- Some(associated_ty) => {
- // 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 (`trait_ref.substitution`).
- let substitution = self.substs_from_path_segment(
- segment,
- Some(associated_ty.into()),
- false,
- None,
- );
- let len_self =
- generics(self.db.upcast(), associated_ty.into()).len_self();
- let substitution = Substitution::from_iter(
- Interner,
- substitution
- .iter(Interner)
- .take(len_self)
- .chain(trait_ref.substitution.iter(Interner)),
- );
- TyKind::Alias(AliasTy::Projection(ProjectionTy {
- associated_ty_id: to_assoc_type_id(associated_ty),
- substitution,
- }))
- .intern(Interner)
- }
- None => {
- // FIXME: report error (associated type not found)
- TyKind::Error.intern(Interner)
- }
- }
- }
- 0 => {
- // Trait object type without dyn; this should be handled in upstream. See
- // `lower_path()`.
- stdx::never!("unexpected fully resolved trait path");
- TyKind::Error.intern(Interner)
- }
- _ => {
- // FIXME report error (ambiguous associated type)
- TyKind::Error.intern(Interner)
- }
- };
- return (ty, None);
- }
- TypeNs::TraitAliasId(_) => {
- // FIXME(trait_alias): Implement trait alias.
- return (TyKind::Error.intern(Interner), None);
- }
- TypeNs::GenericParam(param_id) => match self.type_param_mode {
- ParamLoweringMode::Placeholder => {
- TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into()))
- }
- ParamLoweringMode::Variable => {
- let idx = match self
- .generics()
- .expect("generics in scope")
- .type_or_const_param_idx(param_id.into())
- {
- None => {
- never!("no matching generics");
- return (TyKind::Error.intern(Interner), None);
- }
- Some(idx) => idx,
- };
-
- TyKind::BoundVar(BoundVar::new(self.in_binders, idx))
- }
- }
- .intern(Interner),
- TypeNs::SelfType(impl_id) => {
- let generics = self.generics().expect("impl should have generic param scope");
-
- match self.type_param_mode {
- ParamLoweringMode::Placeholder => {
- // `def` can be either impl itself or item within, and we need impl itself
- // now.
- let generics = generics.parent_or_self();
- let subst = generics.placeholder_subst(self.db);
- self.db.impl_self_ty(impl_id).substitute(Interner, &subst)
- }
- ParamLoweringMode::Variable => {
- let starting_from = match generics.def() {
- GenericDefId::ImplId(_) => 0,
- // `def` is an item within impl. We need to substitute `BoundVar`s but
- // remember that they are for parent (i.e. impl) generic params so they
- // come after our own params.
- _ => generics.len_self(),
- };
- TyBuilder::impl_self_ty(self.db, impl_id)
- .fill_with_bound_vars(self.in_binders, starting_from)
- .build()
- }
- }
- }
- TypeNs::AdtSelfType(adt) => {
- let generics = generics(self.db.upcast(), adt.into());
- let substs = match self.type_param_mode {
- ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db),
- ParamLoweringMode::Variable => {
- generics.bound_vars_subst(self.db, self.in_binders)
- }
- };
- self.db.ty(adt.into()).substitute(Interner, &substs)
- }
-
- TypeNs::AdtId(it) => self.lower_path_inner(resolved_segment, it.into(), infer_args),
- TypeNs::BuiltinType(it) => {
- self.lower_path_inner(resolved_segment, it.into(), infer_args)
- }
- TypeNs::TypeAliasId(it) => {
- self.lower_path_inner(resolved_segment, it.into(), infer_args)
- }
- // FIXME: report error
- TypeNs::EnumVariantId(_) => return (TyKind::Error.intern(Interner), None),
- };
- self.lower_ty_relative_path(ty, Some(resolution), remaining_segments)
- }
-
- fn handle_type_ns_resolution(
- &mut self,
- resolution: &TypeNs,
- resolved_segment: PathSegment<'_>,
- resolved_segment_idx: usize,
- on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic),
- ) {
- let mut prohibit_generics_on_resolved = |reason| {
- if resolved_segment.args_and_bindings.is_some() {
- on_diagnostic(
- self,
- PathLoweringDiagnostic::GenericArgsProhibited {
- segment: resolved_segment_idx as u32,
- reason,
- },
- );
- }
- };
-
- match resolution {
- TypeNs::SelfType(_) => {
- prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy)
- }
- TypeNs::GenericParam(_) => {
- prohibit_generics_on_resolved(GenericArgsProhibitedReason::TyParam)
- }
- TypeNs::AdtSelfType(_) => {
- prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy)
- }
- TypeNs::BuiltinType(_) => {
- prohibit_generics_on_resolved(GenericArgsProhibitedReason::PrimitiveTy)
- }
- TypeNs::AdtId(_)
- | TypeNs::EnumVariantId(_)
- | TypeNs::TypeAliasId(_)
- | TypeNs::TraitId(_)
- | TypeNs::TraitAliasId(_) => {}
- }
- }
-
- pub(crate) fn resolve_path_in_type_ns_fully(
- &mut self,
- path: &Path,
- on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic),
- ) -> Option<TypeNs> {
- let (res, unresolved) = self.resolve_path_in_type_ns(path, on_diagnostic)?;
- if unresolved.is_some() {
- return None;
- }
- Some(res)
- }
-
- pub(crate) fn resolve_path_in_type_ns(
- &mut self,
- path: &Path,
- on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic),
- ) -> Option<(TypeNs, Option<usize>)> {
- let (resolution, remaining_index, _, prefix_info) =
- self.resolver.resolve_path_in_type_ns_with_prefix_info(self.db.upcast(), path)?;
- let segments = path.segments();
-
- match path {
- // `segments.is_empty()` can occur with `self`.
- Path::Normal(..) if !segments.is_empty() => (),
- _ => return Some((resolution, remaining_index)),
- };
-
- let (module_segments, resolved_segment_idx, enum_segment) = match remaining_index {
- None if prefix_info.enum_variant => {
- (segments.strip_last_two(), segments.len() - 1, Some(segments.len() - 2))
- }
- None => (segments.strip_last(), segments.len() - 1, None),
- Some(i) => (segments.take(i - 1), i - 1, None),
- };
-
- for (i, mod_segment) in module_segments.iter().enumerate() {
- if mod_segment.args_and_bindings.is_some() {
- on_diagnostic(
- self,
- PathLoweringDiagnostic::GenericArgsProhibited {
- segment: i as u32,
- reason: GenericArgsProhibitedReason::Module,
- },
- );
- }
- }
-
- if let Some(enum_segment) = enum_segment {
- if segments.get(enum_segment).is_some_and(|it| it.args_and_bindings.is_some())
- && segments.get(enum_segment + 1).is_some_and(|it| it.args_and_bindings.is_some())
- {
- on_diagnostic(
- self,
- PathLoweringDiagnostic::GenericArgsProhibited {
- segment: (enum_segment + 1) as u32,
- reason: GenericArgsProhibitedReason::EnumVariant,
- },
- );
- }
- }
-
- self.handle_type_ns_resolution(
- &resolution,
- segments.get(resolved_segment_idx).expect("should have resolved segment"),
- resolved_segment_idx,
- on_diagnostic,
- );
-
- Some((resolution, remaining_index))
- }
-
- pub(crate) fn resolve_path_in_value_ns(
- &mut self,
- path: &Path,
- hygiene_id: HygieneId,
- on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic),
- ) -> Option<ResolveValueResult> {
- let (res, prefix_info) = self.resolver.resolve_path_in_value_ns_with_prefix_info(
- self.db.upcast(),
- path,
- hygiene_id,
- )?;
-
- let segments = path.segments();
- match path {
- // `segments.is_empty()` can occur with `self`.
- Path::Normal(..) if !segments.is_empty() => (),
- _ => return Some(res),
- };
-
- let (mod_segments, enum_segment) = match res {
- ResolveValueResult::Partial(_, unresolved_segment, _) => {
- (segments.take(unresolved_segment - 1), None)
- }
- ResolveValueResult::ValueNs(ValueNs::EnumVariantId(_), _)
- if prefix_info.enum_variant =>
- {
- (segments.strip_last_two(), segments.len().checked_sub(2))
- }
- ResolveValueResult::ValueNs(..) => (segments.strip_last(), None),
- };
- for (i, mod_segment) in mod_segments.iter().enumerate() {
- if mod_segment.args_and_bindings.is_some() {
- on_diagnostic(
- self,
- PathLoweringDiagnostic::GenericArgsProhibited {
- segment: i as u32,
- reason: GenericArgsProhibitedReason::Module,
- },
- );
- }
- }
-
- if let Some(enum_segment) = enum_segment {
- if segments.get(enum_segment).is_some_and(|it| it.args_and_bindings.is_some())
- && segments.get(enum_segment + 1).is_some_and(|it| it.args_and_bindings.is_some())
- {
- on_diagnostic(
- self,
- PathLoweringDiagnostic::GenericArgsProhibited {
- segment: (enum_segment + 1) as u32,
- reason: GenericArgsProhibitedReason::EnumVariant,
- },
- );
- }
+ #[inline]
+ fn on_path_diagnostic_callback(type_ref: TypeRefId) -> PathDiagnosticCallback<'static> {
+ PathDiagnosticCallback {
+ data: Either::Left(PathDiagnosticCallbackData(type_ref)),
+ callback: |data, this, diag| {
+ let type_ref = data.as_ref().left().unwrap().0;
+ this.push_diagnostic(type_ref, TyLoweringDiagnosticKind::PathDiagnostic(diag))
+ },
}
-
- match &res {
- ResolveValueResult::ValueNs(resolution, _) => {
- let resolved_segment_idx =
- segments.len().checked_sub(1).unwrap_or_else(|| panic!("{path:?}"));
- let resolved_segment = segments.last().unwrap();
-
- let mut prohibit_generics_on_resolved = |reason| {
- if resolved_segment.args_and_bindings.is_some() {
- on_diagnostic(
- self,
- PathLoweringDiagnostic::GenericArgsProhibited {
- segment: resolved_segment_idx as u32,
- reason,
- },
- );
- }
- };
-
- match resolution {
- ValueNs::ImplSelf(_) => {
- prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy)
- }
- // FIXME: rustc generates E0107 (incorrect number of generic arguments) and not
- // E0109 (generic arguments provided for a type that doesn't accept them) for
- // consts and statics, presumably as a defense against future in which consts
- // and statics can be generic, or just because it was easier for rustc implementors.
- // That means we'll show the wrong error code. Because of us it's easier to do it
- // this way :)
- ValueNs::GenericParam(_) | ValueNs::ConstId(_) => {
- prohibit_generics_on_resolved(GenericArgsProhibitedReason::Const)
- }
- ValueNs::StaticId(_) => {
- prohibit_generics_on_resolved(GenericArgsProhibitedReason::Static)
- }
- ValueNs::FunctionId(_) | ValueNs::StructId(_) | ValueNs::EnumVariantId(_) => {}
- ValueNs::LocalBinding(_) => {}
- }
- }
- ResolveValueResult::Partial(resolution, unresolved_idx, _) => {
- let resolved_segment_idx = unresolved_idx - 1;
- let resolved_segment = segments.get(resolved_segment_idx).unwrap();
- self.handle_type_ns_resolution(
- resolution,
- resolved_segment,
- resolved_segment_idx,
- on_diagnostic,
- );
- }
- };
- Some(res)
}
- fn on_path_diagnostic_callback(
- type_ref: TypeRefId,
- ) -> impl FnMut(&mut Self, PathLoweringDiagnostic) {
- move |this, diag| {
- this.push_diagnostic(type_ref, TyLoweringDiagnosticKind::PathDiagnostic(diag))
- }
+ #[inline]
+ fn at_path(&mut self, path_id: PathId) -> PathLoweringContext<'_, 'a> {
+ PathLoweringContext::new(
+ self,
+ Self::on_path_diagnostic_callback(path_id.type_ref()),
+ &self.types_map[path_id],
+ )
}
pub(crate) fn lower_path(&mut self, path: &Path, path_id: PathId) -> (Ty, Option<TypeNs>) {
// Resolve the path (in type namespace)
if let Some(type_ref) = path.type_anchor() {
let (ty, res) = self.lower_ty_ext(type_ref);
- return self.lower_ty_relative_path(ty, res, path.segments());
+ let mut ctx = self.at_path(path_id);
+ return ctx.lower_ty_relative_path(ty, res);
}
- let (resolution, remaining_index) = match self.resolve_path_in_type_ns(
- path,
- &mut Self::on_path_diagnostic_callback(path_id.type_ref()),
- ) {
+ let mut ctx = self.at_path(path_id);
+ let (resolution, remaining_index) = match ctx.resolve_path_in_type_ns() {
Some(it) => it,
None => return (TyKind::Error.intern(Interner), None),
};
@@ -953,354 +580,21 @@ impl<'a> TyLoweringContext<'a> {
return (ty, None);
}
- let (resolved_segment_idx, resolved_segment, remaining_segments) = match remaining_index {
- None => (
- path.segments().len() - 1,
- path.segments().last().expect("resolved path has at least one element"),
- PathSegments::EMPTY,
- ),
- Some(i) => (i - 1, path.segments().get(i - 1).unwrap(), path.segments().skip(i)),
- };
-
- self.lower_partly_resolved_path(
- resolution,
- resolved_segment,
- remaining_segments,
- resolved_segment_idx as u32,
- false,
- &mut Self::on_path_diagnostic_callback(path_id.type_ref()),
- )
- }
-
- fn select_associated_type(&mut self, res: Option<TypeNs>, segment: PathSegment<'_>) -> Ty {
- let Some((generics, res)) = self.generics().zip(res) else {
- return TyKind::Error.intern(Interner);
- };
- let ty = named_associated_type_shorthand_candidates(
- self.db,
- generics.def(),
- res,
- Some(segment.name.clone()),
- move |name, t, associated_ty| {
- let generics = self.generics().unwrap();
-
- if name != segment.name {
- return None;
- }
-
- let parent_subst = t.substitution.clone();
- let parent_subst = match self.type_param_mode {
- ParamLoweringMode::Placeholder => {
- // if we're lowering to placeholders, we have to put them in now.
- let s = generics.placeholder_subst(self.db);
- s.apply(parent_subst, Interner)
- }
- ParamLoweringMode::Variable => {
- // We need to shift in the bound vars, since
- // `named_associated_type_shorthand_candidates` does not do that.
- parent_subst.shifted_in_from(Interner, self.in_binders)
- }
- };
-
- // 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(segment, Some(associated_ty.into()), false, None);
-
- let len_self =
- crate::generics::generics(self.db.upcast(), associated_ty.into()).len_self();
-
- let substs = Substitution::from_iter(
- Interner,
- substs.iter(Interner).take(len_self).chain(parent_subst.iter(Interner)),
- );
-
- Some(
- TyKind::Alias(AliasTy::Projection(ProjectionTy {
- associated_ty_id: to_assoc_type_id(associated_ty),
- substitution: substs,
- }))
- .intern(Interner),
- )
- },
- );
-
- ty.unwrap_or_else(|| TyKind::Error.intern(Interner))
- }
-
- fn lower_path_inner(
- &mut self,
- segment: PathSegment<'_>,
- typeable: TyDefId,
- infer_args: bool,
- ) -> Ty {
- let generic_def = match typeable {
- TyDefId::BuiltinType(_) => None,
- TyDefId::AdtId(it) => Some(it.into()),
- TyDefId::TypeAliasId(it) => Some(it.into()),
- };
- let substs = self.substs_from_path_segment(segment, generic_def, infer_args, None);
- self.db.ty(typeable).substitute(Interner, &substs)
- }
-
- /// Collect generic arguments from a path into a `Substs`. See also
- /// `create_substs_for_ast_path` and `def_to_ty` in rustc.
- pub(super) fn substs_from_path(
- &mut self,
- path: &Path,
- // Note that we don't call `db.value_type(resolved)` here,
- // `ValueTyDefId` is just a convenient way to pass generics and
- // special-case enum variants
- resolved: ValueTyDefId,
- infer_args: bool,
- ) -> Substitution {
- let last = path.segments().last();
- let (segment, generic_def) = match resolved {
- ValueTyDefId::FunctionId(it) => (last, Some(it.into())),
- ValueTyDefId::StructId(it) => (last, Some(it.into())),
- ValueTyDefId::UnionId(it) => (last, Some(it.into())),
- ValueTyDefId::ConstId(it) => (last, Some(it.into())),
- ValueTyDefId::StaticId(_) => (last, None),
- ValueTyDefId::EnumVariantId(var) => {
- // the generic args for an enum variant may be either specified
- // on the segment referring to the enum, or on the segment
- // referring to the variant. So `Option::<T>::None` and
- // `Option::None::<T>` are both allowed (though the former is
- // preferred). See also `def_ids_for_path_segments` in rustc.
- let len = path.segments().len();
- let penultimate = len.checked_sub(2).and_then(|idx| path.segments().get(idx));
- let segment = match penultimate {
- Some(segment) if segment.args_and_bindings.is_some() => Some(segment),
- _ => last,
- };
- (segment, Some(var.lookup(self.db.upcast()).parent.into()))
- }
- };
- if let Some(segment) = segment {
- self.substs_from_path_segment(segment, generic_def, infer_args, None)
- } else if let Some(generic_def) = generic_def {
- // lang item
- self.substs_from_args_and_bindings(None, Some(generic_def), infer_args, None)
- } else {
- Substitution::empty(Interner)
- }
- }
-
- pub(super) fn substs_from_path_segment(
- &mut self,
- segment: PathSegment<'_>,
- def: Option<GenericDefId>,
- infer_args: bool,
- explicit_self_ty: Option<Ty>,
- ) -> Substitution {
- self.substs_from_args_and_bindings(
- segment.args_and_bindings,
- def,
- infer_args,
- explicit_self_ty,
- )
- }
-
- fn substs_from_args_and_bindings(
- &mut self,
- args_and_bindings: Option<&GenericArgs>,
- def: Option<GenericDefId>,
- infer_args: bool,
- explicit_self_ty: Option<Ty>,
- ) -> Substitution {
- let Some(def) = def else { return Substitution::empty(Interner) };
-
- // Order is
- // - Optional Self parameter
- // - Lifetime parameters
- // - Type or Const parameters
- // - Parent parameters
- let def_generics = generics(self.db.upcast(), def);
- let (
- parent_params,
- self_param,
- type_params,
- const_params,
- impl_trait_params,
- lifetime_params,
- ) = def_generics.provenance_split();
- let item_len =
- self_param as usize + type_params + const_params + impl_trait_params + lifetime_params;
- let total_len = parent_params + item_len;
-
- let mut substs = Vec::new();
-
- // we need to iterate the lifetime and type/const params separately as our order of them
- // differs from the supplied syntax
-
- let ty_error = || TyKind::Error.intern(Interner).cast(Interner);
- let mut def_toc_iter = def_generics.iter_self_type_or_consts_id();
- let fill_self_param = || {
- if self_param {
- let self_ty = explicit_self_ty.map(|x| x.cast(Interner)).unwrap_or_else(ty_error);
-
- if let Some(id) = def_toc_iter.next() {
- assert!(matches!(id, GenericParamId::TypeParamId(_)));
- substs.push(self_ty);
- }
- }
- };
- let mut had_explicit_args = false;
-
- if let Some(&GenericArgs { ref args, has_self_type, .. }) = args_and_bindings {
- // Fill in the self param first
- if has_self_type && self_param {
- had_explicit_args = true;
- if let Some(id) = def_toc_iter.next() {
- assert!(matches!(id, GenericParamId::TypeParamId(_)));
- had_explicit_args = true;
- if let GenericArg::Type(ty) = &args[0] {
- substs.push(self.lower_ty(*ty).cast(Interner));
- }
- }
- } else {
- fill_self_param()
- };
-
- // Then fill in the supplied lifetime args, or error lifetimes if there are too few
- // (default lifetimes aren't a thing)
- for arg in args
- .iter()
- .filter_map(|arg| match arg {
- GenericArg::Lifetime(arg) => Some(self.lower_lifetime(arg)),
- _ => None,
- })
- .chain(iter::repeat(error_lifetime()))
- .take(lifetime_params)
- {
- substs.push(arg.cast(Interner));
- }
-
- let skip = if has_self_type { 1 } else { 0 };
- // Fill in supplied type and const args
- // Note if non-lifetime args are provided, it should be all of them, but we can't rely on that
- for (arg, id) in args
- .iter()
- .filter(|arg| !matches!(arg, GenericArg::Lifetime(_)))
- .skip(skip)
- .take(type_params + const_params)
- .zip(def_toc_iter)
- {
- had_explicit_args = true;
- let arg = generic_arg_to_chalk(
- self.db,
- id,
- arg,
- self,
- self.types_map,
- |this, type_ref| this.lower_ty(type_ref),
- |this, const_ref, ty| this.lower_const(const_ref, ty),
- |this, lifetime_ref| this.lower_lifetime(lifetime_ref),
- );
- substs.push(arg);
- }
- } else {
- fill_self_param();
- }
-
- let param_to_err = |id| match id {
- GenericParamId::ConstParamId(x) => unknown_const_as_generic(self.db.const_param_ty(x)),
- GenericParamId::TypeParamId(_) => ty_error(),
- GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner),
- };
- // handle defaults. In expression or pattern path segments without
- // explicitly specified type arguments, missing type arguments are inferred
- // (i.e. defaults aren't used).
- // Generic parameters for associated types are not supposed to have defaults, so we just
- // ignore them.
- let is_assoc_ty = || match def {
- GenericDefId::TypeAliasId(id) => {
- matches!(id.lookup(self.db.upcast()).container, ItemContainerId::TraitId(_))
- }
- _ => false,
- };
- let fill_defaults = (!infer_args || had_explicit_args) && !is_assoc_ty();
- if fill_defaults {
- let defaults = &*self.db.generic_defaults(def);
- let (item, _parent) = defaults.split_at(item_len);
- let parent_from = item_len - substs.len();
-
- let mut rem =
- def_generics.iter_id().skip(substs.len()).map(param_to_err).collect::<Vec<_>>();
- // Fill in defaults for type/const params
- for (idx, default_ty) in item[substs.len()..].iter().enumerate() {
- // each default can depend on the previous parameters
- let substs_so_far = Substitution::from_iter(
- Interner,
- substs.iter().cloned().chain(rem[idx..].iter().cloned()),
- );
- substs.push(default_ty.clone().substitute(Interner, &substs_so_far));
- }
- // Fill in remaining parent params
- substs.extend(rem.drain(parent_from..));
- } else {
- // Fill in remaining def params and parent params
- substs.extend(def_generics.iter_id().skip(substs.len()).map(param_to_err));
- }
-
- assert_eq!(substs.len(), total_len, "expected {} substs, got {}", total_len, substs.len());
- Substitution::from_iter(Interner, substs)
- }
-
- pub(crate) fn lower_trait_ref_from_resolved_path(
- &mut self,
- resolved: TraitId,
- segment: PathSegment<'_>,
- explicit_self_ty: Ty,
- ) -> TraitRef {
- let substs = self.trait_ref_substs_from_path(segment, resolved, explicit_self_ty);
- TraitRef { trait_id: to_chalk_trait_id(resolved), substitution: substs }
- }
-
- fn prohibit_generics(
- &mut self,
- path_id: PathId,
- idx: u32,
- segments: PathSegments<'_>,
- reason: GenericArgsProhibitedReason,
- ) {
- segments.iter().zip(idx..).for_each(|(segment, idx)| {
- if segment.args_and_bindings.is_some() {
- self.push_diagnostic(
- path_id.type_ref(),
- TyLoweringDiagnosticKind::PathDiagnostic(
- PathLoweringDiagnostic::GenericArgsProhibited { segment: idx, reason },
- ),
- );
- }
- });
+ ctx.lower_partly_resolved_path(resolution, false)
}
fn lower_trait_ref_from_path(
&mut self,
path_id: PathId,
explicit_self_ty: Ty,
- ) -> Option<TraitRef> {
- let path = &self.types_map[path_id];
- let resolved = match self.resolve_path_in_type_ns_fully(
- path,
- &mut Self::on_path_diagnostic_callback(path_id.type_ref()),
- )? {
+ ) -> Option<(TraitRef, PathLoweringContext<'_, 'a>)> {
+ let mut ctx = self.at_path(path_id);
+ let resolved = match ctx.resolve_path_in_type_ns_fully()? {
// FIXME(trait_alias): We need to handle trait alias here.
TypeNs::TraitId(tr) => tr,
_ => return None,
};
- // Do this after we verify it's indeed a trait to not confuse the user if they're not modules.
- self.prohibit_generics(
- path_id,
- 0,
- path.segments().strip_last(),
- GenericArgsProhibitedReason::Module,
- );
- let segment = path.segments().last().expect("path should have at least one segment");
- Some(self.lower_trait_ref_from_resolved_path(resolved, segment, explicit_self_ty))
+ Some((ctx.lower_trait_ref_from_resolved_path(resolved, explicit_self_ty), ctx))
}
fn lower_trait_ref(
@@ -1308,16 +602,7 @@ impl<'a> TyLoweringContext<'a> {
trait_ref: &HirTraitRef,
explicit_self_ty: Ty,
) -> Option<TraitRef> {
- self.lower_trait_ref_from_path(trait_ref.path, explicit_self_ty)
- }
-
- fn trait_ref_substs_from_path(
- &mut self,
- segment: PathSegment<'_>,
- resolved: TraitId,
- explicit_self_ty: Ty,
- ) -> Substitution {
- self.substs_from_path_segment(segment, Some(resolved.into()), false, Some(explicit_self_ty))
+ self.lower_trait_ref_from_path(trait_ref.path, explicit_self_ty).map(|it| it.0)
}
pub(crate) fn lower_where_predicate<'b>(
@@ -1365,11 +650,18 @@ impl<'a> TyLoweringContext<'a> {
self_ty: Ty,
ignore_bindings: bool,
) -> impl Iterator<Item = QuantifiedWhereClause> + use<'b, 'a> {
- let mut trait_ref = None;
- let clause = match bound {
- &TypeBound::Path(path, TraitBoundModifier::None) => {
- trait_ref = self.lower_trait_ref_from_path(path, self_ty);
- trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders)
+ let mut assoc_bounds = None;
+ let mut clause = None;
+ match bound {
+ &TypeBound::Path(path, TraitBoundModifier::None) | &TypeBound::ForLifetime(_, path) => {
+ // FIXME Don't silently drop the hrtb lifetimes here
+ if let Some((trait_ref, ctx)) = self.lower_trait_ref_from_path(path, self_ty) {
+ if !ignore_bindings {
+ assoc_bounds =
+ ctx.assoc_type_bindings_from_type_bound(bound, trait_ref.clone());
+ }
+ clause = Some(crate::wrap_empty_binders(WhereClause::Implemented(trait_ref)));
+ }
}
&TypeBound::Path(path, TraitBoundModifier::Maybe) => {
let sized_trait = self
@@ -1381,170 +673,21 @@ impl<'a> TyLoweringContext<'a> {
// If we got another trait here ignore the bound completely.
let trait_id = self
.lower_trait_ref_from_path(path, self_ty.clone())
- .map(|trait_ref| trait_ref.hir_trait_id());
+ .map(|(trait_ref, _)| trait_ref.hir_trait_id());
if trait_id == sized_trait {
self.unsized_types.insert(self_ty);
}
- None
- }
- &TypeBound::ForLifetime(_, path) => {
- // FIXME Don't silently drop the hrtb lifetimes here
- trait_ref = self.lower_trait_ref_from_path(path, self_ty);
- trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders)
}
TypeBound::Lifetime(l) => {
let lifetime = self.lower_lifetime(l);
- Some(crate::wrap_empty_binders(WhereClause::TypeOutlives(TypeOutlives {
+ clause = Some(crate::wrap_empty_binders(WhereClause::TypeOutlives(TypeOutlives {
ty: self_ty,
lifetime,
- })))
+ })));
}
- TypeBound::Use(_) | TypeBound::Error => None,
- };
- clause.into_iter().chain(
- trait_ref
- .filter(move |_| !ignore_bindings)
- .map(move |tr| self.assoc_type_bindings_from_type_bound(bound, tr))
- .into_iter()
- .flatten(),
- )
- }
-
- fn assoc_type_bindings_from_type_bound<'b>(
- &'b mut self,
- bound: &'b TypeBound,
- trait_ref: TraitRef,
- ) -> impl Iterator<Item = QuantifiedWhereClause> + use<'b, 'a> {
- let last_segment = match bound {
- &TypeBound::Path(path, TraitBoundModifier::None) | &TypeBound::ForLifetime(_, path) => {
- self.types_map[path].segments().last()
- }
- TypeBound::Path(_, TraitBoundModifier::Maybe)
- | TypeBound::Use(_)
- | TypeBound::Error
- | TypeBound::Lifetime(_) => None,
- };
- last_segment
- .into_iter()
- .filter_map(|segment| segment.args_and_bindings)
- .flat_map(|args_and_bindings| args_and_bindings.bindings.iter())
- .flat_map(move |binding| {
- let found = associated_type_by_name_including_super_traits(
- self.db,
- trait_ref.clone(),
- &binding.name,
- );
- let (super_trait_ref, associated_ty) = match found {
- None => return SmallVec::new(),
- Some(t) => t,
- };
- // 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 (`super_trait_ref.substitution`).
- let substitution = self.substs_from_path_segment(
- // FIXME: This is hack. We shouldn't really build `PathSegment` directly.
- PathSegment { name: &binding.name, args_and_bindings: binding.args.as_ref() },
- Some(associated_ty.into()),
- false, // this is not relevant
- Some(super_trait_ref.self_type_parameter(Interner)),
- );
- let self_params = generics(self.db.upcast(), associated_ty.into()).len_self();
- let substitution = Substitution::from_iter(
- Interner,
- substitution
- .iter(Interner)
- .take(self_params)
- .chain(super_trait_ref.substitution.iter(Interner)),
- );
- let projection_ty = ProjectionTy {
- associated_ty_id: to_assoc_type_id(associated_ty),
- substitution,
- };
- let mut predicates: SmallVec<[_; 1]> = SmallVec::with_capacity(
- binding.type_ref.as_ref().map_or(0, |_| 1) + binding.bounds.len(),
- );
- if let Some(type_ref) = binding.type_ref {
- match (&self.types_map[type_ref], self.impl_trait_mode.mode) {
- (TypeRef::ImplTrait(_), ImplTraitLoweringMode::Disallowed) => (),
- (_, ImplTraitLoweringMode::Disallowed | ImplTraitLoweringMode::Opaque) => {
- let ty = self.lower_ty(type_ref);
- let alias_eq =
- AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty };
- predicates
- .push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq)));
- }
- (_, ImplTraitLoweringMode::Param | ImplTraitLoweringMode::Variable) => {
- // Find the generic index for the target of our `bound`
- let target_param_idx = self
- .resolver
- .where_predicates_in_scope()
- .find_map(|(p, _)| match p {
- WherePredicate::TypeBound {
- target: WherePredicateTypeTarget::TypeOrConstParam(idx),
- bound: b,
- } if b == bound => Some(idx),
- _ => None,
- });
- let ty = if let Some(target_param_idx) = target_param_idx {
- let mut counter = 0;
- let generics = self.generics().expect("generics in scope");
- for (idx, data) in generics.iter_self_type_or_consts() {
- // Count the number of `impl Trait` things that appear before
- // the target of our `bound`.
- // Our counter within `impl_trait_mode` should be that number
- // to properly lower each types within `type_ref`
- if data.type_param().is_some_and(|p| {
- p.provenance == TypeParamProvenance::ArgumentImplTrait
- }) {
- counter += 1;
- }
- if idx == *target_param_idx {
- break;
- }
- }
- let mut ext = TyLoweringContext::new_maybe_unowned(
- self.db,
- self.resolver,
- self.types_map,
- self.types_source_map,
- self.owner,
- )
- .with_type_param_mode(self.type_param_mode);
- match self.impl_trait_mode.mode {
- ImplTraitLoweringMode::Param => {
- ext.impl_trait_mode =
- ImplTraitLoweringState::param(counter);
- }
- ImplTraitLoweringMode::Variable => {
- ext.impl_trait_mode =
- ImplTraitLoweringState::variable(counter);
- }
- _ => unreachable!(),
- }
- let ty = ext.lower_ty(type_ref);
- self.diagnostics.extend(ext.diagnostics);
- ty
- } else {
- self.lower_ty(type_ref)
- };
-
- let alias_eq =
- AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty };
- predicates
- .push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq)));
- }
- }
- }
- for bound in binding.bounds.iter() {
- predicates.extend(self.lower_type_bound(
- bound,
- TyKind::Alias(AliasTy::Projection(projection_ty.clone())).intern(Interner),
- false,
- ));
- }
- predicates
- })
+ TypeBound::Use(_) | TypeBound::Error => {}
+ }
+ clause.into_iter().chain(assoc_bounds.into_iter().flatten())
}
fn lower_dyn_trait(&mut self, bounds: &[TypeBound]) -> Ty {
diff --git a/crates/hir-ty/src/lower/diagnostics.rs b/crates/hir-ty/src/lower/diagnostics.rs
index 7fe196cdbb..5c77bcd073 100644
--- a/crates/hir-ty/src/lower/diagnostics.rs
+++ b/crates/hir-ty/src/lower/diagnostics.rs
@@ -26,11 +26,11 @@ pub enum GenericArgsProhibitedReason {
Static,
/// When there is a generic enum, within the expression `Enum::Variant`,
/// either `Enum` or `Variant` are allowed to have generic arguments, but not both.
- // FIXME: This is not used now but it should be.
EnumVariant,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum PathLoweringDiagnostic {
GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason },
+ ParenthesizedGenericArgsWithoutFnTrait { segment: u32 },
}
diff --git a/crates/hir-ty/src/lower/path.rs b/crates/hir-ty/src/lower/path.rs
new file mode 100644
index 0000000000..22c5bb9923
--- /dev/null
+++ b/crates/hir-ty/src/lower/path.rs
@@ -0,0 +1,911 @@
+//! A wrapper around [`TyLoweringContext`] specifically for lowering paths.
+
+use std::iter;
+
+use chalk_ir::{cast::Cast, fold::Shift, BoundVar};
+use either::Either;
+use hir_def::{
+ data::TraitFlags,
+ expr_store::HygieneId,
+ generics::{TypeParamProvenance, WherePredicate, WherePredicateTypeTarget},
+ path::{GenericArg, GenericArgs, Path, PathSegment, PathSegments},
+ resolver::{ResolveValueResult, TypeNs, ValueNs},
+ type_ref::{TypeBound, TypeRef},
+ GenericDefId, GenericParamId, ItemContainerId, Lookup, TraitId,
+};
+use smallvec::SmallVec;
+use stdx::never;
+
+use crate::{
+ consteval::unknown_const_as_generic,
+ error_lifetime,
+ generics::generics,
+ lower::{
+ generic_arg_to_chalk, named_associated_type_shorthand_candidates, ImplTraitLoweringState,
+ },
+ to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx,
+ utils::associated_type_by_name_including_super_traits,
+ AliasEq, AliasTy, GenericArgsProhibitedReason, ImplTraitLoweringMode, Interner,
+ ParamLoweringMode, PathLoweringDiagnostic, ProjectionTy, QuantifiedWhereClause, Substitution,
+ TraitRef, Ty, TyBuilder, TyDefId, TyKind, TyLoweringContext, ValueTyDefId, WhereClause,
+};
+
+type CallbackData<'a> = Either<
+ super::PathDiagnosticCallbackData,
+ crate::infer::diagnostics::PathDiagnosticCallbackData<'a>,
+>;
+
+// We cannot use `&mut dyn FnMut()` because of lifetime issues, and we don't want to use `Box<dyn FnMut()>`
+// because of the allocation, so we create a lifetime-less callback, tailored for our needs.
+pub(crate) struct PathDiagnosticCallback<'a> {
+ pub(crate) data: CallbackData<'a>,
+ pub(crate) callback: fn(&CallbackData<'_>, &mut TyLoweringContext<'_>, PathLoweringDiagnostic),
+}
+
+pub(crate) struct PathLoweringContext<'a, 'b> {
+ ctx: &'a mut TyLoweringContext<'b>,
+ on_diagnostic: PathDiagnosticCallback<'a>,
+ path: &'a Path,
+ segments: PathSegments<'a>,
+ current_segment_idx: usize,
+ /// Contains the previous segment if `current_segment_idx == segments.len()`
+ current_or_prev_segment: PathSegment<'a>,
+}
+
+impl<'a, 'b> PathLoweringContext<'a, 'b> {
+ #[inline]
+ pub(crate) fn new(
+ ctx: &'a mut TyLoweringContext<'b>,
+ on_diagnostic: PathDiagnosticCallback<'a>,
+ path: &'a Path,
+ ) -> Self {
+ let segments = path.segments();
+ let first_segment = segments.first().unwrap_or(PathSegment::MISSING);
+ Self {
+ ctx,
+ on_diagnostic,
+ path,
+ segments,
+ current_segment_idx: 0,
+ current_or_prev_segment: first_segment,
+ }
+ }
+
+ #[inline]
+ #[cold]
+ fn on_diagnostic(&mut self, diag: PathLoweringDiagnostic) {
+ (self.on_diagnostic.callback)(&self.on_diagnostic.data, self.ctx, diag);
+ }
+
+ #[inline]
+ pub(crate) fn ty_ctx(&mut self) -> &mut TyLoweringContext<'b> {
+ self.ctx
+ }
+
+ #[inline]
+ fn current_segment_u32(&self) -> u32 {
+ self.current_segment_idx as u32
+ }
+
+ #[inline]
+ fn skip_resolved_segment(&mut self) {
+ if !matches!(self.path, Path::LangItem(..)) {
+ // In lang items, the resolved "segment" is not one of the segments. Perhaps we should've put it
+ // point at -1, but I don't feel this is clearer.
+ self.current_segment_idx += 1;
+ }
+ self.update_current_segment();
+ }
+
+ #[inline]
+ fn update_current_segment(&mut self) {
+ self.current_or_prev_segment =
+ self.segments.get(self.current_segment_idx).unwrap_or(self.current_or_prev_segment);
+ }
+
+ #[inline]
+ pub(crate) fn ignore_last_segment(&mut self) {
+ self.segments = self.segments.strip_last();
+ }
+
+ #[inline]
+ pub(crate) fn set_current_segment(&mut self, segment: usize) {
+ self.current_segment_idx = segment;
+ self.current_or_prev_segment = self
+ .segments
+ .get(segment)
+ .expect("invalid segment passed to PathLoweringContext::set_current_segment()");
+ }
+
+ pub(crate) fn lower_ty_relative_path(
+ &mut self,
+ ty: Ty,
+ // We need the original resolution to lower `Self::AssocTy` correctly
+ res: Option<TypeNs>,
+ ) -> (Ty, Option<TypeNs>) {
+ match self.segments.len() - self.current_segment_idx {
+ 0 => (ty, res),
+ 1 => {
+ // resolve unselected assoc types
+ (self.select_associated_type(res), None)
+ }
+ _ => {
+ // FIXME report error (ambiguous associated type)
+ (TyKind::Error.intern(Interner), None)
+ }
+ }
+ }
+
+ fn prohibit_parenthesized_generic_args(&mut self) -> bool {
+ if let Some(generic_args) = self.current_or_prev_segment.args_and_bindings {
+ if generic_args.desugared_from_fn {
+ let segment = self.current_segment_u32();
+ self.on_diagnostic(
+ PathLoweringDiagnostic::ParenthesizedGenericArgsWithoutFnTrait { segment },
+ );
+ return true;
+ }
+ }
+ false
+ }
+
+ // When calling this, the current segment is the resolved segment (we don't advance it yet).
+ pub(crate) fn lower_partly_resolved_path(
+ &mut self,
+ resolution: TypeNs,
+ infer_args: bool,
+ ) -> (Ty, Option<TypeNs>) {
+ let remaining_segments = self.segments.skip(self.current_segment_idx + 1);
+
+ let ty = match resolution {
+ TypeNs::TraitId(trait_) => {
+ let ty = match remaining_segments.len() {
+ 1 => {
+ let trait_ref = self.lower_trait_ref_from_resolved_path(
+ trait_,
+ TyKind::Error.intern(Interner),
+ );
+
+ self.skip_resolved_segment();
+ let segment = self.current_or_prev_segment;
+ let found =
+ self.ctx.db.trait_data(trait_).associated_type_by_name(segment.name);
+
+ match found {
+ Some(associated_ty) => {
+ // 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 (`trait_ref.substitution`).
+ let substitution = self.substs_from_path_segment(
+ associated_ty.into(),
+ false,
+ None,
+ );
+ let len_self =
+ generics(self.ctx.db.upcast(), associated_ty.into()).len_self();
+ let substitution = Substitution::from_iter(
+ Interner,
+ substitution
+ .iter(Interner)
+ .take(len_self)
+ .chain(trait_ref.substitution.iter(Interner)),
+ );
+ TyKind::Alias(AliasTy::Projection(ProjectionTy {
+ associated_ty_id: to_assoc_type_id(associated_ty),
+ substitution,
+ }))
+ .intern(Interner)
+ }
+ None => {
+ // FIXME: report error (associated type not found)
+ TyKind::Error.intern(Interner)
+ }
+ }
+ }
+ 0 => {
+ // Trait object type without dyn; this should be handled in upstream. See
+ // `lower_path()`.
+ stdx::never!("unexpected fully resolved trait path");
+ TyKind::Error.intern(Interner)
+ }
+ _ => {
+ // FIXME report error (ambiguous associated type)
+ TyKind::Error.intern(Interner)
+ }
+ };
+ return (ty, None);
+ }
+ TypeNs::TraitAliasId(_) => {
+ // FIXME(trait_alias): Implement trait alias.
+ return (TyKind::Error.intern(Interner), None);
+ }
+ TypeNs::GenericParam(param_id) => match self.ctx.type_param_mode {
+ ParamLoweringMode::Placeholder => {
+ TyKind::Placeholder(to_placeholder_idx(self.ctx.db, param_id.into()))
+ }
+ ParamLoweringMode::Variable => {
+ let idx = match self
+ .ctx
+ .generics()
+ .expect("generics in scope")
+ .type_or_const_param_idx(param_id.into())
+ {
+ None => {
+ never!("no matching generics");
+ return (TyKind::Error.intern(Interner), None);
+ }
+ Some(idx) => idx,
+ };
+
+ TyKind::BoundVar(BoundVar::new(self.ctx.in_binders, idx))
+ }
+ }
+ .intern(Interner),
+ TypeNs::SelfType(impl_id) => {
+ let generics = self.ctx.generics().expect("impl should have generic param scope");
+
+ match self.ctx.type_param_mode {
+ ParamLoweringMode::Placeholder => {
+ // `def` can be either impl itself or item within, and we need impl itself
+ // now.
+ let generics = generics.parent_or_self();
+ let subst = generics.placeholder_subst(self.ctx.db);
+ self.ctx.db.impl_self_ty(impl_id).substitute(Interner, &subst)
+ }
+ ParamLoweringMode::Variable => {
+ let starting_from = match generics.def() {
+ GenericDefId::ImplId(_) => 0,
+ // `def` is an item within impl. We need to substitute `BoundVar`s but
+ // remember that they are for parent (i.e. impl) generic params so they
+ // come after our own params.
+ _ => generics.len_self(),
+ };
+ TyBuilder::impl_self_ty(self.ctx.db, impl_id)
+ .fill_with_bound_vars(self.ctx.in_binders, starting_from)
+ .build()
+ }
+ }
+ }
+ TypeNs::AdtSelfType(adt) => {
+ let generics = generics(self.ctx.db.upcast(), adt.into());
+ let substs = match self.ctx.type_param_mode {
+ ParamLoweringMode::Placeholder => generics.placeholder_subst(self.ctx.db),
+ ParamLoweringMode::Variable => {
+ generics.bound_vars_subst(self.ctx.db, self.ctx.in_binders)
+ }
+ };
+ self.ctx.db.ty(adt.into()).substitute(Interner, &substs)
+ }
+
+ TypeNs::AdtId(it) => self.lower_path_inner(it.into(), infer_args),
+ TypeNs::BuiltinType(it) => self.lower_path_inner(it.into(), infer_args),
+ TypeNs::TypeAliasId(it) => self.lower_path_inner(it.into(), infer_args),
+ // FIXME: report error
+ TypeNs::EnumVariantId(_) => return (TyKind::Error.intern(Interner), None),
+ };
+
+ self.skip_resolved_segment();
+ self.lower_ty_relative_path(ty, Some(resolution))
+ }
+
+ fn handle_type_ns_resolution(&mut self, resolution: &TypeNs) {
+ let mut prohibit_generics_on_resolved = |reason| {
+ if self.current_or_prev_segment.args_and_bindings.is_some() {
+ let segment = self.current_segment_u32();
+ self.on_diagnostic(PathLoweringDiagnostic::GenericArgsProhibited {
+ segment,
+ reason,
+ });
+ }
+ };
+
+ match resolution {
+ TypeNs::SelfType(_) => {
+ prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy)
+ }
+ TypeNs::GenericParam(_) => {
+ prohibit_generics_on_resolved(GenericArgsProhibitedReason::TyParam)
+ }
+ TypeNs::AdtSelfType(_) => {
+ prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy)
+ }
+ TypeNs::BuiltinType(_) => {
+ prohibit_generics_on_resolved(GenericArgsProhibitedReason::PrimitiveTy)
+ }
+ TypeNs::AdtId(_)
+ | TypeNs::EnumVariantId(_)
+ | TypeNs::TypeAliasId(_)
+ | TypeNs::TraitId(_)
+ | TypeNs::TraitAliasId(_) => {}
+ }
+ }
+
+ pub(crate) fn resolve_path_in_type_ns_fully(&mut self) -> Option<TypeNs> {
+ let (res, unresolved) = self.resolve_path_in_type_ns()?;
+ if unresolved.is_some() {
+ return None;
+ }
+ Some(res)
+ }
+
+ pub(crate) fn resolve_path_in_type_ns(&mut self) -> Option<(TypeNs, Option<usize>)> {
+ let (resolution, remaining_index, _, prefix_info) = self
+ .ctx
+ .resolver
+ .resolve_path_in_type_ns_with_prefix_info(self.ctx.db.upcast(), self.path)?;
+
+ let segments = self.segments;
+ if segments.is_empty() || matches!(self.path, Path::LangItem(..)) {
+ // `segments.is_empty()` can occur with `self`.
+ return Some((resolution, remaining_index));
+ }
+
+ let (module_segments, resolved_segment_idx, enum_segment) = match remaining_index {
+ None if prefix_info.enum_variant => {
+ (segments.strip_last_two(), segments.len() - 1, Some(segments.len() - 2))
+ }
+ None => (segments.strip_last(), segments.len() - 1, None),
+ Some(i) => (segments.take(i - 1), i - 1, None),
+ };
+
+ self.current_segment_idx = resolved_segment_idx;
+ self.current_or_prev_segment =
+ segments.get(resolved_segment_idx).expect("should have resolved segment");
+
+ if matches!(self.path, Path::BarePath(..)) {
+ // Bare paths cannot have generics, so skip them as an optimization.
+ return Some((resolution, remaining_index));
+ }
+
+ for (i, mod_segment) in module_segments.iter().enumerate() {
+ if mod_segment.args_and_bindings.is_some() {
+ self.on_diagnostic(PathLoweringDiagnostic::GenericArgsProhibited {
+ segment: i as u32,
+ reason: GenericArgsProhibitedReason::Module,
+ });
+ }
+ }
+
+ if let Some(enum_segment) = enum_segment {
+ if segments.get(enum_segment).is_some_and(|it| it.args_and_bindings.is_some())
+ && segments.get(enum_segment + 1).is_some_and(|it| it.args_and_bindings.is_some())
+ {
+ self.on_diagnostic(PathLoweringDiagnostic::GenericArgsProhibited {
+ segment: (enum_segment + 1) as u32,
+ reason: GenericArgsProhibitedReason::EnumVariant,
+ });
+ }
+ }
+
+ self.handle_type_ns_resolution(&resolution);
+
+ Some((resolution, remaining_index))
+ }
+
+ pub(crate) fn resolve_path_in_value_ns(
+ &mut self,
+ hygiene_id: HygieneId,
+ ) -> Option<ResolveValueResult> {
+ let (res, prefix_info) = self.ctx.resolver.resolve_path_in_value_ns_with_prefix_info(
+ self.ctx.db.upcast(),
+ self.path,
+ hygiene_id,
+ )?;
+
+ let segments = self.segments;
+ if segments.is_empty() || matches!(self.path, Path::LangItem(..)) {
+ // `segments.is_empty()` can occur with `self`.
+ return Some(res);
+ }
+
+ let (mod_segments, enum_segment, resolved_segment_idx) = match res {
+ ResolveValueResult::Partial(_, unresolved_segment, _) => {
+ (segments.take(unresolved_segment - 1), None, unresolved_segment - 1)
+ }
+ 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),
+ };
+
+ self.current_segment_idx = resolved_segment_idx;
+ self.current_or_prev_segment =
+ segments.get(resolved_segment_idx).expect("should have resolved segment");
+
+ for (i, mod_segment) in mod_segments.iter().enumerate() {
+ if mod_segment.args_and_bindings.is_some() {
+ self.on_diagnostic(PathLoweringDiagnostic::GenericArgsProhibited {
+ segment: i as u32,
+ reason: GenericArgsProhibitedReason::Module,
+ });
+ }
+ }
+
+ if let Some(enum_segment) = enum_segment {
+ if segments.get(enum_segment).is_some_and(|it| it.args_and_bindings.is_some())
+ && segments.get(enum_segment + 1).is_some_and(|it| it.args_and_bindings.is_some())
+ {
+ self.on_diagnostic(PathLoweringDiagnostic::GenericArgsProhibited {
+ segment: (enum_segment + 1) as u32,
+ reason: GenericArgsProhibitedReason::EnumVariant,
+ });
+ }
+ }
+
+ match &res {
+ ResolveValueResult::ValueNs(resolution, _) => {
+ let resolved_segment_idx = self.current_segment_u32();
+ let resolved_segment = self.current_or_prev_segment;
+
+ let mut prohibit_generics_on_resolved = |reason| {
+ if resolved_segment.args_and_bindings.is_some() {
+ self.on_diagnostic(PathLoweringDiagnostic::GenericArgsProhibited {
+ segment: resolved_segment_idx,
+ reason,
+ });
+ }
+ };
+
+ match resolution {
+ ValueNs::ImplSelf(_) => {
+ prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy)
+ }
+ // FIXME: rustc generates E0107 (incorrect number of generic arguments) and not
+ // E0109 (generic arguments provided for a type that doesn't accept them) for
+ // consts and statics, presumably as a defense against future in which consts
+ // and statics can be generic, or just because it was easier for rustc implementors.
+ // That means we'll show the wrong error code. Because of us it's easier to do it
+ // this way :)
+ ValueNs::GenericParam(_) | ValueNs::ConstId(_) => {
+ prohibit_generics_on_resolved(GenericArgsProhibitedReason::Const)
+ }
+ ValueNs::StaticId(_) => {
+ prohibit_generics_on_resolved(GenericArgsProhibitedReason::Static)
+ }
+ ValueNs::FunctionId(_) | ValueNs::StructId(_) | ValueNs::EnumVariantId(_) => {}
+ ValueNs::LocalBinding(_) => {}
+ }
+ }
+ ResolveValueResult::Partial(resolution, _, _) => {
+ self.handle_type_ns_resolution(resolution);
+ }
+ };
+ Some(res)
+ }
+
+ fn select_associated_type(&mut self, res: Option<TypeNs>) -> Ty {
+ let Some((generics, res)) = self.ctx.generics().zip(res) else {
+ return TyKind::Error.intern(Interner);
+ };
+ let segment = self.current_or_prev_segment;
+ let ty = named_associated_type_shorthand_candidates(
+ self.ctx.db,
+ generics.def(),
+ res,
+ Some(segment.name.clone()),
+ move |name, t, associated_ty| {
+ let generics = self.ctx.generics().unwrap();
+
+ if name != segment.name {
+ return None;
+ }
+
+ let parent_subst = t.substitution.clone();
+ let parent_subst = match self.ctx.type_param_mode {
+ ParamLoweringMode::Placeholder => {
+ // if we're lowering to placeholders, we have to put them in now.
+ let s = generics.placeholder_subst(self.ctx.db);
+ s.apply(parent_subst, Interner)
+ }
+ ParamLoweringMode::Variable => {
+ // We need to shift in the bound vars, since
+ // `named_associated_type_shorthand_candidates` does not do that.
+ parent_subst.shifted_in_from(Interner, self.ctx.in_binders)
+ }
+ };
+
+ // 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(), false, None);
+
+ let len_self =
+ crate::generics::generics(self.ctx.db.upcast(), associated_ty.into())
+ .len_self();
+
+ let substs = Substitution::from_iter(
+ Interner,
+ substs.iter(Interner).take(len_self).chain(parent_subst.iter(Interner)),
+ );
+
+ Some(
+ TyKind::Alias(AliasTy::Projection(ProjectionTy {
+ associated_ty_id: to_assoc_type_id(associated_ty),
+ substitution: substs,
+ }))
+ .intern(Interner),
+ )
+ },
+ );
+
+ ty.unwrap_or_else(|| TyKind::Error.intern(Interner))
+ }
+
+ fn lower_path_inner(&mut self, typeable: TyDefId, infer_args: bool) -> Ty {
+ let generic_def = match typeable {
+ TyDefId::BuiltinType(builtin) => return TyBuilder::builtin(builtin),
+ TyDefId::AdtId(it) => it.into(),
+ TyDefId::TypeAliasId(it) => it.into(),
+ };
+ let substs = self.substs_from_path_segment(generic_def, infer_args, None);
+ self.ctx.db.ty(typeable).substitute(Interner, &substs)
+ }
+
+ /// Collect generic arguments from a path into a `Substs`. See also
+ /// `create_substs_for_ast_path` and `def_to_ty` in rustc.
+ pub(crate) fn substs_from_path(
+ &mut self,
+ // Note that we don't call `db.value_type(resolved)` here,
+ // `ValueTyDefId` is just a convenient way to pass generics and
+ // special-case enum variants
+ resolved: ValueTyDefId,
+ infer_args: bool,
+ ) -> Substitution {
+ let prev_current_segment_idx = self.current_segment_idx;
+ let prev_current_segment = self.current_or_prev_segment;
+
+ let generic_def = match resolved {
+ ValueTyDefId::FunctionId(it) => it.into(),
+ ValueTyDefId::StructId(it) => it.into(),
+ ValueTyDefId::UnionId(it) => it.into(),
+ ValueTyDefId::ConstId(it) => it.into(),
+ ValueTyDefId::StaticId(_) => return Substitution::empty(Interner),
+ ValueTyDefId::EnumVariantId(var) => {
+ // the generic args for an enum variant may be either specified
+ // on the segment referring to the enum, or on the segment
+ // referring to the variant. So `Option::<T>::None` and
+ // `Option::None::<T>` are both allowed (though the former is
+ // FIXME: This isn't strictly correct, enum variants may be used not through the enum
+ // (via `use Enum::Variant`). The resolver returns whether they were, but we don't have its result
+ // available here. The worst that can happen is that we will show some confusing diagnostics to the user,
+ // if generics exist on the module and they don't match with the variant.
+ // preferred). See also `def_ids_for_path_segments` in rustc.
+ //
+ // `wrapping_sub(1)` will return a number which `get` will return None for if current_segment_idx<2.
+ // This simplifies the code a bit.
+ let penultimate_idx = self.current_segment_idx.wrapping_sub(1);
+ let penultimate = self.segments.get(penultimate_idx);
+ if let Some(penultimate) = penultimate {
+ if self.current_or_prev_segment.args_and_bindings.is_none()
+ && penultimate.args_and_bindings.is_some()
+ {
+ self.current_segment_idx = penultimate_idx;
+ self.current_or_prev_segment = penultimate;
+ }
+ }
+ var.lookup(self.ctx.db.upcast()).parent.into()
+ }
+ };
+ let result = self.substs_from_path_segment(generic_def, infer_args, None);
+ self.current_segment_idx = prev_current_segment_idx;
+ self.current_or_prev_segment = prev_current_segment;
+ result
+ }
+
+ pub(crate) fn substs_from_path_segment(
+ &mut self,
+ def: GenericDefId,
+ infer_args: bool,
+ explicit_self_ty: Option<Ty>,
+ ) -> Substitution {
+ let prohibit_parens = match def {
+ GenericDefId::TraitId(trait_) => {
+ let trait_data = self.ctx.db.trait_data(trait_);
+ !trait_data.flags.contains(TraitFlags::RUSTC_PAREN_SUGAR)
+ }
+ _ => true,
+ };
+ if prohibit_parens && self.prohibit_parenthesized_generic_args() {
+ return TyBuilder::unknown_subst(self.ctx.db, def);
+ }
+
+ self.substs_from_args_and_bindings(
+ self.current_or_prev_segment.args_and_bindings,
+ def,
+ infer_args,
+ explicit_self_ty,
+ )
+ }
+
+ pub(super) fn substs_from_args_and_bindings(
+ &mut self,
+ args_and_bindings: Option<&GenericArgs>,
+ def: GenericDefId,
+ infer_args: bool,
+ explicit_self_ty: Option<Ty>,
+ ) -> Substitution {
+ // Order is
+ // - Optional Self parameter
+ // - Lifetime parameters
+ // - Type or Const parameters
+ // - Parent parameters
+ let def_generics = generics(self.ctx.db.upcast(), def);
+ let (
+ parent_params,
+ self_param,
+ type_params,
+ const_params,
+ impl_trait_params,
+ lifetime_params,
+ ) = def_generics.provenance_split();
+ let item_len =
+ self_param as usize + type_params + const_params + impl_trait_params + lifetime_params;
+ let total_len = parent_params + item_len;
+
+ let mut substs = Vec::new();
+
+ // we need to iterate the lifetime and type/const params separately as our order of them
+ // differs from the supplied syntax
+
+ let ty_error = || TyKind::Error.intern(Interner).cast(Interner);
+ let mut def_toc_iter = def_generics.iter_self_type_or_consts_id();
+ let fill_self_param = || {
+ if self_param {
+ let self_ty = explicit_self_ty.map(|x| x.cast(Interner)).unwrap_or_else(ty_error);
+
+ if let Some(id) = def_toc_iter.next() {
+ assert!(matches!(id, GenericParamId::TypeParamId(_)));
+ substs.push(self_ty);
+ }
+ }
+ };
+ let mut had_explicit_args = false;
+
+ if let Some(&GenericArgs { ref args, has_self_type, .. }) = args_and_bindings {
+ // Fill in the self param first
+ if has_self_type && self_param {
+ had_explicit_args = true;
+ if let Some(id) = def_toc_iter.next() {
+ assert!(matches!(id, GenericParamId::TypeParamId(_)));
+ had_explicit_args = true;
+ if let GenericArg::Type(ty) = &args[0] {
+ substs.push(self.ctx.lower_ty(*ty).cast(Interner));
+ }
+ }
+ } else {
+ fill_self_param()
+ };
+
+ // Then fill in the supplied lifetime args, or error lifetimes if there are too few
+ // (default lifetimes aren't a thing)
+ for arg in args
+ .iter()
+ .filter_map(|arg| match arg {
+ GenericArg::Lifetime(arg) => Some(self.ctx.lower_lifetime(arg)),
+ _ => None,
+ })
+ .chain(iter::repeat(error_lifetime()))
+ .take(lifetime_params)
+ {
+ substs.push(arg.cast(Interner));
+ }
+
+ let skip = if has_self_type { 1 } else { 0 };
+ // Fill in supplied type and const args
+ // Note if non-lifetime args are provided, it should be all of them, but we can't rely on that
+ for (arg, id) in args
+ .iter()
+ .filter(|arg| !matches!(arg, GenericArg::Lifetime(_)))
+ .skip(skip)
+ .take(type_params + const_params)
+ .zip(def_toc_iter)
+ {
+ had_explicit_args = true;
+ let arg = generic_arg_to_chalk(
+ self.ctx.db,
+ id,
+ arg,
+ self.ctx,
+ self.ctx.types_map,
+ |ctx, type_ref| ctx.lower_ty(type_ref),
+ |ctx, const_ref, ty| ctx.lower_const(const_ref, ty),
+ |ctx, lifetime_ref| ctx.lower_lifetime(lifetime_ref),
+ );
+ substs.push(arg);
+ }
+ } else {
+ fill_self_param();
+ }
+
+ let param_to_err = |id| match id {
+ GenericParamId::ConstParamId(x) => {
+ unknown_const_as_generic(self.ctx.db.const_param_ty(x))
+ }
+ GenericParamId::TypeParamId(_) => ty_error(),
+ GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner),
+ };
+ // handle defaults. In expression or pattern path segments without
+ // explicitly specified type arguments, missing type arguments are inferred
+ // (i.e. defaults aren't used).
+ // Generic parameters for associated types are not supposed to have defaults, so we just
+ // ignore them.
+ let is_assoc_ty = || match def {
+ GenericDefId::TypeAliasId(id) => {
+ matches!(id.lookup(self.ctx.db.upcast()).container, ItemContainerId::TraitId(_))
+ }
+ _ => false,
+ };
+ let fill_defaults = (!infer_args || had_explicit_args) && !is_assoc_ty();
+ if fill_defaults {
+ let defaults = &*self.ctx.db.generic_defaults(def);
+ let (item, _parent) = defaults.split_at(item_len);
+ let parent_from = item_len - substs.len();
+
+ let mut rem =
+ def_generics.iter_id().skip(substs.len()).map(param_to_err).collect::<Vec<_>>();
+ // Fill in defaults for type/const params
+ for (idx, default_ty) in item[substs.len()..].iter().enumerate() {
+ // each default can depend on the previous parameters
+ let substs_so_far = Substitution::from_iter(
+ Interner,
+ substs.iter().cloned().chain(rem[idx..].iter().cloned()),
+ );
+ substs.push(default_ty.clone().substitute(Interner, &substs_so_far));
+ }
+ // Fill in remaining parent params
+ substs.extend(rem.drain(parent_from..));
+ } else {
+ // Fill in remaining def params and parent params
+ substs.extend(def_generics.iter_id().skip(substs.len()).map(param_to_err));
+ }
+
+ assert_eq!(substs.len(), total_len, "expected {} substs, got {}", total_len, substs.len());
+ Substitution::from_iter(Interner, substs)
+ }
+
+ pub(crate) fn lower_trait_ref_from_resolved_path(
+ &mut self,
+ resolved: TraitId,
+ explicit_self_ty: Ty,
+ ) -> TraitRef {
+ let substs = self.trait_ref_substs_from_path(resolved, explicit_self_ty);
+ TraitRef { trait_id: to_chalk_trait_id(resolved), substitution: substs }
+ }
+
+ fn trait_ref_substs_from_path(
+ &mut self,
+ resolved: TraitId,
+ explicit_self_ty: Ty,
+ ) -> Substitution {
+ self.substs_from_path_segment(resolved.into(), false, Some(explicit_self_ty))
+ }
+
+ pub(super) fn assoc_type_bindings_from_type_bound<'c>(
+ mut self,
+ bound: &'c TypeBound,
+ trait_ref: TraitRef,
+ ) -> Option<impl Iterator<Item = QuantifiedWhereClause> + use<'a, 'b, 'c>> {
+ self.current_or_prev_segment.args_and_bindings.map(|args_and_bindings| {
+ args_and_bindings.bindings.iter().flat_map(move |binding| {
+ let found = associated_type_by_name_including_super_traits(
+ self.ctx.db,
+ trait_ref.clone(),
+ &binding.name,
+ );
+ let (super_trait_ref, associated_ty) = match found {
+ None => return SmallVec::new(),
+ Some(t) => t,
+ };
+ // 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 (`super_trait_ref.substitution`).
+ let substitution = self.substs_from_args_and_bindings(
+ binding.args.as_ref(),
+ associated_ty.into(),
+ false, // this is not relevant
+ Some(super_trait_ref.self_type_parameter(Interner)),
+ );
+ let self_params = generics(self.ctx.db.upcast(), associated_ty.into()).len_self();
+ let substitution = Substitution::from_iter(
+ Interner,
+ substitution
+ .iter(Interner)
+ .take(self_params)
+ .chain(super_trait_ref.substitution.iter(Interner)),
+ );
+ let projection_ty = ProjectionTy {
+ associated_ty_id: to_assoc_type_id(associated_ty),
+ substitution,
+ };
+ let mut predicates: SmallVec<[_; 1]> = SmallVec::with_capacity(
+ binding.type_ref.as_ref().map_or(0, |_| 1) + binding.bounds.len(),
+ );
+ if let Some(type_ref) = binding.type_ref {
+ match (&self.ctx.types_map[type_ref], self.ctx.impl_trait_mode.mode) {
+ (TypeRef::ImplTrait(_), ImplTraitLoweringMode::Disallowed) => (),
+ (_, ImplTraitLoweringMode::Disallowed | ImplTraitLoweringMode::Opaque) => {
+ let ty = self.ctx.lower_ty(type_ref);
+ let alias_eq =
+ AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty };
+ predicates
+ .push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq)));
+ }
+ (_, ImplTraitLoweringMode::Param | ImplTraitLoweringMode::Variable) => {
+ // Find the generic index for the target of our `bound`
+ let target_param_idx =
+ self.ctx.resolver.where_predicates_in_scope().find_map(|(p, _)| {
+ match p {
+ WherePredicate::TypeBound {
+ target: WherePredicateTypeTarget::TypeOrConstParam(idx),
+ bound: b,
+ } if b == bound => Some(idx),
+ _ => None,
+ }
+ });
+ let ty = if let Some(target_param_idx) = target_param_idx {
+ let mut counter = 0;
+ let generics = self.ctx.generics().expect("generics in scope");
+ for (idx, data) in generics.iter_self_type_or_consts() {
+ // Count the number of `impl Trait` things that appear before
+ // the target of our `bound`.
+ // Our counter within `impl_trait_mode` should be that number
+ // to properly lower each types within `type_ref`
+ if data.type_param().is_some_and(|p| {
+ p.provenance == TypeParamProvenance::ArgumentImplTrait
+ }) {
+ counter += 1;
+ }
+ if idx == *target_param_idx {
+ break;
+ }
+ }
+ let mut ext = TyLoweringContext::new_maybe_unowned(
+ self.ctx.db,
+ self.ctx.resolver,
+ self.ctx.types_map,
+ self.ctx.types_source_map,
+ self.ctx.owner,
+ )
+ .with_type_param_mode(self.ctx.type_param_mode);
+ match self.ctx.impl_trait_mode.mode {
+ ImplTraitLoweringMode::Param => {
+ ext.impl_trait_mode =
+ ImplTraitLoweringState::param(counter);
+ }
+ ImplTraitLoweringMode::Variable => {
+ ext.impl_trait_mode =
+ ImplTraitLoweringState::variable(counter);
+ }
+ _ => unreachable!(),
+ }
+ let ty = ext.lower_ty(type_ref);
+ self.ctx.diagnostics.extend(ext.diagnostics);
+ ty
+ } else {
+ self.ctx.lower_ty(type_ref)
+ };
+
+ let alias_eq =
+ AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty };
+ predicates
+ .push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq)));
+ }
+ }
+ }
+ for bound in binding.bounds.iter() {
+ predicates.extend(self.ctx.lower_type_bound(
+ bound,
+ TyKind::Alias(AliasTy::Projection(projection_ty.clone())).intern(Interner),
+ false,
+ ));
+ }
+ predicates
+ })
+ })
+ }
+}
diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs
index 84d8950b1a..41304bbd8a 100644
--- a/crates/hir-ty/src/mir.rs
+++ b/crates/hir-ty/src/mir.rs
@@ -10,7 +10,7 @@ use crate::{
lang_items::is_box,
mapping::ToChalk,
CallableDefId, ClosureId, Const, ConstScalar, InferenceResult, Interner, MemoryMap,
- Substitution, TraitEnvironment, Ty, TyKind,
+ Substitution, TraitEnvironment, Ty, TyExt, TyKind,
};
use base_db::CrateId;
use chalk_ir::Mutability;
@@ -144,6 +144,13 @@ impl<V, T> ProjectionElem<V, T> {
closure_field: impl FnOnce(ClosureId, &Substitution, usize) -> Ty,
krate: CrateId,
) -> Ty {
+ // we only bail on mir building when there are type mismatches
+ // but error types may pop up resulting in us still attempting to build the mir
+ // so just propagate the error type
+ if base.is_unknown() {
+ return TyKind::Error.intern(Interner);
+ }
+
if matches!(base.kind(Interner), TyKind::Alias(_) | TyKind::AssociatedType(..)) {
base = normalize(
db,
@@ -166,7 +173,7 @@ impl<V, T> ProjectionElem<V, T> {
TyKind::Error.intern(Interner)
}
},
- ProjectionElem::Field(Either::Left(f)) => match &base.kind(Interner) {
+ ProjectionElem::Field(Either::Left(f)) => match base.kind(Interner) {
TyKind::Adt(_, subst) => {
db.field_types(f.parent)[f.local_id].clone().substitute(Interner, subst)
}
diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs
index f1e86daea2..9625ae5f88 100644
--- a/crates/hir-ty/src/mir/eval/tests.rs
+++ b/crates/hir-ty/src/mir/eval/tests.rs
@@ -912,3 +912,36 @@ fn main() {
"",
);
}
+
+#[test]
+fn regression_19021() {
+ check_pass(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+#[lang = "owned_box"]
+struct Box<T>(T);
+
+impl<T> Deref for Box<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+struct Foo;
+
+fn main() {
+ let x = Box(Foo);
+ let y = &Foo;
+
+ || match x {
+ ref x => x,
+ _ => y,
+ };
+}
+"#,
+ );
+}
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs
index 549450e9be..f88696692e 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -8,8 +8,8 @@ use hir_def::{
data::adt::{StructKind, VariantData},
expr_store::{Body, HygieneId},
hir::{
- ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ExprId, LabelId, Literal,
- LiteralOrConst, MatchArm, Pat, PatId, RecordFieldPat, RecordLitField,
+ ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ExprId, LabelId, Literal, MatchArm,
+ Pat, PatId, RecordFieldPat, RecordLitField,
},
lang_item::{LangItem, LangItemTarget},
path::Path,
@@ -1358,20 +1358,10 @@ impl<'ctx> MirLowerCtx<'ctx> {
Ok(())
}
- fn lower_literal_or_const_to_operand(
- &mut self,
- ty: Ty,
- loc: &LiteralOrConst,
- ) -> Result<Operand> {
- match loc {
- LiteralOrConst::Literal(l) => self.lower_literal_to_operand(ty, l),
- LiteralOrConst::Const(c) => {
- let c = match &self.body.pats[*c] {
- Pat::Path(p) => p,
- _ => not_supported!(
- "only `char` and numeric types are allowed in range patterns"
- ),
- };
+ fn lower_literal_or_const_to_operand(&mut self, ty: Ty, loc: &ExprId) -> Result<Operand> {
+ match &self.body.exprs[*loc] {
+ Expr::Literal(l) => self.lower_literal_to_operand(ty, l),
+ Expr::Path(c) => {
let edition = self.edition();
let unresolved_name =
|| MirLowerError::unresolved_path(self.db, c, edition, &self.body.types);
@@ -1392,6 +1382,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
}
}
+ _ => {
+ not_supported!("only `char` and numeric types are allowed in range patterns");
+ }
}
}
diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs
index 2ffea34c85..289175feef 100644
--- a/crates/hir-ty/src/mir/lower/pattern_matching.rs
+++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs
@@ -1,6 +1,6 @@
//! MIR lowering for patterns
-use hir_def::{hir::LiteralOrConst, AssocItemId};
+use hir_def::{hir::ExprId, AssocItemId};
use crate::{
mir::{
@@ -207,7 +207,7 @@ impl MirLowerCtx<'_> {
)?
}
Pat::Range { start, end } => {
- let mut add_check = |l: &LiteralOrConst, binop| -> Result<()> {
+ let mut add_check = |l: &ExprId, binop| -> Result<()> {
let lv =
self.lower_literal_or_const_to_operand(self.infer[pattern].clone(), l)?;
let else_target = *current_else.get_or_insert_with(|| self.new_basic_block());
diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs
index 69ec35f406..f5a4d4ff35 100644
--- a/crates/hir-ty/src/tests.rs
+++ b/crates/hir-ty/src/tests.rs
@@ -117,7 +117,7 @@ fn check_impl(
expected.trim_start_matches("adjustments:").trim().to_owned(),
);
} else {
- panic!("unexpected annotation: {expected}");
+ panic!("unexpected annotation: {expected} @ {range:?}");
}
had_annotations = true;
}
diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs
index 7992f1feee..ef94814d58 100644
--- a/crates/hir-ty/src/tests/coercion.rs
+++ b/crates/hir-ty/src/tests/coercion.rs
@@ -185,11 +185,10 @@ fn test() {
let t = &mut 1;
let x = match 1 {
1 => t as *mut i32,
+ //^^^^^^^^^^^^^ adjustments: Pointer(MutToConstPointer)
2 => t as &i32,
//^^^^^^^^^ expected *mut i32, got &'? i32
_ => t as *const i32,
- // ^^^^^^^^^^^^^^^ adjustments: Pointer(MutToConstPointer)
-
};
x;
//^ type: *const i32
@@ -536,7 +535,7 @@ fn test() {
#[test]
fn coerce_unsize_generic() {
- check(
+ check_no_mismatches(
r#"
//- minicore: coerce_unsized
struct Foo<T> { t: T };
@@ -544,9 +543,7 @@ struct Bar<T>(Foo<T>);
fn test() {
let _: &Foo<[usize]> = &Foo { t: [1, 2, 3] };
- //^^^^^^^^^^^^^^^^^^^^^ expected &'? Foo<[usize]>, got &'? Foo<[i32; 3]>
let _: &Bar<[usize]> = &Bar(Foo { t: [1, 2, 3] });
- //^^^^^^^^^^^^^^^^^^^^^^^^^^ expected &'? Bar<[usize]>, got &'? Bar<[i32; 3]>
}
"#,
);
@@ -958,3 +955,24 @@ fn f() {
"#,
);
}
+
+#[test]
+fn coerce_nested_unsized_struct() {
+ check_types(
+ r#"
+//- minicore: fn, coerce_unsized, dispatch_from_dyn, sized
+use core::marker::Unsize;
+
+struct Foo<T: ?Sized>(T);
+
+fn need(_: &Foo<dyn Fn(i32) -> i32>) {
+}
+
+fn test() {
+ let callback = |x| x;
+ //^ i32
+ need(&Foo(callback));
+}
+"#,
+ )
+}
diff --git a/crates/hir-ty/src/tests/diagnostics.rs b/crates/hir-ty/src/tests/diagnostics.rs
index def06f2d59..855034117c 100644
--- a/crates/hir-ty/src/tests/diagnostics.rs
+++ b/crates/hir-ty/src/tests/diagnostics.rs
@@ -153,3 +153,53 @@ fn consume() -> Option<()> {
"#,
);
}
+
+#[test]
+fn method_call_on_field() {
+ check(
+ r#"
+struct S {
+ field: fn(f32) -> u32,
+ field2: u32
+}
+
+fn main() {
+ let s = S { field: |_| 0, field2: 0 };
+ s.field(0);
+ // ^ expected f32, got i32
+ // ^^^^^^^^^^ type: u32
+ s.field2(0);
+ // ^ type: i32
+ // ^^^^^^^^^^^ type: {unknown}
+ s.not_a_field(0);
+ // ^ type: i32
+ // ^^^^^^^^^^^^^^^^ type: {unknown}
+}
+"#,
+ );
+}
+
+#[test]
+fn method_call_on_assoc() {
+ check(
+ r#"
+struct S;
+
+impl S {
+ fn not_a_method() -> f32 { 0.0 }
+ fn not_a_method2(this: Self, param: f32) -> Self { this }
+ fn not_a_method3(param: f32) -> Self { S }
+}
+
+fn main() {
+ S.not_a_method(0);
+ // ^^^^^^^^^^^^^^^^^ type: f32
+ S.not_a_method2(0);
+ // ^ expected f32, got i32
+ // ^^^^^^^^^^^^^^^^^^ type: S
+ S.not_a_method3(0);
+ // ^^^^^^^^^^^^^^^^^^ type: S
+}
+"#,
+ );
+}
diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs
index e5f791ea6f..3a258ecad1 100644
--- a/crates/hir-ty/src/tests/method_resolution.rs
+++ b/crates/hir-ty/src/tests/method_resolution.rs
@@ -1210,7 +1210,7 @@ impl<T> Slice<T> {
fn main() {
let foo: Slice<u32>;
foo.into_vec(); // we shouldn't crash on this at least
-} //^^^^^^^^^^^^^^ {unknown}
+} //^^^^^^^^^^^^^^ ()
"#,
);
}
diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs
index dda7bfb2ba..f0eb41b1ce 100644
--- a/crates/hir-ty/src/tests/traits.rs
+++ b/crates/hir-ty/src/tests/traits.rs
@@ -4694,21 +4694,21 @@ fn f<T: Send, U>() {
Struct::<T>::IS_SEND;
//^^^^^^^^^^^^^^^^^^^^Yes
Struct::<U>::IS_SEND;
- //^^^^^^^^^^^^^^^^^^^^Yes
+ //^^^^^^^^^^^^^^^^^^^^{unknown}
Struct::<*const T>::IS_SEND;
- //^^^^^^^^^^^^^^^^^^^^^^^^^^^Yes
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^{unknown}
Enum::<T>::IS_SEND;
//^^^^^^^^^^^^^^^^^^Yes
Enum::<U>::IS_SEND;
- //^^^^^^^^^^^^^^^^^^Yes
+ //^^^^^^^^^^^^^^^^^^{unknown}
Enum::<*const T>::IS_SEND;
- //^^^^^^^^^^^^^^^^^^^^^^^^^Yes
+ //^^^^^^^^^^^^^^^^^^^^^^^^^{unknown}
Union::<T>::IS_SEND;
//^^^^^^^^^^^^^^^^^^^Yes
Union::<U>::IS_SEND;
- //^^^^^^^^^^^^^^^^^^^Yes
+ //^^^^^^^^^^^^^^^^^^^{unknown}
Union::<*const T>::IS_SEND;
- //^^^^^^^^^^^^^^^^^^^^^^^^^^Yes
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^{unknown}
PhantomData::<T>::IS_SEND;
//^^^^^^^^^^^^^^^^^^^^^^^^^Yes
PhantomData::<U>::IS_SEND;
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 64e982c42d..1ed0daa375 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -6,10 +6,11 @@
use cfg::{CfgExpr, CfgOptions};
use either::Either;
use hir_def::{
+ expr_store::ExprOrPatPtr,
hir::ExprOrPatId,
path::{hir_segment_to_ast_segment, ModPath},
type_ref::TypesSourceMap,
- AssocItemId, DefWithBodyId, SyntheticSyntax,
+ DefWithBodyId, SyntheticSyntax,
};
use hir_expand::{name::Name, HirFileId, InFile};
use hir_ty::{
@@ -24,7 +25,7 @@ use syntax::{
};
use triomphe::Arc;
-use crate::{AssocItem, Field, Local, Trait, Type};
+use crate::{AssocItem, Field, Function, Local, Trait, Type};
pub use hir_def::VariantId;
pub use hir_ty::{
@@ -111,18 +112,19 @@ diagnostics![
UnusedMut,
UnusedVariable,
GenericArgsProhibited,
+ ParenthesizedGenericArgsWithoutFnTrait,
];
#[derive(Debug)]
pub struct BreakOutsideOfLoop {
- pub expr: InFile<AstPtr<ast::Expr>>,
+ pub expr: InFile<ExprOrPatPtr>,
pub is_break: bool,
pub bad_value_break: bool,
}
#[derive(Debug)]
pub struct TypedHole {
- pub expr: InFile<AstPtr<ast::Expr>>,
+ pub expr: InFile<ExprOrPatPtr>,
pub expected: Type,
}
@@ -221,26 +223,26 @@ pub struct NoSuchField {
#[derive(Debug)]
pub struct PrivateAssocItem {
- pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
+ pub expr_or_pat: InFile<ExprOrPatPtr>,
pub item: AssocItem,
}
#[derive(Debug)]
pub struct MismatchedTupleStructPatArgCount {
- pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
+ pub expr_or_pat: InFile<ExprOrPatPtr>,
pub expected: usize,
pub found: usize,
}
#[derive(Debug)]
pub struct ExpectedFunction {
- pub call: InFile<AstPtr<ast::Expr>>,
+ pub call: InFile<ExprOrPatPtr>,
pub found: Type,
}
#[derive(Debug)]
pub struct UnresolvedField {
- pub expr: InFile<AstPtr<ast::Expr>>,
+ pub expr: InFile<ExprOrPatPtr>,
pub receiver: Type,
pub name: Name,
pub method_with_same_name_exists: bool,
@@ -248,26 +250,26 @@ pub struct UnresolvedField {
#[derive(Debug)]
pub struct UnresolvedMethodCall {
- pub expr: InFile<AstPtr<ast::Expr>>,
+ pub expr: InFile<ExprOrPatPtr>,
pub receiver: Type,
pub name: Name,
pub field_with_same_name: Option<Type>,
- pub assoc_func_with_same_name: Option<AssocItemId>,
+ pub assoc_func_with_same_name: Option<Function>,
}
#[derive(Debug)]
pub struct UnresolvedAssocItem {
- pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
+ pub expr_or_pat: InFile<ExprOrPatPtr>,
}
#[derive(Debug)]
pub struct UnresolvedIdent {
- pub node: InFile<(AstPtr<Either<ast::Expr, ast::Pat>>, Option<TextRange>)>,
+ pub node: InFile<(ExprOrPatPtr, Option<TextRange>)>,
}
#[derive(Debug)]
pub struct PrivateField {
- pub expr: InFile<AstPtr<ast::Expr>>,
+ pub expr: InFile<ExprOrPatPtr>,
pub field: Field,
}
@@ -280,7 +282,7 @@ pub enum UnsafeLint {
#[derive(Debug)]
pub struct MissingUnsafe {
- pub node: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
+ pub node: InFile<ExprOrPatPtr>,
pub lint: UnsafeLint,
pub reason: UnsafetyReason,
}
@@ -302,7 +304,7 @@ pub struct ReplaceFilterMapNextWithFindMap {
#[derive(Debug)]
pub struct MismatchedArgCount {
- pub call_expr: InFile<AstPtr<ast::Expr>>,
+ pub call_expr: InFile<ExprOrPatPtr>,
pub expected: usize,
pub found: usize,
}
@@ -321,7 +323,7 @@ pub struct NonExhaustiveLet {
#[derive(Debug)]
pub struct TypeMismatch {
- pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
+ pub expr_or_pat: InFile<ExprOrPatPtr>,
pub expected: Type,
pub actual: Type,
}
@@ -395,13 +397,13 @@ pub struct RemoveUnnecessaryElse {
#[derive(Debug)]
pub struct CastToUnsized {
- pub expr: InFile<AstPtr<ast::Expr>>,
+ pub expr: InFile<ExprOrPatPtr>,
pub cast_ty: Type,
}
#[derive(Debug)]
pub struct InvalidCast {
- pub expr: InFile<AstPtr<ast::Expr>>,
+ pub expr: InFile<ExprOrPatPtr>,
pub error: CastError,
pub expr_ty: Type,
pub cast_ty: Type,
@@ -413,6 +415,11 @@ pub struct GenericArgsProhibited {
pub reason: GenericArgsProhibitedReason,
}
+#[derive(Debug)]
+pub struct ParenthesizedGenericArgsWithoutFnTrait {
+ pub args: InFile<AstPtr<ast::ParenthesizedArgList>>,
+}
+
impl AnyDiagnostic {
pub(crate) fn body_validation_diagnostic(
db: &dyn HirDatabase,
@@ -428,9 +435,7 @@ impl AnyDiagnostic {
.collect();
let record = match record {
- Either::Left(record_expr) => {
- source_map.expr_syntax(record_expr).ok()?.map(AstPtr::wrap_left)
- }
+ Either::Left(record_expr) => source_map.expr_syntax(record_expr).ok()?,
Either::Right(record_pat) => source_map.pat_syntax(record_pat).ok()?,
};
let file = record.file_id;
@@ -474,7 +479,7 @@ impl AnyDiagnostic {
return Some(
ReplaceFilterMapNextWithFindMap {
file: next_source_ptr.file_id,
- next_expr: next_source_ptr.value,
+ next_expr: next_source_ptr.value.cast()?,
}
.into(),
);
@@ -484,7 +489,9 @@ impl AnyDiagnostic {
match source_map.expr_syntax(match_expr) {
Ok(source_ptr) => {
let root = source_ptr.file_syntax(db.upcast());
- if let ast::Expr::MatchExpr(match_expr) = &source_ptr.value.to_node(&root) {
+ if let Either::Left(ast::Expr::MatchExpr(match_expr)) =
+ &source_ptr.value.to_node(&root)
+ {
match match_expr.expr() {
Some(scrut_expr) if match_expr.match_arm_list().is_some() => {
return Some(
@@ -561,7 +568,7 @@ impl AnyDiagnostic {
let pat_syntax =
|pat| source_map.pat_syntax(pat).inspect_err(|_| stdx::never!("synthetic syntax")).ok();
let expr_or_pat_syntax = |id| match id {
- ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(|it| it.map(AstPtr::wrap_left)),
+ ExprOrPatId::ExprId(expr) => expr_syntax(expr),
ExprOrPatId::PatId(pat) => pat_syntax(pat),
};
Some(match d {
@@ -622,7 +629,7 @@ impl AnyDiagnostic {
field_with_same_name: field_with_same_name
.clone()
.map(|ty| Type::new(db, def, ty)),
- assoc_func_with_same_name: *assoc_func_with_same_name,
+ assoc_func_with_same_name: assoc_func_with_same_name.map(Into::into),
}
.into()
}
@@ -633,7 +640,7 @@ impl AnyDiagnostic {
&InferenceDiagnostic::UnresolvedIdent { id } => {
let node = match id {
ExprOrPatId::ExprId(id) => match source_map.expr_syntax(id) {
- Ok(syntax) => syntax.map(|it| (it.wrap_left(), None)),
+ Ok(syntax) => syntax.map(|it| (it, None)),
Err(SyntheticSyntax) => source_map
.format_args_implicit_capture(id)?
.map(|(node, range)| (node.wrap_left(), Some(range))),
@@ -652,7 +659,7 @@ impl AnyDiagnostic {
}
&InferenceDiagnostic::MismatchedTupleStructPatArgCount { pat, expected, found } => {
let expr_or_pat = match pat {
- ExprOrPatId::ExprId(expr) => expr_syntax(expr)?.map(AstPtr::wrap_left),
+ ExprOrPatId::ExprId(expr) => expr_syntax(expr)?,
ExprOrPatId::PatId(pat) => {
let InFile { file_id, value } = pat_syntax(pat)?;
@@ -702,8 +709,8 @@ impl AnyDiagnostic {
diag: &PathLoweringDiagnostic,
path: InFile<ast::Path>,
) -> Option<AnyDiagnostic> {
- Some(match diag {
- &PathLoweringDiagnostic::GenericArgsProhibited { segment, reason } => {
+ Some(match *diag {
+ PathLoweringDiagnostic::GenericArgsProhibited { segment, reason } => {
let segment = hir_segment_to_ast_segment(&path.value, segment)?;
let args = if let Some(generics) = segment.generic_arg_list() {
AstPtr::new(&generics).wrap_left()
@@ -713,6 +720,12 @@ impl AnyDiagnostic {
let args = path.with_value(args);
GenericArgsProhibited { args, reason }.into()
}
+ PathLoweringDiagnostic::ParenthesizedGenericArgsWithoutFnTrait { segment } => {
+ let segment = hir_segment_to_ast_segment(&path.value, segment)?;
+ let args = AstPtr::new(&segment.parenthesized_arg_list()?);
+ let args = path.with_value(args);
+ ParenthesizedGenericArgsWithoutFnTrait { args }.into()
+ }
})
}
diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs
index 537401afdc..72df07ef8c 100644
--- a/crates/hir/src/from_id.rs
+++ b/crates/hir/src/from_id.rs
@@ -49,6 +49,7 @@ from_id![
(hir_def::LifetimeParamId, crate::LifetimeParam),
(hir_def::MacroId, crate::Macro),
(hir_def::ExternCrateId, crate::ExternCrateDecl),
+ (hir_def::ExternBlockId, crate::ExternBlock),
];
impl From<AdtId> for Adt {
diff --git a/crates/hir/src/has_source.rs b/crates/hir/src/has_source.rs
index 82c90ac301..a34b498083 100644
--- a/crates/hir/src/has_source.rs
+++ b/crates/hir/src/has_source.rs
@@ -248,7 +248,7 @@ impl HasSource for Param {
let ast @ InFile { file_id, value } = source_map.expr_syntax(expr_id).ok()?;
let root = db.parse_or_expand(file_id);
match value.to_node(&root) {
- ast::Expr::ClosureExpr(it) => it
+ Either::Left(ast::Expr::ClosureExpr(it)) => it
.param_list()?
.params()
.nth(self.idx)
@@ -301,7 +301,7 @@ impl HasSource for InlineAsmOperand {
let root = src.file_syntax(db.upcast());
return src
.map(|ast| match ast.to_node(&root) {
- ast::Expr::AsmExpr(asm) => asm
+ Either::Left(ast::Expr::AsmExpr(asm)) => asm
.asm_pieces()
.filter_map(|it| match it {
ast::AsmPiece::AsmOperandNamed(it) => Some(it),
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 56090bc6b6..5923a1bc30 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -55,8 +55,8 @@ use hir_def::{
resolver::{HasResolver, Resolver},
type_ref::TypesSourceMap,
AdtId, AssocItemId, AssocItemLoc, AttrDefId, CallableDefId, ConstId, ConstParamId,
- CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId,
- GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstId, ItemContainerId,
+ CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId,
+ FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstId, ItemContainerId,
LifetimeParamId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId,
SyntheticSyntax, TraitAliasId, TupleId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId,
};
@@ -1957,7 +1957,7 @@ impl DefWithBody {
ExprOrPatId::PatId(pat) => source_map.pat_syntax(pat).map(Either::Right),
};
let expr_or_pat = match expr_or_pat {
- Ok(Either::Left(expr)) => expr.map(AstPtr::wrap_left),
+ Ok(Either::Left(expr)) => expr,
Ok(Either::Right(InFile { file_id, value: pat })) => {
// cast from Either<Pat, SelfParam> -> Either<_, Pat>
let Some(ptr) = AstPtr::try_from_raw(pat.syntax_node_ptr()) else {
@@ -2003,7 +2003,7 @@ impl DefWithBody {
match source_map.expr_syntax(node) {
Ok(node) => acc.push(
MissingUnsafe {
- node: node.map(|it| it.wrap_left()),
+ node,
lint: UnsafeLint::DeprecatedSafe2024,
reason: UnsafetyReason::UnsafeFnCall,
}
@@ -2327,6 +2327,13 @@ impl Function {
db.function_data(self.id).is_async()
}
+ pub fn extern_block(self, db: &dyn HirDatabase) -> Option<ExternBlock> {
+ match self.id.lookup(db.upcast()).container {
+ ItemContainerId::ExternBlockId(id) => Some(ExternBlock { id }),
+ _ => None,
+ }
+ }
+
pub fn returns_impl_future(self, db: &dyn HirDatabase) -> bool {
if self.is_async(db) {
return true;
@@ -2761,6 +2768,13 @@ impl Static {
Type::from_value_def(db, self.id)
}
+ pub fn extern_block(self, db: &dyn HirDatabase) -> Option<ExternBlock> {
+ match self.id.lookup(db.upcast()).container {
+ ItemContainerId::ExternBlockId(id) => Some(ExternBlock { id }),
+ _ => None,
+ }
+ }
+
/// Evaluate the static initializer.
pub fn eval(self, db: &dyn HirDatabase) -> Result<EvaluatedConst, ConstEvalError> {
db.const_eval(self.id.into(), Substitution::empty(Interner), None)
@@ -2929,6 +2943,17 @@ impl HasVisibility for TypeAlias {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ExternBlock {
+ pub(crate) id: ExternBlockId,
+}
+
+impl ExternBlock {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ Module { id: self.id.module(db.upcast()) }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StaticLifetime;
impl StaticLifetime {
@@ -4592,10 +4617,7 @@ impl CaptureUsages {
match span {
mir::MirSpan::ExprId(expr) => {
if let Ok(expr) = source_map.expr_syntax(expr) {
- result.push(CaptureUsageSource {
- is_ref,
- source: expr.map(AstPtr::wrap_left),
- })
+ result.push(CaptureUsageSource { is_ref, source: expr })
}
}
mir::MirSpan::PatId(pat) => {
@@ -6180,9 +6202,15 @@ impl HasContainer for TraitAlias {
}
}
+impl HasContainer for ExternBlock {
+ fn container(&self, db: &dyn HirDatabase) -> ItemContainer {
+ ItemContainer::Module(Module { id: self.id.lookup(db.upcast()).container })
+ }
+}
+
fn container_id_to_hir(c: ItemContainerId) -> ItemContainer {
match c {
- ItemContainerId::ExternBlockId(_id) => ItemContainer::ExternBlock(),
+ ItemContainerId::ExternBlockId(id) => ItemContainer::ExternBlock(ExternBlock { id }),
ItemContainerId::ModuleId(id) => ItemContainer::Module(Module { id }),
ItemContainerId::ImplId(id) => ItemContainer::Impl(Impl { id }),
ItemContainerId::TraitId(id) => ItemContainer::Trait(Trait { id }),
@@ -6194,7 +6222,7 @@ pub enum ItemContainer {
Trait(Trait),
Impl(Impl),
Module(Module),
- ExternBlock(),
+ ExternBlock(ExternBlock),
Crate(CrateId),
}
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 882a27182f..c9145f7d21 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -1998,6 +1998,7 @@ to_def_impls![
(crate::Adt, ast::Adt, adt_to_def),
(crate::ExternCrateDecl, ast::ExternCrate, extern_crate_to_def),
(crate::InlineAsmOperand, ast::AsmOperandNamed, asm_operand_to_def),
+ (crate::ExternBlock, ast::ExternBlock, extern_block_to_def),
(MacroCallId, ast::MacroCall, macro_call_to_macro_call),
];
diff --git a/crates/hir/src/semantics/child_by_source.rs b/crates/hir/src/semantics/child_by_source.rs
index d5dfb98571..d0fdf5cbdf 100644
--- a/crates/hir/src/semantics/child_by_source.rs
+++ b/crates/hir/src/semantics/child_by_source.rs
@@ -74,6 +74,9 @@ impl ChildBySource for ItemScope {
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
self.declarations().for_each(|item| add_module_def(db, res, file_id, item));
self.impls().for_each(|imp| insert_item_loc(db, res, file_id, imp, keys::IMPL));
+ self.extern_blocks().for_each(|extern_block| {
+ insert_item_loc(db, res, file_id, extern_block, keys::EXTERN_BLOCK)
+ });
self.extern_crate_decls()
.for_each(|ext| insert_item_loc(db, res, file_id, ext, keys::EXTERN_CRATE));
self.use_decls().for_each(|ext| insert_item_loc(db, res, file_id, ext, keys::USE));
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs
index 3c9e7065c4..4481b8855f 100644
--- a/crates/hir/src/semantics/source_to_def.rs
+++ b/crates/hir/src/semantics/source_to_def.rs
@@ -92,10 +92,10 @@ use hir_def::{
DynMap,
},
hir::{BindingId, Expr, LabelId},
- AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId,
- FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, Lookup, MacroId,
- ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId,
- VariantId,
+ AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId,
+ ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId,
+ Lookup, MacroId, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId,
+ UnionId, UseId, VariantId,
};
use hir_expand::{
attrs::AttrId, name::AsName, ExpansionInfo, HirFileId, HirFileIdExt, InMacroFile, MacroCallId,
@@ -308,6 +308,12 @@ impl SourceToDefCtx<'_, '_> {
) -> Option<ExternCrateId> {
self.to_def(src, keys::EXTERN_CRATE)
}
+ pub(super) fn extern_block_to_def(
+ &mut self,
+ src: InFile<&ast::ExternBlock>,
+ ) -> Option<ExternBlockId> {
+ self.to_def(src, keys::EXTERN_BLOCK)
+ }
#[allow(dead_code)]
pub(super) fn use_to_def(&mut self, src: InFile<&ast::Use>) -> Option<UseId> {
self.to_def(src, keys::USE)
@@ -352,7 +358,7 @@ impl SourceToDefCtx<'_, '_> {
let src = src.cloned().map(ast::Pat::from);
let pat_id = source_map.node_pat(src.as_ref())?;
// the pattern could resolve to a constant, verify that this is not the case
- if let crate::Pat::Bind { id, .. } = body[pat_id] {
+ if let crate::Pat::Bind { id, .. } = body[pat_id.as_pat()?] {
Some((container, id))
} else {
None
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 23e7518883..9019863f7f 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -18,7 +18,7 @@ use hir_def::{
scope::{ExprScopes, ScopeId},
Body, BodySourceMap, HygieneId,
},
- hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat, PatId},
+ hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat},
lang_item::LangItem,
lower::LowerCtx,
nameres::MacroSubNs,
@@ -139,7 +139,7 @@ impl SourceAnalyzer {
sm.node_expr(src.as_ref())
}
- fn pat_id(&self, pat: &ast::Pat) -> Option<PatId> {
+ fn pat_id(&self, pat: &ast::Pat) -> Option<ExprOrPatId> {
// FIXME: macros, see `expr_id`
let src = InFile { file_id: self.file_id, value: pat };
self.body_source_map()?.node_pat(src)
@@ -147,7 +147,7 @@ impl SourceAnalyzer {
fn binding_id_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingId> {
let pat_id = self.pat_id(&pat.clone().into())?;
- if let Pat::Bind { id, .. } = self.body()?.pats[pat_id] {
+ if let Pat::Bind { id, .. } = self.body()?.pats[pat_id.as_pat()?] {
Some(id)
} else {
None
@@ -210,11 +210,20 @@ impl SourceAnalyzer {
db: &dyn HirDatabase,
pat: &ast::Pat,
) -> Option<(Type, Option<Type>)> {
- let pat_id = self.pat_id(pat)?;
+ let expr_or_pat_id = self.pat_id(pat)?;
let infer = self.infer.as_ref()?;
- let coerced =
- infer.pat_adjustments.get(&pat_id).and_then(|adjusts| adjusts.last().cloned());
- let ty = infer[pat_id].clone();
+ let coerced = match expr_or_pat_id {
+ ExprOrPatId::ExprId(idx) => infer
+ .expr_adjustments
+ .get(&idx)
+ .and_then(|adjusts| adjusts.last().cloned())
+ .map(|adjust| adjust.target),
+ ExprOrPatId::PatId(idx) => {
+ infer.pat_adjustments.get(&idx).and_then(|adjusts| adjusts.last().cloned())
+ }
+ };
+
+ let ty = infer[expr_or_pat_id].clone();
let mk_ty = |ty| Type::new_with_resolver(db, &self.resolver, ty);
Some((mk_ty(ty), coerced.map(mk_ty)))
}
@@ -248,7 +257,7 @@ impl SourceAnalyzer {
) -> Option<BindingMode> {
let id = self.pat_id(&pat.clone().into())?;
let infer = self.infer.as_ref()?;
- infer.binding_modes.get(id).map(|bm| match bm {
+ infer.binding_modes.get(id.as_pat()?).map(|bm| match bm {
hir_ty::BindingMode::Move => BindingMode::Move,
hir_ty::BindingMode::Ref(hir_ty::Mutability::Mut) => BindingMode::Ref(Mutability::Mut),
hir_ty::BindingMode::Ref(hir_ty::Mutability::Not) => {
@@ -266,7 +275,7 @@ impl SourceAnalyzer {
Some(
infer
.pat_adjustments
- .get(&pat_id)?
+ .get(&pat_id.as_pat()?)?
.iter()
.map(|ty| Type::new_with_resolver(db, &self.resolver, ty.clone()))
.collect(),
@@ -649,10 +658,10 @@ impl SourceAnalyzer {
let field_name = field.field_name()?.as_name();
let record_pat = ast::RecordPat::cast(field.syntax().parent().and_then(|p| p.parent())?)?;
let pat_id = self.pat_id(&record_pat.into())?;
- let variant = self.infer.as_ref()?.variant_resolution_for_pat(pat_id)?;
+ let variant = self.infer.as_ref()?.variant_resolution_for_pat(pat_id.as_pat()?)?;
let variant_data = variant.variant_data(db.upcast());
let field = FieldId { parent: variant, local_id: variant_data.field(&field_name)? };
- let (adt, subst) = self.infer.as_ref()?.type_of_pat.get(pat_id)?.as_adt()?;
+ let (adt, subst) = self.infer.as_ref()?.type_of_pat.get(pat_id.as_pat()?)?.as_adt()?;
let field_ty =
db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst);
Some((
@@ -682,12 +691,20 @@ impl SourceAnalyzer {
db: &dyn HirDatabase,
pat: &ast::IdentPat,
) -> Option<ModuleDef> {
- let pat_id = self.pat_id(&pat.clone().into())?;
+ let expr_or_pat_id = self.pat_id(&pat.clone().into())?;
let body = self.body()?;
- let path = match &body[pat_id] {
- Pat::Path(path) => path,
- _ => return None,
+
+ let path = match expr_or_pat_id {
+ ExprOrPatId::ExprId(idx) => match &body[idx] {
+ Expr::Path(path) => path,
+ _ => return None,
+ },
+ ExprOrPatId::PatId(idx) => match &body[idx] {
+ Pat::Path(path) => path,
+ _ => return None,
+ },
};
+
let res = resolve_hir_path(db, &self.resolver, path, HygieneId::ROOT, TypesMap::EMPTY)?;
match res {
PathResolution::Def(def) => Some(def),
@@ -782,8 +799,9 @@ impl SourceAnalyzer {
}
prefer_value_ns = true;
} else if let Some(path_pat) = parent().and_then(ast::PathPat::cast) {
- let pat_id = self.pat_id(&path_pat.into())?;
- if let Some((assoc, subs)) = infer.assoc_resolutions_for_pat(pat_id) {
+ let expr_or_pat_id = self.pat_id(&path_pat.into())?;
+ if let Some((assoc, subs)) = infer.assoc_resolutions_for_expr_or_pat(expr_or_pat_id)
+ {
let (assoc, subst) = match assoc {
AssocItemId::ConstId(const_id) => {
let (konst, subst) =
@@ -807,7 +825,7 @@ impl SourceAnalyzer {
return Some((PathResolution::Def(AssocItem::from(assoc).into()), Some(subst)));
}
if let Some(VariantId::EnumVariantId(variant)) =
- infer.variant_resolution_for_pat(pat_id)
+ infer.variant_resolution_for_expr_or_pat(expr_or_pat_id)
{
return Some((PathResolution::Def(ModuleDef::Variant(variant.into())), None));
}
@@ -824,7 +842,7 @@ impl SourceAnalyzer {
|| parent().and_then(ast::TupleStructPat::cast).map(ast::Pat::from);
if let Some(pat) = record_pat.or_else(tuple_struct_pat) {
let pat_id = self.pat_id(&pat)?;
- let variant_res_for_pat = infer.variant_resolution_for_pat(pat_id);
+ let variant_res_for_pat = infer.variant_resolution_for_pat(pat_id.as_pat()?);
if let Some(VariantId::EnumVariantId(variant)) = variant_res_for_pat {
return Some((
PathResolution::Def(ModuleDef::Variant(variant.into())),
@@ -866,7 +884,8 @@ impl SourceAnalyzer {
// Case where path is a qualifier of another path, e.g. foo::bar::Baz where we are
// trying to resolve foo::bar.
- if path.parent_path().is_some() {
+ if let Some(parent_path) = path.parent_path() {
+ let parent_hir_path = Path::from_src(&mut ctx, parent_path);
return match resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map) {
None if meta_path.is_some() => path
.first_segment()
@@ -876,6 +895,42 @@ impl SourceAnalyzer {
.map(PathResolution::ToolModule)
})
.map(|it| (it, None)),
+ // Case the type name conflict with use module,
+ // e.g.
+ // ```
+ // use std::str;
+ // fn main() {
+ // str::from_utf8(); // as module std::str
+ // str::len(); // as primitive type str
+ // str::no_exist_item(); // as primitive type str
+ // }
+ // ```
+ Some(it) if matches!(it, PathResolution::Def(ModuleDef::BuiltinType(_))) => {
+ if let (Some(mod_path), Some(parent_hir_path)) =
+ (hir_path.mod_path(), parent_hir_path)
+ {
+ if let Some(ModuleDefId::ModuleId(id)) = self
+ .resolver
+ .resolve_module_path_in_items(db.upcast(), mod_path)
+ .take_types()
+ {
+ let parent_hir_name =
+ parent_hir_path.segments().get(1).map(|it| it.name);
+ let module = crate::Module { id };
+ if module
+ .scope(db, None)
+ .into_iter()
+ .any(|(name, _)| Some(&name) == parent_hir_name)
+ {
+ return Some((
+ PathResolution::Def(ModuleDef::Module(module)),
+ None,
+ ));
+ };
+ }
+ }
+ Some((it, None))
+ }
// FIXME: We do not show substitutions for parts of path, because this is really complex
// due to the interactions with associated items of `impl`s and associated items of associated
// types.
@@ -1043,7 +1098,7 @@ impl SourceAnalyzer {
let body = self.body()?;
let infer = self.infer.as_ref()?;
- let pat_id = self.pat_id(&pattern.clone().into())?;
+ let pat_id = self.pat_id(&pattern.clone().into())?.as_pat()?;
let substs = infer.type_of_pat[pat_id].as_adt()?.1;
let (variant, missing_fields, _exhaustive) =
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 5899ec5a00..4a9e2256e9 100644
--- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs
+++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
@@ -6,7 +6,9 @@ use ide_db::syntax_helpers::suggest_name;
use ide_db::RootDatabase;
use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast};
use itertools::Itertools;
-use syntax::ast::edit_in_place::Removable;
+use syntax::ast::edit::IndentLevel;
+use syntax::ast::edit_in_place::Indent;
+use syntax::ast::syntax_factory::SyntaxFactory;
use syntax::ast::{self, make, AstNode, MatchArmList, MatchExpr, Pat};
use crate::{utils, AssistContext, AssistId, AssistKind, Assists};
@@ -200,8 +202,8 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
AssistId("add_missing_match_arms", AssistKind::QuickFix),
"Fill match arms",
ctx.sema.original_range(match_expr.syntax()).range,
- |edit| {
- let new_match_arm_list = match_arm_list.clone_for_update();
+ |builder| {
+ let make = SyntaxFactory::new();
// having any hidden variants means that we need a catch-all arm
needs_catch_all_arm |= has_hidden_variants;
@@ -211,89 +213,85 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
// filter out hidden patterns because they're handled by the catch-all arm
!hidden
})
- .map(|(pat, _)| {
- make::match_arm(pat, None, make::ext::expr_todo()).clone_for_update()
- });
+ .map(|(pat, _)| make.match_arm(pat, None, make::ext::expr_todo()));
- let catch_all_arm = new_match_arm_list
+ let mut arms: Vec<_> = match_arm_list
.arms()
- .find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
- if let Some(arm) = catch_all_arm {
- let is_empty_expr = arm.expr().is_none_or(|e| match e {
- ast::Expr::BlockExpr(b) => {
- b.statements().next().is_none() && b.tail_expr().is_none()
+ .filter(|arm| {
+ if matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))) {
+ let is_empty_expr = arm.expr().is_none_or(|e| match e {
+ ast::Expr::BlockExpr(b) => {
+ b.statements().next().is_none() && b.tail_expr().is_none()
+ }
+ ast::Expr::TupleExpr(t) => t.fields().next().is_none(),
+ _ => false,
+ });
+ if is_empty_expr {
+ false
+ } else {
+ cov_mark::hit!(add_missing_match_arms_empty_expr);
+ true
+ }
+ } else {
+ true
}
- ast::Expr::TupleExpr(t) => t.fields().next().is_none(),
- _ => false,
- });
- if is_empty_expr {
- arm.remove();
- } else {
- cov_mark::hit!(add_missing_match_arms_empty_expr);
- }
- }
+ })
+ .collect();
- let mut added_arms = Vec::new();
- let mut todo_placeholders = Vec::new();
- for arm in missing_arms {
- todo_placeholders.push(arm.expr().unwrap());
- added_arms.push(arm);
- }
+ let first_new_arm_idx = arms.len();
+ arms.extend(missing_arms);
if needs_catch_all_arm && !has_catch_all_arm {
cov_mark::hit!(added_wildcard_pattern);
- let arm =
- make::match_arm(make::wildcard_pat().into(), None, make::ext::expr_todo())
- .clone_for_update();
- todo_placeholders.push(arm.expr().unwrap());
- added_arms.push(arm);
- }
-
- let first_new_arm = added_arms.first().cloned();
- let last_new_arm = added_arms.last().cloned();
-
- for arm in added_arms {
- new_match_arm_list.add_arm(arm);
+ let arm = make.match_arm(make::wildcard_pat().into(), None, make::ext::expr_todo());
+ arms.push(arm);
}
- if let Some(cap) = ctx.config.snippet_cap {
- if let Some(it) = first_new_arm
- .and_then(|arm| arm.syntax().descendants().find_map(ast::WildcardPat::cast))
- {
- edit.add_placeholder_snippet(cap, it);
- }
-
- for placeholder in todo_placeholders {
- edit.add_placeholder_snippet(cap, placeholder);
- }
-
- if let Some(arm) = last_new_arm {
- edit.add_tabstop_after(cap, arm);
- }
- }
+ let new_match_arm_list = make.match_arm_list(arms);
- // FIXME: Hack for mutable syntax trees not having great support for macros
+ // FIXME: Hack for syntax trees not having great support for macros
// Just replace the element that the original range came from
let old_place = {
// Find the original element
let file = ctx.sema.parse(arm_list_range.file_id);
let old_place = file.syntax().covering_element(arm_list_range.range);
- // Make `old_place` mut
match old_place {
- syntax::SyntaxElement::Node(it) => {
- syntax::SyntaxElement::from(edit.make_syntax_mut(it))
- }
+ syntax::SyntaxElement::Node(it) => it,
syntax::SyntaxElement::Token(it) => {
// If a token is found, it is '{' or '}'
// The parent is `{ ... }`
- let parent = it.parent().expect("Token must have a parent.");
- syntax::SyntaxElement::from(edit.make_syntax_mut(parent))
+ it.parent().expect("Token must have a parent.")
}
}
};
- syntax::ted::replace(old_place, new_match_arm_list.syntax());
+ let mut editor = builder.make_editor(&old_place);
+ new_match_arm_list.indent(IndentLevel::from_node(&old_place));
+ editor.replace(old_place, new_match_arm_list.syntax());
+
+ if let Some(cap) = ctx.config.snippet_cap {
+ if let Some(it) = new_match_arm_list
+ .arms()
+ .nth(first_new_arm_idx)
+ .and_then(|arm| arm.syntax().descendants().find_map(ast::WildcardPat::cast))
+ {
+ editor.add_annotation(it.syntax(), builder.make_placeholder_snippet(cap));
+ }
+
+ for arm in new_match_arm_list.arms().skip(first_new_arm_idx) {
+ if let Some(expr) = arm.expr() {
+ editor.add_annotation(expr.syntax(), builder.make_placeholder_snippet(cap));
+ }
+ }
+
+ if let Some(arm) = new_match_arm_list.arms().skip(first_new_arm_idx).last() {
+ editor.add_annotation(arm.syntax(), builder.make_tabstop_after(cap));
+ }
+ }
+
+ editor.add_mappings(make.finish_with_mappings());
+ builder.add_file_edits(ctx.file_id(), editor);
},
)
}
@@ -1377,6 +1375,9 @@ fn main() {
);
}
+ // FIXME: Preserving comments is quite hard in the current transitional syntax editing model.
+ // Once we migrate to new trivia model addressed in #6854, remove the ignore attribute.
+ #[ignore]
#[test]
fn add_missing_match_arms_preserves_comments() {
check_assist(
@@ -1405,6 +1406,9 @@ fn foo(a: A) {
);
}
+ // FIXME: Preserving comments is quite hard in the current transitional syntax editing model.
+ // Once we migrate to new trivia model addressed in #6854, remove the ignore attribute.
+ #[ignore]
#[test]
fn add_missing_match_arms_preserves_comments_empty() {
check_assist(
@@ -1502,10 +1506,10 @@ enum Test {
fn foo(t: Test) {
m!(match t {
- Test::A => ${1:todo!()},
- Test::B => ${2:todo!()},
- Test::C => ${3:todo!()},$0
-});
+ Test::A => ${1:todo!()},
+ Test::B => ${2:todo!()},
+ Test::C => ${3:todo!()},$0
+ });
}"#,
);
}
diff --git a/crates/ide-assists/src/handlers/expand_glob_import.rs b/crates/ide-assists/src/handlers/expand_glob_import.rs
index 094fdc46eb..0b95d6177f 100644
--- a/crates/ide-assists/src/handlers/expand_glob_import.rs
+++ b/crates/ide-assists/src/handlers/expand_glob_import.rs
@@ -3,10 +3,11 @@ use hir::{AssocItem, Enum, HasVisibility, Module, ModuleDef, Name, PathResolutio
use ide_db::{
defs::{Definition, NameRefClass},
search::SearchScope,
+ source_change::SourceChangeBuilder,
};
use stdx::never;
use syntax::{
- ast::{self, make},
+ ast::{self, make, Use, UseTree, VisibilityKind},
ted, AstNode, Direction, SyntaxNode, SyntaxToken, T,
};
@@ -43,6 +44,7 @@ use crate::{
pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let star = ctx.find_token_syntax_at_offset(T![*])?;
let use_tree = star.parent().and_then(ast::UseTree::cast)?;
+ let use_item = star.parent_ancestors().find_map(ast::Use::cast)?;
let (parent, mod_path) = find_parent_and_path(&star)?;
let target_module = match ctx.sema.resolve_path(&mod_path)? {
PathResolution::Def(ModuleDef::Module(it)) => Expandable::Module(it),
@@ -53,8 +55,9 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let current_scope = ctx.sema.scope(&star.parent()?)?;
let current_module = current_scope.module();
- let refs_in_target = find_refs_in_mod(ctx, target_module, current_module)?;
- let imported_defs = find_imported_defs(ctx, star)?;
+ if !is_visible_from(ctx, &target_module, current_module) {
+ return None;
+ }
let target = parent.either(|n| n.syntax().clone(), |n| n.syntax().clone());
acc.add(
@@ -62,37 +65,149 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext<'_>) ->
"Expand glob import",
target.text_range(),
|builder| {
- let use_tree = builder.make_mut(use_tree);
-
- let names_to_import = find_names_to_import(ctx, refs_in_target, imported_defs);
- let expanded = make::use_tree_list(names_to_import.iter().map(|n| {
- let path = make::ext::ident_path(
- &n.display(ctx.db(), current_module.krate().edition(ctx.db())).to_string(),
- );
- make::use_tree(path, None, None, false)
- }))
- .clone_for_update();
-
- match use_tree.star_token() {
- Some(star) => {
- let needs_braces = use_tree.path().is_some() && names_to_import.len() != 1;
- if needs_braces {
- ted::replace(star, expanded.syntax())
- } else {
- let without_braces = expanded
- .syntax()
- .children_with_tokens()
- .filter(|child| !matches!(child.kind(), T!['{'] | T!['}']))
- .collect();
- ted::replace_with_many(star, without_braces)
- }
- }
- None => never!(),
- }
+ build_expanded_import(
+ ctx,
+ builder,
+ use_tree,
+ use_item,
+ target_module,
+ current_module,
+ false,
+ )
+ },
+ )
+}
+
+// Assist: expand_glob_reexport
+//
+// Expands non-private glob imports.
+//
+// ```
+// mod foo {
+// pub struct Bar;
+// pub struct Baz;
+// }
+//
+// pub use foo::*$0;
+// ```
+// ->
+// ```
+// mod foo {
+// pub struct Bar;
+// pub struct Baz;
+// }
+//
+// pub use foo::{Bar, Baz};
+// ```
+pub(crate) fn expand_glob_reexport(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let star = ctx.find_token_syntax_at_offset(T![*])?;
+ let use_tree = star.parent().and_then(ast::UseTree::cast)?;
+ let use_item = star.parent_ancestors().find_map(ast::Use::cast)?;
+ let (parent, mod_path) = find_parent_and_path(&star)?;
+ let target_module = match ctx.sema.resolve_path(&mod_path)? {
+ PathResolution::Def(ModuleDef::Module(it)) => Expandable::Module(it),
+ PathResolution::Def(ModuleDef::Adt(hir::Adt::Enum(e))) => Expandable::Enum(e),
+ _ => return None,
+ };
+
+ let current_scope = ctx.sema.scope(&star.parent()?)?;
+ let current_module = current_scope.module();
+
+ if let VisibilityKind::PubSelf = get_export_visibility_kind(&use_item) {
+ return None;
+ }
+ if !is_visible_from(ctx, &target_module, current_module) {
+ return None;
+ }
+
+ let target = parent.either(|n| n.syntax().clone(), |n| n.syntax().clone());
+ acc.add(
+ AssistId("expand_glob_reexport", AssistKind::RefactorRewrite),
+ "Expand glob reexport",
+ target.text_range(),
+ |builder| {
+ build_expanded_import(
+ ctx,
+ builder,
+ use_tree,
+ use_item,
+ target_module,
+ current_module,
+ true,
+ )
},
)
}
+fn build_expanded_import(
+ ctx: &AssistContext<'_>,
+ builder: &mut SourceChangeBuilder,
+ use_tree: UseTree,
+ use_item: Use,
+ target_module: Expandable,
+ current_module: Module,
+ reexport_public_items: bool,
+) {
+ let (must_be_pub, visible_from) = if !reexport_public_items {
+ (false, current_module)
+ } else {
+ match get_export_visibility_kind(&use_item) {
+ VisibilityKind::Pub => (true, current_module.krate().root_module()),
+ VisibilityKind::PubCrate => (false, current_module.krate().root_module()),
+ _ => (false, current_module),
+ }
+ };
+
+ let refs_in_target = find_refs_in_mod(ctx, target_module, visible_from, must_be_pub);
+ let imported_defs = find_imported_defs(ctx, use_item);
+
+ let filtered_defs =
+ if reexport_public_items { refs_in_target } else { refs_in_target.used_refs(ctx) };
+
+ let use_tree = builder.make_mut(use_tree);
+
+ let names_to_import = find_names_to_import(filtered_defs, imported_defs);
+ let expanded = make::use_tree_list(names_to_import.iter().map(|n| {
+ let path = make::ext::ident_path(
+ &n.display(ctx.db(), current_module.krate().edition(ctx.db())).to_string(),
+ );
+ make::use_tree(path, None, None, false)
+ }))
+ .clone_for_update();
+
+ match use_tree.star_token() {
+ Some(star) => {
+ let needs_braces = use_tree.path().is_some() && names_to_import.len() != 1;
+ if needs_braces {
+ ted::replace(star, expanded.syntax())
+ } else {
+ let without_braces = expanded
+ .syntax()
+ .children_with_tokens()
+ .filter(|child| !matches!(child.kind(), T!['{'] | T!['}']))
+ .collect();
+ ted::replace_with_many(star, without_braces)
+ }
+ }
+ None => never!(),
+ }
+}
+
+fn get_export_visibility_kind(use_item: &Use) -> VisibilityKind {
+ use syntax::ast::HasVisibility as _;
+ match use_item.visibility() {
+ Some(vis) => match vis.kind() {
+ VisibilityKind::PubCrate => VisibilityKind::PubCrate,
+ VisibilityKind::Pub => VisibilityKind::Pub,
+ VisibilityKind::PubSelf => VisibilityKind::PubSelf,
+ // We don't handle pub(in ...) and pub(super) yet
+ VisibilityKind::In(_) => VisibilityKind::PubSelf,
+ VisibilityKind::PubSuper => VisibilityKind::PubSelf,
+ },
+ None => VisibilityKind::PubSelf,
+ }
+}
+
enum Expandable {
Module(Module),
Enum(Enum),
@@ -130,14 +245,17 @@ struct Ref {
// could be alias
visible_name: Name,
def: Definition,
+ is_pub: bool,
}
impl Ref {
- fn from_scope_def(name: Name, scope_def: ScopeDef) -> Option<Self> {
+ fn from_scope_def(ctx: &AssistContext<'_>, name: Name, scope_def: ScopeDef) -> Option<Self> {
match scope_def {
- ScopeDef::ModuleDef(def) => {
- Some(Ref { visible_name: name, def: Definition::from(def) })
- }
+ ScopeDef::ModuleDef(def) => Some(Ref {
+ visible_name: name,
+ def: Definition::from(def),
+ is_pub: matches!(def.visibility(ctx.db()), hir::Visibility::Public),
+ }),
_ => None,
}
}
@@ -180,32 +298,32 @@ fn find_refs_in_mod(
ctx: &AssistContext<'_>,
expandable: Expandable,
visible_from: Module,
-) -> Option<Refs> {
- if !is_expandable_visible_from(ctx, &expandable, visible_from) {
- return None;
- }
-
+ must_be_pub: bool,
+) -> Refs {
match expandable {
Expandable::Module(module) => {
let module_scope = module.scope(ctx.db(), Some(visible_from));
- let refs =
- module_scope.into_iter().filter_map(|(n, d)| Ref::from_scope_def(n, d)).collect();
- Some(Refs(refs))
+ let refs = module_scope
+ .into_iter()
+ .filter_map(|(n, d)| Ref::from_scope_def(ctx, n, d))
+ .filter(|r| !must_be_pub || r.is_pub)
+ .collect();
+ Refs(refs)
}
- Expandable::Enum(enm) => Some(Refs(
+ Expandable::Enum(enm) => Refs(
enm.variants(ctx.db())
.into_iter()
- .map(|v| Ref { visible_name: v.name(ctx.db()), def: Definition::Variant(v) })
+ .map(|v| Ref {
+ visible_name: v.name(ctx.db()),
+ def: Definition::Variant(v),
+ is_pub: true,
+ })
.collect(),
- )),
+ ),
}
}
-fn is_expandable_visible_from(
- ctx: &AssistContext<'_>,
- expandable: &Expandable,
- from: Module,
-) -> bool {
+fn is_visible_from(ctx: &AssistContext<'_>, expandable: &Expandable, from: Module) -> bool {
fn is_mod_visible_from(ctx: &AssistContext<'_>, module: Module, from: Module) -> bool {
match module.parent(ctx.db()) {
Some(parent) => {
@@ -246,50 +364,34 @@ fn is_expandable_visible_from(
// use foo::*$0;
// use baz::Baz;
// ↑ ---------------
-fn find_imported_defs(ctx: &AssistContext<'_>, star: SyntaxToken) -> Option<Vec<Definition>> {
- let parent_use_item_syntax = star.parent_ancestors().find_map(|n| {
- if ast::Use::can_cast(n.kind()) {
- Some(n)
- } else {
- None
- }
- })?;
-
- Some(
- [Direction::Prev, Direction::Next]
- .into_iter()
- .flat_map(|dir| {
- parent_use_item_syntax
- .siblings(dir.to_owned())
- .filter(|n| ast::Use::can_cast(n.kind()))
- })
- .flat_map(|n| n.descendants().filter_map(ast::NameRef::cast))
- .filter_map(|r| match NameRefClass::classify(&ctx.sema, &r)? {
- NameRefClass::Definition(
- def @ (Definition::Macro(_)
- | Definition::Module(_)
- | Definition::Function(_)
- | Definition::Adt(_)
- | Definition::Variant(_)
- | Definition::Const(_)
- | Definition::Static(_)
- | Definition::Trait(_)
- | Definition::TypeAlias(_)),
- _,
- ) => Some(def),
- _ => None,
- })
- .collect(),
- )
+fn find_imported_defs(ctx: &AssistContext<'_>, use_item: Use) -> Vec<Definition> {
+ [Direction::Prev, Direction::Next]
+ .into_iter()
+ .flat_map(|dir| {
+ use_item.syntax().siblings(dir.to_owned()).filter(|n| ast::Use::can_cast(n.kind()))
+ })
+ .flat_map(|n| n.descendants().filter_map(ast::NameRef::cast))
+ .filter_map(|r| match NameRefClass::classify(&ctx.sema, &r)? {
+ NameRefClass::Definition(
+ def @ (Definition::Macro(_)
+ | Definition::Module(_)
+ | Definition::Function(_)
+ | Definition::Adt(_)
+ | Definition::Variant(_)
+ | Definition::Const(_)
+ | Definition::Static(_)
+ | Definition::Trait(_)
+ | Definition::TypeAlias(_)),
+ _,
+ ) => Some(def),
+ _ => None,
+ })
+ .collect()
}
-fn find_names_to_import(
- ctx: &AssistContext<'_>,
- refs_in_target: Refs,
- imported_defs: Vec<Definition>,
-) -> Vec<Name> {
- let used_refs = refs_in_target.used_refs(ctx).filter_out_by_defs(imported_defs);
- used_refs.0.iter().map(|r| r.visible_name.clone()).collect()
+fn find_names_to_import(refs_in_target: Refs, imported_defs: Vec<Definition>) -> Vec<Name> {
+ let final_refs = refs_in_target.filter_out_by_defs(imported_defs);
+ final_refs.0.iter().map(|r| r.visible_name.clone()).collect()
}
#[cfg(test)]
@@ -1036,4 +1138,83 @@ mod abc {
}"#,
)
}
+
+ #[test]
+ fn expanding_glob_reexport() {
+ check_assist(
+ expand_glob_reexport,
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ struct Qux;
+
+ pub fn f() {}
+
+ pub(crate) fn g() {}
+ pub(self) fn h() {}
+}
+
+pub use foo::*$0;
+",
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ struct Qux;
+
+ pub fn f() {}
+
+ pub(crate) fn g() {}
+ pub(self) fn h() {}
+}
+
+pub use foo::{Bar, Baz, f};
+",
+ )
+ }
+
+ #[test]
+ fn expanding_recursive_glob_reexport() {
+ check_assist(
+ expand_glob_reexport,
+ r"
+mod foo {
+ pub use bar::*;
+ mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ }
+}
+
+pub use foo::*$0;
+",
+ r"
+mod foo {
+ pub use bar::*;
+ mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ }
+}
+
+pub use foo::{Bar, Baz};
+",
+ )
+ }
+
+ #[test]
+ fn expanding_reexport_is_not_applicable_for_private_import() {
+ check_assist_not_applicable(
+ expand_glob_reexport,
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+}
+
+use foo::*$0;
+",
+ );
+ }
}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index 5c95b25f28..179742f91b 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -270,6 +270,7 @@ mod handlers {
destructure_tuple_binding::destructure_tuple_binding,
destructure_struct_binding::destructure_struct_binding,
expand_glob_import::expand_glob_import,
+ expand_glob_import::expand_glob_reexport,
explicit_enum_discriminant::explicit_enum_discriminant,
extract_expressions_from_format_string::extract_expressions_from_format_string,
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 4b0fa704a2..0662527a38 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -910,6 +910,29 @@ fn qux(bar: Bar, baz: Baz) {}
}
#[test]
+fn doctest_expand_glob_reexport() {
+ check_doc_test(
+ "expand_glob_reexport",
+ r#####"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+}
+
+pub use foo::*$0;
+"#####,
+ r#####"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+}
+
+pub use foo::{Bar, Baz};
+"#####,
+ )
+}
+
+#[test]
fn doctest_explicit_enum_discriminant() {
check_doc_test(
"explicit_enum_discriminant",
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index 2c39a8fdfe..28e2853096 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -2,17 +2,18 @@
mod format_like;
-use hir::ItemInNs;
-use ide_db::text_edit::TextEdit;
+use base_db::SourceDatabase;
+use hir::{ItemInNs, Semantics};
use ide_db::{
documentation::{Documentation, HasDocs},
imports::insert_use::ImportScope,
+ text_edit::TextEdit,
ty_filter::TryEnum,
- SnippetCap,
+ RootDatabase, SnippetCap,
};
use stdx::never;
use syntax::{
- ast::{self, make, AstNode, AstToken},
+ ast::{self, AstNode, AstToken},
SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
TextRange, TextSize,
};
@@ -48,7 +49,8 @@ pub(crate) fn complete_postfix(
};
let expr_ctx = &dot_access.ctx;
- let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal);
+ let receiver_text =
+ get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
let cap = match ctx.config.snippet_cap {
Some(it) => it,
@@ -172,13 +174,15 @@ 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, node_to_replace_with) = include_references(dot_receiver);
- let receiver_text =
- get_receiver_text(&node_to_replace_with, receiver_is_ambiguous_float_literal);
- let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) {
- Some(it) => it,
- None => return,
- };
+ 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);
+ receiver_text.insert_str(0, &prefix);
+ let postfix_snippet =
+ match build_postfix_snippet_builder(ctx, cap, &dot_receiver_including_refs) {
+ Some(it) => it,
+ None => return,
+ };
if !ctx.config.snippets.is_empty() {
add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
@@ -222,7 +226,7 @@ pub(crate) fn complete_postfix(
postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
.add_to(acc, ctx.db);
- if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) {
+ if let Some(parent) = dot_receiver_including_refs.syntax().parent().and_then(|p| p.parent()) {
if matches!(parent.kind(), STMT_LIST | EXPR_STMT) {
postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
.add_to(acc, ctx.db);
@@ -231,9 +235,9 @@ pub(crate) fn complete_postfix(
}
}
- if let ast::Expr::Literal(literal) = dot_receiver.clone() {
+ if let ast::Expr::Literal(literal) = dot_receiver_including_refs.clone() {
if let Some(literal_text) = ast::String::cast(literal.token()) {
- add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
+ add_format_like_completions(acc, ctx, &dot_receiver_including_refs, cap, &literal_text);
}
}
@@ -260,14 +264,20 @@ pub(crate) fn complete_postfix(
}
}
-fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
- let mut text = if receiver_is_ambiguous_float_literal {
- let text = receiver.syntax().text();
- let without_dot = ..text.len() - TextSize::of('.');
- text.slice(without_dot).to_string()
- } else {
- receiver.to_string()
+fn get_receiver_text(
+ sema: &Semantics<'_, RootDatabase>,
+ receiver: &ast::Expr,
+ receiver_is_ambiguous_float_literal: bool,
+) -> String {
+ // Do not just call `receiver.to_string()`, as that will mess up whitespaces inside macros.
+ let Some(mut range) = sema.original_range_opt(receiver.syntax()) else {
+ return receiver.to_string();
};
+ if receiver_is_ambiguous_float_literal {
+ range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.'))
+ }
+ let file_text = sema.db.file_text(range.file_id.file_id());
+ let mut text = file_text[range.range].to_owned();
// The receiver texts should be interpreted as-is, as they are expected to be
// normal Rust expressions.
@@ -284,7 +294,7 @@ fn escape_snippet_bits(text: &mut String) {
stdx::replace(text, '$', "\\$");
}
-fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
+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)
@@ -292,7 +302,7 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
resulting_element = ast::Expr::from(field_expr);
}
- let mut new_element_opt = initial_element.clone();
+ let mut prefix = String::new();
while let Some(parent_deref_element) =
resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)
@@ -303,7 +313,7 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
resulting_element = ast::Expr::from(parent_deref_element);
- new_element_opt = make::expr_prefix(syntax::T![*], new_element_opt).into();
+ prefix.insert(0, '*');
}
if let Some(first_ref_expr) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) {
@@ -317,7 +327,7 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
let exclusive = parent_ref_element.mut_token().is_some();
resulting_element = ast::Expr::from(parent_ref_element);
- new_element_opt = make::expr_ref(new_element_opt, exclusive);
+ prefix.insert_str(0, if exclusive { "&mut " } else { "&" });
}
} else {
// If we do not find any ref expressions, restore
@@ -325,7 +335,7 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
resulting_element = initial_element.clone();
}
- (resulting_element, new_element_opt)
+ (resulting_element, prefix)
}
fn build_postfix_snippet_builder<'ctx>(
@@ -901,4 +911,31 @@ fn main() {
"#,
);
}
+
+ #[test]
+ fn inside_macro() {
+ check_edit(
+ "box",
+ r#"
+macro_rules! assert {
+ ( $it:expr $(,)? ) => { $it };
+}
+
+fn foo() {
+ let a = true;
+ assert!(if a == false { true } else { false }.$0);
+}
+ "#,
+ r#"
+macro_rules! assert {
+ ( $it:expr $(,)? ) => { $it };
+}
+
+fn foo() {
+ let a = true;
+ assert!(Box::new(if a == false { true } else { false }));
+}
+ "#,
+ );
+ }
}
diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs
index bad5360805..6f71c3d9bd 100644
--- a/crates/ide-db/src/defs.rs
+++ b/crates/ide-db/src/defs.rs
@@ -108,7 +108,7 @@ impl Definition {
ItemContainer::Trait(it) => Some(it.into()),
ItemContainer::Impl(it) => Some(it.into()),
ItemContainer::Module(it) => Some(it.into()),
- ItemContainer::ExternBlock() | ItemContainer::Crate(_) => None,
+ ItemContainer::ExternBlock(_) | ItemContainer::Crate(_) => None,
}
}
match self {
diff --git a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
index 7126617cde..0520bb3fe9 100644
--- a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
+++ b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
@@ -40,7 +40,7 @@ pub(crate) fn mismatched_arg_count(
Diagnostic::new(
DiagnosticCode::RustcHardError("E0107"),
message,
- invalid_args_range(ctx, d.call_expr.map(AstPtr::wrap_left), d.expected, d.found),
+ invalid_args_range(ctx, d.call_expr, d.expected, d.found),
)
}
diff --git a/crates/ide-diagnostics/src/handlers/parenthesized_generic_args_without_fn_trait.rs b/crates/ide-diagnostics/src/handlers/parenthesized_generic_args_without_fn_trait.rs
new file mode 100644
index 0000000000..ccf5172341
--- /dev/null
+++ b/crates/ide-diagnostics/src/handlers/parenthesized_generic_args_without_fn_trait.rs
@@ -0,0 +1,59 @@
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
+
+// Diagnostic: parenthesized-generic-args-without-fn-trait
+//
+// This diagnostic is shown when a `Fn`-trait-style generic parameters (`Trait(A, B) -> C`)
+// was used on non-`Fn` trait/type.
+pub(crate) fn parenthesized_generic_args_without_fn_trait(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::ParenthesizedGenericArgsWithoutFnTrait,
+) -> Diagnostic {
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0214"),
+ "parenthesized type parameters may only be used with a `Fn` trait",
+ d.args.map(Into::into),
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn fn_traits_work() {
+ check_diagnostics(
+ r#"
+//- minicore: async_fn, fn
+fn foo<
+ A: Fn(),
+ B: FnMut() -> i32,
+ C: FnOnce(&str, bool),
+ D: AsyncFn::(u32) -> u32,
+ E: AsyncFnMut(),
+ F: AsyncFnOnce() -> bool,
+>() {}
+ "#,
+ );
+ }
+
+ #[test]
+ fn non_fn_trait() {
+ check_diagnostics(
+ r#"
+struct Struct<T>(T);
+enum Enum<T> { EnumVariant(T) }
+type TypeAlias<T> = bool;
+
+type Foo = TypeAlias() -> bool;
+ // ^^ error: parenthesized type parameters may only be used with a `Fn` trait
+
+fn foo(_a: Struct(i32)) {
+ // ^^^^^ error: parenthesized type parameters may only be used with a `Fn` trait
+ let _ = <Enum::(u32)>::EnumVariant(0);
+ // ^^^^^^^ error: parenthesized type parameters may only be used with a `Fn` trait
+}
+ "#,
+ );
+ }
+}
diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
index 73dcbc13b7..7cf8282d05 100644
--- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs
+++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
@@ -1235,4 +1235,25 @@ fn f() {
"#,
);
}
+
+ #[test]
+ fn complex_enum_variant_non_ref_pat() {
+ check_diagnostics(
+ r#"
+enum Enum { Variant }
+
+trait Trait {
+ type Assoc;
+}
+impl Trait for () {
+ type Assoc = Enum;
+}
+
+fn foo(v: &Enum) {
+ let <Enum>::Variant = v;
+ let <() as Trait>::Assoc::Variant = v;
+}
+ "#,
+ );
+ }
}
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
index 4accd181ca..dfb03eee73 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_field.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
@@ -1,5 +1,6 @@
use std::iter;
+use either::Either;
use hir::{db::ExpandDatabase, Adt, FileRange, HasSource, HirDisplay, InFile, Struct, Union};
use ide_db::text_edit::TextEdit;
use ide_db::{
@@ -41,7 +42,7 @@ pub(crate) fn unresolved_field(
),
adjusted_display_range(ctx, d.expr, &|expr| {
Some(
- match expr {
+ match expr.left()? {
ast::Expr::MethodCallExpr(it) => it.name_ref(),
ast::Expr::FieldExpr(it) => it.name_ref(),
_ => None,
@@ -72,7 +73,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedField) -> Option<Vec<A
fn field_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedField) -> Option<Assist> {
// Get the FileRange of the invalid field access
let root = ctx.sema.db.parse_or_expand(d.expr.file_id);
- let expr = d.expr.value.to_node(&root);
+ let expr = d.expr.value.to_node(&root).left()?;
let error_range = ctx.sema.original_range_opt(expr.syntax())?;
let field_name = d.name.as_str();
@@ -263,7 +264,7 @@ fn record_field_layout(
// FIXME: We should fill out the call here, move the cursor and trigger signature help
fn method_fix(
ctx: &DiagnosticsContext<'_>,
- expr_ptr: &InFile<AstPtr<ast::Expr>>,
+ expr_ptr: &InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
) -> Option<Assist> {
let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
let expr = expr_ptr.value.to_node(&root);
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
index 4ab649cc16..e4de107249 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_method.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
@@ -1,4 +1,4 @@
-use hir::{db::ExpandDatabase, AssocItem, FileRange, HirDisplay, InFile};
+use hir::{db::ExpandDatabase, FileRange, HirDisplay, InFile};
use ide_db::text_edit::TextEdit;
use ide_db::{
assists::{Assist, AssistId, AssistKind},
@@ -35,7 +35,7 @@ pub(crate) fn unresolved_method(
),
adjusted_display_range(ctx, d.expr, &|expr| {
Some(
- match expr {
+ match expr.left()? {
ast::Expr::MethodCallExpr(it) => it.name_ref(),
ast::Expr::FieldExpr(it) => it.name_ref(),
_ => None,
@@ -85,7 +85,7 @@ fn field_fix(
let expr_ptr = &d.expr;
let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
let expr = expr_ptr.value.to_node(&root);
- let (file_id, range) = match expr {
+ let (file_id, range) = match expr.left()? {
ast::Expr::MethodCallExpr(mcall) => {
let FileRange { range, file_id } =
ctx.sema.original_range_opt(mcall.receiver()?.syntax())?;
@@ -112,12 +112,12 @@ fn field_fix(
}
fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Assist> {
- if let Some(assoc_item_id) = d.assoc_func_with_same_name {
+ if let Some(f) = d.assoc_func_with_same_name {
let db = ctx.sema.db;
let expr_ptr = &d.expr;
let root = db.parse_or_expand(expr_ptr.file_id);
- let expr: ast::Expr = expr_ptr.value.to_node(&root);
+ let expr: ast::Expr = expr_ptr.value.to_node(&root).left()?;
let call = ast::MethodCallExpr::cast(expr.syntax().clone())?;
let range = InFile::new(expr_ptr.file_id, call.syntax().text_range())
@@ -127,30 +127,25 @@ fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -
let receiver = call.receiver()?;
let receiver_type = &ctx.sema.type_of_expr(&receiver)?.original;
- let need_to_take_receiver_as_first_arg = match hir::AssocItem::from(assoc_item_id) {
- AssocItem::Function(f) => {
- let assoc_fn_params = f.assoc_fn_params(db);
- if assoc_fn_params.is_empty() {
- false
- } else {
- assoc_fn_params
- .first()
- .map(|first_arg| {
- // For generic type, say `Box`, take `Box::into_raw(b: Self)` as example,
- // type of `b` is `Self`, which is `Box<T, A>`, containing unspecified generics.
- // However, type of `receiver` is specified, it could be `Box<i32, Global>` or something like that,
- // so `first_arg.ty() == receiver_type` evaluate to `false` here.
- // Here add `first_arg.ty().as_adt() == receiver_type.as_adt()` as guard,
- // apply `.as_adt()` over `Box<T, A>` or `Box<i32, Global>` gets `Box`, so we get `true` here.
-
- // FIXME: it fails when type of `b` is `Box` with other generic param different from `receiver`
- first_arg.ty() == receiver_type
- || first_arg.ty().as_adt() == receiver_type.as_adt()
- })
- .unwrap_or(false)
- }
- }
- _ => false,
+ let assoc_fn_params = f.assoc_fn_params(db);
+ let need_to_take_receiver_as_first_arg = if assoc_fn_params.is_empty() {
+ false
+ } else {
+ assoc_fn_params
+ .first()
+ .map(|first_arg| {
+ // For generic type, say `Box`, take `Box::into_raw(b: Self)` as example,
+ // type of `b` is `Self`, which is `Box<T, A>`, containing unspecified generics.
+ // However, type of `receiver` is specified, it could be `Box<i32, Global>` or something like that,
+ // so `first_arg.ty() == receiver_type` evaluate to `false` here.
+ // Here add `first_arg.ty().as_adt() == receiver_type.as_adt()` as guard,
+ // apply `.as_adt()` over `Box<T, A>` or `Box<i32, Global>` gets `Box`, so we get `true` here.
+
+ // FIXME: it fails when type of `b` is `Box` with other generic param different from `receiver`
+ first_arg.ty() == receiver_type
+ || first_arg.ty().as_adt() == receiver_type.as_adt()
+ })
+ .unwrap_or(false)
};
let mut receiver_type_adt_name =
diff --git a/crates/ide-diagnostics/src/handlers/unused_variables.rs b/crates/ide-diagnostics/src/handlers/unused_variables.rs
index 67ece56694..d5caf4de33 100644
--- a/crates/ide-diagnostics/src/handlers/unused_variables.rs
+++ b/crates/ide-diagnostics/src/handlers/unused_variables.rs
@@ -263,4 +263,17 @@ fn main() {
"#,
);
}
+
+ // regression test as we used to panic in this scenario
+ #[test]
+ fn unknown_struct_pattern_param_type() {
+ check_diagnostics(
+ r#"
+struct S { field : u32 }
+fn f(S { field }: error) {
+ // ^^^^^ 💡 warn: unused variable
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 50c91a6960..3ea41aa7e8 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -43,6 +43,7 @@ mod handlers {
pub(crate) mod mutability_errors;
pub(crate) mod no_such_field;
pub(crate) mod non_exhaustive_let;
+ pub(crate) mod parenthesized_generic_args_without_fn_trait;
pub(crate) mod private_assoc_item;
pub(crate) mod private_field;
pub(crate) mod remove_trailing_return;
@@ -466,7 +467,12 @@ pub fn semantic_diagnostics(
Some(it) => it,
None => continue,
},
- AnyDiagnostic::GenericArgsProhibited(d) => handlers::generic_args_prohibited::generic_args_prohibited(&ctx, &d)
+ AnyDiagnostic::GenericArgsProhibited(d) => {
+ handlers::generic_args_prohibited::generic_args_prohibited(&ctx, &d)
+ }
+ AnyDiagnostic::ParenthesizedGenericArgsWithoutFnTrait(d) => {
+ handlers::parenthesized_generic_args_without_fn_trait::parenthesized_generic_args_without_fn_trait(&ctx, &d)
+ }
};
res.push(d)
}
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
index 006e6e8246..e47891bbdf 100644
--- a/crates/ide/src/annotations.rs
+++ b/crates/ide/src/annotations.rs
@@ -1,6 +1,6 @@
use hir::{HasSource, InFile, InRealFile, Semantics};
use ide_db::{
- defs::Definition, helpers::visit_file_defs, FileId, FilePosition, FileRange, FxHashSet,
+ defs::Definition, helpers::visit_file_defs, FileId, FilePosition, FileRange, FxIndexSet,
RootDatabase,
};
use itertools::Itertools;
@@ -55,7 +55,7 @@ pub(crate) fn annotations(
config: &AnnotationConfig,
file_id: FileId,
) -> Vec<Annotation> {
- let mut annotations = FxHashSet::default();
+ let mut annotations = FxIndexSet::default();
if config.annotate_runnables {
for runnable in runnables(db, file_id) {
@@ -170,7 +170,12 @@ pub(crate) fn annotations(
}));
}
- annotations.into_iter().sorted_by_key(|a| (a.range.start(), a.range.end())).collect()
+ annotations
+ .into_iter()
+ .sorted_by_key(|a| {
+ (a.range.start(), a.range.end(), matches!(a.kind, AnnotationKind::Runnable(..)))
+ })
+ .collect()
}
pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
@@ -537,6 +542,20 @@ fn main() {
},
Annotation {
range: 69..73,
+ kind: HasReferences {
+ pos: FilePositionWrapper {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 69,
+ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ Annotation {
+ range: 69..73,
kind: Runnable(
Runnable {
use_name_in_title: false,
@@ -559,20 +578,6 @@ fn main() {
},
),
},
- Annotation {
- range: 69..73,
- kind: HasReferences {
- pos: FilePositionWrapper {
- file_id: FileId(
- 0,
- ),
- offset: 69,
- },
- data: Some(
- [],
- ),
- },
- },
]
"#]],
);
@@ -719,6 +724,20 @@ fn main() {
},
Annotation {
range: 61..65,
+ kind: HasReferences {
+ pos: FilePositionWrapper {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 61,
+ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ Annotation {
+ range: 61..65,
kind: Runnable(
Runnable {
use_name_in_title: false,
@@ -741,20 +760,6 @@ fn main() {
},
),
},
- Annotation {
- range: 61..65,
- kind: HasReferences {
- pos: FilePositionWrapper {
- file_id: FileId(
- 0,
- ),
- offset: 61,
- },
- data: Some(
- [],
- ),
- },
- },
]
"#]],
);
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index bdafb701ff..60a904233a 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -3290,4 +3290,38 @@ fn main() {
"#,
);
}
+
+ #[test]
+ fn shadow_builtin_type_by_module() {
+ check(
+ r#"
+mod Foo{
+pub mod str {
+ // ^^^
+ pub fn foo() {}
+}
+}
+
+fn main() {
+ use Foo::str;
+ let s = st$0r::foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_goto_module_because_str_is_builtin_type() {
+ check(
+ r#"
+mod str {
+pub fn foo() {}
+}
+
+fn main() {
+ let s = st$0r::f();
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide/src/inlay_hints/extern_block.rs b/crates/ide/src/inlay_hints/extern_block.rs
index 2bc91b68ed..652dff0bc5 100644
--- a/crates/ide/src/inlay_hints/extern_block.rs
+++ b/crates/ide/src/inlay_hints/extern_block.rs
@@ -7,7 +7,7 @@ use crate::{InlayHint, InlayHintsConfig};
pub(super) fn extern_block_hints(
acc: &mut Vec<InlayHint>,
- FamousDefs(_sema, _): &FamousDefs<'_, '_>,
+ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
extern_block: ast::ExternBlock,
@@ -16,6 +16,7 @@ pub(super) fn extern_block_hints(
return None;
}
let abi = extern_block.abi()?;
+ sema.to_def(&extern_block)?;
acc.push(InlayHint {
range: abi.syntax().text_range(),
position: crate::InlayHintPosition::Before,
@@ -33,7 +34,7 @@ pub(super) fn extern_block_hints(
pub(super) fn fn_hints(
acc: &mut Vec<InlayHint>,
- FamousDefs(_sema, _): &FamousDefs<'_, '_>,
+ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
fn_: &ast::Fn,
@@ -43,14 +44,16 @@ pub(super) fn fn_hints(
if !implicit_unsafe {
return None;
}
- let fn_ = fn_.fn_token()?;
- acc.push(item_hint(config, extern_block, fn_));
+ let fn_token = fn_.fn_token()?;
+ if sema.to_def(fn_).is_some_and(|def| def.extern_block(sema.db).is_some()) {
+ acc.push(item_hint(config, extern_block, fn_token));
+ }
Some(())
}
pub(super) fn static_hints(
acc: &mut Vec<InlayHint>,
- FamousDefs(_sema, _): &FamousDefs<'_, '_>,
+ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
static_: &ast::Static,
@@ -60,8 +63,10 @@ pub(super) fn static_hints(
if !implicit_unsafe {
return None;
}
- let static_ = static_.static_token()?;
- acc.push(item_hint(config, extern_block, static_));
+ let static_token = static_.static_token()?;
+ if sema.to_def(static_).is_some_and(|def| def.extern_block(sema.db).is_some()) {
+ acc.push(item_hint(config, extern_block, static_token));
+ }
Some(())
}
diff --git a/crates/ide/src/inlay_hints/implicit_drop.rs b/crates/ide/src/inlay_hints/implicit_drop.rs
index 27c7c3d498..390139d214 100644
--- a/crates/ide/src/inlay_hints/implicit_drop.rs
+++ b/crates/ide/src/inlay_hints/implicit_drop.rs
@@ -54,7 +54,8 @@ pub(super) fn hints(
};
let range = match terminator.span {
MirSpan::ExprId(e) => match source_map.expr_syntax(e) {
- Ok(s) => {
+ // don't show inlay hint for macro
+ Ok(s) if !s.file_id.is_macro() => {
let root = &s.file_syntax(sema.db);
let expr = s.value.to_node(root);
let expr = expr.syntax();
@@ -69,11 +70,11 @@ pub(super) fn hints(
}
}
}
- Err(_) => continue,
+ _ => continue,
},
MirSpan::PatId(p) => match source_map.pat_syntax(p) {
- Ok(s) => s.value.text_range(),
- Err(_) => continue,
+ Ok(s) if !s.file_id.is_macro() => s.value.text_range(),
+ _ => continue,
},
MirSpan::BindingId(b) => {
match source_map
@@ -81,13 +82,13 @@ pub(super) fn hints(
.iter()
.find_map(|p| source_map.pat_syntax(*p).ok())
{
- Some(s) => s.value.text_range(),
- None => continue,
+ Some(s) if !s.file_id.is_macro() => s.value.text_range(),
+ _ => continue,
}
}
MirSpan::SelfParam => match source_map.self_param_syntax() {
- Some(s) => s.value.text_range(),
- None => continue,
+ Some(s) if !s.file_id.is_macro() => s.value.text_range(),
+ _ => continue,
},
MirSpan::Unknown => continue,
};
@@ -231,4 +232,25 @@ mod tests {
"#,
);
}
+
+ #[test]
+ fn ignore_inlay_hint_for_macro_call() {
+ check_with_config(
+ ONLY_DROP_CONFIG,
+ r#"
+ struct X;
+
+ macro_rules! my_macro {
+ () => {{
+ let bbb = X;
+ bbb
+ }};
+ }
+
+ fn test() -> X {
+ my_macro!()
+ }
+"#,
+ );
+ }
}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 78c9f2309a..509ae3204c 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -4,8 +4,8 @@ use arrayvec::ArrayVec;
use ast::HasName;
use cfg::{CfgAtom, CfgExpr};
use hir::{
- db::HirDatabase, sym, AsAssocItem, AttrsWithOwner, HasAttrs, HasCrate, HasSource, HirFileIdExt,
- ModPath, Name, PathKind, Semantics, Symbol,
+ db::HirDatabase, sym, symbols::FxIndexSet, AsAssocItem, AttrsWithOwner, HasAttrs, HasCrate,
+ HasSource, HirFileIdExt, ModPath, Name, PathKind, Semantics, Symbol,
};
use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn};
use ide_db::{
@@ -13,7 +13,7 @@ use ide_db::{
documentation::docs_from_attrs,
helpers::visit_file_defs,
search::{FileReferenceNode, SearchScope},
- FilePosition, FxHashMap, FxHashSet, RootDatabase, SymbolKind,
+ FilePosition, FxHashMap, FxIndexMap, RootDatabase, SymbolKind,
};
use itertools::Itertools;
use smallvec::SmallVec;
@@ -61,8 +61,8 @@ pub enum RunnableKind {
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum RunnableDiscKind {
- Test,
TestMod,
+ Test,
DocTest,
Bench,
Bin,
@@ -130,7 +130,7 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
let mut res = Vec::new();
// Record all runnables that come from macro expansions here instead.
// In case an expansion creates multiple runnables we want to name them to avoid emitting a bunch of equally named runnables.
- let mut in_macro_expansion = FxHashMap::<hir::HirFileId, Vec<Runnable>>::default();
+ let mut in_macro_expansion = FxIndexMap::<hir::HirFileId, Vec<Runnable>>::default();
let mut add_opt = |runnable: Option<Runnable>, def| {
if let Some(runnable) = runnable.filter(|runnable| runnable.nav.file_id == file_id) {
if let Some(def) = def {
@@ -182,20 +182,7 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
r
})
}));
- res.sort_by(|Runnable { nav, kind, .. }, Runnable { nav: nav_b, kind: kind_b, .. }| {
- // full_range.start < focus_range.start < name, should give us a decent unique ordering
- nav.full_range
- .start()
- .cmp(&nav_b.full_range.start())
- .then_with(|| {
- let t_0 = || TextSize::from(0);
- nav.focus_range
- .map_or_else(t_0, |it| it.start())
- .cmp(&nav_b.focus_range.map_or_else(t_0, |it| it.start()))
- })
- .then_with(|| kind.disc().cmp(&kind_b.disc()))
- .then_with(|| nav.name.cmp(&nav_b.name))
- });
+ res.sort_by(cmp_runnables);
res
}
@@ -215,12 +202,30 @@ pub(crate) fn related_tests(
search_scope: Option<SearchScope>,
) -> Vec<Runnable> {
let sema = Semantics::new(db);
- let mut res: FxHashSet<Runnable> = FxHashSet::default();
+ let mut res: FxIndexSet<Runnable> = FxIndexSet::default();
let syntax = sema.parse_guess_edition(position.file_id).syntax().clone();
find_related_tests(&sema, &syntax, position, search_scope, &mut res);
- res.into_iter().collect()
+ res.into_iter().sorted_by(cmp_runnables).collect()
+}
+
+fn cmp_runnables(
+ Runnable { nav, kind, .. }: &Runnable,
+ Runnable { nav: nav_b, kind: kind_b, .. }: &Runnable,
+) -> std::cmp::Ordering {
+ // full_range.start < focus_range.start < name, should give us a decent unique ordering
+ nav.full_range
+ .start()
+ .cmp(&nav_b.full_range.start())
+ .then_with(|| {
+ let t_0 = || TextSize::from(0);
+ nav.focus_range
+ .map_or_else(t_0, |it| it.start())
+ .cmp(&nav_b.focus_range.map_or_else(t_0, |it| it.start()))
+ })
+ .then_with(|| kind.disc().cmp(&kind_b.disc()))
+ .then_with(|| nav.name.cmp(&nav_b.name))
}
fn find_related_tests(
@@ -228,7 +233,7 @@ fn find_related_tests(
syntax: &SyntaxNode,
position: FilePosition,
search_scope: Option<SearchScope>,
- tests: &mut FxHashSet<Runnable>,
+ tests: &mut FxIndexSet<Runnable>,
) {
// FIXME: why is this using references::find_defs, this should use ide_db::search
let defs = match references::find_defs(sema, syntax, position.offset) {
@@ -268,7 +273,7 @@ fn find_related_tests_in_module(
syntax: &SyntaxNode,
fn_def: &ast::Fn,
parent_module: &hir::Module,
- tests: &mut FxHashSet<Runnable>,
+ tests: &mut FxIndexSet<Runnable>,
) {
let fn_name = match fn_def.name() {
Some(it) => it,
@@ -1228,8 +1233,8 @@ gen_main!();
"(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..315, name: \"\", kind: Module })",
"(TestMod, NavigationTarget { file_id: FileId(0), full_range: 267..292, focus_range: 271..276, name: \"tests\", kind: Module, description: \"mod tests\" })",
"(Test, NavigationTarget { file_id: FileId(0), full_range: 283..290, name: \"foo_test\", kind: Function })",
- "(Test, NavigationTarget { file_id: FileId(0), full_range: 293..301, name: \"foo_test2\", kind: Function }, true)",
"(TestMod, NavigationTarget { file_id: FileId(0), full_range: 293..301, name: \"tests2\", kind: Module, description: \"mod tests2\" }, true)",
+ "(Test, NavigationTarget { file_id: FileId(0), full_range: 293..301, name: \"foo_test2\", kind: Function }, true)",
"(Bin, NavigationTarget { file_id: FileId(0), full_range: 302..314, name: \"main\", kind: Function })",
]
"#]],
@@ -1258,10 +1263,10 @@ foo!();
"#,
expect![[r#"
[
+ "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 210..217, name: \"foo_tests\", kind: Module, description: \"mod foo_tests\" }, true)",
"(Test, NavigationTarget { file_id: FileId(0), full_range: 210..217, name: \"foo0\", kind: Function }, true)",
"(Test, NavigationTarget { file_id: FileId(0), full_range: 210..217, name: \"foo1\", kind: Function }, true)",
"(Test, NavigationTarget { file_id: FileId(0), full_range: 210..217, name: \"foo2\", kind: Function }, true)",
- "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 210..217, name: \"foo_tests\", kind: Module, description: \"mod foo_tests\" }, true)",
]
"#]],
);
@@ -1501,18 +1506,18 @@ mod tests {
file_id: FileId(
0,
),
- full_range: 121..185,
- focus_range: 136..145,
- name: "foo2_test",
+ full_range: 52..115,
+ focus_range: 67..75,
+ name: "foo_test",
kind: Function,
},
NavigationTarget {
file_id: FileId(
0,
),
- full_range: 52..115,
- focus_range: 67..75,
- name: "foo_test",
+ full_range: 121..185,
+ focus_range: 136..145,
+ name: "foo2_test",
kind: Function,
},
]
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 479c0b381a..194fde1160 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -703,6 +703,7 @@ fn highlight_name_ref_by_syntax(
};
match parent.kind() {
+ EXTERN_CRATE => HlTag::Symbol(SymbolKind::Module) | HlMod::CrateRoot,
METHOD_CALL_EXPR => ast::MethodCallExpr::cast(parent)
.and_then(|it| highlight_method_call(sema, krate, &it, edition))
.unwrap_or_else(|| SymbolKind::Method.into()),
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 5ff96ae2a7..eb77c14c2a 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -50,6 +50,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">test</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span>
<span class="comment documentation">//! ```</span>
+<span class="comment documentation">//! ```rust</span>
+<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">extern</span><span class="none injected"> </span><span class="keyword injected">crate</span><span class="none injected"> </span><span class="self_keyword crate_root injected">self</span><span class="semicolon injected">;</span>
+<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">extern</span><span class="none injected"> </span><span class="keyword injected">crate</span><span class="none injected"> </span><span class="module crate_root injected">std</span><span class="semicolon injected">;</span>
+<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">extern</span><span class="none injected"> </span><span class="keyword injected">crate</span><span class="none injected"> </span><span class="module crate_root injected">core</span><span class="semicolon injected">;</span>
+<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">extern</span><span class="none injected"> </span><span class="keyword injected">crate</span><span class="none injected"> </span><span class="module crate_root injected">alloc</span><span class="semicolon injected">;</span>
+<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">extern</span><span class="none injected"> </span><span class="keyword injected">crate</span><span class="none injected"> </span><span class="module crate_root injected">proc_macro</span><span class="semicolon injected">;</span>
+<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">extern</span><span class="none injected"> </span><span class="keyword injected">crate</span><span class="none injected"> </span><span class="module crate_root injected">test</span><span class="semicolon injected">;</span>
+<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">extern</span><span class="none injected"> </span><span class="keyword injected">crate</span><span class="none injected"> </span><span class="module crate_root injected">Krate</span><span class="semicolon injected">;</span>
+<span class="comment documentation">//! ```</span>
<span class="keyword">mod</span> <span class="module declaration">outline_module</span><span class="semicolon">;</span>
<span class="comment documentation">/// ```</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
index 9be7c92fc7..9477d0d1b8 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
@@ -48,17 +48,6 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<pre><code><span class="keyword">use</span> <span class="module">inner</span><span class="operator">::</span><span class="brace">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="module declaration">inner_mod</span><span class="brace">}</span><span class="semicolon">;</span>
<span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration public">ops</span> <span class="brace">{</span>
- <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_once"</span><span class="attribute_bracket attribute">]</span>
- <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnOnce</span><span class="angle">&lt;</span><span class="type_param declaration">Args</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">}</span>
-
- <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_mut"</span><span class="attribute_bracket attribute">]</span>
- <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnMut</span><span class="angle">&lt;</span><span class="type_param declaration">Args</span><span class="angle">&gt;</span><span class="colon">:</span> <span class="trait public">FnOnce</span><span class="angle">&lt;</span><span class="type_param">Args</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">}</span>
-
- <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn"</span><span class="attribute_bracket attribute">]</span>
- <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">Fn</span><span class="angle">&lt;</span><span class="type_param declaration">Args</span><span class="angle">&gt;</span><span class="colon">:</span> <span class="trait public">FnMut</span><span class="angle">&lt;</span><span class="type_param">Args</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="brace">}</span>
-
<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span>
<span class="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
<span class="brace">}</span>
@@ -125,8 +114,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="const_param const">FOO</span>
<span class="brace">}</span>
-<span class="keyword">use</span> <span class="module public">ops</span><span class="operator">::</span><span class="trait public">Fn</span><span class="semicolon">;</span>
-<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle">&lt;</span><span class="type_param declaration">F</span><span class="colon">:</span> <span class="trait public">Fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="value_param callable declaration">f</span><span class="colon">:</span> <span class="type_param">F</span><span class="parenthesis">)</span> <span class="brace">{</span>
+<span class="keyword">use</span> <span class="module crate_root default_library library">core</span><span class="operator">::</span><span class="module default_library library">ops</span><span class="operator">::</span><span class="trait default_library library">Fn</span><span class="semicolon">;</span>
+<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle">&lt;</span><span class="type_param declaration">F</span><span class="colon">:</span> <span class="trait default_library library">Fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="value_param callable declaration">f</span><span class="colon">:</span> <span class="type_param">F</span><span class="parenthesis">)</span> <span class="brace">{</span>
<span class="value_param callable">f</span><span class="parenthesis">(</span><span class="parenthesis">)</span>
<span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index b9520ae2bb..3775265f23 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -136,22 +136,11 @@ use self::foo as bar;
fn test_highlighting() {
check_highlighting(
r#"
-//- minicore: derive, copy
+//- minicore: derive, copy, fn
//- /main.rs crate:main deps:foo
use inner::{self as inner_mod};
mod inner {}
-pub mod ops {
- #[lang = "fn_once"]
- pub trait FnOnce<Args> {}
-
- #[lang = "fn_mut"]
- pub trait FnMut<Args>: FnOnce<Args> {}
-
- #[lang = "fn"]
- pub trait Fn<Args>: FnMut<Args> {}
-}
-
struct Foo {
x: u32,
}
@@ -218,7 +207,7 @@ fn const_param<const FOO: usize>() -> usize {
FOO
}
-use ops::Fn;
+use core::ops::Fn;
fn baz<F: Fn() -> ()>(f: F) {
f()
}
@@ -722,6 +711,15 @@ fn test_highlight_doc_comment() {
//! fn test() {}
//! ```
+//! ```rust
+//! extern crate self;
+//! extern crate std;
+//! extern crate core;
+//! extern crate alloc;
+//! extern crate proc_macro;
+//! extern crate test;
+//! extern crate Krate;
+//! ```
mod outline_module;
/// ```
@@ -1084,6 +1082,9 @@ pub struct Struct;
);
}
+// Rainbow highlighting uses a deterministic hash (fxhash) but the hashing does differ
+// depending on the pointer width so only runs this on 64-bit targets.
+#[cfg(target_pointer_width = "64")]
#[test]
fn test_rainbow_highlighting() {
check_highlighting(
diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs
index 66553a2661..be0de6c936 100644
--- a/crates/intern/src/symbol/symbols.rs
+++ b/crates/intern/src/symbol/symbols.rs
@@ -13,15 +13,35 @@ use crate::{
macro_rules! define_symbols {
(@WITH_NAME: $($alias:ident = $value:literal,)* @PLAIN: $($name:ident,)*) => {
- // Ideally we would be emitting `const` here, but then we no longer have stable addresses
- // which is what we are relying on for equality! In the future if consts can refer to
- // statics we should swap these for `const`s and have the string literal being pointed
- // to be statics to refer to such that their address is stable.
+ // We define symbols as both `const`s and `static`s because some const code requires const symbols,
+ // but code from before the transition relies on the lifetime of the predefined symbols and making them
+ // `const`s make it error (because now they're temporaries). In the future we probably should only
+ // use consts.
+
+ /// Predefined symbols as `const`s (instead of the default `static`s).
+ pub mod consts {
+ use super::{Symbol, TaggedArcPtr};
+
+ // The strings should be in `static`s so that symbol equality holds.
+ $(
+ pub const $name: Symbol = {
+ static SYMBOL_STR: &str = stringify!($name);
+ Symbol { repr: TaggedArcPtr::non_arc(&SYMBOL_STR) }
+ };
+ )*
+ $(
+ pub const $alias: Symbol = {
+ static SYMBOL_STR: &str = $value;
+ Symbol { repr: TaggedArcPtr::non_arc(&SYMBOL_STR) }
+ };
+ )*
+ }
+
$(
- pub static $name: Symbol = Symbol { repr: TaggedArcPtr::non_arc(&stringify!($name)) };
+ pub static $name: Symbol = consts::$name;
)*
$(
- pub static $alias: Symbol = Symbol { repr: TaggedArcPtr::non_arc(&$value) };
+ pub static $alias: Symbol = consts::$alias;
)*
@@ -428,6 +448,7 @@ define_symbols! {
rustc_layout_scalar_valid_range_start,
rustc_legacy_const_generics,
rustc_macro_transparency,
+ rustc_paren_sugar,
rustc_reallocator,
rustc_reservation_impl,
rustc_safe_intrinsic,
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 5654c04a59..67ee9d1119 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -94,7 +94,9 @@ pub fn load_workspace(
let contents = loader.load_sync(path);
let path = vfs::VfsPath::from(path.to_path_buf());
vfs.set_file_contents(path.clone(), contents);
- vfs.file_id(&path)
+ vfs.file_id(&path).and_then(|(file_id, excluded)| {
+ (excluded == vfs::FileExcluded::No).then_some(file_id)
+ })
},
extra_env,
);
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index e4a6113462..b5f4e43a11 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -277,6 +277,9 @@ impl CargoWorkspace {
/// Fetches the metadata for the given `cargo_toml` manifest.
/// A successful result may contain another metadata error if the initial fetching failed but
/// the `--no-deps` retry succeeded.
+ ///
+ /// The sysroot is used to set the `RUSTUP_TOOLCHAIN` env var when invoking cargo
+ /// to ensure that the rustup proxy uses the correct toolchain.
pub fn fetch_metadata(
cargo_toml: &ManifestPath,
current_dir: &AbsPath,
diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs
index fc1fd7b877..0c73447468 100644
--- a/crates/project-model/src/lib.rs
+++ b/crates/project-model/src/lib.rs
@@ -260,19 +260,19 @@ fn parse_cfg(s: &str) -> Result<cfg::CfgAtom, String> {
}
#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum SysrootSourceWorkspaceConfig {
+pub enum RustSourceWorkspaceConfig {
CargoMetadata(CargoMetadataConfig),
Stitched,
}
-impl Default for SysrootSourceWorkspaceConfig {
+impl Default for RustSourceWorkspaceConfig {
fn default() -> Self {
- SysrootSourceWorkspaceConfig::default_cargo()
+ RustSourceWorkspaceConfig::default_cargo()
}
}
-impl SysrootSourceWorkspaceConfig {
+impl RustSourceWorkspaceConfig {
pub fn default_cargo() -> Self {
- SysrootSourceWorkspaceConfig::CargoMetadata(Default::default())
+ RustSourceWorkspaceConfig::CargoMetadata(Default::default())
}
}
diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs
index 8f633d24be..544ba43ba6 100644
--- a/crates/project-model/src/sysroot.rs
+++ b/crates/project-model/src/sysroot.rs
@@ -22,38 +22,40 @@ use toolchain::{probe_for_binary, Tool};
use crate::{
cargo_workspace::CargoMetadataConfig, utf8_stdout, CargoWorkspace, ManifestPath,
- SysrootSourceWorkspaceConfig,
+ RustSourceWorkspaceConfig,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Sysroot {
root: Option<AbsPathBuf>,
- src_root: Option<AbsPathBuf>,
- workspace: SysrootWorkspace,
+ rust_lib_src_root: Option<AbsPathBuf>,
+ workspace: RustLibSrcWorkspace,
error: Option<String>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
-pub(crate) enum SysrootWorkspace {
+pub enum RustLibSrcWorkspace {
Workspace(CargoWorkspace),
Stitched(Stitched),
Empty,
}
#[derive(Debug, Clone, Eq, PartialEq)]
-pub(crate) struct Stitched {
- crates: Arena<SysrootCrateData>,
+pub struct Stitched {
+ crates: Arena<RustLibSrcCrateData>,
}
-impl ops::Index<SysrootCrate> for Stitched {
- type Output = SysrootCrateData;
- fn index(&self, index: SysrootCrate) -> &SysrootCrateData {
+impl ops::Index<RustLibSrcCrate> for Stitched {
+ type Output = RustLibSrcCrateData;
+ fn index(&self, index: RustLibSrcCrate) -> &RustLibSrcCrateData {
&self.crates[index]
}
}
impl Stitched {
- pub(crate) fn public_deps(&self) -> impl Iterator<Item = (CrateName, SysrootCrate, bool)> + '_ {
+ pub(crate) fn public_deps(
+ &self,
+ ) -> impl Iterator<Item = (CrateName, RustLibSrcCrate, bool)> + '_ {
// core is added as a dependency before std in order to
// mimic rustcs dependency order
[("core", true), ("alloc", false), ("std", true), ("test", false)].into_iter().filter_map(
@@ -63,32 +65,37 @@ impl Stitched {
)
}
- pub(crate) fn proc_macro(&self) -> Option<SysrootCrate> {
+ pub(crate) fn proc_macro(&self) -> Option<RustLibSrcCrate> {
self.by_name("proc_macro")
}
- pub(crate) fn crates(&self) -> impl ExactSizeIterator<Item = SysrootCrate> + '_ {
+ pub(crate) fn crates(&self) -> impl ExactSizeIterator<Item = RustLibSrcCrate> + '_ {
self.crates.iter().map(|(id, _data)| id)
}
- fn by_name(&self, name: &str) -> Option<SysrootCrate> {
+ fn by_name(&self, name: &str) -> Option<RustLibSrcCrate> {
let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
Some(id)
}
}
-pub(crate) type SysrootCrate = Idx<SysrootCrateData>;
+pub(crate) type RustLibSrcCrate = Idx<RustLibSrcCrateData>;
#[derive(Debug, Clone, Eq, PartialEq)]
-pub(crate) struct SysrootCrateData {
+pub(crate) struct RustLibSrcCrateData {
pub(crate) name: String,
pub(crate) root: ManifestPath,
- pub(crate) deps: Vec<SysrootCrate>,
+ pub(crate) deps: Vec<RustLibSrcCrate>,
}
impl Sysroot {
pub const fn empty() -> Sysroot {
- Sysroot { root: None, src_root: None, workspace: SysrootWorkspace::Empty, error: None }
+ Sysroot {
+ root: None,
+ rust_lib_src_root: None,
+ workspace: RustLibSrcWorkspace::Empty,
+ error: None,
+ }
}
/// Returns sysroot "root" directory, where `bin/`, `etc/`, `lib/`, `libexec/`
@@ -100,15 +107,15 @@ impl Sysroot {
/// Returns the sysroot "source" directory, where stdlib sources are located, like:
/// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library`
- pub fn src_root(&self) -> Option<&AbsPath> {
- self.src_root.as_deref()
+ pub fn rust_lib_src_root(&self) -> Option<&AbsPath> {
+ self.rust_lib_src_root.as_deref()
}
- pub fn is_empty(&self) -> bool {
+ pub fn is_rust_lib_src_empty(&self) -> bool {
match &self.workspace {
- SysrootWorkspace::Workspace(ws) => ws.packages().next().is_none(),
- SysrootWorkspace::Stitched(stitched) => stitched.crates.is_empty(),
- SysrootWorkspace::Empty => true,
+ RustLibSrcWorkspace::Workspace(ws) => ws.packages().next().is_none(),
+ RustLibSrcWorkspace::Stitched(stitched) => stitched.crates.is_empty(),
+ RustLibSrcWorkspace::Empty => true,
}
}
@@ -118,13 +125,13 @@ impl Sysroot {
pub fn num_packages(&self) -> usize {
match &self.workspace {
- SysrootWorkspace::Workspace(ws) => ws.packages().count(),
- SysrootWorkspace::Stitched(c) => c.crates().count(),
- SysrootWorkspace::Empty => 0,
+ RustLibSrcWorkspace::Workspace(ws) => ws.packages().count(),
+ RustLibSrcWorkspace::Stitched(c) => c.crates().count(),
+ RustLibSrcWorkspace::Empty => 0,
}
}
- pub(crate) fn workspace(&self) -> &SysrootWorkspace {
+ pub(crate) fn workspace(&self) -> &RustLibSrcWorkspace {
&self.workspace
}
}
@@ -133,33 +140,33 @@ impl Sysroot {
/// Attempts to discover the toolchain's sysroot from the given `dir`.
pub fn discover(dir: &AbsPath, extra_env: &FxHashMap<String, String>) -> Sysroot {
let sysroot_dir = discover_sysroot_dir(dir, extra_env);
- let sysroot_src_dir = sysroot_dir.as_ref().ok().map(|sysroot_dir| {
- discover_sysroot_src_dir_or_add_component(sysroot_dir, dir, extra_env)
+ let rust_lib_src_dir = sysroot_dir.as_ref().ok().map(|sysroot_dir| {
+ discover_rust_lib_src_dir_or_add_component(sysroot_dir, dir, extra_env)
});
- Sysroot::assemble(Some(sysroot_dir), sysroot_src_dir)
+ Sysroot::assemble(Some(sysroot_dir), rust_lib_src_dir)
}
pub fn discover_with_src_override(
current_dir: &AbsPath,
extra_env: &FxHashMap<String, String>,
- sysroot_src_dir: AbsPathBuf,
+ rust_lib_src_dir: AbsPathBuf,
) -> Sysroot {
let sysroot_dir = discover_sysroot_dir(current_dir, extra_env);
- Sysroot::assemble(Some(sysroot_dir), Some(Ok(sysroot_src_dir)))
+ Sysroot::assemble(Some(sysroot_dir), Some(Ok(rust_lib_src_dir)))
}
- pub fn discover_sysroot_src_dir(sysroot_dir: AbsPathBuf) -> Sysroot {
- let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir)
+ pub fn discover_rust_lib_src_dir(sysroot_dir: AbsPathBuf) -> Sysroot {
+ let rust_lib_src_dir = discover_rust_lib_src_dir(&sysroot_dir)
.ok_or_else(|| format_err!("can't find standard library sources in {sysroot_dir}"));
- Sysroot::assemble(Some(Ok(sysroot_dir)), Some(sysroot_src_dir))
+ Sysroot::assemble(Some(Ok(sysroot_dir)), Some(rust_lib_src_dir))
}
pub fn discover_rustc_src(&self) -> Option<ManifestPath> {
get_rustc_src(self.root()?)
}
- pub fn new(sysroot_dir: Option<AbsPathBuf>, sysroot_src_dir: Option<AbsPathBuf>) -> Sysroot {
- Self::assemble(sysroot_dir.map(Ok), sysroot_src_dir.map(Ok))
+ pub fn new(sysroot_dir: Option<AbsPathBuf>, rust_lib_src_dir: Option<AbsPathBuf>) -> Sysroot {
+ Self::assemble(sysroot_dir.map(Ok), rust_lib_src_dir.map(Ok))
}
/// Returns a command to run a tool preferring the cargo proxies if the sysroot exists.
@@ -200,7 +207,7 @@ impl Sysroot {
fn assemble(
sysroot_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
- sysroot_src_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
+ rust_lib_src_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
) -> Sysroot {
let mut errors = String::new();
let root = match sysroot_dir {
@@ -211,8 +218,8 @@ impl Sysroot {
}
None => None,
};
- let src_root = match sysroot_src_dir {
- Some(Ok(sysroot_src_dir)) => Some(sysroot_src_dir),
+ let rust_lib_src_root = match rust_lib_src_dir {
+ Some(Ok(rust_lib_src_dir)) => Some(rust_lib_src_dir),
Some(Err(e)) => {
format_to!(errors, "{e}\n");
None
@@ -221,24 +228,28 @@ impl Sysroot {
};
Sysroot {
root,
- src_root,
- workspace: SysrootWorkspace::Empty,
+ rust_lib_src_root,
+ workspace: RustLibSrcWorkspace::Empty,
error: errors.is_empty().not().then_some(errors),
}
}
- pub fn load_workspace(&mut self, sysroot_source_config: &SysrootSourceWorkspaceConfig) {
- assert!(matches!(self.workspace, SysrootWorkspace::Empty), "workspace already loaded");
- let Self { root: _, src_root: Some(src_root), workspace, error: _ } = self else { return };
- if let SysrootSourceWorkspaceConfig::CargoMetadata(cargo_config) = sysroot_source_config {
+ pub fn load_workspace(
+ &self,
+ sysroot_source_config: &RustSourceWorkspaceConfig,
+ ) -> Option<RustLibSrcWorkspace> {
+ assert!(matches!(self.workspace, RustLibSrcWorkspace::Empty), "workspace already loaded");
+ let Self { root: _, rust_lib_src_root: Some(src_root), workspace: _, error: _ } = self
+ else {
+ return None;
+ };
+ if let RustSourceWorkspaceConfig::CargoMetadata(cargo_config) = sysroot_source_config {
let library_manifest = ManifestPath::try_from(src_root.join("Cargo.toml")).unwrap();
if fs::metadata(&library_manifest).is_ok() {
if let Some(loaded) =
- Self::load_library_via_cargo(library_manifest, src_root, cargo_config)
+ self.load_library_via_cargo(library_manifest, src_root, cargo_config)
{
- *workspace = loaded;
- self.load_core_check();
- return;
+ return Some(loaded);
}
}
}
@@ -255,7 +266,7 @@ impl Sysroot {
.find(|it| fs::metadata(it).is_ok());
if let Some(root) = root {
- stitched.crates.alloc(SysrootCrateData {
+ stitched.crates.alloc(RustLibSrcCrateData {
name: name.into(),
root,
deps: Vec::new(),
@@ -286,21 +297,23 @@ impl Sysroot {
}
}
}
- *workspace = SysrootWorkspace::Stitched(stitched);
- self.load_core_check();
+ Some(RustLibSrcWorkspace::Stitched(stitched))
}
- fn load_core_check(&mut self) {
+ pub fn set_workspace(&mut self, workspace: RustLibSrcWorkspace) {
+ self.workspace = workspace;
if self.error.is_none() {
- if let Some(src_root) = &self.src_root {
+ if let Some(src_root) = &self.rust_lib_src_root {
let has_core = match &self.workspace {
- SysrootWorkspace::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
- SysrootWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(),
- SysrootWorkspace::Empty => true,
+ RustLibSrcWorkspace::Workspace(ws) => {
+ ws.packages().any(|p| ws[p].name == "core")
+ }
+ RustLibSrcWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(),
+ RustLibSrcWorkspace::Empty => true,
};
if !has_core {
- let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
- " (env var `RUST_SRC_PATH` is set and may be incorrect, try unsetting it)"
+ let var_note = if env::var_os("rust_lib_src_PATH").is_some() {
+ " (env var `rust_lib_src_PATH` is set and may be incorrect, try unsetting it)"
} else {
", try running `rustup component add rust-src` to possibly fix this"
};
@@ -313,10 +326,11 @@ impl Sysroot {
}
fn load_library_via_cargo(
+ &self,
library_manifest: ManifestPath,
- sysroot_src_dir: &AbsPathBuf,
+ rust_lib_src_dir: &AbsPathBuf,
cargo_config: &CargoMetadataConfig,
- ) -> Option<SysrootWorkspace> {
+ ) -> Option<RustLibSrcWorkspace> {
tracing::debug!("Loading library metadata: {library_manifest}");
let mut cargo_config = cargo_config.clone();
// the sysroot uses `public-dependency`, so we make cargo think it's a nightly
@@ -327,9 +341,9 @@ impl Sysroot {
let (mut res, _) = match CargoWorkspace::fetch_metadata(
&library_manifest,
- sysroot_src_dir,
+ rust_lib_src_dir,
&cargo_config,
- &Sysroot::empty(),
+ self,
// Make sure we never attempt to write to the sysroot
true,
&|_| (),
@@ -391,7 +405,7 @@ impl Sysroot {
});
let cargo_workspace = CargoWorkspace::new(res, library_manifest, Default::default());
- Some(SysrootWorkspace::Workspace(cargo_workspace))
+ Some(RustLibSrcWorkspace::Workspace(cargo_workspace))
}
}
@@ -407,36 +421,38 @@ fn discover_sysroot_dir(
Ok(AbsPathBuf::assert(Utf8PathBuf::from(stdout)))
}
-fn discover_sysroot_src_dir(sysroot_path: &AbsPathBuf) -> Option<AbsPathBuf> {
- if let Ok(path) = env::var("RUST_SRC_PATH") {
+fn discover_rust_lib_src_dir(sysroot_path: &AbsPathBuf) -> Option<AbsPathBuf> {
+ if let Ok(path) = env::var("rust_lib_src_PATH") {
if let Ok(path) = AbsPathBuf::try_from(path.as_str()) {
let core = path.join("core");
if fs::metadata(&core).is_ok() {
- tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {path}");
+ tracing::debug!("Discovered sysroot by rust_lib_src_PATH: {path}");
return Some(path);
}
- tracing::debug!("RUST_SRC_PATH is set, but is invalid (no core: {core:?}), ignoring");
+ tracing::debug!(
+ "rust_lib_src_PATH is set, but is invalid (no core: {core:?}), ignoring"
+ );
} else {
- tracing::debug!("RUST_SRC_PATH is set, but is invalid, ignoring");
+ tracing::debug!("rust_lib_src_PATH is set, but is invalid, ignoring");
}
}
- get_rust_src(sysroot_path)
+ get_rust_lib_src(sysroot_path)
}
-fn discover_sysroot_src_dir_or_add_component(
+fn discover_rust_lib_src_dir_or_add_component(
sysroot_path: &AbsPathBuf,
current_dir: &AbsPath,
extra_env: &FxHashMap<String, String>,
) -> Result<AbsPathBuf> {
- discover_sysroot_src_dir(sysroot_path)
+ discover_rust_lib_src_dir(sysroot_path)
.or_else(|| {
let mut rustup = toolchain::command(Tool::Rustup.prefer_proxy(), current_dir);
rustup.envs(extra_env);
rustup.args(["component", "add", "rust-src"]);
tracing::info!("adding rust-src component by {:?}", rustup);
utf8_stdout(&mut rustup).ok()?;
- get_rust_src(sysroot_path)
+ get_rust_lib_src(sysroot_path)
})
.ok_or_else(|| {
tracing::error!(%sysroot_path, "can't load standard library, try installing `rust-src`");
@@ -461,11 +477,11 @@ fn get_rustc_src(sysroot_path: &AbsPath) -> Option<ManifestPath> {
}
}
-fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
- let rust_src = sysroot_path.join("lib/rustlib/src/rust/library");
- tracing::debug!("checking sysroot library: {rust_src}");
- if fs::metadata(&rust_src).is_ok() {
- Some(rust_src)
+fn get_rust_lib_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
+ let rust_lib_src = sysroot_path.join("lib/rustlib/src/rust/library");
+ tracing::debug!("checking sysroot library: {rust_lib_src}");
+ if fs::metadata(&rust_lib_src).is_ok() {
+ Some(rust_lib_src)
} else {
None
}
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index 2856086543..54eb0e3478 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -12,9 +12,9 @@ use span::FileId;
use triomphe::Arc;
use crate::{
- sysroot::SysrootWorkspace, workspace::ProjectWorkspaceKind, CargoWorkspace, CfgOverrides,
- ManifestPath, ProjectJson, ProjectJsonData, ProjectWorkspace, Sysroot,
- SysrootSourceWorkspaceConfig, WorkspaceBuildScripts,
+ sysroot::RustLibSrcWorkspace, workspace::ProjectWorkspaceKind, CargoWorkspace, CfgOverrides,
+ ManifestPath, ProjectJson, ProjectJsonData, ProjectWorkspace, RustSourceWorkspaceConfig,
+ Sysroot, WorkspaceBuildScripts,
};
fn load_cargo(file: &str) -> (CrateGraph, ProcMacroPaths) {
@@ -42,7 +42,6 @@ fn load_workspace_from_metadata(file: &str) -> ProjectWorkspace {
build_scripts: WorkspaceBuildScripts::default(),
rustc: Err(None),
error: None,
- set_test: true,
},
cfg_overrides: Default::default(),
sysroot: Sysroot::empty(),
@@ -50,6 +49,7 @@ fn load_workspace_from_metadata(file: &str) -> ProjectWorkspace {
toolchain: None,
target_layout: Err("target_data_layout not loaded".into()),
extra_includes: Vec::new(),
+ set_test: true,
}
}
@@ -65,6 +65,7 @@ fn load_rust_project(file: &str) -> (CrateGraph, ProcMacroPaths) {
target_layout: Err(Arc::from("test has no data layout")),
cfg_overrides: Default::default(),
extra_includes: Vec::new(),
+ set_test: true,
};
to_crate_graph(project_workspace, &mut Default::default())
}
@@ -125,7 +126,10 @@ fn get_fake_sysroot() -> Sysroot {
let sysroot_dir = AbsPathBuf::assert(sysroot_path);
let sysroot_src_dir = sysroot_dir.clone();
let mut sysroot = Sysroot::new(Some(sysroot_dir), Some(sysroot_src_dir));
- sysroot.load_workspace(&SysrootSourceWorkspaceConfig::default_cargo());
+ let loaded_sysroot = sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo());
+ if let Some(loaded_sysroot) = loaded_sysroot {
+ sysroot.set_workspace(loaded_sysroot);
+ }
sysroot
}
@@ -271,15 +275,17 @@ fn smoke_test_real_sysroot_cargo() {
AbsPath::assert(Utf8Path::new(env!("CARGO_MANIFEST_DIR"))),
&Default::default(),
);
- sysroot.load_workspace(&SysrootSourceWorkspaceConfig::default_cargo());
- assert!(matches!(sysroot.workspace(), SysrootWorkspace::Workspace(_)));
+ let loaded_sysroot = sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo());
+ if let Some(loaded_sysroot) = loaded_sysroot {
+ sysroot.set_workspace(loaded_sysroot);
+ }
+ assert!(matches!(sysroot.workspace(), RustLibSrcWorkspace::Workspace(_)));
let project_workspace = ProjectWorkspace {
kind: ProjectWorkspaceKind::Cargo {
cargo: cargo_workspace,
build_scripts: WorkspaceBuildScripts::default(),
rustc: Err(None),
error: None,
- set_test: true,
},
sysroot,
rustc_cfg: Vec::new(),
@@ -287,6 +293,7 @@ fn smoke_test_real_sysroot_cargo() {
toolchain: None,
target_layout: Err("target_data_layout not loaded".into()),
extra_includes: Vec::new(),
+ set_test: true,
};
project_workspace.to_crate_graph(
&mut {
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index dcd62753cb..f5d46daa80 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -2,7 +2,7 @@
//! metadata` or `rust-project.json`) into representation stored in the salsa
//! database -- `CrateGraph`.
-use std::{collections::VecDeque, fmt, fs, iter, ops::Deref, sync};
+use std::{collections::VecDeque, fmt, fs, iter, ops::Deref, sync, thread};
use anyhow::Context;
use base_db::{
@@ -23,10 +23,10 @@ use crate::{
cargo_workspace::{CargoMetadataConfig, DepKind, PackageData, RustLibSource},
env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env},
project_json::{Crate, CrateArrayIdx},
- sysroot::{SysrootCrate, SysrootWorkspace},
+ sysroot::{RustLibSrcCrate, RustLibSrcWorkspace},
toolchain_info::{rustc_cfg, target_data_layout, target_tuple, version, QueryConfig},
CargoConfig, CargoWorkspace, CfgOverrides, InvocationStrategy, ManifestPath, Package,
- ProjectJson, ProjectManifest, Sysroot, SysrootSourceWorkspaceConfig, TargetData, TargetKind,
+ ProjectJson, ProjectManifest, RustSourceWorkspaceConfig, Sysroot, TargetData, TargetKind,
WorkspaceBuildScripts,
};
use tracing::{debug, error, info};
@@ -64,6 +64,8 @@ pub struct ProjectWorkspace {
pub cfg_overrides: CfgOverrides,
/// Additional includes to add for the VFS.
pub extra_includes: Vec<AbsPathBuf>,
+ /// Set `cfg(test)` for local crates
+ pub set_test: bool,
}
#[derive(Clone)]
@@ -79,7 +81,6 @@ pub enum ProjectWorkspaceKind {
/// The rustc workspace loaded for this workspace. An `Err(None)` means loading has been
/// disabled or was otherwise not requested.
rustc: Result<Box<(CargoWorkspace, WorkspaceBuildScripts)>, Option<String>>,
- set_test: bool,
},
/// Project workspace was specified using a `rust-project.json` file.
Json(ProjectJson),
@@ -98,7 +99,6 @@ pub enum ProjectWorkspaceKind {
file: ManifestPath,
/// Is this file a cargo script file?
cargo: Option<(CargoWorkspace, WorkspaceBuildScripts, Option<Arc<anyhow::Error>>)>,
- set_test: bool,
},
}
@@ -113,9 +113,10 @@ impl fmt::Debug for ProjectWorkspace {
target_layout,
cfg_overrides,
extra_includes,
+ set_test,
} = self;
match kind {
- ProjectWorkspaceKind::Cargo { cargo, error: _, build_scripts, rustc, set_test } => f
+ ProjectWorkspaceKind::Cargo { cargo, error: _, build_scripts, rustc } => f
.debug_struct("Cargo")
.field("root", &cargo.workspace_root().file_name())
.field("n_packages", &cargo.packages().len())
@@ -141,11 +142,12 @@ impl fmt::Debug for ProjectWorkspace {
.field("toolchain", &toolchain)
.field("data_layout", &target_layout)
.field("n_cfg_overrides", &cfg_overrides.len())
- .field("n_extra_includes", &extra_includes.len());
+ .field("n_extra_includes", &extra_includes.len())
+ .field("set_test", set_test);
debug_struct.finish()
}
- ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, set_test } => f
+ ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script } => f
.debug_struct("DetachedFiles")
.field("file", &file)
.field("cargo_script", &cargo_script.is_some())
@@ -186,7 +188,7 @@ impl ProjectWorkspace {
let project_location = project_json.parent().to_path_buf();
let project_json: ProjectJson =
ProjectJson::new(Some(project_json.clone()), &project_location, data);
- ProjectWorkspace::load_inline(project_json, config)
+ ProjectWorkspace::load_inline(project_json, config, progress)
}
ProjectManifest::CargoScript(rust_file) => {
ProjectWorkspace::load_detached_file(rust_file, config)?
@@ -204,19 +206,33 @@ impl ProjectWorkspace {
config: &CargoConfig,
progress: &dyn Fn(String),
) -> Result<ProjectWorkspace, anyhow::Error> {
- let mut sysroot = match (&config.sysroot, &config.sysroot_src) {
+ progress("Discovering sysroot".to_owned());
+ let CargoConfig {
+ features,
+ rustc_source,
+ extra_args,
+ extra_env,
+ set_test,
+ cfg_overrides,
+ extra_includes,
+ sysroot,
+ sysroot_src,
+ target,
+ ..
+ } = config;
+ let mut sysroot = match (sysroot, sysroot_src) {
(Some(RustLibSource::Discover), None) => {
- Sysroot::discover(cargo_toml.parent(), &config.extra_env)
+ Sysroot::discover(cargo_toml.parent(), extra_env)
}
(Some(RustLibSource::Discover), Some(sysroot_src)) => {
Sysroot::discover_with_src_override(
cargo_toml.parent(),
- &config.extra_env,
+ extra_env,
sysroot_src.clone(),
)
}
(Some(RustLibSource::Path(path)), None) => {
- Sysroot::discover_sysroot_src_dir(path.clone())
+ Sysroot::discover_rust_lib_src_dir(path.clone())
}
(Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => {
Sysroot::new(Some(sysroot.clone()), Some(sysroot_src.clone()))
@@ -224,100 +240,147 @@ impl ProjectWorkspace {
(None, _) => Sysroot::empty(),
};
- let rustc_dir = match &config.rustc_source {
- Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
- .map_err(|p| Some(format!("rustc source path is not absolute: {p}"))),
- Some(RustLibSource::Discover) => sysroot
- .discover_rustc_src()
- .ok_or_else(|| Some("Failed to discover rustc source for sysroot.".to_owned())),
- None => Err(None),
- };
-
- tracing::info!(workspace = %cargo_toml, src_root = ?sysroot.src_root(), root = ?sysroot.root(), "Using sysroot");
+ tracing::info!(workspace = %cargo_toml, src_root = ?sysroot.rust_lib_src_root(), root = ?sysroot.root(), "Using sysroot");
+ progress("Querying project metadata".to_owned());
let toolchain_config = QueryConfig::Cargo(&sysroot, cargo_toml);
let targets =
- target_tuple::get(toolchain_config, config.target.as_deref(), &config.extra_env)
- .unwrap_or_default();
- let toolchain = version::get(toolchain_config, &config.extra_env)
- .inspect_err(|e| {
- tracing::error!(%e,
- "failed fetching toolchain version for {cargo_toml:?} workspace"
- )
- })
- .ok()
- .flatten();
- let rustc_cfg =
- rustc_cfg::get(toolchain_config, targets.first().map(Deref::deref), &config.extra_env);
- let cfg_overrides = config.cfg_overrides.clone();
- let data_layout = target_data_layout::get(
- toolchain_config,
- targets.first().map(Deref::deref),
- &config.extra_env,
- );
- if let Err(e) = &data_layout {
- tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace");
- }
- sysroot.load_workspace(&SysrootSourceWorkspaceConfig::CargoMetadata(
- sysroot_metadata_config(&config.extra_env, &targets),
- ));
+ target_tuple::get(toolchain_config, target.as_deref(), extra_env).unwrap_or_default();
+
+ // We spawn a bunch of processes to query various information about the workspace's
+ // toolchain and sysroot
+ // We can speed up loading a bit by spawning all of these processes in parallel (especially
+ // on systems were process spawning is delayed)
+ let join = thread::scope(|s| {
+ let workspace_dir = cargo_toml.parent();
+ let toolchain = s.spawn(|| {
+ version::get(toolchain_config, extra_env)
+ .inspect_err(|e| {
+ tracing::error!(%e,
+ "failed fetching toolchain version for {cargo_toml:?} workspace"
+ )
+ })
+ .ok()
+ .flatten()
+ });
- let rustc = rustc_dir.and_then(|rustc_dir| {
- info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
- match CargoWorkspace::fetch_metadata(
- &rustc_dir,
- cargo_toml.parent(),
- &CargoMetadataConfig {
- features: crate::CargoFeatures::default(),
- targets: targets.clone(),
- extra_args: config.extra_args.clone(),
- extra_env: config.extra_env.clone(),
- },
- &sysroot,
- false,
- progress,
- ) {
- Ok((meta, _error)) => {
- let workspace = CargoWorkspace::new(meta, cargo_toml.clone(), Env::default());
- let build_scripts = WorkspaceBuildScripts::rustc_crates(
- &workspace,
- cargo_toml.parent(),
- &config.extra_env,
+ let rustc_cfg = s.spawn(|| {
+ rustc_cfg::get(toolchain_config, targets.first().map(Deref::deref), extra_env)
+ });
+ let data_layout = s.spawn(|| {
+ target_data_layout::get(
+ toolchain_config,
+ targets.first().map(Deref::deref),
+ extra_env,
+ ).inspect_err(|e| {
+ tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace")
+ })
+ });
+
+ let rustc_dir = s.spawn(|| {
+ let rustc_dir = match rustc_source {
+ Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
+ .map_err(|p| Some(format!("rustc source path is not absolute: {p}"))),
+ Some(RustLibSource::Discover) => {
+ sysroot.discover_rustc_src().ok_or_else(|| {
+ Some("Failed to discover rustc source for sysroot.".to_owned())
+ })
+ }
+ None => Err(None),
+ };
+ rustc_dir.and_then(|rustc_dir| {
+ info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
+ match CargoWorkspace::fetch_metadata(
+ &rustc_dir,
+ workspace_dir,
+ &CargoMetadataConfig {
+ features: crate::CargoFeatures::default(),
+ targets: targets.clone(),
+ extra_args: extra_args.clone(),
+ extra_env: extra_env.clone(),
+ },
&sysroot,
- );
- Ok(Box::new((workspace, build_scripts)))
- }
- Err(e) => {
- tracing::error!(
- %e,
- "Failed to read Cargo metadata from rustc source at {rustc_dir}",
- );
- Err(Some(format!(
- "Failed to read Cargo metadata from rustc source at {rustc_dir}: {e}"
- )))
- }
- }
+ false,
+ &|_| (),
+ ) {
+ Ok((meta, _error)) => {
+ let workspace =
+ CargoWorkspace::new(meta, cargo_toml.clone(), Env::default());
+ let build_scripts = WorkspaceBuildScripts::rustc_crates(
+ &workspace,
+ workspace_dir,
+ extra_env,
+ &sysroot,
+ );
+ Ok(Box::new((workspace, build_scripts)))
+ }
+ Err(e) => {
+ tracing::error!(
+ %e,
+ "Failed to read Cargo metadata from rustc source at {rustc_dir}",
+ );
+ Err(Some(format!(
+ "Failed to read Cargo metadata from rustc source at {rustc_dir}: {e}"
+ )))
+ }
+ }
+ })
+ });
+
+ let cargo_metadata = s.spawn(|| {
+ CargoWorkspace::fetch_metadata(
+ cargo_toml,
+ workspace_dir,
+ &CargoMetadataConfig {
+ features: features.clone(),
+ targets: targets.clone(),
+ extra_args: extra_args.clone(),
+ extra_env: extra_env.clone(),
+ },
+ &sysroot,
+ false,
+ &|_| (),
+ )
+ });
+ let loaded_sysroot = s.spawn(|| {
+ sysroot.load_workspace(&RustSourceWorkspaceConfig::CargoMetadata(
+ sysroot_metadata_config(extra_env, &targets),
+ ))
+ });
+ let cargo_config_extra_env =
+ s.spawn(|| cargo_config_env(cargo_toml, extra_env, &sysroot));
+ thread::Result::Ok((
+ toolchain.join()?,
+ rustc_cfg.join()?,
+ data_layout.join()?,
+ rustc_dir.join()?,
+ loaded_sysroot.join()?,
+ cargo_metadata.join()?,
+ cargo_config_extra_env.join()?,
+ ))
});
- let (meta, error) = CargoWorkspace::fetch_metadata(
- cargo_toml,
- cargo_toml.parent(),
- &CargoMetadataConfig {
- features: config.features.clone(),
- targets,
- extra_args: config.extra_args.clone(),
- extra_env: config.extra_env.clone(),
- },
- &sysroot,
- false,
- progress,
- )
- .with_context(|| {
+ let (
+ toolchain,
+ rustc_cfg,
+ data_layout,
+ rustc,
+ loaded_sysroot,
+ cargo_metadata,
+ cargo_config_extra_env,
+ ) = match join {
+ Ok(it) => it,
+ Err(e) => std::panic::resume_unwind(e),
+ };
+
+ let (meta, error) = cargo_metadata.with_context(|| {
format!(
"Failed to read Cargo metadata from Cargo.toml file {cargo_toml}, {toolchain:?}",
)
})?;
- let cargo_config_extra_env = cargo_config_env(cargo_toml, &config.extra_env, &sysroot);
let cargo = CargoWorkspace::new(meta, cargo_toml.clone(), cargo_config_extra_env);
+ if let Some(loaded_sysroot) = loaded_sysroot {
+ sysroot.set_workspace(loaded_sysroot);
+ }
Ok(ProjectWorkspace {
kind: ProjectWorkspaceKind::Cargo {
@@ -325,35 +388,70 @@ impl ProjectWorkspace {
build_scripts: WorkspaceBuildScripts::default(),
rustc,
error: error.map(Arc::new),
- set_test: config.set_test,
},
sysroot,
rustc_cfg,
- cfg_overrides,
+ cfg_overrides: cfg_overrides.clone(),
toolchain,
target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
- extra_includes: config.extra_includes.clone(),
+ extra_includes: extra_includes.clone(),
+ set_test: *set_test,
})
}
- pub fn load_inline(project_json: ProjectJson, config: &CargoConfig) -> ProjectWorkspace {
+ pub fn load_inline(
+ project_json: ProjectJson,
+ config: &CargoConfig,
+ progress: &dyn Fn(String),
+ ) -> ProjectWorkspace {
+ progress("Discovering sysroot".to_owned());
let mut sysroot =
Sysroot::new(project_json.sysroot.clone(), project_json.sysroot_src.clone());
- sysroot.load_workspace(&SysrootSourceWorkspaceConfig::Stitched);
+ let loaded_sysroot = sysroot.load_workspace(&RustSourceWorkspaceConfig::Stitched);
+ if let Some(loaded_sysroot) = loaded_sysroot {
+ sysroot.set_workspace(loaded_sysroot);
+ }
+
+ tracing::info!(workspace = %project_json.manifest_or_root(), src_root = ?sysroot.rust_lib_src_root(), root = ?sysroot.root(), "Using sysroot");
+ progress("Querying project metadata".to_owned());
let query_config = QueryConfig::Rustc(&sysroot, project_json.path().as_ref());
- let toolchain = version::get(query_config, &config.extra_env).ok().flatten();
+ let targets = target_tuple::get(query_config, config.target.as_deref(), &config.extra_env)
+ .unwrap_or_default();
+
+ // We spawn a bunch of processes to query various information about the workspace's
+ // toolchain and sysroot
+ // We can speed up loading a bit by spawning all of these processes in parallel (especially
+ // on systems were process spawning is delayed)
+ let join = thread::scope(|s| {
+ let toolchain =
+ s.spawn(|| version::get(query_config, &config.extra_env).ok().flatten());
+ let rustc_cfg = s.spawn(|| {
+ rustc_cfg::get(query_config, targets.first().map(Deref::deref), &config.extra_env)
+ });
+ let data_layout = s.spawn(|| {
+ target_data_layout::get(
+ query_config,
+ targets.first().map(Deref::deref),
+ &config.extra_env,
+ )
+ });
+ thread::Result::Ok((toolchain.join()?, rustc_cfg.join()?, data_layout.join()?))
+ });
+
+ let (toolchain, rustc_cfg, target_layout) = match join {
+ Ok(it) => it,
+ Err(e) => std::panic::resume_unwind(e),
+ };
- let target = config.target.as_deref();
- let rustc_cfg = rustc_cfg::get(query_config, target, &config.extra_env);
- let data_layout = target_data_layout::get(query_config, target, &config.extra_env);
ProjectWorkspace {
kind: ProjectWorkspaceKind::Json(project_json),
sysroot,
rustc_cfg,
toolchain,
- target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
+ target_layout: target_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
cfg_overrides: config.cfg_overrides.clone(),
extra_includes: config.extra_includes.clone(),
+ set_test: config.set_test,
}
}
@@ -363,7 +461,7 @@ impl ProjectWorkspace {
) -> anyhow::Result<ProjectWorkspace> {
let dir = detached_file.parent();
let mut sysroot = match &config.sysroot {
- Some(RustLibSource::Path(path)) => Sysroot::discover_sysroot_src_dir(path.clone()),
+ Some(RustLibSource::Path(path)) => Sysroot::discover_rust_lib_src_dir(path.clone()),
Some(RustLibSource::Discover) => Sysroot::discover(dir, &config.extra_env),
None => Sysroot::empty(),
};
@@ -374,9 +472,12 @@ impl ProjectWorkspace {
.unwrap_or_default();
let rustc_cfg = rustc_cfg::get(query_config, None, &config.extra_env);
let data_layout = target_data_layout::get(query_config, None, &config.extra_env);
- sysroot.load_workspace(&SysrootSourceWorkspaceConfig::CargoMetadata(
+ let loaded_sysroot = sysroot.load_workspace(&RustSourceWorkspaceConfig::CargoMetadata(
sysroot_metadata_config(&config.extra_env, &targets),
));
+ if let Some(loaded_sysroot) = loaded_sysroot {
+ sysroot.set_workspace(loaded_sysroot);
+ }
let cargo_script = CargoWorkspace::fetch_metadata(
detached_file,
@@ -406,7 +507,6 @@ impl ProjectWorkspace {
kind: ProjectWorkspaceKind::DetachedFile {
file: detached_file.to_owned(),
cargo: cargo_script,
- set_test: config.set_test,
},
sysroot,
rustc_cfg,
@@ -414,6 +514,7 @@ impl ProjectWorkspace {
target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
cfg_overrides: config.cfg_overrides.clone(),
extra_includes: config.extra_includes.clone(),
+ set_test: config.set_test,
})
}
@@ -545,7 +646,7 @@ impl ProjectWorkspace {
pub fn to_roots(&self) -> Vec<PackageRoot> {
let mk_sysroot = || {
let mut r = match self.sysroot.workspace() {
- SysrootWorkspace::Workspace(ws) => ws
+ RustLibSrcWorkspace::Workspace(ws) => ws
.packages()
.filter_map(|pkg| {
if ws[pkg].is_local {
@@ -566,12 +667,17 @@ impl ProjectWorkspace {
Some(PackageRoot { is_local: false, include, exclude })
})
.collect(),
- SysrootWorkspace::Stitched(_) | SysrootWorkspace::Empty => vec![],
+ RustLibSrcWorkspace::Stitched(_) | RustLibSrcWorkspace::Empty => vec![],
};
r.push(PackageRoot {
is_local: false,
- include: self.sysroot.src_root().map(|it| it.to_path_buf()).into_iter().collect(),
+ include: self
+ .sysroot
+ .rust_lib_src_root()
+ .map(|it| it.to_path_buf())
+ .into_iter()
+ .collect(),
exclude: Vec::new(),
});
r
@@ -593,7 +699,7 @@ impl ProjectWorkspace {
.into_iter()
.chain(mk_sysroot())
.collect::<Vec<_>>(),
- ProjectWorkspaceKind::Cargo { cargo, rustc, build_scripts, error: _, set_test: _ } => {
+ ProjectWorkspaceKind::Cargo { cargo, rustc, build_scripts, error: _ } => {
cargo
.packages()
.map(|pkg| {
@@ -728,8 +834,9 @@ impl ProjectWorkspace {
sysroot,
extra_env,
cfg_overrides,
+ self.set_test,
),
- ProjectWorkspaceKind::Cargo { cargo, rustc, build_scripts, error: _, set_test } => {
+ ProjectWorkspaceKind::Cargo { cargo, rustc, build_scripts, error: _ } => {
cargo_to_crate_graph(
load,
rustc.as_ref().map(|a| a.as_ref()).ok(),
@@ -738,10 +845,10 @@ impl ProjectWorkspace {
rustc_cfg.clone(),
cfg_overrides,
build_scripts,
- *set_test,
+ self.set_test,
)
}
- ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, set_test, .. } => {
+ ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, .. } => {
if let Some((cargo, build_scripts, _)) = cargo_script {
cargo_to_crate_graph(
&mut |path| load(path),
@@ -751,7 +858,7 @@ impl ProjectWorkspace {
rustc_cfg.clone(),
cfg_overrides,
build_scripts,
- *set_test,
+ self.set_test,
)
} else {
detached_file_to_crate_graph(
@@ -760,7 +867,7 @@ impl ProjectWorkspace {
file,
sysroot,
cfg_overrides,
- *set_test,
+ self.set_test,
)
}
}
@@ -782,34 +889,22 @@ impl ProjectWorkspace {
} = other;
(match (kind, o_kind) {
(
- ProjectWorkspaceKind::Cargo {
- cargo,
- rustc,
- build_scripts: _,
- error: _,
- set_test: _,
- },
+ ProjectWorkspaceKind::Cargo { cargo, rustc, build_scripts: _, error: _ },
ProjectWorkspaceKind::Cargo {
cargo: o_cargo,
rustc: o_rustc,
build_scripts: _,
error: _,
- set_test: _,
},
) => cargo == o_cargo && rustc == o_rustc,
(ProjectWorkspaceKind::Json(project), ProjectWorkspaceKind::Json(o_project)) => {
project == o_project
}
(
- ProjectWorkspaceKind::DetachedFile {
- file,
- cargo: Some((cargo_script, _, _)),
- set_test: _,
- },
+ ProjectWorkspaceKind::DetachedFile { file, cargo: Some((cargo_script, _, _)) },
ProjectWorkspaceKind::DetachedFile {
file: o_file,
cargo: Some((o_cargo_script, _, _)),
- set_test: _,
},
) => file == o_file && cargo_script == o_cargo_script,
_ => return false,
@@ -837,13 +932,13 @@ fn project_json_to_crate_graph(
sysroot: &Sysroot,
extra_env: &FxHashMap<String, String>,
override_cfg: &CfgOverrides,
+ set_test: bool,
) -> (CrateGraph, ProcMacroPaths) {
let mut res = (CrateGraph::default(), ProcMacroPaths::default());
let (crate_graph, proc_macros) = &mut res;
let (public_deps, libproc_macro) =
sysroot_to_crate_graph(crate_graph, sysroot, rustc_cfg.clone(), load);
- let r_a_cfg_flag = CfgAtom::Flag(sym::rust_analyzer.clone());
let mut cfg_cache: FxHashMap<&str, Vec<CfgAtom>> = FxHashMap::default();
let idx_to_crate_id: FxHashMap<CrateArrayIdx, CrateId> = project
@@ -862,6 +957,7 @@ fn project_json_to_crate_graph(
proc_macro_dylib_path,
is_proc_macro,
repository,
+ is_workspace_member,
..
},
file_id,
@@ -879,19 +975,28 @@ fn project_json_to_crate_graph(
None => &rustc_cfg,
};
- let mut cfg_options = target_cfgs
- .iter()
- .chain(cfg.iter())
- .chain(iter::once(&r_a_cfg_flag))
- .cloned()
- .collect();
- override_cfg.apply(
- &mut cfg_options,
- display_name
- .as_ref()
- .map(|it| it.canonical_name().as_str())
- .unwrap_or_default(),
- );
+ let cfg_options = {
+ let mut cfg_options: CfgOptions =
+ target_cfgs.iter().chain(cfg.iter()).cloned().collect();
+
+ if *is_workspace_member {
+ if set_test {
+ // Add test cfg for local crates
+ cfg_options.insert_atom(sym::test.clone());
+ }
+ cfg_options.insert_atom(sym::rust_analyzer.clone());
+ }
+
+ override_cfg.apply(
+ &mut cfg_options,
+ display_name
+ .as_ref()
+ .map(|it| it.canonical_name().as_str())
+ .unwrap_or_default(),
+ );
+ cfg_options
+ };
+
let crate_graph_crate_id = crate_graph.add_crate_root(
file_id,
*edition,
@@ -1385,7 +1490,7 @@ fn sysroot_to_crate_graph(
) -> (SysrootPublicDeps, Option<CrateId>) {
let _p = tracing::info_span!("sysroot_to_crate_graph").entered();
match sysroot.workspace() {
- SysrootWorkspace::Workspace(cargo) => {
+ RustLibSrcWorkspace::Workspace(cargo) => {
let (mut cg, mut pm) = cargo_to_crate_graph(
load,
None,
@@ -1460,7 +1565,7 @@ fn sysroot_to_crate_graph(
(SysrootPublicDeps { deps: pub_deps }, libproc_macro)
}
- SysrootWorkspace::Stitched(stitched) => {
+ RustLibSrcWorkspace::Stitched(stitched) => {
let cfg_options = Arc::new({
let mut cfg_options = CfgOptions::default();
cfg_options.extend(rustc_cfg);
@@ -1468,7 +1573,7 @@ fn sysroot_to_crate_graph(
cfg_options.insert_atom(sym::miri.clone());
cfg_options
});
- let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = stitched
+ let sysroot_crates: FxHashMap<RustLibSrcCrate, CrateId> = stitched
.crates()
.filter_map(|krate| {
let file_id = load(&stitched[krate].root)?;
@@ -1513,7 +1618,7 @@ fn sysroot_to_crate_graph(
stitched.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
(public_deps, libproc_macro)
}
- SysrootWorkspace::Empty => (SysrootPublicDeps { deps: vec![] }, None),
+ RustLibSrcWorkspace::Empty => (SysrootPublicDeps { deps: vec![] }, None),
}
}
diff --git a/crates/project-model/test_data/output/rust_project_cfg_groups.txt b/crates/project-model/test_data/output/rust_project_cfg_groups.txt
index 2026ab2b8c..587d3c1782 100644
--- a/crates/project-model/test_data/output/rust_project_cfg_groups.txt
+++ b/crates/project-model/test_data/output/rust_project_cfg_groups.txt
@@ -420,6 +420,7 @@
"group1_other_cfg=other_config",
"group2_cfg=yet_another_config",
"rust_analyzer",
+ "test",
"true",
],
),
@@ -496,6 +497,7 @@
"group2_cfg=fourth_config",
"group2_cfg=yet_another_config",
"rust_analyzer",
+ "test",
"true",
"unrelated_cfg",
],
diff --git a/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt b/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt
index a0e14b8fcb..00805c79bc 100644
--- a/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt
+++ b/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt
@@ -417,6 +417,7 @@
cfg_options: CfgOptions(
[
"rust_analyzer",
+ "test",
"true",
],
),
diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs
index 199f61e70f..e9ca12deaf 100644
--- a/crates/rust-analyzer/src/cli/rustc_tests.rs
+++ b/crates/rust-analyzer/src/cli/rustc_tests.rs
@@ -12,8 +12,8 @@ use paths::Utf8PathBuf;
use profile::StopWatch;
use project_model::toolchain_info::{target_data_layout, QueryConfig};
use project_model::{
- CargoConfig, ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, RustLibSource, Sysroot,
- SysrootSourceWorkspaceConfig,
+ CargoConfig, ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, RustLibSource,
+ RustSourceWorkspaceConfig, Sysroot,
};
use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
@@ -75,7 +75,11 @@ impl Tester {
};
let mut sysroot = Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env);
- sysroot.load_workspace(&SysrootSourceWorkspaceConfig::default_cargo());
+ let loaded_sysroot = sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo());
+ if let Some(loaded_sysroot) = loaded_sysroot {
+ sysroot.set_workspace(loaded_sysroot);
+ }
+
let data_layout = target_data_layout::get(
QueryConfig::Rustc(&sysroot, tmp_file.parent().unwrap().as_ref()),
None,
@@ -86,7 +90,6 @@ impl Tester {
kind: ProjectWorkspaceKind::DetachedFile {
file: ManifestPath::try_from(tmp_file).unwrap(),
cargo: None,
- set_test: true,
},
sysroot,
rustc_cfg: vec![],
@@ -94,6 +97,7 @@ impl Tester {
target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
cfg_overrides: Default::default(),
extra_includes: vec![],
+ set_test: true,
};
let load_cargo_config = LoadCargoConfig {
load_out_dirs_from_check: false,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index e915e55722..d7e9a5c586 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -84,10 +84,10 @@ config_data! {
completion_snippets_custom: FxHashMap<String, SnippetDef> = Config::completion_snippets_default(),
- /// These directories will be ignored by rust-analyzer. They are
+ /// These paths (file/directories) will be ignored by rust-analyzer. They are
/// relative to the workspace root, and globs are not supported. You may
/// also need to add the folders to Code's `files.watcherExclude`.
- files_excludeDirs: Vec<Utf8PathBuf> = vec![],
+ files_exclude | files_excludeDirs: Vec<Utf8PathBuf> = vec![],
@@ -1792,7 +1792,7 @@ impl Config {
fn discovered_projects(&self) -> Vec<ManifestOrProjectJson> {
let exclude_dirs: Vec<_> =
- self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect();
+ self.files_exclude().iter().map(|p| self.root_path.join(p)).collect();
let mut projects = vec![];
for fs_proj in &self.discovered_projects_from_filesystem {
@@ -1914,10 +1914,14 @@ impl Config {
}
_ => FilesWatcher::Server,
},
- exclude: self.files_excludeDirs().iter().map(|it| self.root_path.join(it)).collect(),
+ exclude: self.excluded().collect(),
}
}
+ pub fn excluded(&self) -> impl Iterator<Item = AbsPathBuf> + use<'_> {
+ self.files_exclude().iter().map(|it| self.root_path.join(it))
+ }
+
pub fn notifications(&self) -> NotificationsConfig {
NotificationsConfig {
cargo_toml_not_found: self.notifications_cargoTomlNotFound().to_owned(),
@@ -3798,8 +3802,10 @@ mod tests {
(config, _, _) = config.apply_change(change);
assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true)));
+ let target =
+ Utf8PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap_or("target".to_owned()));
assert!(
- matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer")))
+ matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(target.join("rust-analyzer")))
);
}
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index b52f64aaac..70105cda00 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -650,7 +650,8 @@ impl GlobalStateSnapshot {
RwLockReadGuard::map(self.vfs.read(), |(it, _)| it)
}
- pub(crate) fn url_to_file_id(&self, url: &Url) -> anyhow::Result<FileId> {
+ /// Returns `None` if the file was excluded.
+ pub(crate) fn url_to_file_id(&self, url: &Url) -> anyhow::Result<Option<FileId>> {
url_to_file_id(&self.vfs_read(), url)
}
@@ -658,7 +659,8 @@ impl GlobalStateSnapshot {
file_id_to_url(&self.vfs_read(), id)
}
- pub(crate) fn vfs_path_to_file_id(&self, vfs_path: &VfsPath) -> anyhow::Result<FileId> {
+ /// Returns `None` if the file was excluded.
+ pub(crate) fn vfs_path_to_file_id(&self, vfs_path: &VfsPath) -> anyhow::Result<Option<FileId>> {
vfs_path_to_file_id(&self.vfs_read(), vfs_path)
}
@@ -750,14 +752,21 @@ pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
url_from_abs_path(path)
}
-pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> anyhow::Result<FileId> {
+/// Returns `None` if the file was excluded.
+pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> anyhow::Result<Option<FileId>> {
let path = from_proto::vfs_path(url)?;
- let res = vfs.file_id(&path).ok_or_else(|| anyhow::format_err!("file not found: {path}"))?;
- Ok(res)
+ vfs_path_to_file_id(vfs, &path)
}
-pub(crate) fn vfs_path_to_file_id(vfs: &vfs::Vfs, vfs_path: &VfsPath) -> anyhow::Result<FileId> {
- let res =
+/// Returns `None` if the file was excluded.
+pub(crate) fn vfs_path_to_file_id(
+ vfs: &vfs::Vfs,
+ vfs_path: &VfsPath,
+) -> anyhow::Result<Option<FileId>> {
+ let (file_id, excluded) =
vfs.file_id(vfs_path).ok_or_else(|| anyhow::format_err!("file not found: {vfs_path}"))?;
- Ok(res)
+ match excluded {
+ vfs::FileExcluded::Yes => Ok(None),
+ vfs::FileExcluded::No => Ok(Some(file_id)),
+ }
}
diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs
index 48856d19e1..55344a4d6a 100644
--- a/crates/rust-analyzer/src/handlers/notification.rs
+++ b/crates/rust-analyzer/src/handlers/notification.rs
@@ -22,6 +22,7 @@ use crate::{
mem_docs::DocumentData,
reload,
target_spec::TargetSpec,
+ try_default,
};
pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> anyhow::Result<()> {
@@ -74,6 +75,14 @@ pub(crate) fn handle_did_open_text_document(
tracing::error!("duplicate DidOpenTextDocument: {}", path);
}
+ if let Some(abs_path) = path.as_path() {
+ if state.config.excluded().any(|excluded| abs_path.starts_with(&excluded)) {
+ tracing::trace!("opened excluded file {abs_path}");
+ state.vfs.write().0.insert_excluded_file(path);
+ return Ok(());
+ }
+ }
+
let contents = params.text_document.text.into_bytes();
state.vfs.write().0.set_file_contents(path, Some(contents));
if state.config.discover_workspace_config().is_some() {
@@ -127,7 +136,8 @@ pub(crate) fn handle_did_close_text_document(
tracing::error!("orphan DidCloseTextDocument: {}", path);
}
- if let Some(file_id) = state.vfs.read().0.file_id(&path) {
+ // Clear diagnostics also for excluded files, just in case.
+ if let Some((file_id, _)) = state.vfs.read().0.file_id(&path) {
state.diagnostics.clear_native_for(file_id);
}
@@ -146,7 +156,7 @@ pub(crate) fn handle_did_save_text_document(
) -> anyhow::Result<()> {
if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) {
let snap = state.snapshot();
- let file_id = snap.vfs_path_to_file_id(&vfs_path)?;
+ let file_id = try_default!(snap.vfs_path_to_file_id(&vfs_path)?);
let sr = snap.analysis.source_root_id(file_id)?;
if state.config.script_rebuild_on_save(Some(sr)) && state.build_deps_changed {
@@ -290,7 +300,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
let _p = tracing::info_span!("run_flycheck").entered();
let file_id = state.vfs.read().0.file_id(&vfs_path);
- if let Some(file_id) = file_id {
+ if let Some((file_id, vfs::FileExcluded::No)) = file_id {
let world = state.snapshot();
let invocation_strategy_once = state.config.flycheck(None).invocation_strategy_once();
let may_flycheck_workspace = state.config.flycheck_workspace(None);
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index ed028f1d37..1b144d9073 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -53,6 +53,7 @@ use crate::{
},
target_spec::{CargoTargetSpec, TargetSpec},
test_runner::{CargoTestHandle, TestTarget},
+ try_default,
};
pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
@@ -83,7 +84,8 @@ pub(crate) fn handle_analyzer_status(
let mut file_id = None;
if let Some(tdi) = params.text_document {
match from_proto::file_id(&snap, &tdi.uri) {
- Ok(it) => file_id = Some(it),
+ Ok(Some(it)) => file_id = Some(it),
+ Ok(None) => {}
Err(_) => format_to!(buf, "file {} not found in vfs", tdi.uri),
}
}
@@ -141,7 +143,7 @@ pub(crate) fn handle_view_syntax_tree(
params: lsp_ext::ViewSyntaxTreeParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_view_syntax_tree").entered();
- let id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let res = snap.analysis.view_syntax_tree(id)?;
Ok(res)
}
@@ -151,7 +153,7 @@ pub(crate) fn handle_view_hir(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_view_hir").entered();
- let position = from_proto::file_position(&snap, params)?;
+ let position = try_default!(from_proto::file_position(&snap, params)?);
let res = snap.analysis.view_hir(position)?;
Ok(res)
}
@@ -161,7 +163,7 @@ pub(crate) fn handle_view_mir(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_view_mir").entered();
- let position = from_proto::file_position(&snap, params)?;
+ let position = try_default!(from_proto::file_position(&snap, params)?);
let res = snap.analysis.view_mir(position)?;
Ok(res)
}
@@ -171,7 +173,7 @@ pub(crate) fn handle_interpret_function(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_interpret_function").entered();
- let position = from_proto::file_position(&snap, params)?;
+ let position = try_default!(from_proto::file_position(&snap, params)?);
let res = snap.analysis.interpret_function(position)?;
Ok(res)
}
@@ -180,7 +182,7 @@ pub(crate) fn handle_view_file_text(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentIdentifier,
) -> anyhow::Result<String> {
- let file_id = from_proto::file_id(&snap, &params.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.uri)?);
Ok(snap.analysis.file_text(file_id)?.to_string())
}
@@ -189,7 +191,7 @@ pub(crate) fn handle_view_item_tree(
params: lsp_ext::ViewItemTreeParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_view_item_tree").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let res = snap.analysis.view_item_tree(file_id)?;
Ok(res)
}
@@ -315,7 +317,7 @@ pub(crate) fn handle_expand_macro(
params: lsp_ext::ExpandMacroParams,
) -> anyhow::Result<Option<lsp_ext::ExpandedMacro>> {
let _p = tracing::info_span!("handle_expand_macro").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let offset = from_proto::offset(&line_index, params.position)?;
@@ -328,7 +330,7 @@ pub(crate) fn handle_selection_range(
params: lsp_types::SelectionRangeParams,
) -> anyhow::Result<Option<Vec<lsp_types::SelectionRange>>> {
let _p = tracing::info_span!("handle_selection_range").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let res: anyhow::Result<Vec<lsp_types::SelectionRange>> = params
.positions
@@ -371,7 +373,7 @@ pub(crate) fn handle_matching_brace(
params: lsp_ext::MatchingBraceParams,
) -> anyhow::Result<Vec<Position>> {
let _p = tracing::info_span!("handle_matching_brace").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
params
.positions
@@ -395,7 +397,7 @@ pub(crate) fn handle_join_lines(
) -> anyhow::Result<Vec<lsp_types::TextEdit>> {
let _p = tracing::info_span!("handle_join_lines").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let config = snap.config.join_lines();
let line_index = snap.file_line_index(file_id)?;
@@ -419,7 +421,7 @@ pub(crate) fn handle_on_enter(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
let _p = tracing::info_span!("handle_on_enter").entered();
- let position = from_proto::file_position(&snap, params)?;
+ let position = try_default!(from_proto::file_position(&snap, params)?);
let edit = match snap.analysis.on_enter(position)? {
None => return Ok(None),
Some(it) => it,
@@ -439,7 +441,8 @@ pub(crate) fn handle_on_type_formatting(
return Ok(None);
}
- let mut position = from_proto::file_position(&snap, params.text_document_position)?;
+ let mut position =
+ try_default!(from_proto::file_position(&snap, params.text_document_position)?);
let line_index = snap.file_line_index(position.file_id)?;
// in `ide`, the `on_type` invariant is that
@@ -465,32 +468,33 @@ pub(crate) fn handle_on_type_formatting(
Ok(Some(change))
}
+pub(crate) fn empty_diagnostic_report() -> lsp_types::DocumentDiagnosticReportResult {
+ lsp_types::DocumentDiagnosticReportResult::Report(lsp_types::DocumentDiagnosticReport::Full(
+ lsp_types::RelatedFullDocumentDiagnosticReport {
+ related_documents: None,
+ full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport {
+ result_id: Some("rust-analyzer".to_owned()),
+ items: vec![],
+ },
+ },
+ ))
+}
+
pub(crate) fn handle_document_diagnostics(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentDiagnosticParams,
) -> anyhow::Result<lsp_types::DocumentDiagnosticReportResult> {
- let empty = || {
- lsp_types::DocumentDiagnosticReportResult::Report(
- lsp_types::DocumentDiagnosticReport::Full(
- lsp_types::RelatedFullDocumentDiagnosticReport {
- related_documents: None,
- full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport {
- result_id: Some("rust-analyzer".to_owned()),
- items: vec![],
- },
- },
- ),
- )
+ let file_id = match from_proto::file_id(&snap, &params.text_document.uri)? {
+ Some(it) => it,
+ None => return Ok(empty_diagnostic_report()),
};
-
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let source_root = snap.analysis.source_root_id(file_id)?;
if !snap.analysis.is_local_source_root(source_root)? {
- return Ok(empty());
+ return Ok(empty_diagnostic_report());
}
let config = snap.config.diagnostics(Some(source_root));
if !config.enabled {
- return Ok(empty());
+ return Ok(empty_diagnostic_report());
}
let line_index = snap.file_line_index(file_id)?;
let supports_related = snap.config.text_document_diagnostic_related_document_support();
@@ -546,7 +550,7 @@ pub(crate) fn handle_document_symbol(
params: lsp_types::DocumentSymbolParams,
) -> anyhow::Result<Option<lsp_types::DocumentSymbolResponse>> {
let _p = tracing::info_span!("handle_document_symbol").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let mut parents: Vec<(lsp_types::DocumentSymbol, Option<usize>)> = Vec::new();
@@ -760,7 +764,7 @@ pub(crate) fn handle_will_rename_files(
}
})
.filter_map(|(file_id, new_name)| {
- snap.analysis.will_rename_file(file_id, &new_name).ok()?
+ snap.analysis.will_rename_file(file_id?, &new_name).ok()?
})
.collect();
@@ -782,7 +786,8 @@ pub(crate) fn handle_goto_definition(
params: lsp_types::GotoDefinitionParams,
) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = tracing::info_span!("handle_goto_definition").entered();
- let position = from_proto::file_position(&snap, params.text_document_position_params)?;
+ let position =
+ try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let nav_info = match snap.analysis.goto_definition(position)? {
None => return Ok(None),
Some(it) => it,
@@ -797,7 +802,10 @@ pub(crate) fn handle_goto_declaration(
params: lsp_types::request::GotoDeclarationParams,
) -> anyhow::Result<Option<lsp_types::request::GotoDeclarationResponse>> {
let _p = tracing::info_span!("handle_goto_declaration").entered();
- let position = from_proto::file_position(&snap, params.text_document_position_params.clone())?;
+ let position = try_default!(from_proto::file_position(
+ &snap,
+ params.text_document_position_params.clone()
+ )?);
let nav_info = match snap.analysis.goto_declaration(position)? {
None => return handle_goto_definition(snap, params),
Some(it) => it,
@@ -812,7 +820,8 @@ pub(crate) fn handle_goto_implementation(
params: lsp_types::request::GotoImplementationParams,
) -> anyhow::Result<Option<lsp_types::request::GotoImplementationResponse>> {
let _p = tracing::info_span!("handle_goto_implementation").entered();
- let position = from_proto::file_position(&snap, params.text_document_position_params)?;
+ let position =
+ try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let nav_info = match snap.analysis.goto_implementation(position)? {
None => return Ok(None),
Some(it) => it,
@@ -827,7 +836,8 @@ pub(crate) fn handle_goto_type_definition(
params: lsp_types::request::GotoTypeDefinitionParams,
) -> anyhow::Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
let _p = tracing::info_span!("handle_goto_type_definition").entered();
- let position = from_proto::file_position(&snap, params.text_document_position_params)?;
+ let position =
+ try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let nav_info = match snap.analysis.goto_type_definition(position)? {
None => return Ok(None),
Some(it) => it,
@@ -880,7 +890,7 @@ pub(crate) fn handle_parent_module(
}
// check if invoked at the crate root
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let crate_id = match snap.analysis.crates_for(file_id)?.first() {
Some(&crate_id) => crate_id,
None => return Ok(None),
@@ -904,7 +914,7 @@ pub(crate) fn handle_parent_module(
}
// locate parent module by semantics
- let position = from_proto::file_position(&snap, params)?;
+ let position = try_default!(from_proto::file_position(&snap, params)?);
let navs = snap.analysis.parent_module(position)?;
let res = to_proto::goto_definition_response(&snap, None, navs)?;
Ok(Some(res))
@@ -915,7 +925,7 @@ pub(crate) fn handle_runnables(
params: lsp_ext::RunnablesParams,
) -> anyhow::Result<Vec<lsp_ext::Runnable>> {
let _p = tracing::info_span!("handle_runnables").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let source_root = snap.analysis.source_root_id(file_id).ok();
let line_index = snap.file_line_index(file_id)?;
let offset = params.position.and_then(|it| from_proto::offset(&line_index, it).ok());
@@ -1035,7 +1045,7 @@ pub(crate) fn handle_related_tests(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<Vec<lsp_ext::TestInfo>> {
let _p = tracing::info_span!("handle_related_tests").entered();
- let position = from_proto::file_position(&snap, params)?;
+ let position = try_default!(from_proto::file_position(&snap, params)?);
let tests = snap.analysis.related_tests(position, None)?;
let mut res = Vec::new();
@@ -1053,7 +1063,8 @@ pub(crate) fn handle_completion(
lsp_types::CompletionParams { text_document_position, context,.. }: lsp_types::CompletionParams,
) -> anyhow::Result<Option<lsp_types::CompletionResponse>> {
let _p = tracing::info_span!("handle_completion").entered();
- let mut position = from_proto::file_position(&snap, text_document_position.clone())?;
+ let mut position =
+ try_default!(from_proto::file_position(&snap, text_document_position.clone())?);
let line_index = snap.file_line_index(position.file_id)?;
let completion_trigger_character =
context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next());
@@ -1102,7 +1113,8 @@ pub(crate) fn handle_completion_resolve(
let resolve_data: lsp_ext::CompletionResolveData = serde_json::from_value(data)?;
- let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?;
+ let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?
+ .expect("we never provide completions for excluded files");
let line_index = snap.file_line_index(file_id)?;
// FIXME: We should fix up the position when retrying the cancelled request instead
let Ok(offset) = from_proto::offset(&line_index, resolve_data.position.position) else {
@@ -1185,7 +1197,7 @@ pub(crate) fn handle_folding_range(
params: FoldingRangeParams,
) -> anyhow::Result<Option<Vec<FoldingRange>>> {
let _p = tracing::info_span!("handle_folding_range").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let folds = snap.analysis.folding_ranges(file_id)?;
let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?;
@@ -1202,7 +1214,8 @@ pub(crate) fn handle_signature_help(
params: lsp_types::SignatureHelpParams,
) -> anyhow::Result<Option<lsp_types::SignatureHelp>> {
let _p = tracing::info_span!("handle_signature_help").entered();
- let position = from_proto::file_position(&snap, params.text_document_position_params)?;
+ let position =
+ try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let help = match snap.analysis.signature_help(position)? {
Some(it) => it,
None => return Ok(None),
@@ -1221,7 +1234,7 @@ pub(crate) fn handle_hover(
PositionOrRange::Position(position) => Range::new(position, position),
PositionOrRange::Range(range) => range,
};
- let file_range = from_proto::file_range(&snap, &params.text_document, range)?;
+ let file_range = try_default!(from_proto::file_range(&snap, &params.text_document, range)?);
let hover = snap.config.hover();
let info = match snap.analysis.hover(&hover, file_range)? {
@@ -1255,7 +1268,7 @@ pub(crate) fn handle_prepare_rename(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<Option<PrepareRenameResponse>> {
let _p = tracing::info_span!("handle_prepare_rename").entered();
- let position = from_proto::file_position(&snap, params)?;
+ let position = try_default!(from_proto::file_position(&snap, params)?);
let change = snap.analysis.prepare_rename(position)?.map_err(to_proto::rename_error)?;
@@ -1269,7 +1282,7 @@ pub(crate) fn handle_rename(
params: RenameParams,
) -> anyhow::Result<Option<WorkspaceEdit>> {
let _p = tracing::info_span!("handle_rename").entered();
- let position = from_proto::file_position(&snap, params.text_document_position)?;
+ let position = try_default!(from_proto::file_position(&snap, params.text_document_position)?);
let mut change =
snap.analysis.rename(position, &params.new_name)?.map_err(to_proto::rename_error)?;
@@ -1304,7 +1317,7 @@ pub(crate) fn handle_references(
params: lsp_types::ReferenceParams,
) -> anyhow::Result<Option<Vec<Location>>> {
let _p = tracing::info_span!("handle_references").entered();
- let position = from_proto::file_position(&snap, params.text_document_position)?;
+ let position = try_default!(from_proto::file_position(&snap, params.text_document_position)?);
let exclude_imports = snap.config.find_all_refs_exclude_imports();
let exclude_tests = snap.config.find_all_refs_exclude_tests();
@@ -1375,9 +1388,9 @@ pub(crate) fn handle_code_action(
return Ok(None);
}
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
- let frange = from_proto::file_range(&snap, &params.text_document, params.range)?;
+ let frange = try_default!(from_proto::file_range(&snap, &params.text_document, params.range)?);
let source_root = snap.analysis.source_root_id(file_id)?;
let mut assists_config = snap.config.assist(Some(source_root));
@@ -1455,7 +1468,8 @@ pub(crate) fn handle_code_action_resolve(
return Err(invalid_params_error("code action without data".to_owned()).into());
};
- let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?;
+ let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?
+ .expect("we never provide code actions for excluded files");
if snap.file_version(file_id) != params.version {
return Err(invalid_params_error("stale code action".to_owned()).into());
}
@@ -1551,7 +1565,7 @@ pub(crate) fn handle_code_lens(
return Ok(Some(Vec::default()));
}
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let target_spec = TargetSpec::for_file(&snap, file_id)?;
let annotations = snap.analysis.annotations(
@@ -1613,7 +1627,8 @@ pub(crate) fn handle_document_highlight(
params: lsp_types::DocumentHighlightParams,
) -> anyhow::Result<Option<Vec<lsp_types::DocumentHighlight>>> {
let _p = tracing::info_span!("handle_document_highlight").entered();
- let position = from_proto::file_position(&snap, params.text_document_position_params)?;
+ let position =
+ try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let line_index = snap.file_line_index(position.file_id)?;
let source_root = snap.analysis.source_root_id(position.file_id)?;
@@ -1639,12 +1654,12 @@ pub(crate) fn handle_ssr(
params: lsp_ext::SsrParams,
) -> anyhow::Result<lsp_types::WorkspaceEdit> {
let _p = tracing::info_span!("handle_ssr").entered();
- let selections = params
+ let selections = try_default!(params
.selections
.iter()
.map(|range| from_proto::file_range(&snap, &params.position.text_document, *range))
- .collect::<Result<Vec<_>, _>>()?;
- let position = from_proto::file_position(&snap, params.position)?;
+ .collect::<Result<Option<Vec<_>>, _>>()?);
+ let position = try_default!(from_proto::file_position(&snap, params.position)?);
let source_change = snap.analysis.structural_search_replace(
&params.query,
params.parse_only,
@@ -1660,11 +1675,11 @@ pub(crate) fn handle_inlay_hints(
) -> anyhow::Result<Option<Vec<InlayHint>>> {
let _p = tracing::info_span!("handle_inlay_hints").entered();
let document_uri = &params.text_document.uri;
- let FileRange { file_id, range } = from_proto::file_range(
+ let FileRange { file_id, range } = try_default!(from_proto::file_range(
&snap,
&TextDocumentIdentifier::new(document_uri.to_owned()),
params.range,
- )?;
+ )?);
let line_index = snap.file_line_index(file_id)?;
let range = TextRange::new(
range.start().min(line_index.index.len()),
@@ -1744,7 +1759,8 @@ pub(crate) fn handle_call_hierarchy_prepare(
params: CallHierarchyPrepareParams,
) -> anyhow::Result<Option<Vec<CallHierarchyItem>>> {
let _p = tracing::info_span!("handle_call_hierarchy_prepare").entered();
- let position = from_proto::file_position(&snap, params.text_document_position_params)?;
+ let position =
+ try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let nav_info = match snap.analysis.call_hierarchy(position)? {
None => return Ok(None),
@@ -1769,7 +1785,7 @@ pub(crate) fn handle_call_hierarchy_incoming(
let item = params.item;
let doc = TextDocumentIdentifier::new(item.uri);
- let frange = from_proto::file_range(&snap, &doc, item.selection_range)?;
+ let frange = try_default!(from_proto::file_range(&snap, &doc, item.selection_range)?);
let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
let config = snap.config.call_hierarchy();
@@ -1807,7 +1823,7 @@ pub(crate) fn handle_call_hierarchy_outgoing(
let item = params.item;
let doc = TextDocumentIdentifier::new(item.uri);
- let frange = from_proto::file_range(&snap, &doc, item.selection_range)?;
+ let frange = try_default!(from_proto::file_range(&snap, &doc, item.selection_range)?);
let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
let line_index = snap.file_line_index(fpos.file_id)?;
@@ -1842,7 +1858,7 @@ pub(crate) fn handle_semantic_tokens_full(
) -> anyhow::Result<Option<SemanticTokensResult>> {
let _p = tracing::info_span!("handle_semantic_tokens_full").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?;
@@ -1872,7 +1888,7 @@ pub(crate) fn handle_semantic_tokens_full_delta(
) -> anyhow::Result<Option<SemanticTokensFullDeltaResult>> {
let _p = tracing::info_span!("handle_semantic_tokens_full_delta").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?;
@@ -1915,7 +1931,7 @@ pub(crate) fn handle_semantic_tokens_range(
) -> anyhow::Result<Option<SemanticTokensRangeResult>> {
let _p = tracing::info_span!("handle_semantic_tokens_range").entered();
- let frange = from_proto::file_range(&snap, &params.text_document, params.range)?;
+ let frange = try_default!(from_proto::file_range(&snap, &params.text_document, params.range)?);
let text = snap.analysis.file_text(frange.file_id)?;
let line_index = snap.file_line_index(frange.file_id)?;
@@ -1940,7 +1956,7 @@ pub(crate) fn handle_open_docs(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<ExternalDocsResponse> {
let _p = tracing::info_span!("handle_open_docs").entered();
- let position = from_proto::file_position(&snap, params)?;
+ let position = try_default!(from_proto::file_position(&snap, params)?);
let ws_and_sysroot = snap.workspaces.iter().find_map(|ws| match &ws.kind {
ProjectWorkspaceKind::Cargo { cargo, .. }
@@ -1982,7 +1998,7 @@ pub(crate) fn handle_open_cargo_toml(
params: lsp_ext::OpenCargoTomlParams,
) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = tracing::info_span!("handle_open_cargo_toml").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let cargo_spec = match TargetSpec::for_file(&snap, file_id)? {
Some(TargetSpec::Cargo(it)) => it,
@@ -2000,8 +2016,8 @@ pub(crate) fn handle_move_item(
params: lsp_ext::MoveItemParams,
) -> anyhow::Result<Vec<lsp_ext::SnippetTextEdit>> {
let _p = tracing::info_span!("handle_move_item").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
- let range = from_proto::file_range(&snap, &params.text_document, params.range)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
+ let range = try_default!(from_proto::file_range(&snap, &params.text_document, params.range)?);
let direction = match params.direction {
lsp_ext::MoveItemDirection::Up => ide::Direction::Up,
@@ -2022,7 +2038,7 @@ pub(crate) fn handle_view_recursive_memory_layout(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<Option<lsp_ext::RecursiveMemoryLayout>> {
let _p = tracing::info_span!("handle_view_recursive_memory_layout").entered();
- let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let offset = from_proto::offset(&line_index, params.position)?;
@@ -2210,7 +2226,7 @@ fn run_rustfmt(
text_document: TextDocumentIdentifier,
range: Option<lsp_types::Range>,
) -> anyhow::Result<Option<Vec<lsp_types::TextEdit>>> {
- let file_id = from_proto::file_id(snap, &text_document.uri)?;
+ let file_id = try_default!(from_proto::file_id(snap, &text_document.uri)?);
let file = snap.analysis.file_text(file_id)?;
// Determine the edition of the crate the file belongs to (if there's multiple, we pick the
@@ -2275,7 +2291,7 @@ fn run_rustfmt(
.into());
}
- let frange = from_proto::file_range(snap, &text_document, range)?;
+ let frange = try_default!(from_proto::file_range(snap, &text_document, range)?);
let start_line = line_index.index.line_col(frange.range.start()).line;
let end_line = line_index.index.line_col(frange.range.end()).line;
@@ -2284,7 +2300,8 @@ fn run_rustfmt(
cmd.arg(
json!([{
"file": "stdin",
- "range": [start_line, end_line]
+ // LineCol is 0-based, but rustfmt is 1-based.
+ "range": [start_line + 1, end_line + 1]
}])
.to_string(),
);
@@ -2416,15 +2433,15 @@ pub(crate) fn internal_testing_fetch_config(
state: GlobalStateSnapshot,
params: InternalTestingFetchConfigParams,
) -> anyhow::Result<Option<InternalTestingFetchConfigResponse>> {
- let source_root = params
- .text_document
- .map(|it| {
+ let source_root = match params.text_document {
+ Some(it) => Some(
state
.analysis
- .source_root_id(from_proto::file_id(&state, &it.uri)?)
- .map_err(anyhow::Error::from)
- })
- .transpose()?;
+ .source_root_id(try_default!(from_proto::file_id(&state, &it.uri)?))
+ .map_err(anyhow::Error::from)?,
+ ),
+ None => None,
+ };
Ok(Some(match params.config {
InternalTestingFetchConfigOption::AssistEmitMustUse => {
InternalTestingFetchConfigResponse::AssistEmitMustUse(
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index 5cdc51a1c1..c6aa8ba170 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -25,6 +25,14 @@ use vfs::{AbsPathBuf, VfsPath};
use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
+#[track_caller]
+fn file_id(vfs: &vfs::Vfs, path: &VfsPath) -> vfs::FileId {
+ match vfs.file_id(path) {
+ Some((file_id, vfs::FileExcluded::No)) => file_id,
+ None | Some((_, vfs::FileExcluded::Yes)) => panic!("can't find virtual file for {path}"),
+ }
+}
+
#[test]
fn integrated_highlighting_benchmark() {
if std::env::var("RUN_SLOW_BENCHES").is_err() {
@@ -62,7 +70,7 @@ fn integrated_highlighting_benchmark() {
let file_id = {
let file = workspace_to_load.join(file);
let path = VfsPath::from(AbsPathBuf::assert(file));
- vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
+ file_id(&vfs, &path)
};
{
@@ -130,7 +138,7 @@ fn integrated_completion_benchmark() {
let file_id = {
let file = workspace_to_load.join(file);
let path = VfsPath::from(AbsPathBuf::assert(file));
- vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
+ file_id(&vfs, &path)
};
// kick off parsing and index population
@@ -324,7 +332,7 @@ fn integrated_diagnostics_benchmark() {
let file_id = {
let file = workspace_to_load.join(file);
let path = VfsPath::from(AbsPathBuf::assert(file));
- vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
+ file_id(&vfs, &path)
};
let diagnostics_config = DiagnosticsConfig {
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index 1221f7c701..27d6225cdb 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -173,3 +173,14 @@ fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8;
hasher.finalize()
}
+
+#[doc(hidden)]
+macro_rules! try_default_ {
+ ($it:expr $(,)?) => {
+ match $it {
+ Some(it) => it,
+ None => return Ok(Default::default()),
+ }
+ };
+}
+pub(crate) use try_default_ as try_default;
diff --git a/crates/rust-analyzer/src/lsp/from_proto.rs b/crates/rust-analyzer/src/lsp/from_proto.rs
index 47e9961cf1..6375a1a054 100644
--- a/crates/rust-analyzer/src/lsp/from_proto.rs
+++ b/crates/rust-analyzer/src/lsp/from_proto.rs
@@ -9,7 +9,7 @@ use vfs::AbsPathBuf;
use crate::{
global_state::GlobalStateSnapshot,
line_index::{LineIndex, PositionEncoding},
- lsp_ext,
+ lsp_ext, try_default,
};
pub(crate) fn abs_path(url: &lsp_types::Url) -> anyhow::Result<AbsPathBuf> {
@@ -61,37 +61,44 @@ pub(crate) fn text_range(
}
}
-pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> anyhow::Result<FileId> {
+/// Returns `None` if the file was excluded.
+pub(crate) fn file_id(
+ snap: &GlobalStateSnapshot,
+ url: &lsp_types::Url,
+) -> anyhow::Result<Option<FileId>> {
snap.url_to_file_id(url)
}
+/// Returns `None` if the file was excluded.
pub(crate) fn file_position(
snap: &GlobalStateSnapshot,
tdpp: lsp_types::TextDocumentPositionParams,
-) -> anyhow::Result<FilePosition> {
- let file_id = file_id(snap, &tdpp.text_document.uri)?;
+) -> anyhow::Result<Option<FilePosition>> {
+ let file_id = try_default!(file_id(snap, &tdpp.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let offset = offset(&line_index, tdpp.position)?;
- Ok(FilePosition { file_id, offset })
+ Ok(Some(FilePosition { file_id, offset }))
}
+/// Returns `None` if the file was excluded.
pub(crate) fn file_range(
snap: &GlobalStateSnapshot,
text_document_identifier: &lsp_types::TextDocumentIdentifier,
range: lsp_types::Range,
-) -> anyhow::Result<FileRange> {
+) -> anyhow::Result<Option<FileRange>> {
file_range_uri(snap, &text_document_identifier.uri, range)
}
+/// Returns `None` if the file was excluded.
pub(crate) fn file_range_uri(
snap: &GlobalStateSnapshot,
document: &lsp_types::Url,
range: lsp_types::Range,
-) -> anyhow::Result<FileRange> {
- let file_id = file_id(snap, document)?;
+) -> anyhow::Result<Option<FileRange>> {
+ let file_id = try_default!(file_id(snap, document)?);
let line_index = snap.file_line_index(file_id)?;
let range = text_range(&line_index, range)?;
- Ok(FileRange { file_id, range })
+ Ok(Some(FileRange { file_id, range }))
}
pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> {
@@ -108,6 +115,7 @@ pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind>
Some(assist_kind)
}
+/// Returns `None` if the file was excluded.
pub(crate) fn annotation(
snap: &GlobalStateSnapshot,
range: lsp_types::Range,
@@ -121,7 +129,7 @@ pub(crate) fn annotation(
return Ok(None);
}
let pos @ FilePosition { file_id, .. } =
- file_position(snap, params.text_document_position_params)?;
+ try_default!(file_position(snap, params.text_document_position_params)?);
let line_index = snap.file_line_index(file_id)?;
Ok(Annotation {
@@ -133,7 +141,7 @@ pub(crate) fn annotation(
if snap.url_file_version(&params.text_document.uri) != Some(data.version) {
return Ok(None);
}
- let pos @ FilePosition { file_id, .. } = file_position(snap, params)?;
+ let pos @ FilePosition { file_id, .. } = try_default!(file_position(snap, params)?);
let line_index = snap.file_line_index(file_id)?;
Ok(Annotation {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index ebc65373b5..f5d9469f26 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -27,7 +27,10 @@ use crate::{
FetchWorkspaceResponse, GlobalState,
},
hack_recover_crate_name,
- handlers::dispatch::{NotificationDispatcher, RequestDispatcher},
+ handlers::{
+ dispatch::{NotificationDispatcher, RequestDispatcher},
+ request::empty_diagnostic_report,
+ },
lsp::{
from_proto, to_proto,
utils::{notification_is, Progress},
@@ -548,6 +551,9 @@ impl GlobalState {
self.mem_docs
.iter()
.map(|path| vfs.file_id(path).unwrap())
+ .filter_map(|(file_id, excluded)| {
+ (excluded == vfs::FileExcluded::No).then_some(file_id)
+ })
.filter(|&file_id| {
let source_root = db.file_source_root(file_id);
// Only publish diagnostics for files in the workspace, not from crates.io deps
@@ -632,6 +638,9 @@ impl GlobalState {
.mem_docs
.iter()
.map(|path| self.vfs.read().0.file_id(path).unwrap())
+ .filter_map(|(file_id, excluded)| {
+ (excluded == vfs::FileExcluded::No).then_some(file_id)
+ })
.filter(|&file_id| {
let source_root = db.file_source_root(file_id);
!db.source_root(source_root).is_library
@@ -879,7 +888,10 @@ impl GlobalState {
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
let _p = tracing::info_span!("GlobalState::check_if_indexed").entered();
tracing::debug!(?uri, "handling uri");
- let id = from_proto::file_id(&snap, &uri).expect("unable to get FileId");
+ let Some(id) = from_proto::file_id(&snap, &uri).expect("unable to get FileId")
+ else {
+ return;
+ };
if let Ok(crates) = &snap.analysis.crates_for(id) {
if crates.is_empty() {
if snap.config.discover_workspace_config().is_some() {
@@ -987,13 +999,14 @@ impl GlobalState {
);
for diag in diagnostics {
match url_to_file_id(&self.vfs.read().0, &diag.url) {
- Ok(file_id) => self.diagnostics.add_check_diagnostic(
+ Ok(Some(file_id)) => self.diagnostics.add_check_diagnostic(
id,
&package_id,
file_id,
diag.diagnostic,
diag.fix,
),
+ Ok(None) => {}
Err(err) => {
error!(
"flycheck {id}: File with cargo diagnostic not found in VFS: {}",
@@ -1115,17 +1128,7 @@ impl GlobalState {
.on_latency_sensitive::<NO_RETRY, lsp_request::SemanticTokensRangeRequest>(handlers::handle_semantic_tokens_range)
// FIXME: Some of these NO_RETRY could be retries if the file they are interested didn't change.
// All other request handlers
- .on_with_vfs_default::<lsp_request::DocumentDiagnosticRequest>(handlers::handle_document_diagnostics, || lsp_types::DocumentDiagnosticReportResult::Report(
- lsp_types::DocumentDiagnosticReport::Full(
- lsp_types::RelatedFullDocumentDiagnosticReport {
- related_documents: None,
- full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport {
- result_id: Some("rust-analyzer".to_owned()),
- items: vec![],
- },
- },
- ),
- ), || lsp_server::ResponseError {
+ .on_with_vfs_default::<lsp_request::DocumentDiagnosticRequest>(handlers::handle_document_diagnostics, empty_diagnostic_report, || lsp_server::ResponseError {
code: lsp_server::ErrorCode::ServerCancelled as i32,
message: "server cancelled the request".to_owned(),
data: serde_json::to_value(lsp_types::DiagnosticServerCancellationData {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index d18e577047..ba72ea35df 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -316,6 +316,7 @@ impl GlobalState {
let workspace = project_model::ProjectWorkspace::load_inline(
it.clone(),
&cargo_config,
+ &progress,
);
Ok(workspace)
}
@@ -705,7 +706,9 @@ impl GlobalState {
let load = |path: &AbsPath| {
let vfs_path = vfs::VfsPath::from(path.to_path_buf());
self.crate_graph_file_dependencies.insert(vfs_path.clone());
- vfs.file_id(&vfs_path)
+ vfs.file_id(&vfs_path).and_then(|(file_id, excluded)| {
+ (excluded == vfs::FileExcluded::No).then_some(file_id)
+ })
};
ws_to_crate_graph(&self.workspaces, self.config.extra_env(None), load)
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index 2b3c0a47a2..6f26bdc2cf 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -21,12 +21,14 @@ use lsp_types::{
notification::DidOpenTextDocument,
request::{
CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
- InlayHintRequest, InlayHintResolveRequest, WillRenameFiles, WorkspaceSymbolRequest,
+ InlayHintRequest, InlayHintResolveRequest, RangeFormatting, WillRenameFiles,
+ WorkspaceSymbolRequest,
},
CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
- DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
- InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
- RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
+ DocumentFormattingParams, DocumentRangeFormattingParams, FileRename, FormattingOptions,
+ GotoDefinitionParams, HoverParams, InlayHint, InlayHintLabel, InlayHintParams,
+ PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem,
+ TextDocumentPositionParams, WorkDoneProgressParams,
};
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams};
use serde_json::json;
@@ -661,6 +663,70 @@ fn main() {}
}
#[test]
+fn test_format_document_range() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/lib.rs
+fn main() {
+ let unit_offsets_cache = collect(dwarf.units ()) ?;
+}
+"#,
+ )
+ .with_config(serde_json::json!({
+ "rustfmt": {
+ "overrideCommand": [ "rustfmt", "+nightly", ],
+ "rangeFormatting": { "enable": true }
+ },
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ server.request::<RangeFormatting>(
+ DocumentRangeFormattingParams {
+ range: Range {
+ end: Position { line: 1, character: 0 },
+ start: Position { line: 1, character: 0 },
+ },
+ text_document: server.doc_id("src/lib.rs"),
+ options: FormattingOptions {
+ tab_size: 4,
+ insert_spaces: false,
+ insert_final_newline: None,
+ trim_final_newlines: None,
+ trim_trailing_whitespace: None,
+ properties: HashMap::new(),
+ },
+ work_done_progress_params: WorkDoneProgressParams::default(),
+ },
+ json!([
+ {
+ "newText": "",
+ "range": {
+ "start": { "character": 48, "line": 1 },
+ "end": { "character": 50, "line": 1 },
+ },
+ },
+ {
+ "newText": "",
+ "range": {
+ "start": { "character": 53, "line": 1 },
+ "end": { "character": 55, "line": 1 },
+ },
+ }
+ ]),
+ );
+}
+
+#[test]
fn test_missing_module_code_action() {
if skip_slow_tests() {
return;
@@ -1086,7 +1152,11 @@ fn resolve_proc_macro() {
&AbsPathBuf::assert_utf8(std::env::current_dir().unwrap()),
&Default::default(),
);
- sysroot.load_workspace(&project_model::SysrootSourceWorkspaceConfig::default_cargo());
+ let loaded_sysroot =
+ sysroot.load_workspace(&project_model::RustSourceWorkspaceConfig::default_cargo());
+ if let Some(loaded_sysroot) = loaded_sysroot {
+ sysroot.set_workspace(loaded_sysroot);
+ }
let proc_macro_server_path = sysroot.discover_proc_macro_srv().unwrap();
@@ -1372,6 +1442,40 @@ pub fn foo() {}
name = "bar"
version = "0.0.0"
+[dependencies]
+foo = { path = "../foo" }
+
+//- /bar/src/lib.rs
+"#,
+ )
+ .root("foo")
+ .root("bar")
+ .root("baz")
+ .with_config(json!({
+ "files": {
+ "exclude": ["foo"]
+ }
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ server.request::<WorkspaceSymbolRequest>(Default::default(), json!([]));
+
+ let server = Project::with_fixture(
+ r#"
+//- /foo/Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /foo/src/lib.rs
+pub fn foo() {}
+
+//- /bar/Cargo.toml
+[package]
+name = "bar"
+version = "0.0.0"
+
//- /bar/src/lib.rs
pub fn bar() {}
@@ -1388,7 +1492,7 @@ version = "0.0.0"
.root("baz")
.with_config(json!({
"files": {
- "excludeDirs": ["foo", "bar"]
+ "exclude": ["foo", "bar"]
}
}))
.server()
diff --git a/crates/stdx/src/panic_context.rs b/crates/stdx/src/panic_context.rs
index 4ec74c0742..a35d50b78d 100644
--- a/crates/stdx/src/panic_context.rs
+++ b/crates/stdx/src/panic_context.rs
@@ -1,28 +1,25 @@
//! A micro-crate to enhance panic messages with context info.
-//!
-//! FIXME: upstream to <https://github.com/kriomant/panic-context> ?
use std::{cell::RefCell, panic, sync::Once};
-pub fn enter(context: String) -> PanicContext {
- static ONCE: Once = Once::new();
- ONCE.call_once(PanicContext::init);
-
- with_ctx(|ctx| ctx.push(context));
- PanicContext { _priv: () }
-}
-
+/// Dummy for leveraging RAII cleanup to pop frames.
#[must_use]
pub struct PanicContext {
+ // prevent arbitrary construction
_priv: (),
}
-impl PanicContext {
+impl Drop for PanicContext {
+ fn drop(&mut self) {
+ with_ctx(|ctx| assert!(ctx.pop().is_some()));
+ }
+}
+
+pub fn enter(frame: String) -> PanicContext {
#[allow(clippy::print_stderr)]
- fn init() {
+ fn set_hook() {
let default_hook = panic::take_hook();
- #[allow(deprecated)]
- let hook = move |panic_info: &panic::PanicInfo<'_>| {
+ panic::set_hook(Box::new(move |panic_info| {
with_ctx(|ctx| {
if !ctx.is_empty() {
eprintln!("Panic context:");
@@ -30,17 +27,16 @@ impl PanicContext {
eprintln!("> {frame}\n");
}
}
- default_hook(panic_info);
});
- };
- panic::set_hook(Box::new(hook));
+ default_hook(panic_info);
+ }));
}
-}
-impl Drop for PanicContext {
- fn drop(&mut self) {
- with_ctx(|ctx| assert!(ctx.pop().is_some()));
- }
+ static SET_HOOK: Once = Once::new();
+ SET_HOOK.call_once(set_hook);
+
+ with_ctx(|ctx| ctx.push(frame));
+ PanicContext { _priv: () }
}
fn with_ctx(f: impl FnOnce(&mut Vec<String>)) {
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 291fc646e2..aedf810b79 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -710,52 +710,6 @@ impl ast::Fn {
}
}
-impl Removable for ast::MatchArm {
- fn remove(&self) {
- if let Some(sibling) = self.syntax().prev_sibling_or_token() {
- if sibling.kind() == SyntaxKind::WHITESPACE {
- ted::remove(sibling);
- }
- }
- if let Some(sibling) = self.syntax().next_sibling_or_token() {
- if sibling.kind() == T![,] {
- ted::remove(sibling);
- }
- }
- ted::remove(self.syntax());
- }
-}
-
-impl ast::MatchArmList {
- pub fn add_arm(&self, arm: ast::MatchArm) {
- normalize_ws_between_braces(self.syntax());
- let mut elements = Vec::new();
- let position = match self.arms().last() {
- Some(last_arm) => {
- if needs_comma(&last_arm) {
- ted::append_child(last_arm.syntax(), make::token(SyntaxKind::COMMA));
- }
- Position::after(last_arm.syntax().clone())
- }
- None => match self.l_curly_token() {
- Some(it) => Position::after(it),
- None => Position::last_child_of(self.syntax()),
- },
- };
- let indent = IndentLevel::from_node(self.syntax()) + 1;
- elements.push(make::tokens::whitespace(&format!("\n{indent}")).into());
- elements.push(arm.syntax().clone().into());
- if needs_comma(&arm) {
- ted::append_child(arm.syntax(), make::token(SyntaxKind::COMMA));
- }
- ted::insert_all(position, elements);
-
- fn needs_comma(arm: &ast::MatchArm) -> bool {
- arm.expr().is_some_and(|e| !e.is_block_like()) && arm.comma_token().is_none()
- }
- }
-}
-
impl ast::LetStmt {
pub fn set_ty(&self, ty: Option<ast::Type>) {
match ty {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index ff027ac584..9dc2d83253 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -837,7 +837,8 @@ pub fn match_guard(condition: ast::Expr) -> ast::MatchGuard {
pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
let arms_str = arms.into_iter().fold(String::new(), |mut acc, arm| {
- let needs_comma = arm.expr().is_none_or(|it| !it.is_block_like());
+ let needs_comma =
+ arm.comma_token().is_none() && arm.expr().is_none_or(|it| !it.is_block_like());
let comma = if needs_comma { "," } else { "" };
let arm = arm.syntax();
format_to_acc!(acc, " {arm}{comma}\n")
diff --git a/crates/test-fixture/src/lib.rs b/crates/test-fixture/src/lib.rs
index ca59658359..613f27c795 100644
--- a/crates/test-fixture/src/lib.rs
+++ b/crates/test-fixture/src/lib.rs
@@ -17,7 +17,7 @@ use hir_expand::{
tt::{Leaf, TokenTree, TopSubtree, TopSubtreeBuilder, TtElement, TtIter},
FileRange,
};
-use intern::Symbol;
+use intern::{sym, Symbol};
use rustc_hash::FxHashMap;
use span::{Edition, EditionedFileId, FileId, Span};
use stdx::itertools::Itertools;
@@ -511,6 +511,21 @@ pub fn issue_18898(_attr: TokenStream, input: TokenStream) -> TokenStream {
disabled: false,
},
),
+ (
+ r#"
+#[proc_macro_attribute]
+pub fn disallow_cfg(_attr: TokenStream, input: TokenStream) -> TokenStream {
+ input
+}
+"#
+ .into(),
+ ProcMacro {
+ name: Symbol::intern("disallow_cfg"),
+ kind: ProcMacroKind::Attr,
+ expander: sync::Arc::new(DisallowCfgProcMacroExpander),
+ disabled: false,
+ },
+ ),
])
}
@@ -865,3 +880,30 @@ impl ProcMacroExpander for Issue18898ProcMacroExpander {
})
}
}
+
+// Reads ident type within string quotes, for issue #17479.
+#[derive(Debug)]
+struct DisallowCfgProcMacroExpander;
+impl ProcMacroExpander for DisallowCfgProcMacroExpander {
+ fn expand(
+ &self,
+ subtree: &TopSubtree,
+ _: Option<&TopSubtree>,
+ _: &Env,
+ _: Span,
+ _: Span,
+ _: Span,
+ _: Option<String>,
+ ) -> Result<TopSubtree, ProcMacroExpansionError> {
+ for tt in subtree.token_trees().flat_tokens() {
+ if let tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) = tt {
+ if ident.sym == sym::cfg || ident.sym == sym::cfg_attr {
+ return Err(ProcMacroExpansionError::Panic(
+ "cfg or cfg_attr found in DisallowCfgProcMacroExpander".to_owned(),
+ ));
+ }
+ }
+ }
+ Ok(subtree.clone())
+ }
+}
diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs
index 36be9937d3..e7279fa1f6 100644
--- a/crates/test-utils/src/lib.rs
+++ b/crates/test-utils/src/lib.rs
@@ -396,12 +396,19 @@ pub fn skip_slow_tests() -> bool {
if should_skip {
eprintln!("ignoring slow test");
} else {
- let path = project_root().join("./target/.slow_tests_cookie");
+ let path = target_dir().join(".slow_tests_cookie");
fs::write(path, ".").unwrap();
}
should_skip
}
+pub fn target_dir() -> Utf8PathBuf {
+ match std::env::var("CARGO_TARGET_DIR") {
+ Ok(target) => Utf8PathBuf::from(target),
+ Err(_) => project_root().join("target"),
+ }
+}
+
/// Returns the path to the root directory of `rust-analyzer` project.
pub fn project_root() -> Utf8PathBuf {
let dir = env!("CARGO_MANIFEST_DIR");
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 4ed68d18e8..202afebde7 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -647,18 +647,21 @@ pub mod ops {
#[lang = "fn"]
#[fundamental]
+ #[rustc_paren_sugar]
pub trait Fn<Args: Tuple>: FnMut<Args> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
#[lang = "fn_mut"]
#[fundamental]
+ #[rustc_paren_sugar]
pub trait FnMut<Args: Tuple>: FnOnce<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
#[lang = "fn_once"]
#[fundamental]
+ #[rustc_paren_sugar]
pub trait FnOnce<Args: Tuple> {
#[lang = "fn_once_output"]
type Output;
@@ -736,12 +739,14 @@ pub mod ops {
#[lang = "async_fn"]
#[fundamental]
+ #[rustc_paren_sugar]
pub trait AsyncFn<Args: Tuple>: AsyncFnMut<Args> {
extern "rust-call" fn async_call(&self, args: Args) -> Self::CallRefFuture<'_>;
}
#[lang = "async_fn_mut"]
#[fundamental]
+ #[rustc_paren_sugar]
pub trait AsyncFnMut<Args: Tuple>: AsyncFnOnce<Args> {
#[lang = "call_ref_future"]
type CallRefFuture<'a>: Future<Output = Self::Output>
@@ -752,6 +757,7 @@ pub mod ops {
#[lang = "async_fn_once"]
#[fundamental]
+ #[rustc_paren_sugar]
pub trait AsyncFnOnce<Args: Tuple> {
#[lang = "async_fn_once_output"]
type Output;
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs
index 0ae8b7baf4..3200334176 100644
--- a/crates/vfs-notify/src/lib.rs
+++ b/crates/vfs-notify/src/lib.rs
@@ -280,8 +280,9 @@ impl NotifyActor {
return false;
}
- root == path
- || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
+ // We want to filter out subdirectories that are roots themselves, because they will be visited separately.
+ dirs.exclude.iter().all(|it| it != path)
+ && (root == path || dirs.include.iter().all(|it| it != path))
});
let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| {
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs
index a26444e9ea..3feca512e5 100644
--- a/crates/vfs/src/lib.rs
+++ b/crates/vfs/src/lib.rs
@@ -100,6 +100,9 @@ pub enum FileState {
Exists(u64),
/// The file is deleted.
Deleted,
+ /// The file was specifically excluded by the user. We still include excluded files
+ /// when they're opened (without their contents).
+ Excluded,
}
/// Changed file in the [`Vfs`].
@@ -164,10 +167,22 @@ pub enum ChangeKind {
Delete,
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FileExcluded {
+ Yes,
+ No,
+}
+
impl Vfs {
/// Id of the given path if it exists in the `Vfs` and is not deleted.
- pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
- self.interner.get(path).filter(|&it| matches!(self.get(it), FileState::Exists(_)))
+ pub fn file_id(&self, path: &VfsPath) -> Option<(FileId, FileExcluded)> {
+ let file_id = self.interner.get(path)?;
+ let file_state = self.get(file_id);
+ match file_state {
+ FileState::Exists(_) => Some((file_id, FileExcluded::No)),
+ FileState::Deleted => None,
+ FileState::Excluded => Some((file_id, FileExcluded::Yes)),
+ }
}
/// File path corresponding to the given `file_id`.
@@ -216,6 +231,7 @@ impl Vfs {
}
Change::Modify(v, new_hash)
}
+ (FileState::Excluded, _) => return false,
};
let mut set_data = |change_kind| {
@@ -297,6 +313,13 @@ impl Vfs {
fn get(&self, file_id: FileId) -> FileState {
self.data[file_id.0 as usize]
}
+
+ /// We cannot ignore excluded files, because this will lead to errors when the client
+ /// requests semantic information for them, so we instead mark them specially.
+ pub fn insert_excluded_file(&mut self, path: VfsPath) {
+ let file_id = self.alloc_file_id(path);
+ self.data[file_id.0 as usize] = FileState::Excluded;
+ }
}
impl fmt::Debug for Vfs {
diff --git a/docs/book/README.md b/docs/book/README.md
index a9d10df664..043524b234 100644
--- a/docs/book/README.md
+++ b/docs/book/README.md
@@ -26,4 +26,4 @@ Start with the mdbook [User Guide](https://rust-lang.github.io/mdBook/guide/inst
Four sections are generated dynamically: assists, configuration, diagnostics and features. Their content is found in the `generated.md` files
of the respective book section, for example `src/configuration_generated.md`, and are included in the book via mdbook's
[include](https://rust-lang.github.io/mdBook/format/mdbook.html#including-files) functionality. Generated files can be rebuilt by running the various
-test cases that generate them, or by simply running all of the `rust-analyzer` tests with `cargo test`.
+test cases that generate them, or by simply running all of the `rust-analyzer` tests with `cargo test` and `cargo xtask codegen`.
diff --git a/docs/book/book.toml b/docs/book/book.toml
index ba3c1dede5..5ca4badde8 100644
--- a/docs/book/book.toml
+++ b/docs/book/book.toml
@@ -9,10 +9,10 @@ title = "rust-analyzer"
edition = "2021"
[output.html]
-edit-url-template = "https://github.com/rust-lang/rust-analyzer/edit/master/manual/{path}"
-git-repository-url = "https://github.com/rust-lang/rust-analyzer/tree/master/manual"
+edit-url-template = "https://github.com/rust-lang/rust-analyzer/edit/master/docs/book/{path}"
+git-repository-url = "https://github.com/rust-lang/rust-analyzer/tree/master/docs/book"
mathjax-support = true
-site-url = "/manual/"
+site-url = "/book/"
[output.html.playground]
editable = true
@@ -34,8 +34,3 @@ use-boolean-and = true
[output.html.fold]
enable = true
level = 3
-
-[preprocessor.toc]
-command = "mdbook-toc"
-renderer = ["html"]
-max-level = 3
diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md
index b3ed1e6df0..9dc4f1f2d2 100644
--- a/docs/book/src/SUMMARY.md
+++ b/docs/book/src/SUMMARY.md
@@ -2,6 +2,9 @@
- [Introduction](README.md)
- [Installation](installation.md)
+ - [VS Code](vs_code.md)
+ - [rust-analyzer Binary](rust_analyzer_binary.md)
+ - [Other Editors](other_editors.md)
- [Troubleshooting](troubleshooting.md)
- [Configuration](configuration.md)
- [Non-Cargo Based Projects](non_cargo_based_projects.md)
diff --git a/docs/book/src/assists_generated.md b/docs/book/src/assists_generated.md
new file mode 100644
index 0000000000..3617badeef
--- /dev/null
+++ b/docs/book/src/assists_generated.md
@@ -0,0 +1,3820 @@
+//! Generated by `cargo xtask codegen assists-doc-tests`, do not edit by hand.
+
+### `add_braces`
+**Source:** [add_braces.rs](crates/ide-assists/src/handlers/add_braces.rs#8)
+
+Adds braces to lambda and match arm expressions.
+
+#### Before
+```rust
+fn foo(n: i32) -> i32 {
+ match n {
+ 1 =>┃ n + 1,
+ _ => 0
+ }
+}
+```
+
+#### After
+```rust
+fn foo(n: i32) -> i32 {
+ match n {
+ 1 => {
+ n + 1
+ },
+ _ => 0
+ }
+}
+```
+
+
+### `add_explicit_type`
+**Source:** [add_explicit_type.rs](crates/ide-assists/src/handlers/add_explicit_type.rs#7)
+
+Specify type for a let binding.
+
+#### Before
+```rust
+fn main() {
+ let x┃ = 92;
+}
+```
+
+#### After
+```rust
+fn main() {
+ let x: i32 = 92;
+}
+```
+
+
+### `add_hash`
+**Source:** [raw_string.rs](crates/ide-assists/src/handlers/raw_string.rs#89)
+
+Adds a hash to a raw string literal.
+
+#### Before
+```rust
+fn main() {
+ r#"Hello,┃ World!"#;
+}
+```
+
+#### After
+```rust
+fn main() {
+ r##"Hello, World!"##;
+}
+```
+
+
+### `add_impl_default_members`
+**Source:** [add_missing_impl_members.rs](crates/ide-assists/src/handlers/add_missing_impl_members.rs#58)
+
+Adds scaffold for overriding default impl members.
+
+#### Before
+```rust
+trait Trait {
+ type X;
+ fn foo(&self);
+ fn bar(&self) {}
+}
+
+impl Trait for () {
+ type X = ();
+ fn foo(&self) {}┃
+}
+```
+
+#### After
+```rust
+trait Trait {
+ type X;
+ fn foo(&self);
+ fn bar(&self) {}
+}
+
+impl Trait for () {
+ type X = ();
+ fn foo(&self) {}
+
+ ┃fn bar(&self) {}
+}
+```
+
+
+### `add_impl_missing_members`
+**Source:** [add_missing_impl_members.rs](crates/ide-assists/src/handlers/add_missing_impl_members.rs#16)
+
+Adds scaffold for required impl members.
+
+#### Before
+```rust
+trait Trait<T> {
+ type X;
+ fn foo(&self) -> T;
+ fn bar(&self) {}
+}
+
+impl Trait<u32> for () {┃
+
+}
+```
+
+#### After
+```rust
+trait Trait<T> {
+ type X;
+ fn foo(&self) -> T;
+ fn bar(&self) {}
+}
+
+impl Trait<u32> for () {
+ ┃type X;
+
+ fn foo(&self) -> u32 {
+ todo!()
+ }
+}
+```
+
+
+### `add_label_to_loop`
+**Source:** [add_label_to_loop.rs](crates/ide-assists/src/handlers/add_label_to_loop.rs#9)
+
+Adds a label to a loop.
+
+#### Before
+```rust
+fn main() {
+ loop┃ {
+ break;
+ continue;
+ }
+}
+```
+
+#### After
+```rust
+fn main() {
+ 'l: loop {
+ break 'l;
+ continue 'l;
+ }
+}
+```
+
+
+### `add_lifetime_to_type`
+**Source:** [add_lifetime_to_type.rs](crates/ide-assists/src/handlers/add_lifetime_to_type.rs#5)
+
+Adds a new lifetime to a struct, enum or union.
+
+#### Before
+```rust
+struct Point {
+ x: &┃u32,
+ y: u32,
+}
+```
+
+#### After
+```rust
+struct Point<'a> {
+ x: &'a u32,
+ y: u32,
+}
+```
+
+
+### `add_missing_match_arms`
+**Source:** [add_missing_match_arms.rs](crates/ide-assists/src/handlers/add_missing_match_arms.rs#14)
+
+Adds missing clauses to a `match` expression.
+
+#### Before
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ ┃
+ }
+}
+```
+
+#### After
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } => ${1:todo!()},
+ Action::Stop => ${2:todo!()},┃
+ }
+}
+```
+
+
+### `add_return_type`
+**Source:** [add_return_type.rs](crates/ide-assists/src/handlers/add_return_type.rs#6)
+
+Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
+type specified. This assists is useable in a functions or closures tail expression or return type position.
+
+#### Before
+```rust
+fn foo() { 4┃2i32 }
+```
+
+#### After
+```rust
+fn foo() -> i32 { 42i32 }
+```
+
+
+### `add_turbo_fish`
+**Source:** [add_turbo_fish.rs](crates/ide-assists/src/handlers/add_turbo_fish.rs#14)
+
+Adds `::<_>` to a call of a generic method or function.
+
+#### Before
+```rust
+fn make<T>() -> T { todo!() }
+fn main() {
+ let x = make┃();
+}
+```
+
+#### After
+```rust
+fn make<T>() -> T { todo!() }
+fn main() {
+ let x = make::<${0:_}>();
+}
+```
+
+
+### `apply_demorgan`
+**Source:** [apply_demorgan.rs](crates/ide-assists/src/handlers/apply_demorgan.rs#16)
+
+Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws).
+This transforms expressions of the form `!l || !r` into `!(l && r)`.
+This also works with `&&`. This assist can only be applied with the cursor
+on either `||` or `&&`.
+
+#### Before
+```rust
+fn main() {
+ if x != 4 ||┃ y < 3.14 {}
+}
+```
+
+#### After
+```rust
+fn main() {
+ if !(x == 4 && y >= 3.14) {}
+}
+```
+
+
+### `apply_demorgan_iterator`
+**Source:** [apply_demorgan.rs](crates/ide-assists/src/handlers/apply_demorgan.rs#132)
+
+Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws) to
+`Iterator::all` and `Iterator::any`.
+
+This transforms expressions of the form `!iter.any(|x| predicate(x))` into
+`iter.all(|x| !predicate(x))` and vice versa. This also works the other way for
+`Iterator::all` into `Iterator::any`.
+
+#### Before
+```rust
+fn main() {
+ let arr = [1, 2, 3];
+ if !arr.into_iter().┃any(|num| num == 4) {
+ println!("foo");
+ }
+}
+```
+
+#### After
+```rust
+fn main() {
+ let arr = [1, 2, 3];
+ if arr.into_iter().all(|num| num != 4) {
+ println!("foo");
+ }
+}
+```
+
+
+### `auto_import`
+**Source:** [auto_import.rs](crates/ide-assists/src/handlers/auto_import.rs#73)
+
+If the name is unresolved, provides all possible imports for it.
+
+#### Before
+```rust
+fn main() {
+ let map = HashMap┃::new();
+}
+```
+
+#### After
+```rust
+use std::collections::HashMap;
+
+fn main() {
+ let map = HashMap::new();
+}
+```
+
+
+### `bind_unused_param`
+**Source:** [bind_unused_param.rs](crates/ide-assists/src/handlers/bind_unused_param.rs#12)
+
+Binds unused function parameter to an underscore.
+
+#### Before
+```rust
+fn some_function(x: i32┃) {}
+```
+
+#### After
+```rust
+fn some_function(x: i32) {
+ let _ = x;
+}
+```
+
+
+### `bool_to_enum`
+**Source:** [bool_to_enum.rs](crates/ide-assists/src/handlers/bool_to_enum.rs#29)
+
+This converts boolean local variables, fields, constants, and statics into a new
+enum with two variants `Bool::True` and `Bool::False`, as well as replacing
+all assignments with the variants and replacing all usages with `== Bool::True` or
+`== Bool::False`.
+
+#### Before
+```rust
+fn main() {
+ let ┃bool = true;
+
+ if bool {
+ println!("foo");
+ }
+}
+```
+
+#### After
+```rust
+#[derive(PartialEq, Eq)]
+enum Bool { True, False }
+
+fn main() {
+ let bool = Bool::True;
+
+ if bool == Bool::True {
+ println!("foo");
+ }
+}
+```
+
+
+### `change_visibility`
+**Source:** [change_visibility.rs](crates/ide-assists/src/handlers/change_visibility.rs#13)
+
+Adds or changes existing visibility specifier.
+
+#### Before
+```rust
+┃fn frobnicate() {}
+```
+
+#### After
+```rust
+pub(crate) fn frobnicate() {}
+```
+
+
+### `comment_to_doc`
+**Source:** [convert_comment_from_or_to_doc.rs](crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs#9)
+
+Converts comments to documentation.
+
+#### Before
+```rust
+// Wow what ┃a nice module
+// I sure hope this shows up when I hover over it
+```
+
+#### After
+```rust
+//! Wow what a nice module
+//! I sure hope this shows up when I hover over it
+```
+
+
+### `convert_bool_then_to_if`
+**Source:** [convert_bool_then.rs](crates/ide-assists/src/handlers/convert_bool_then.rs#131)
+
+Converts a `bool::then` method call to an equivalent if expression.
+
+#### Before
+```rust
+fn main() {
+ (0 == 0).then┃(|| val)
+}
+```
+
+#### After
+```rust
+fn main() {
+ if 0 == 0 {
+ Some(val)
+ } else {
+ None
+ }
+}
+```
+
+
+### `convert_closure_to_fn`
+**Source:** [convert_closure_to_fn.rs](crates/ide-assists/src/handlers/convert_closure_to_fn.rs#25)
+
+This converts a closure to a freestanding function, changing all captures to parameters.
+
+#### Before
+```rust
+fn main() {
+ let mut s = String::new();
+ let closure = |┃a| s.push_str(a);
+ closure("abc");
+}
+```
+
+#### After
+```rust
+fn main() {
+ let mut s = String::new();
+ fn closure(a: &str, s: &mut String) {
+ s.push_str(a)
+ }
+ closure("abc", &mut s);
+}
+```
+
+
+### `convert_for_loop_with_for_each`
+**Source:** [convert_iter_for_each_to_for.rs](crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs#76)
+
+Converts a for loop into a for_each loop on the Iterator.
+
+#### Before
+```rust
+fn main() {
+ let x = vec![1, 2, 3];
+ for┃ v in x {
+ let y = v * 2;
+ }
+}
+```
+
+#### After
+```rust
+fn main() {
+ let x = vec![1, 2, 3];
+ x.into_iter().for_each(|v| {
+ let y = v * 2;
+ });
+}
+```
+
+
+### `convert_from_to_tryfrom`
+**Source:** [convert_from_to_tryfrom.rs](crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs#10)
+
+Converts a From impl to a TryFrom impl, wrapping returns in `Ok`.
+
+#### Before
+```rust
+impl ┃From<usize> for Thing {
+ fn from(val: usize) -> Self {
+ Thing {
+ b: val.to_string(),
+ a: val
+ }
+ }
+}
+```
+
+#### After
+```rust
+impl TryFrom<usize> for Thing {
+ type Error = ${0:()};
+
+ fn try_from(val: usize) -> Result<Self, Self::Error> {
+ Ok(Thing {
+ b: val.to_string(),
+ a: val
+ })
+ }
+}
+```
+
+
+### `convert_if_to_bool_then`
+**Source:** [convert_bool_then.rs](crates/ide-assists/src/handlers/convert_bool_then.rs#20)
+
+Converts an if expression into a corresponding `bool::then` call.
+
+#### Before
+```rust
+fn main() {
+ if┃ cond {
+ Some(val)
+ } else {
+ None
+ }
+}
+```
+
+#### After
+```rust
+fn main() {
+ cond.then(|| val)
+}
+```
+
+
+### `convert_integer_literal`
+**Source:** [convert_integer_literal.rs](crates/ide-assists/src/handlers/convert_integer_literal.rs#5)
+
+Converts the base of integer literals to other bases.
+
+#### Before
+```rust
+const _: i32 = 10┃;
+```
+
+#### After
+```rust
+const _: i32 = 0b1010;
+```
+
+
+### `convert_into_to_from`
+**Source:** [convert_into_to_from.rs](crates/ide-assists/src/handlers/convert_into_to_from.rs#8)
+
+Converts an Into impl to an equivalent From impl.
+
+#### Before
+```rust
+impl ┃Into<Thing> for usize {
+ fn into(self) -> Thing {
+ Thing {
+ b: self.to_string(),
+ a: self
+ }
+ }
+}
+```
+
+#### After
+```rust
+impl From<usize> for Thing {
+ fn from(val: usize) -> Self {
+ Thing {
+ b: val.to_string(),
+ a: val
+ }
+ }
+}
+```
+
+
+### `convert_iter_for_each_to_for`
+**Source:** [convert_iter_for_each_to_for.rs](crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs#11)
+
+Converts an Iterator::for_each function into a for loop.
+
+#### Before
+```rust
+fn main() {
+ let iter = iter::repeat((9, 2));
+ iter.for_each┃(|(x, y)| {
+ println!("x: {}, y: {}", x, y);
+ });
+}
+```
+
+#### After
+```rust
+fn main() {
+ let iter = iter::repeat((9, 2));
+ for (x, y) in iter {
+ println!("x: {}, y: {}", x, y);
+ }
+}
+```
+
+
+### `convert_let_else_to_match`
+**Source:** [convert_let_else_to_match.rs](crates/ide-assists/src/handlers/convert_let_else_to_match.rs#9)
+
+Converts let-else statement to let statement and match expression.
+
+#### Before
+```rust
+fn main() {
+ let Ok(mut x) = f() else┃ { return };
+}
+```
+
+#### After
+```rust
+fn main() {
+ let mut x = match f() {
+ Ok(x) => x,
+ _ => return,
+ };
+}
+```
+
+
+### `convert_match_to_let_else`
+**Source:** [convert_match_to_let_else.rs](crates/ide-assists/src/handlers/convert_match_to_let_else.rs#12)
+
+Converts let statement with match initializer to let-else statement.
+
+#### Before
+```rust
+fn foo(opt: Option<()>) {
+ let val┃ = match opt {
+ Some(it) => it,
+ None => return,
+ };
+}
+```
+
+#### After
+```rust
+fn foo(opt: Option<()>) {
+ let Some(val) = opt else { return };
+}
+```
+
+
+### `convert_named_struct_to_tuple_struct`
+**Source:** [convert_named_struct_to_tuple_struct.rs](crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs#11)
+
+Converts struct with named fields to tuple struct, and analogously for enum variants with named
+fields.
+
+#### Before
+```rust
+struct Point┃ { x: f32, y: f32 }
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point { x, y }
+ }
+
+ pub fn x(&self) -> f32 {
+ self.x
+ }
+
+ pub fn y(&self) -> f32 {
+ self.y
+ }
+}
+```
+
+#### After
+```rust
+struct Point(f32, f32);
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point(x, y)
+ }
+
+ pub fn x(&self) -> f32 {
+ self.0
+ }
+
+ pub fn y(&self) -> f32 {
+ self.1
+ }
+}
+```
+
+
+### `convert_nested_function_to_closure`
+**Source:** [convert_nested_function_to_closure.rs](crates/ide-assists/src/handlers/convert_nested_function_to_closure.rs#7)
+
+Converts a function that is defined within the body of another function into a closure.
+
+#### Before
+```rust
+fn main() {
+ fn fo┃o(label: &str, number: u64) {
+ println!("{}: {}", label, number);
+ }
+
+ foo("Bar", 100);
+}
+```
+
+#### After
+```rust
+fn main() {
+ let foo = |label: &str, number: u64| {
+ println!("{}: {}", label, number);
+ };
+
+ foo("Bar", 100);
+}
+```
+
+
+### `convert_to_guarded_return`
+**Source:** [convert_to_guarded_return.rs](crates/ide-assists/src/handlers/convert_to_guarded_return.rs#24)
+
+Replace a large conditional with a guarded return.
+
+#### Before
+```rust
+fn main() {
+ ┃if cond {
+ foo();
+ bar();
+ }
+}
+```
+
+#### After
+```rust
+fn main() {
+ if !cond {
+ return;
+ }
+ foo();
+ bar();
+}
+```
+
+
+### `convert_tuple_return_type_to_struct`
+**Source:** [convert_tuple_return_type_to_struct.rs](crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs#20)
+
+This converts the return type of a function from a tuple type
+into a tuple struct and updates the body accordingly.
+
+#### Before
+```rust
+fn bar() {
+ let (a, b, c) = foo();
+}
+
+fn foo() -> (┃u32, u32, u32) {
+ (1, 2, 3)
+}
+```
+
+#### After
+```rust
+fn bar() {
+ let FooResult(a, b, c) = foo();
+}
+
+struct FooResult(u32, u32, u32);
+
+fn foo() -> FooResult {
+ FooResult(1, 2, 3)
+}
+```
+
+
+### `convert_tuple_struct_to_named_struct`
+**Source:** [convert_tuple_struct_to_named_struct.rs](crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs#10)
+
+Converts tuple struct to struct with named fields, and analogously for tuple enum variants.
+
+#### Before
+```rust
+struct Point┃(f32, f32);
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point(x, y)
+ }
+
+ pub fn x(&self) -> f32 {
+ self.0
+ }
+
+ pub fn y(&self) -> f32 {
+ self.1
+ }
+}
+```
+
+#### After
+```rust
+struct Point { field1: f32, field2: f32 }
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point { field1: x, field2: y }
+ }
+
+ pub fn x(&self) -> f32 {
+ self.field1
+ }
+
+ pub fn y(&self) -> f32 {
+ self.field2
+ }
+}
+```
+
+
+### `convert_two_arm_bool_match_to_matches_macro`
+**Source:** [convert_two_arm_bool_match_to_matches_macro.rs](crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs#8)
+
+Convert 2-arm match that evaluates to a boolean into the equivalent matches! invocation.
+
+#### Before
+```rust
+fn main() {
+ match scrutinee┃ {
+ Some(val) if val.cond() => true,
+ _ => false,
+ }
+}
+```
+
+#### After
+```rust
+fn main() {
+ matches!(scrutinee, Some(val) if val.cond())
+}
+```
+
+
+### `convert_while_to_loop`
+**Source:** [convert_while_to_loop.rs](crates/ide-assists/src/handlers/convert_while_to_loop.rs#20)
+
+Replace a while with a loop.
+
+#### Before
+```rust
+fn main() {
+ ┃while cond {
+ foo();
+ }
+}
+```
+
+#### After
+```rust
+fn main() {
+ loop {
+ if !cond {
+ break;
+ }
+ foo();
+ }
+}
+```
+
+
+### `destructure_struct_binding`
+**Source:** [destructure_struct_binding.rs](crates/ide-assists/src/handlers/destructure_struct_binding.rs#18)
+
+Destructures a struct binding in place.
+
+#### Before
+```rust
+struct Foo {
+ bar: i32,
+ baz: i32,
+}
+fn main() {
+ let ┃foo = Foo { bar: 1, baz: 2 };
+ let bar2 = foo.bar;
+ let baz2 = &foo.baz;
+}
+```
+
+#### After
+```rust
+struct Foo {
+ bar: i32,
+ baz: i32,
+}
+fn main() {
+ let Foo { bar, baz } = Foo { bar: 1, baz: 2 };
+ let bar2 = bar;
+ let baz2 = &baz;
+}
+```
+
+
+### `destructure_tuple_binding`
+**Source:** [destructure_tuple_binding.rs](crates/ide-assists/src/handlers/destructure_tuple_binding.rs#19)
+
+Destructures a tuple binding in place.
+
+#### Before
+```rust
+fn main() {
+ let ┃t = (1,2);
+ let v = t.0;
+}
+```
+
+#### After
+```rust
+fn main() {
+ let (┃_0, _1) = (1,2);
+ let v = _0;
+}
+```
+
+
+### `desugar_async_into_impl_future`
+**Source:** [toggle_async_sugar.rs](crates/ide-assists/src/handlers/toggle_async_sugar.rs#103)
+
+Rewrites asynchronous function from `async fn` into `-> impl Future`.
+This action does not touch the function body and therefore `0`
+block does not transform to `async { 0 }`.
+
+#### Before
+```rust
+pub as┃ync fn foo() -> usize {
+ 0
+}
+```
+
+#### After
+```rust
+pub fn foo() -> impl core::future::Future<Output = usize> {
+ 0
+}
+```
+
+
+### `desugar_doc_comment`
+**Source:** [desugar_doc_comment.rs](crates/ide-assists/src/handlers/desugar_doc_comment.rs#14)
+
+Desugars doc-comments to the attribute form.
+
+#### Before
+```rust
+/// Multi-line┃
+/// comment
+```
+
+#### After
+```rust
+#[doc = r"Multi-line
+comment"]
+```
+
+
+### `expand_glob_import`
+**Source:** [expand_glob_import.rs](crates/ide-assists/src/handlers/expand_glob_import.rs#18)
+
+Expands glob imports.
+
+#### Before
+```rust
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+}
+
+use foo::*┃;
+
+fn qux(bar: Bar, baz: Baz) {}
+```
+
+#### After
+```rust
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+}
+
+use foo::{Bar, Baz};
+
+fn qux(bar: Bar, baz: Baz) {}
+```
+
+
+### `explicit_enum_discriminant`
+**Source:** [explicit_enum_discriminant.rs](crates/ide-assists/src/handlers/explicit_enum_discriminant.rs#11)
+
+Adds explicit discriminant to all enum variants.
+
+#### Before
+```rust
+enum TheEnum┃ {
+ Foo,
+ Bar,
+ Baz = 42,
+ Quux,
+}
+```
+
+#### After
+```rust
+enum TheEnum {
+ Foo = 0,
+ Bar = 1,
+ Baz = 42,
+ Quux = 43,
+}
+```
+
+
+### `extract_constant`
+**Source:** [extract_variable.rs](crates/ide-assists/src/handlers/extract_variable.rs#35)
+
+Extracts subexpression into a constant.
+
+#### Before
+```rust
+fn main() {
+ ┃(1 + 2)┃ * 4;
+}
+```
+
+#### After
+```rust
+fn main() {
+ const ┃VAR_NAME: i32 = 1 + 2;
+ VAR_NAME * 4;
+}
+```
+
+
+### `extract_expressions_from_format_string`
+**Source:** [extract_expressions_from_format_string.rs](crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs#14)
+
+Move an expression out of a format string.
+
+#### Before
+```rust
+fn main() {
+ print!("{var} {x + 1}┃");
+}
+```
+
+#### After
+```rust
+fn main() {
+ print!("{var} {}"┃, x + 1);
+}
+```
+
+
+### `extract_function`
+**Source:** [extract_function.rs](crates/ide-assists/src/handlers/extract_function.rs#39)
+
+Extracts selected statements and comments into new function.
+
+#### Before
+```rust
+fn main() {
+ let n = 1;
+ ┃let m = n + 2;
+ // calculate
+ let k = m + n;┃
+ let g = 3;
+}
+```
+
+#### After
+```rust
+fn main() {
+ let n = 1;
+ fun_name(n);
+ let g = 3;
+}
+
+fn ┃fun_name(n: i32) {
+ let m = n + 2;
+ // calculate
+ let k = m + n;
+}
+```
+
+
+### `extract_module`
+**Source:** [extract_module.rs](crates/ide-assists/src/handlers/extract_module.rs#29)
+
+Extracts a selected region as separate module. All the references, visibility and imports are
+resolved.
+
+#### Before
+```rust
+┃fn foo(name: i32) -> i32 {
+ name + 1
+}┃
+
+fn bar(name: i32) -> i32 {
+ name + 2
+}
+```
+
+#### After
+```rust
+mod modname {
+ pub(crate) fn foo(name: i32) -> i32 {
+ name + 1
+ }
+}
+
+fn bar(name: i32) -> i32 {
+ name + 2
+}
+```
+
+
+### `extract_static`
+**Source:** [extract_variable.rs](crates/ide-assists/src/handlers/extract_variable.rs#52)
+
+Extracts subexpression into a static.
+
+#### Before
+```rust
+fn main() {
+ ┃(1 + 2)┃ * 4;
+}
+```
+
+#### After
+```rust
+fn main() {
+ static ┃VAR_NAME: i32 = 1 + 2;
+ VAR_NAME * 4;
+}
+```
+
+
+### `extract_struct_from_enum_variant`
+**Source:** [extract_struct_from_enum_variant.rs](crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs#26)
+
+Extracts a struct from enum variant.
+
+#### Before
+```rust
+enum A { ┃One(u32, u32) }
+```
+
+#### After
+```rust
+struct One(u32, u32);
+
+enum A { One(One) }
+```
+
+
+### `extract_type_alias`
+**Source:** [extract_type_alias.rs](crates/ide-assists/src/handlers/extract_type_alias.rs#10)
+
+Extracts the selected type as a type alias.
+
+#### Before
+```rust
+struct S {
+ field: ┃(u8, u8, u8)┃,
+}
+```
+
+#### After
+```rust
+type ┃Type = (u8, u8, u8);
+
+struct S {
+ field: Type,
+}
+```
+
+
+### `extract_variable`
+**Source:** [extract_variable.rs](crates/ide-assists/src/handlers/extract_variable.rs#18)
+
+Extracts subexpression into a variable.
+
+#### Before
+```rust
+fn main() {
+ ┃(1 + 2)┃ * 4;
+}
+```
+
+#### After
+```rust
+fn main() {
+ let ┃var_name = 1 + 2;
+ var_name * 4;
+}
+```
+
+
+### `fill_record_pattern_fields`
+**Source:** [fill_record_pattern_fields.rs](crates/ide-assists/src/handlers/fill_record_pattern_fields.rs#8)
+
+Fills fields by replacing rest pattern in record patterns.
+
+#### Before
+```rust
+struct Bar { y: Y, z: Z }
+
+fn foo(bar: Bar) {
+ let Bar { ..┃ } = bar;
+}
+```
+
+#### After
+```rust
+struct Bar { y: Y, z: Z }
+
+fn foo(bar: Bar) {
+ let Bar { y, z } = bar;
+}
+```
+
+
+### `fix_visibility`
+**Source:** [fix_visibility.rs](crates/ide-assists/src/handlers/fix_visibility.rs#14)
+
+Makes inaccessible item public.
+
+#### Before
+```rust
+mod m {
+ fn frobnicate() {}
+}
+fn main() {
+ m::frobnicate┃();
+}
+```
+
+#### After
+```rust
+mod m {
+ ┃pub(crate) fn frobnicate() {}
+}
+fn main() {
+ m::frobnicate();
+}
+```
+
+
+### `flip_binexpr`
+**Source:** [flip_binexpr.rs](crates/ide-assists/src/handlers/flip_binexpr.rs#8)
+
+Flips operands of a binary expression.
+
+#### Before
+```rust
+fn main() {
+ let _ = 90 +┃ 2;
+}
+```
+
+#### After
+```rust
+fn main() {
+ let _ = 2 + 90;
+}
+```
+
+
+### `flip_comma`
+**Source:** [flip_comma.rs](crates/ide-assists/src/handlers/flip_comma.rs#10)
+
+Flips two comma-separated items.
+
+#### Before
+```rust
+fn main() {
+ ((1, 2),┃ (3, 4));
+}
+```
+
+#### After
+```rust
+fn main() {
+ ((3, 4), (1, 2));
+}
+```
+
+
+### `flip_trait_bound`
+**Source:** [flip_trait_bound.rs](crates/ide-assists/src/handlers/flip_trait_bound.rs#9)
+
+Flips two trait bounds.
+
+#### Before
+```rust
+fn foo<T: Clone +┃ Copy>() { }
+```
+
+#### After
+```rust
+fn foo<T: Copy + Clone>() { }
+```
+
+
+### `generate_constant`
+**Source:** [generate_constant.rs](crates/ide-assists/src/handlers/generate_constant.rs#14)
+
+Generate a named constant.
+
+#### Before
+```rust
+struct S { i: usize }
+impl S { pub fn new(n: usize) {} }
+fn main() {
+ let v = S::new(CAPA┃CITY);
+}
+```
+
+#### After
+```rust
+struct S { i: usize }
+impl S { pub fn new(n: usize) {} }
+fn main() {
+ const CAPACITY: usize = ┃;
+ let v = S::new(CAPACITY);
+}
+```
+
+
+### `generate_default_from_enum_variant`
+**Source:** [generate_default_from_enum_variant.rs](crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs#6)
+
+Adds a Default impl for an enum using a variant.
+
+#### Before
+```rust
+enum Version {
+ Undefined,
+ Minor┃,
+ Major,
+}
+```
+
+#### After
+```rust
+enum Version {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Default for Version {
+ fn default() -> Self {
+ Self::Minor
+ }
+}
+```
+
+
+### `generate_default_from_new`
+**Source:** [generate_default_from_new.rs](crates/ide-assists/src/handlers/generate_default_from_new.rs#13)
+
+Generates default implementation from new method.
+
+#### Before
+```rust
+struct Example { _inner: () }
+
+impl Example {
+ pub fn n┃ew() -> Self {
+ Self { _inner: () }
+ }
+}
+```
+
+#### After
+```rust
+struct Example { _inner: () }
+
+impl Example {
+ pub fn new() -> Self {
+ Self { _inner: () }
+ }
+}
+
+impl Default for Example {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+```
+
+
+### `generate_delegate_methods`
+**Source:** [generate_delegate_methods.rs](crates/ide-assists/src/handlers/generate_delegate_methods.rs#15)
+
+Generate delegate methods.
+
+#### Before
+```rust
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ ag┃e: Age,
+}
+```
+
+#### After
+```rust
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ age: Age,
+}
+
+impl Person {
+ ┃fn age(&self) -> u8 {
+ self.age.age()
+ }
+}
+```
+
+
+### `generate_delegate_trait`
+**Source:** [generate_delegate_trait.rs](crates/ide-assists/src/handlers/generate_delegate_trait.rs#29)
+
+Generate delegate trait implementation for `StructField`s.
+
+#### Before
+```rust
+trait SomeTrait {
+ type T;
+ fn fn_(arg: u32) -> u32;
+ fn method_(&mut self) -> bool;
+}
+struct A;
+impl SomeTrait for A {
+ type T = u32;
+
+ fn fn_(arg: u32) -> u32 {
+ 42
+ }
+
+ fn method_(&mut self) -> bool {
+ false
+ }
+}
+struct B {
+ a┃: A,
+}
+```
+
+#### After
+```rust
+trait SomeTrait {
+ type T;
+ fn fn_(arg: u32) -> u32;
+ fn method_(&mut self) -> bool;
+}
+struct A;
+impl SomeTrait for A {
+ type T = u32;
+
+ fn fn_(arg: u32) -> u32 {
+ 42
+ }
+
+ fn method_(&mut self) -> bool {
+ false
+ }
+}
+struct B {
+ a: A,
+}
+
+impl SomeTrait for B {
+ type T = <A as SomeTrait>::T;
+
+ fn fn_(arg: u32) -> u32 {
+ <A as SomeTrait>::fn_(arg)
+ }
+
+ fn method_(&mut self) -> bool {
+ <A as SomeTrait>::method_(&mut self.a)
+ }
+}
+```
+
+
+### `generate_deref`
+**Source:** [generate_deref.rs](crates/ide-assists/src/handlers/generate_deref.rs#16)
+
+Generate `Deref` impl using the given struct field.
+
+#### Before
+```rust
+struct A;
+struct B {
+ ┃a: A
+}
+```
+
+#### After
+```rust
+struct A;
+struct B {
+ a: A
+}
+
+impl core::ops::Deref for B {
+ type Target = A;
+
+ fn deref(&self) -> &Self::Target {
+ &self.a
+ }
+}
+```
+
+
+### `generate_derive`
+**Source:** [generate_derive.rs](crates/ide-assists/src/handlers/generate_derive.rs#8)
+
+Adds a new `#[derive()]` clause to a struct or enum.
+
+#### Before
+```rust
+struct Point {
+ x: u32,
+ y: u32,┃
+}
+```
+
+#### After
+```rust
+#[derive(┃)]
+struct Point {
+ x: u32,
+ y: u32,
+}
+```
+
+
+### `generate_doc_example`
+**Source:** [generate_documentation_template.rs](crates/ide-assists/src/handlers/generate_documentation_template.rs#76)
+
+Generates a rustdoc example when editing an item's documentation.
+
+#### Before
+```rust
+/// Adds two numbers.┃
+pub fn add(a: i32, b: i32) -> i32 { a + b }
+```
+
+#### After
+```rust
+/// Adds two numbers.
+///
+/// # Examples
+///
+/// ```
+/// use ra_test_fixture::add;
+///
+/// assert_eq!(add(a, b), );
+/// ```
+pub fn add(a: i32, b: i32) -> i32 { a + b }
+```
+
+
+### `generate_documentation_template`
+**Source:** [generate_documentation_template.rs](crates/ide-assists/src/handlers/generate_documentation_template.rs#13)
+
+Adds a documentation template above a function definition / declaration.
+
+#### Before
+```rust
+pub struct S;
+impl S {
+ pub unsafe fn set_len┃(&mut self, len: usize) -> Result<(), std::io::Error> {
+ /* ... */
+ }
+}
+```
+
+#### After
+```rust
+pub struct S;
+impl S {
+ /// Sets the length of this [`S`].
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if .
+ ///
+ /// # Safety
+ ///
+ /// .
+ pub unsafe fn set_len(&mut self, len: usize) -> Result<(), std::io::Error> {
+ /* ... */
+ }
+}
+```
+
+
+### `generate_enum_as_method`
+**Source:** [generate_enum_projection_method.rs](crates/ide-assists/src/handlers/generate_enum_projection_method.rs#59)
+
+Generate an `as_` method for this enum variant.
+
+#### Before
+```rust
+enum Value {
+ Number(i32),
+ Text(String)┃,
+}
+```
+
+#### After
+```rust
+enum Value {
+ Number(i32),
+ Text(String),
+}
+
+impl Value {
+ fn as_text(&self) -> Option<&String> {
+ if let Self::Text(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+}
+```
+
+
+### `generate_enum_is_method`
+**Source:** [generate_enum_is_method.rs](crates/ide-assists/src/handlers/generate_enum_is_method.rs#11)
+
+Generate an `is_` method for this enum variant.
+
+#### Before
+```rust
+enum Version {
+ Undefined,
+ Minor┃,
+ Major,
+}
+```
+
+#### After
+```rust
+enum Version {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Version {
+ /// Returns `true` if the version is [`Minor`].
+ ///
+ /// [`Minor`]: Version::Minor
+ #[must_use]
+ fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor)
+ }
+}
+```
+
+
+### `generate_enum_try_into_method`
+**Source:** [generate_enum_projection_method.rs](crates/ide-assists/src/handlers/generate_enum_projection_method.rs#12)
+
+Generate a `try_into_` method for this enum variant.
+
+#### Before
+```rust
+enum Value {
+ Number(i32),
+ Text(String)┃,
+}
+```
+
+#### After
+```rust
+enum Value {
+ Number(i32),
+ Text(String),
+}
+
+impl Value {
+ fn try_into_text(self) -> Result<String, Self> {
+ if let Self::Text(v) = self {
+ Ok(v)
+ } else {
+ Err(self)
+ }
+ }
+}
+```
+
+
+### `generate_enum_variant`
+**Source:** [generate_enum_variant.rs](crates/ide-assists/src/handlers/generate_enum_variant.rs#10)
+
+Adds a variant to an enum.
+
+#### Before
+```rust
+enum Countries {
+ Ghana,
+}
+
+fn main() {
+ let country = Countries::Lesotho┃;
+}
+```
+
+#### After
+```rust
+enum Countries {
+ Ghana,
+ Lesotho,
+}
+
+fn main() {
+ let country = Countries::Lesotho;
+}
+```
+
+
+### `generate_fn_type_alias_named`
+**Source:** [generate_fn_type_alias.rs](crates/ide-assists/src/handlers/generate_fn_type_alias.rs#10)
+
+Generate a type alias for the function with named parameters.
+
+#### Before
+```rust
+unsafe fn fo┃o(n: i32) -> i32 { 42i32 }
+```
+
+#### After
+```rust
+type ${0:FooFn} = unsafe fn(n: i32) -> i32;
+
+unsafe fn foo(n: i32) -> i32 { 42i32 }
+```
+
+
+### `generate_fn_type_alias_unnamed`
+**Source:** [generate_fn_type_alias.rs](crates/ide-assists/src/handlers/generate_fn_type_alias.rs#24)
+
+Generate a type alias for the function with unnamed parameters.
+
+#### Before
+```rust
+unsafe fn fo┃o(n: i32) -> i32 { 42i32 }
+```
+
+#### After
+```rust
+type ${0:FooFn} = unsafe fn(i32) -> i32;
+
+unsafe fn foo(n: i32) -> i32 { 42i32 }
+```
+
+
+### `generate_from_impl_for_enum`
+**Source:** [generate_from_impl_for_enum.rs](crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs#8)
+
+Adds a From impl for this enum variant with one tuple field.
+
+#### Before
+```rust
+enum A { ┃One(u32) }
+```
+
+#### After
+```rust
+enum A { One(u32) }
+
+impl From<u32> for A {
+ fn from(v: u32) -> Self {
+ Self::One(v)
+ }
+}
+```
+
+
+### `generate_function`
+**Source:** [generate_function.rs](crates/ide-assists/src/handlers/generate_function.rs#28)
+
+Adds a stub function with a signature matching the function under the cursor.
+
+#### Before
+```rust
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ bar┃("", baz());
+}
+
+```
+
+#### After
+```rust
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ bar("", baz());
+}
+
+fn bar(arg: &str, baz: Baz) ${0:-> _} {
+ todo!()
+}
+
+```
+
+
+### `generate_getter`
+**Source:** [generate_getter_or_setter.rs](crates/ide-assists/src/handlers/generate_getter_or_setter.rs#73)
+
+Generate a getter method.
+
+#### Before
+```rust
+struct Person {
+ nam┃e: String,
+}
+```
+
+#### After
+```rust
+struct Person {
+ name: String,
+}
+
+impl Person {
+ fn ┃name(&self) -> &str {
+ &self.name
+ }
+}
+```
+
+
+### `generate_getter_mut`
+**Source:** [generate_getter_or_setter.rs](crates/ide-assists/src/handlers/generate_getter_or_setter.rs#127)
+
+Generate a mut getter method.
+
+#### Before
+```rust
+struct Person {
+ nam┃e: String,
+}
+```
+
+#### After
+```rust
+struct Person {
+ name: String,
+}
+
+impl Person {
+ fn ┃name_mut(&mut self) -> &mut String {
+ &mut self.name
+ }
+}
+```
+
+
+### `generate_impl`
+**Source:** [generate_impl.rs](crates/ide-assists/src/handlers/generate_impl.rs#20)
+
+Adds a new inherent impl for a type.
+
+#### Before
+```rust
+struct Ctx┃<T: Clone> {
+ data: T,
+}
+```
+
+#### After
+```rust
+struct Ctx<T: Clone> {
+ data: T,
+}
+
+impl<T: Clone> Ctx<T> {┃}
+```
+
+
+### `generate_is_empty_from_len`
+**Source:** [generate_is_empty_from_len.rs](crates/ide-assists/src/handlers/generate_is_empty_from_len.rs#12)
+
+Generates is_empty implementation from the len method.
+
+#### Before
+```rust
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ p┃ub fn len(&self) -> usize {
+ self.data.len()
+ }
+}
+```
+
+#### After
+```rust
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ pub fn len(&self) -> usize {
+ self.data.len()
+ }
+
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+}
+```
+
+
+### `generate_mut_trait_impl`
+**Source:** [generate_mut_trait_impl.rs](crates/ide-assists/src/handlers/generate_mut_trait_impl.rs#12)
+
+Adds a IndexMut impl from the `Index` trait.
+
+#### Before
+```rust
+pub enum Axis { X = 0, Y = 1, Z = 2 }
+
+impl<T> core::ops::Index┃<Axis> for [T; 3] {
+ type Output = T;
+
+ fn index(&self, index: Axis) -> &Self::Output {
+ &self[index as usize]
+ }
+}
+```
+
+#### After
+```rust
+pub enum Axis { X = 0, Y = 1, Z = 2 }
+
+┃impl<T> core::ops::IndexMut<Axis> for [T; 3] {
+ fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
+ &self[index as usize]
+ }
+}
+
+impl<T> core::ops::Index<Axis> for [T; 3] {
+ type Output = T;
+
+ fn index(&self, index: Axis) -> &Self::Output {
+ &self[index as usize]
+ }
+}
+```
+
+
+### `generate_new`
+**Source:** [generate_new.rs](crates/ide-assists/src/handlers/generate_new.rs#14)
+
+Adds a `fn new` for a type.
+
+#### Before
+```rust
+struct Ctx<T: Clone> {
+ data: T,┃
+}
+```
+
+#### After
+```rust
+struct Ctx<T: Clone> {
+ data: T,
+}
+
+impl<T: Clone> Ctx<T> {
+ fn ┃new(data: T) -> Self {
+ Self { data }
+ }
+}
+```
+
+
+### `generate_setter`
+**Source:** [generate_getter_or_setter.rs](crates/ide-assists/src/handlers/generate_getter_or_setter.rs#13)
+
+Generate a setter method.
+
+#### Before
+```rust
+struct Person {
+ nam┃e: String,
+}
+```
+
+#### After
+```rust
+struct Person {
+ name: String,
+}
+
+impl Person {
+ fn ┃set_name(&mut self, name: String) {
+ self.name = name;
+ }
+}
+```
+
+
+### `generate_trait_from_impl`
+**Source:** [generate_trait_from_impl.rs](crates/ide-assists/src/handlers/generate_trait_from_impl.rs#18)
+
+Generate trait for an already defined inherent impl and convert impl to a trait impl.
+
+#### Before
+```rust
+struct Foo<const N: usize>([i32; N]);
+
+macro_rules! const_maker {
+ ($t:ty, $v:tt) => {
+ const CONST: $t = $v;
+ };
+}
+
+impl<const N: usize> Fo┃o<N> {
+ // Used as an associated constant.
+ const CONST_ASSOC: usize = N * 4;
+
+ fn create() -> Option<()> {
+ Some(())
+ }
+
+ const_maker! {i32, 7}
+}
+```
+
+#### After
+```rust
+struct Foo<const N: usize>([i32; N]);
+
+macro_rules! const_maker {
+ ($t:ty, $v:tt) => {
+ const CONST: $t = $v;
+ };
+}
+
+trait ${0:NewTrait}<const N: usize> {
+ // Used as an associated constant.
+ const CONST_ASSOC: usize = N * 4;
+
+ fn create() -> Option<()>;
+
+ const_maker! {i32, 7}
+}
+
+impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
+ // Used as an associated constant.
+ const CONST_ASSOC: usize = N * 4;
+
+ fn create() -> Option<()> {
+ Some(())
+ }
+
+ const_maker! {i32, 7}
+}
+```
+
+
+### `generate_trait_impl`
+**Source:** [generate_impl.rs](crates/ide-assists/src/handlers/generate_impl.rs#66)
+
+Adds a new trait impl for a type.
+
+#### Before
+```rust
+struct ┃Ctx<T: Clone> {
+ data: T,
+}
+```
+
+#### After
+```rust
+struct Ctx<T: Clone> {
+ data: T,
+}
+
+impl<T: Clone> ${0:_} for Ctx<T> {}
+```
+
+
+### `inline_call`
+**Source:** [inline_call.rs](crates/ide-assists/src/handlers/inline_call.rs#170)
+
+Inlines a function or method body creating a `let` statement per parameter unless the parameter
+can be inlined. The parameter will be inlined either if it the supplied argument is a simple local
+or if the parameter is only accessed inside the function body once.
+
+#### Before
+```rust
+fn foo(name: Option<&str>) {
+ let name = name.unwrap┃();
+}
+```
+
+#### After
+```rust
+fn foo(name: Option<&str>) {
+ let name = match name {
+ Some(val) => val,
+ None => panic!("called `Option::unwrap()` on a `None` value"),
+ };
+}
+```
+
+
+### `inline_const_as_literal`
+**Source:** [inline_const_as_literal.rs](crates/ide-assists/src/handlers/inline_const_as_literal.rs#6)
+
+Evaluate and inline const variable as literal.
+
+#### Before
+```rust
+const STRING: &str = "Hello, World!";
+
+fn something() -> &'static str {
+ STRING┃
+}
+```
+
+#### After
+```rust
+const STRING: &str = "Hello, World!";
+
+fn something() -> &'static str {
+ "Hello, World!"
+}
+```
+
+
+### `inline_into_callers`
+**Source:** [inline_call.rs](crates/ide-assists/src/handlers/inline_call.rs#32)
+
+Inline a function or method body into all of its callers where possible, creating a `let` statement per parameter
+unless the parameter can be inlined. The parameter will be inlined either if it the supplied argument is a simple local
+or if the parameter is only accessed inside the function body once.
+If all calls can be inlined the function will be removed.
+
+#### Before
+```rust
+fn print(_: &str) {}
+fn foo┃(word: &str) {
+ if !word.is_empty() {
+ print(word);
+ }
+}
+fn bar() {
+ foo("안녕하세요");
+ foo("여러분");
+}
+```
+
+#### After
+```rust
+fn print(_: &str) {}
+
+fn bar() {
+ {
+ let word: &str = "안녕하세요";
+ if !word.is_empty() {
+ print(word);
+ }
+ };
+ {
+ let word: &str = "여러분";
+ if !word.is_empty() {
+ print(word);
+ }
+ };
+}
+```
+
+
+### `inline_local_variable`
+**Source:** [inline_local_variable.rs](crates/ide-assists/src/handlers/inline_local_variable.rs#17)
+
+Inlines a local variable.
+
+#### Before
+```rust
+fn main() {
+ let x┃ = 1 + 2;
+ x * 4;
+}
+```
+
+#### After
+```rust
+fn main() {
+ (1 + 2) * 4;
+}
+```
+
+
+### `inline_macro`
+**Source:** [inline_macro.rs](crates/ide-assists/src/handlers/inline_macro.rs#7)
+
+Takes a macro and inlines it one step.
+
+#### Before
+```rust
+macro_rules! num {
+ (+$($t:tt)+) => (1 + num!($($t )+));
+ (-$($t:tt)+) => (-1 + num!($($t )+));
+ (+) => (1);
+ (-) => (-1);
+}
+
+fn main() {
+ let number = num┃!(+ + + - + +);
+ println!("{number}");
+}
+```
+
+#### After
+```rust
+macro_rules! num {
+ (+$($t:tt)+) => (1 + num!($($t )+));
+ (-$($t:tt)+) => (-1 + num!($($t )+));
+ (+) => (1);
+ (-) => (-1);
+}
+
+fn main() {
+ let number = 1+num!(+ + - + +);
+ println!("{number}");
+}
+```
+
+
+### `inline_type_alias`
+**Source:** [inline_type_alias.rs](crates/ide-assists/src/handlers/inline_type_alias.rs#106)
+
+Replace a type alias with its concrete type.
+
+#### Before
+```rust
+type A<T = u32> = Vec<T>;
+
+fn main() {
+ let a: ┃A;
+}
+```
+
+#### After
+```rust
+type A<T = u32> = Vec<T>;
+
+fn main() {
+ let a: Vec<u32>;
+}
+```
+
+
+### `inline_type_alias_uses`
+**Source:** [inline_type_alias.rs](crates/ide-assists/src/handlers/inline_type_alias.rs#24)
+
+Inline a type alias into all of its uses where possible.
+
+#### Before
+```rust
+type ┃A = i32;
+fn id(x: A) -> A {
+ x
+};
+fn foo() {
+ let _: A = 3;
+}
+```
+
+#### After
+```rust
+
+fn id(x: i32) -> i32 {
+ x
+};
+fn foo() {
+ let _: i32 = 3;
+}
+```
+
+
+### `into_to_qualified_from`
+**Source:** [into_to_qualified_from.rs](crates/ide-assists/src/handlers/into_to_qualified_from.rs#10)
+
+Convert an `into` method call to a fully qualified `from` call.
+
+#### Before
+```rust
+//- minicore: from
+struct B;
+impl From<i32> for B {
+ fn from(a: i32) -> Self {
+ B
+ }
+}
+
+fn main() -> () {
+ let a = 3;
+ let b: B = a.in┃to();
+}
+```
+
+#### After
+```rust
+struct B;
+impl From<i32> for B {
+ fn from(a: i32) -> Self {
+ B
+ }
+}
+
+fn main() -> () {
+ let a = 3;
+ let b: B = B::from(a);
+}
+```
+
+
+### `introduce_named_generic`
+**Source:** [introduce_named_generic.rs](crates/ide-assists/src/handlers/introduce_named_generic.rs#7)
+
+Replaces `impl Trait` function argument with the named generic.
+
+#### Before
+```rust
+fn foo(bar: ┃impl Bar) {}
+```
+
+#### After
+```rust
+fn foo<┃B: Bar>(bar: B) {}
+```
+
+
+### `introduce_named_lifetime`
+**Source:** [introduce_named_lifetime.rs](crates/ide-assists/src/handlers/introduce_named_lifetime.rs#13)
+
+Change an anonymous lifetime to a named lifetime.
+
+#### Before
+```rust
+impl Cursor<'_┃> {
+ fn node(self) -> &SyntaxNode {
+ match self {
+ Cursor::Replace(node) | Cursor::Before(node) => node,
+ }
+ }
+}
+```
+
+#### After
+```rust
+impl<'a> Cursor<'a> {
+ fn node(self) -> &SyntaxNode {
+ match self {
+ Cursor::Replace(node) | Cursor::Before(node) => node,
+ }
+ }
+}
+```
+
+
+### `invert_if`
+**Source:** [invert_if.rs](crates/ide-assists/src/handlers/invert_if.rs#13)
+
+This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
+This also works with `!=`. This assist can only be applied with the cursor on `if`.
+
+#### Before
+```rust
+fn main() {
+ if┃ !y { A } else { B }
+}
+```
+
+#### After
+```rust
+fn main() {
+ if y { B } else { A }
+}
+```
+
+
+### `line_to_block`
+**Source:** [convert_comment_block.rs](crates/ide-assists/src/handlers/convert_comment_block.rs#9)
+
+Converts comments between block and single-line form.
+
+#### Before
+```rust
+ // Multi-line┃
+ // comment
+```
+
+#### After
+```rust
+ /*
+ Multi-line
+ comment
+ */
+```
+
+
+### `make_raw_string`
+**Source:** [raw_string.rs](crates/ide-assists/src/handlers/raw_string.rs#7)
+
+Adds `r#` to a plain string literal.
+
+#### Before
+```rust
+fn main() {
+ "Hello,┃ World!";
+}
+```
+
+#### After
+```rust
+fn main() {
+ r#"Hello, World!"#;
+}
+```
+
+
+### `make_usual_string`
+**Source:** [raw_string.rs](crates/ide-assists/src/handlers/raw_string.rs#47)
+
+Turns a raw string into a plain string.
+
+#### Before
+```rust
+fn main() {
+ r#"Hello,┃ "World!""#;
+}
+```
+
+#### After
+```rust
+fn main() {
+ "Hello, \"World!\"";
+}
+```
+
+
+### `merge_imports`
+**Source:** [merge_imports.rs](crates/ide-assists/src/handlers/merge_imports.rs#21)
+
+Merges neighbor imports with a common prefix.
+
+#### Before
+```rust
+use std::┃fmt::Formatter;
+use std::io;
+```
+
+#### After
+```rust
+use std::{fmt::Formatter, io};
+```
+
+
+### `merge_match_arms`
+**Source:** [merge_match_arms.rs](crates/ide-assists/src/handlers/merge_match_arms.rs#12)
+
+Merges the current match arm with the following if their bodies are identical.
+
+#### Before
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ ┃Action::Move(..) => foo(),
+ Action::Stop => foo(),
+ }
+}
+```
+
+#### After
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move(..) | Action::Stop => foo(),
+ }
+}
+```
+
+
+### `merge_nested_if`
+**Source:** [merge_nested_if.rs](crates/ide-assists/src/handlers/merge_nested_if.rs#11)
+
+This transforms if expressions of the form `if x { if y {A} }` into `if x && y {A}`
+This assist can only be applied with the cursor on `if`.
+
+#### Before
+```rust
+fn main() {
+ i┃f x == 3 { if y == 4 { 1 } }
+}
+```
+
+#### After
+```rust
+fn main() {
+ if x == 3 && y == 4 { 1 }
+}
+```
+
+
+### `move_arm_cond_to_match_guard`
+**Source:** [move_guard.rs](crates/ide-assists/src/handlers/move_guard.rs#69)
+
+Moves if expression from match arm body into a guard.
+
+#### Before
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } => ┃if distance > 10 { foo() },
+ _ => (),
+ }
+}
+```
+
+#### After
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } if distance > 10 => foo(),
+ _ => (),
+ }
+}
+```
+
+
+### `move_bounds_to_where_clause`
+**Source:** [move_bounds.rs](crates/ide-assists/src/handlers/move_bounds.rs#12)
+
+Moves inline type bounds to a where clause.
+
+#### Before
+```rust
+fn apply<T, U, ┃F: FnOnce(T) -> U>(f: F, x: T) -> U {
+ f(x)
+}
+```
+
+#### After
+```rust
+fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
+ f(x)
+}
+```
+
+
+### `move_const_to_impl`
+**Source:** [move_const_to_impl.rs](crates/ide-assists/src/handlers/move_const_to_impl.rs#14)
+
+Move a local constant item in a method to impl's associated constant. All the references will be
+qualified with `Self::`.
+
+#### Before
+```rust
+struct S;
+impl S {
+ fn foo() -> usize {
+ /// The answer.
+ const C┃: usize = 42;
+
+ C * C
+ }
+}
+```
+
+#### After
+```rust
+struct S;
+impl S {
+ /// The answer.
+ const C: usize = 42;
+
+ fn foo() -> usize {
+ Self::C * Self::C
+ }
+}
+```
+
+
+### `move_from_mod_rs`
+**Source:** [move_from_mod_rs.rs](crates/ide-assists/src/handlers/move_from_mod_rs.rs#12)
+
+Moves xxx/mod.rs to xxx.rs.
+
+#### Before
+```rust
+//- /main.rs
+mod a;
+//- /a/mod.rs
+┃fn t() {}┃
+```
+
+#### After
+```rust
+fn t() {}
+```
+
+
+### `move_guard_to_arm_body`
+**Source:** [move_guard.rs](crates/ide-assists/src/handlers/move_guard.rs#8)
+
+Moves match guard into match arm body.
+
+#### Before
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } ┃if distance > 10 => foo(),
+ _ => (),
+ }
+}
+```
+
+#### After
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } => if distance > 10 {
+ foo()
+ },
+ _ => (),
+ }
+}
+```
+
+
+### `move_module_to_file`
+**Source:** [move_module_to_file.rs](crates/ide-assists/src/handlers/move_module_to_file.rs#15)
+
+Moves inline module's contents to a separate file.
+
+#### Before
+```rust
+mod ┃foo {
+ fn t() {}
+}
+```
+
+#### After
+```rust
+mod foo;
+```
+
+
+### `move_to_mod_rs`
+**Source:** [move_to_mod_rs.rs](crates/ide-assists/src/handlers/move_to_mod_rs.rs#12)
+
+Moves xxx.rs to xxx/mod.rs.
+
+#### Before
+```rust
+//- /main.rs
+mod a;
+//- /a.rs
+┃fn t() {}┃
+```
+
+#### After
+```rust
+fn t() {}
+```
+
+
+### `normalize_import`
+**Source:** [normalize_import.rs](crates/ide-assists/src/handlers/normalize_import.rs#9)
+
+Normalizes an import.
+
+#### Before
+```rust
+use┃ std::{io, {fmt::Formatter}};
+```
+
+#### After
+```rust
+use std::{fmt::Formatter, io};
+```
+
+
+### `promote_local_to_const`
+**Source:** [promote_local_to_const.rs](crates/ide-assists/src/handlers/promote_local_to_const.rs#17)
+
+Promotes a local variable to a const item changing its name to a `SCREAMING_SNAKE_CASE` variant
+if the local uses no non-const expressions.
+
+#### Before
+```rust
+fn main() {
+ let foo┃ = true;
+
+ if foo {
+ println!("It's true");
+ } else {
+ println!("It's false");
+ }
+}
+```
+
+#### After
+```rust
+fn main() {
+ const ┃FOO: bool = true;
+
+ if FOO {
+ println!("It's true");
+ } else {
+ println!("It's false");
+ }
+}
+```
+
+
+### `pull_assignment_up`
+**Source:** [pull_assignment_up.rs](crates/ide-assists/src/handlers/pull_assignment_up.rs#11)
+
+Extracts variable assignment to outside an if or match statement.
+
+#### Before
+```rust
+fn main() {
+ let mut foo = 6;
+
+ if true {
+ ┃foo = 5;
+ } else {
+ foo = 4;
+ }
+}
+```
+
+#### After
+```rust
+fn main() {
+ let mut foo = 6;
+
+ foo = if true {
+ 5
+ } else {
+ 4
+ };
+}
+```
+
+
+### `qualify_method_call`
+**Source:** [qualify_method_call.rs](crates/ide-assists/src/handlers/qualify_method_call.rs#10)
+
+Replaces the method call with a qualified function call.
+
+#### Before
+```rust
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+fn main() {
+ let foo = Foo;
+ foo.fo┃o();
+}
+```
+
+#### After
+```rust
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+fn main() {
+ let foo = Foo;
+ Foo::foo(&foo);
+}
+```
+
+
+### `qualify_path`
+**Source:** [qualify_path.rs](crates/ide-assists/src/handlers/qualify_path.rs#24)
+
+If the name is unresolved, provides all possible qualified paths for it.
+
+#### Before
+```rust
+fn main() {
+ let map = HashMap┃::new();
+}
+```
+
+#### After
+```rust
+fn main() {
+ let map = std::collections::HashMap::new();
+}
+```
+
+
+### `reformat_number_literal`
+**Source:** [number_representation.rs](crates/ide-assists/src/handlers/number_representation.rs#7)
+
+Adds or removes separators from integer literal.
+
+#### Before
+```rust
+const _: i32 = 1012345┃;
+```
+
+#### After
+```rust
+const _: i32 = 1_012_345;
+```
+
+
+### `remove_dbg`
+**Source:** [remove_dbg.rs](crates/ide-assists/src/handlers/remove_dbg.rs#9)
+
+Removes `dbg!()` macro call.
+
+#### Before
+```rust
+fn main() {
+ let x = ┃dbg!(42 * dbg!(4 + 2));┃
+}
+```
+
+#### After
+```rust
+fn main() {
+ let x = 42 * (4 + 2);
+}
+```
+
+
+### `remove_hash`
+**Source:** [raw_string.rs](crates/ide-assists/src/handlers/raw_string.rs#117)
+
+Removes a hash from a raw string literal.
+
+#### Before
+```rust
+fn main() {
+ r#"Hello,┃ World!"#;
+}
+```
+
+#### After
+```rust
+fn main() {
+ r"Hello, World!";
+}
+```
+
+
+### `remove_mut`
+**Source:** [remove_mut.rs](crates/ide-assists/src/handlers/remove_mut.rs#5)
+
+Removes the `mut` keyword.
+
+#### Before
+```rust
+impl Walrus {
+ fn feed(&mut┃ self, amount: u32) {}
+}
+```
+
+#### After
+```rust
+impl Walrus {
+ fn feed(&self, amount: u32) {}
+}
+```
+
+
+### `remove_parentheses`
+**Source:** [remove_parentheses.rs](crates/ide-assists/src/handlers/remove_parentheses.rs#5)
+
+Removes redundant parentheses.
+
+#### Before
+```rust
+fn main() {
+ _ = ┃(2) + 2;
+}
+```
+
+#### After
+```rust
+fn main() {
+ _ = 2 + 2;
+}
+```
+
+
+### `remove_unused_imports`
+**Source:** [remove_unused_imports.rs](crates/ide-assists/src/handlers/remove_unused_imports.rs#17)
+
+Removes any use statements in the current selection that are unused.
+
+#### Before
+```rust
+struct X();
+mod foo {
+ use super::X┃;
+}
+```
+
+#### After
+```rust
+struct X();
+mod foo {
+}
+```
+
+
+### `remove_unused_param`
+**Source:** [remove_unused_param.rs](crates/ide-assists/src/handlers/remove_unused_param.rs#15)
+
+Removes unused function parameter.
+
+#### Before
+```rust
+fn frobnicate(x: i32┃) {}
+
+fn main() {
+ frobnicate(92);
+}
+```
+
+#### After
+```rust
+fn frobnicate() {}
+
+fn main() {
+ frobnicate();
+}
+```
+
+
+### `reorder_fields`
+**Source:** [reorder_fields.rs](crates/ide-assists/src/handlers/reorder_fields.rs#8)
+
+Reorder the fields of record literals and record patterns in the same order as in
+the definition.
+
+#### Before
+```rust
+struct Foo {foo: i32, bar: i32};
+const test: Foo = ┃Foo {bar: 0, foo: 1}
+```
+
+#### After
+```rust
+struct Foo {foo: i32, bar: i32};
+const test: Foo = Foo {foo: 1, bar: 0}
+```
+
+
+### `reorder_impl_items`
+**Source:** [reorder_impl_items.rs](crates/ide-assists/src/handlers/reorder_impl_items.rs#11)
+
+Reorder the items of an `impl Trait`. The items will be ordered
+in the same order as in the trait definition.
+
+#### Before
+```rust
+trait Foo {
+ type A;
+ const B: u8;
+ fn c();
+}
+
+struct Bar;
+┃impl Foo for Bar┃ {
+ const B: u8 = 17;
+ fn c() {}
+ type A = String;
+}
+```
+
+#### After
+```rust
+trait Foo {
+ type A;
+ const B: u8;
+ fn c();
+}
+
+struct Bar;
+impl Foo for Bar {
+ type A = String;
+ const B: u8 = 17;
+ fn c() {}
+}
+```
+
+
+### `replace_arith_with_checked`
+**Source:** [replace_arith_op.rs](crates/ide-assists/src/handlers/replace_arith_op.rs#9)
+
+Replaces arithmetic on integers with the `checked_*` equivalent.
+
+#### Before
+```rust
+fn main() {
+ let x = 1 ┃+ 2;
+}
+```
+
+#### After
+```rust
+fn main() {
+ let x = 1.checked_add(2);
+}
+```
+
+
+### `replace_arith_with_saturating`
+**Source:** [replace_arith_op.rs](crates/ide-assists/src/handlers/replace_arith_op.rs#28)
+
+Replaces arithmetic on integers with the `saturating_*` equivalent.
+
+#### Before
+```rust
+fn main() {
+ let x = 1 ┃+ 2;
+}
+```
+
+#### After
+```rust
+fn main() {
+ let x = 1.saturating_add(2);
+}
+```
+
+
+### `replace_arith_with_wrapping`
+**Source:** [replace_arith_op.rs](crates/ide-assists/src/handlers/replace_arith_op.rs#50)
+
+Replaces arithmetic on integers with the `wrapping_*` equivalent.
+
+#### Before
+```rust
+fn main() {
+ let x = 1 ┃+ 2;
+}
+```
+
+#### After
+```rust
+fn main() {
+ let x = 1.wrapping_add(2);
+}
+```
+
+
+### `replace_char_with_string`
+**Source:** [replace_string_with_char.rs](crates/ide-assists/src/handlers/replace_string_with_char.rs#51)
+
+Replace a char literal with a string literal.
+
+#### Before
+```rust
+fn main() {
+ find('{┃');
+}
+```
+
+#### After
+```rust
+fn main() {
+ find("{");
+}
+```
+
+
+### `replace_derive_with_manual_impl`
+**Source:** [replace_derive_with_manual_impl.rs](crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs#20)
+
+Converts a `derive` impl into a manual one.
+
+#### Before
+```rust
+#[derive(Deb┃ug, Display)]
+struct S;
+```
+
+#### After
+```rust
+#[derive(Display)]
+struct S;
+
+impl Debug for S {
+ ┃fn fmt(&self, f: &mut Formatter) -> Result<()> {
+ f.debug_struct("S").finish()
+ }
+}
+```
+
+
+### `replace_if_let_with_match`
+**Source:** [replace_if_let_with_match.rs](crates/ide-assists/src/handlers/replace_if_let_with_match.rs#20)
+
+Replaces a `if let` expression with a `match` expression.
+
+#### Before
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ ┃if let Action::Move { distance } = action {
+ foo(distance)
+ } else {
+ bar()
+ }
+}
+```
+
+#### After
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } => foo(distance),
+ _ => bar(),
+ }
+}
+```
+
+
+### `replace_is_some_with_if_let_some`
+**Source:** [replace_is_method_with_if_let_method.rs](crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs#9)
+
+Replace `if x.is_some()` with `if let Some(_tmp) = x` or `if x.is_ok()` with `if let Ok(_tmp) = x`.
+
+#### Before
+```rust
+fn main() {
+ let x = Some(1);
+ if x.is_som┃e() {}
+}
+```
+
+#### After
+```rust
+fn main() {
+ let x = Some(1);
+ if let Some(${0:x1}) = x {}
+}
+```
+
+
+### `replace_let_with_if_let`
+**Source:** [replace_let_with_if_let.rs](crates/ide-assists/src/handlers/replace_let_with_if_let.rs#9)
+
+Replaces `let` with an `if let`.
+
+#### Before
+```rust
+
+fn main(action: Action) {
+ ┃let x = compute();
+}
+
+fn compute() -> Option<i32> { None }
+```
+
+#### After
+```rust
+
+fn main(action: Action) {
+ if let Some(x) = compute() {
+ }
+}
+
+fn compute() -> Option<i32> { None }
+```
+
+
+### `replace_match_with_if_let`
+**Source:** [replace_if_let_with_match.rs](crates/ide-assists/src/handlers/replace_if_let_with_match.rs#188)
+
+Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
+
+#### Before
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ ┃match action {
+ Action::Move { distance } => foo(distance),
+ _ => bar(),
+ }
+}
+```
+
+#### After
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ if let Action::Move { distance } = action {
+ foo(distance)
+ } else {
+ bar()
+ }
+}
+```
+
+
+### `replace_named_generic_with_impl`
+**Source:** [replace_named_generic_with_impl.rs](crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs#18)
+
+Replaces named generic with an `impl Trait` in function argument.
+
+#### Before
+```rust
+fn new<P┃: AsRef<Path>>(location: P) -> Self {}
+```
+
+#### After
+```rust
+fn new(location: impl AsRef<Path>) -> Self {}
+```
+
+
+### `replace_qualified_name_with_use`
+**Source:** [replace_qualified_name_with_use.rs](crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs#13)
+
+Adds a use statement for a given fully-qualified name.
+
+#### Before
+```rust
+fn process(map: std::collections::┃HashMap<String, String>) {}
+```
+
+#### After
+```rust
+use std::collections::HashMap;
+
+fn process(map: HashMap<String, String>) {}
+```
+
+
+### `replace_string_with_char`
+**Source:** [replace_string_with_char.rs](crates/ide-assists/src/handlers/replace_string_with_char.rs#11)
+
+Replace string literal with char literal.
+
+#### Before
+```rust
+fn main() {
+ find("{┃");
+}
+```
+
+#### After
+```rust
+fn main() {
+ find('{');
+}
+```
+
+
+### `replace_try_expr_with_match`
+**Source:** [replace_try_expr_with_match.rs](crates/ide-assists/src/handlers/replace_try_expr_with_match.rs#18)
+
+Replaces a `try` expression with a `match` expression.
+
+#### Before
+```rust
+fn handle() {
+ let pat = Some(true)┃?;
+}
+```
+
+#### After
+```rust
+fn handle() {
+ let pat = match Some(true) {
+ Some(it) => it,
+ None => return None,
+ };
+}
+```
+
+
+### `replace_turbofish_with_explicit_type`
+**Source:** [replace_turbofish_with_explicit_type.rs](crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs#12)
+
+Converts `::<_>` to an explicit type assignment.
+
+#### Before
+```rust
+fn make<T>() -> T { ) }
+fn main() {
+ let a = make┃::<i32>();
+}
+```
+
+#### After
+```rust
+fn make<T>() -> T { ) }
+fn main() {
+ let a: i32 = make();
+}
+```
+
+
+### `replace_with_eager_method`
+**Source:** [replace_method_eager_lazy.rs](crates/ide-assists/src/handlers/replace_method_eager_lazy.rs#89)
+
+Replace `unwrap_or_else` with `unwrap_or` and `ok_or_else` with `ok_or`.
+
+#### Before
+```rust
+fn foo() {
+ let a = Some(1);
+ a.unwra┃p_or_else(|| 2);
+}
+```
+
+#### After
+```rust
+fn foo() {
+ let a = Some(1);
+ a.unwrap_or(2);
+}
+```
+
+
+### `replace_with_lazy_method`
+**Source:** [replace_method_eager_lazy.rs](crates/ide-assists/src/handlers/replace_method_eager_lazy.rs#9)
+
+Replace `unwrap_or` with `unwrap_or_else` and `ok_or` with `ok_or_else`.
+
+#### Before
+```rust
+fn foo() {
+ let a = Some(1);
+ a.unwra┃p_or(2);
+}
+```
+
+#### After
+```rust
+fn foo() {
+ let a = Some(1);
+ a.unwrap_or_else(|| 2);
+}
+```
+
+
+### `sort_items`
+**Source:** [sort_items.rs](crates/ide-assists/src/handlers/sort_items.rs#12)
+
+Sorts item members alphabetically: fields, enum variants and methods.
+
+#### Before
+```rust
+struct ┃Foo┃ { second: u32, first: String }
+```
+
+#### After
+```rust
+struct Foo { first: String, second: u32 }
+```
+
+---
+
+#### Before
+```rust
+trait ┃Bar┃ {
+ fn second(&self) -> u32;
+ fn first(&self) -> String;
+}
+```
+
+#### After
+```rust
+trait Bar {
+ fn first(&self) -> String;
+ fn second(&self) -> u32;
+}
+```
+
+---
+
+#### Before
+```rust
+struct Baz;
+impl ┃Baz┃ {
+ fn second(&self) -> u32;
+ fn first(&self) -> String;
+}
+```
+
+#### After
+```rust
+struct Baz;
+impl Baz {
+ fn first(&self) -> String;
+ fn second(&self) -> u32;
+}
+```
+
+---
+There is a difference between sorting enum variants:
+
+#### Before
+```rust
+enum ┃Animal┃ {
+ Dog(String, f64),
+ Cat { weight: f64, name: String },
+}
+```
+
+#### After
+```rust
+enum Animal {
+ Cat { weight: f64, name: String },
+ Dog(String, f64),
+}
+```
+
+and sorting a single enum struct variant:
+
+#### Before
+```rust
+enum Animal {
+ Dog(String, f64),
+ Cat ┃{ weight: f64, name: String }┃,
+}
+```
+
+#### After
+```rust
+enum Animal {
+ Dog(String, f64),
+ Cat { name: String, weight: f64 },
+}
+```
+
+
+### `split_import`
+**Source:** [split_import.rs](crates/ide-assists/src/handlers/split_import.rs#5)
+
+Wraps the tail of import into braces.
+
+#### Before
+```rust
+use std::┃collections::HashMap;
+```
+
+#### After
+```rust
+use std::{collections::HashMap};
+```
+
+
+### `sugar_impl_future_into_async`
+**Source:** [toggle_async_sugar.rs](crates/ide-assists/src/handlers/toggle_async_sugar.rs#13)
+
+Rewrites asynchronous function from `-> impl Future` into `async fn`.
+This action does not touch the function body and therefore `async { 0 }`
+block does not transform to just `0`.
+
+#### Before
+```rust
+pub fn foo() -> impl core::future::F┃uture<Output = usize> {
+ async { 0 }
+}
+```
+
+#### After
+```rust
+pub async fn foo() -> usize {
+ async { 0 }
+}
+```
+
+
+### `toggle_ignore`
+**Source:** [toggle_ignore.rs](crates/ide-assists/src/handlers/toggle_ignore.rs#8)
+
+Adds `#[ignore]` attribute to the test.
+
+#### Before
+```rust
+┃#[test]
+fn arithmetics {
+ assert_eq!(2 + 2, 5);
+}
+```
+
+#### After
+```rust
+#[test]
+#[ignore]
+fn arithmetics {
+ assert_eq!(2 + 2, 5);
+}
+```
+
+
+### `toggle_macro_delimiter`
+**Source:** [toggle_macro_delimiter.rs](crates/ide-assists/src/handlers/toggle_macro_delimiter.rs#9)
+
+Change macro delimiters in the order of `( -> { -> [ -> (`.
+
+#### Before
+```rust
+macro_rules! sth {
+ () => {};
+}
+
+sth!┃( );
+```
+
+#### After
+```rust
+macro_rules! sth {
+ () => {};
+}
+
+sth!{ }
+```
+
+
+### `unmerge_match_arm`
+**Source:** [unmerge_match_arm.rs](crates/ide-assists/src/handlers/unmerge_match_arm.rs#10)
+
+Splits the current match with a `|` pattern into two arms with identical bodies.
+
+#### Before
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move(..) ┃| Action::Stop => foo(),
+ }
+}
+```
+
+#### After
+```rust
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move(..) => foo(),
+ Action::Stop => foo(),
+ }
+}
+```
+
+
+### `unmerge_use`
+**Source:** [unmerge_use.rs](crates/ide-assists/src/handlers/unmerge_use.rs#12)
+
+Extracts single use item from use list.
+
+#### Before
+```rust
+use std::fmt::{Debug, Display┃};
+```
+
+#### After
+```rust
+use std::fmt::{Debug};
+use std::fmt::Display;
+```
+
+
+### `unnecessary_async`
+**Source:** [unnecessary_async.rs](crates/ide-assists/src/handlers/unnecessary_async.rs#17)
+
+Removes the `async` mark from functions which have no `.await` in their body.
+Looks for calls to the functions and removes the `.await` on the call site.
+
+#### Before
+```rust
+pub asy┃nc fn foo() {}
+pub async fn bar() { foo().await }
+```
+
+#### After
+```rust
+pub fn foo() {}
+pub async fn bar() { foo() }
+```
+
+
+### `unqualify_method_call`
+**Source:** [unqualify_method_call.rs](crates/ide-assists/src/handlers/unqualify_method_call.rs#9)
+
+Transforms universal function call syntax into a method call.
+
+#### Before
+```rust
+fn main() {
+ std::ops::Add::add┃(1, 2);
+}
+```
+
+#### After
+```rust
+use std::ops::Add;
+
+fn main() {
+ 1.add(2);
+}
+```
+
+
+### `unwrap_block`
+**Source:** [unwrap_block.rs](crates/ide-assists/src/handlers/unwrap_block.rs#12)
+
+This assist removes if...else, for, while and loop control statements to just keep the body.
+
+#### Before
+```rust
+fn foo() {
+ if true {┃
+ println!("foo");
+ }
+}
+```
+
+#### After
+```rust
+fn foo() {
+ println!("foo");
+}
+```
+
+
+### `unwrap_option_return_type`
+**Source:** [unwrap_return_type.rs](crates/ide-assists/src/handlers/unwrap_return_type.rs#13)
+
+Unwrap the function's return type.
+
+#### Before
+```rust
+fn foo() -> Option<i32>┃ { Some(42i32) }
+```
+
+#### After
+```rust
+fn foo() -> i32 { 42i32 }
+```
+
+
+### `unwrap_result_return_type`
+**Source:** [unwrap_return_type.rs](crates/ide-assists/src/handlers/unwrap_return_type.rs#26)
+
+Unwrap the function's return type.
+
+#### Before
+```rust
+fn foo() -> Result<i32>┃ { Ok(42i32) }
+```
+
+#### After
+```rust
+fn foo() -> i32 { 42i32 }
+```
+
+
+### `unwrap_tuple`
+**Source:** [unwrap_tuple.rs](crates/ide-assists/src/handlers/unwrap_tuple.rs#8)
+
+Unwrap the tuple to different variables.
+
+#### Before
+```rust
+fn main() {
+ ┃let (foo, bar) = ("Foo", "Bar");
+}
+```
+
+#### After
+```rust
+fn main() {
+ let foo = "Foo";
+ let bar = "Bar";
+}
+```
+
+
+### `wrap_return_type_in_option`
+**Source:** [wrap_return_type.rs](crates/ide-assists/src/handlers/wrap_return_type.rs#16)
+
+Wrap the function's return type into Option.
+
+#### Before
+```rust
+fn foo() -> i32┃ { 42i32 }
+```
+
+#### After
+```rust
+fn foo() -> Option<i32> { Some(42i32) }
+```
+
+
+### `wrap_return_type_in_result`
+**Source:** [wrap_return_type.rs](crates/ide-assists/src/handlers/wrap_return_type.rs#29)
+
+Wrap the function's return type into Result.
+
+#### Before
+```rust
+fn foo() -> i32┃ { 42i32 }
+```
+
+#### After
+```rust
+fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
+```
+
+
+### `wrap_unwrap_cfg_attr`
+**Source:** [wrap_unwrap_cfg_attr.rs](crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs#12)
+
+Wraps an attribute to a cfg_attr attribute or unwraps a cfg_attr attribute to the inner attributes.
+
+#### Before
+```rust
+#[derive┃(Debug)]
+struct S {
+ field: i32
+}
+```
+
+#### After
+```rust
+#[cfg_attr(┃, derive(Debug))]
+struct S {
+ field: i32
+}
+```
diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md
index 5567892660..0c6674b140 100644
--- a/docs/book/src/configuration_generated.md
+++ b/docs/book/src/configuration_generated.md
@@ -470,9 +470,9 @@ The warnings will be indicated by a blue squiggly underline in code
and a blue icon in the `Problems Panel`.
-**rust-analyzer.files.excludeDirs** (default: [])
+**rust-analyzer.files.exclude** (default: [])
- These directories will be ignored by rust-analyzer. They are
+ These paths (file/directories) will be ignored by rust-analyzer. They are
relative to the workspace root, and globs are not supported. You may
also need to add the folders to Code's `files.watcherExclude`.
diff --git a/docs/book/src/diagnostics_generated.md b/docs/book/src/diagnostics_generated.md
new file mode 100644
index 0000000000..d34c459ad0
--- /dev/null
+++ b/docs/book/src/diagnostics_generated.md
@@ -0,0 +1,516 @@
+//! Generated by `cargo xtask codegen diagnostics-docs`, do not edit by hand.
+
+#### attribute-expansion-disabled
+
+Source: [macro_error.rs](crates/ide-diagnostics/src/handlers/macro_error.rs#7)
+
+
+This diagnostic is shown for attribute proc macros when attribute expansions have been disabled.
+
+
+
+
+#### await-outside-of-async
+
+Source: [await_outside_of_async.rs](crates/ide-diagnostics/src/handlers/await_outside_of_async.rs#3)
+
+
+This diagnostic is triggered if the `await` keyword is used outside of an async function or block
+
+
+
+
+#### break-outside-of-loop
+
+Source: [break_outside_of_loop.rs](crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs#3)
+
+
+This diagnostic is triggered if the `break` keyword is used outside of a loop.
+
+
+
+
+#### cast-to-unsized
+
+Source: [invalid_cast.rs](crates/ide-diagnostics/src/handlers/invalid_cast.rs#106)
+
+
+This diagnostic is triggered when casting to an unsized type
+
+
+
+
+#### expected-function
+
+Source: [expected_function.rs](crates/ide-diagnostics/src/handlers/expected_function.rs#5)
+
+
+This diagnostic is triggered if a call is made on something that is not callable.
+
+
+
+
+#### generic-args-prohibited
+
+Source: [generic_args_prohibited.rs](crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs#10)
+
+
+This diagnostic is shown when generic arguments are provided for a type that does not accept
+generic arguments.
+
+
+
+
+#### inactive-code
+
+Source: [inactive_code.rs](crates/ide-diagnostics/src/handlers/inactive_code.rs#6)
+
+
+This diagnostic is shown for code with inactive `#[cfg]` attributes.
+
+
+
+
+#### incoherent-impl
+
+Source: [incoherent_impl.rs](crates/ide-diagnostics/src/handlers/incoherent_impl.rs#6)
+
+
+This diagnostic is triggered if the targe type of an impl is from a foreign crate.
+
+
+
+
+#### incorrect-ident-case
+
+Source: [incorrect_case.rs](crates/ide-diagnostics/src/handlers/incorrect_case.rs#13)
+
+
+This diagnostic is triggered if an item name doesn't follow [Rust naming convention](https://doc.rust-lang.org/1.0.0/style/style/naming/README.html).
+
+
+
+
+#### invalid-cast
+
+Source: [invalid_cast.rs](crates/ide-diagnostics/src/handlers/invalid_cast.rs#18)
+
+
+This diagnostic is triggered if the code contains an illegal cast
+
+
+
+
+#### invalid-derive-target
+
+Source: [invalid_derive_target.rs](crates/ide-diagnostics/src/handlers/invalid_derive_target.rs#3)
+
+
+This diagnostic is shown when the derive attribute is used on an item other than a `struct`,
+`enum` or `union`.
+
+
+
+
+#### macro-def-error
+
+Source: [macro_error.rs](crates/ide-diagnostics/src/handlers/macro_error.rs#24)
+
+
+This diagnostic is shown for macro expansion errors.
+
+
+
+
+#### macro-error
+
+Source: [macro_error.rs](crates/ide-diagnostics/src/handlers/macro_error.rs#3)
+
+
+This diagnostic is shown for macro expansion errors.
+
+
+
+
+#### malformed-derive
+
+Source: [malformed_derive.rs](crates/ide-diagnostics/src/handlers/malformed_derive.rs#3)
+
+
+This diagnostic is shown when the derive attribute has invalid input.
+
+
+
+
+#### mismatched-arg-count
+
+Source: [mismatched_arg_count.rs](crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs#31)
+
+
+This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
+
+
+
+
+#### mismatched-tuple-struct-pat-arg-count
+
+Source: [mismatched_arg_count.rs](crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs#11)
+
+
+This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
+
+
+
+
+#### missing-fields
+
+Source: [missing_fields.rs](crates/ide-diagnostics/src/handlers/missing_fields.rs#19)
+
+
+This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
+
+Example:
+
+```rust
+struct A { a: u8, b: u8 }
+
+let a = A { a: 10 };
+```
+
+
+
+
+#### missing-match-arm
+
+Source: [missing_match_arms.rs](crates/ide-diagnostics/src/handlers/missing_match_arms.rs#3)
+
+
+This diagnostic is triggered if `match` block is missing one or more match arms.
+
+
+
+
+#### missing-unsafe
+
+Source: [missing_unsafe.rs](crates/ide-diagnostics/src/handlers/missing_unsafe.rs#10)
+
+
+This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
+
+
+
+
+#### moved-out-of-ref
+
+Source: [moved_out_of_ref.rs](crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs#4)
+
+
+This diagnostic is triggered on moving non copy things out of references.
+
+
+
+
+#### need-mut
+
+Source: [mutability_errors.rs](crates/ide-diagnostics/src/handlers/mutability_errors.rs#8)
+
+
+This diagnostic is triggered on mutating an immutable variable.
+
+
+
+
+#### no-such-field
+
+Source: [no_such_field.rs](crates/ide-diagnostics/src/handlers/no_such_field.rs#12)
+
+
+This diagnostic is triggered if created structure does not have field provided in record.
+
+
+
+
+#### non-exhaustive-let
+
+Source: [non_exhaustive_let.rs](crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs#3)
+
+
+This diagnostic is triggered if a `let` statement without an `else` branch has a non-exhaustive
+pattern.
+
+
+
+
+#### private-assoc-item
+
+Source: [private_assoc_item.rs](crates/ide-diagnostics/src/handlers/private_assoc_item.rs#3)
+
+
+This diagnostic is triggered if the referenced associated item is not visible from the current
+module.
+
+
+
+
+#### private-field
+
+Source: [private_field.rs](crates/ide-diagnostics/src/handlers/private_field.rs#3)
+
+
+This diagnostic is triggered if the accessed field is not visible from the current module.
+
+
+
+
+#### proc-macro-disabled
+
+Source: [macro_error.rs](crates/ide-diagnostics/src/handlers/macro_error.rs#11)
+
+
+This diagnostic is shown for proc macros that have been specifically disabled via `rust-analyzer.procMacro.ignored`.
+
+
+
+
+#### remove-trailing-return
+
+Source: [remove_trailing_return.rs](crates/ide-diagnostics/src/handlers/remove_trailing_return.rs#8)
+
+
+This diagnostic is triggered when there is a redundant `return` at the end of a function
+or closure.
+
+
+
+
+#### remove-unnecessary-else
+
+Source: [remove_unnecessary_else.rs](crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs#17)
+
+
+This diagnostic is triggered when there is an `else` block for an `if` expression whose
+then branch diverges (e.g. ends with a `return`, `continue`, `break` e.t.c).
+
+
+
+
+#### replace-filter-map-next-with-find-map
+
+Source: [replace_filter_map_next_with_find_map.rs](crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs#11)
+
+
+This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
+
+
+
+
+#### trait-impl-incorrect-safety
+
+Source: [trait_impl_incorrect_safety.rs](crates/ide-diagnostics/src/handlers/trait_impl_incorrect_safety.rs#6)
+
+
+Diagnoses incorrect safety annotations of trait impls.
+
+
+
+
+#### trait-impl-missing-assoc_item
+
+Source: [trait_impl_missing_assoc_item.rs](crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs#7)
+
+
+Diagnoses missing trait items in a trait impl.
+
+
+
+
+#### trait-impl-orphan
+
+Source: [trait_impl_orphan.rs](crates/ide-diagnostics/src/handlers/trait_impl_orphan.rs#5)
+
+
+Only traits defined in the current crate can be implemented for arbitrary types
+
+
+
+
+#### trait-impl-redundant-assoc_item
+
+Source: [trait_impl_redundant_assoc_item.rs](crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs#12)
+
+
+Diagnoses redundant trait items in a trait impl.
+
+
+
+
+#### type-mismatch
+
+Source: [type_mismatch.rs](crates/ide-diagnostics/src/handlers/type_mismatch.rs#20)
+
+
+This diagnostic is triggered when the type of an expression or pattern does not match
+the expected type.
+
+
+
+
+#### typed-hole
+
+Source: [typed_hole.rs](crates/ide-diagnostics/src/handlers/typed_hole.rs#18)
+
+
+This diagnostic is triggered when an underscore expression is used in an invalid position.
+
+
+
+
+#### undeclared-label
+
+Source: [undeclared_label.rs](crates/ide-diagnostics/src/handlers/undeclared_label.rs#3)
+
+
+
+
+
+
+#### unimplemented-builtin-macro
+
+Source: [unimplemented_builtin_macro.rs](crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs#3)
+
+
+This diagnostic is shown for builtin macros which are not yet implemented by rust-analyzer
+
+
+
+
+#### unlinked-file
+
+Source: [unlinked_file.rs](crates/ide-diagnostics/src/handlers/unlinked_file.rs#20)
+
+
+This diagnostic is shown for files that are not included in any crate, or files that are part of
+crates rust-analyzer failed to discover. The file will not have IDE features available.
+
+
+
+
+#### unnecessary-braces
+
+Source: [useless_braces.rs](crates/ide-diagnostics/src/handlers/useless_braces.rs#9)
+
+
+Diagnostic for unnecessary braces in `use` items.
+
+
+
+
+#### unreachable-label
+
+Source: [unreachable_label.rs](crates/ide-diagnostics/src/handlers/unreachable_label.rs#3)
+
+
+
+
+
+
+#### unresolved-assoc-item
+
+Source: [unresolved_assoc_item.rs](crates/ide-diagnostics/src/handlers/unresolved_assoc_item.rs#3)
+
+
+This diagnostic is triggered if the referenced associated item does not exist.
+
+
+
+
+#### unresolved-extern-crate
+
+Source: [unresolved_extern_crate.rs](crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs#3)
+
+
+This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
+
+
+
+
+#### unresolved-field
+
+Source: [unresolved_field.rs](crates/ide-diagnostics/src/handlers/unresolved_field.rs#23)
+
+
+This diagnostic is triggered if a field does not exist on a given type.
+
+
+
+
+#### unresolved-ident
+
+Source: [unresolved_ident.rs](crates/ide-diagnostics/src/handlers/unresolved_ident.rs#3)
+
+
+This diagnostic is triggered if an expr-position ident is invalid.
+
+
+
+
+#### unresolved-import
+
+Source: [unresolved_import.rs](crates/ide-diagnostics/src/handlers/unresolved_import.rs#3)
+
+
+This diagnostic is triggered if rust-analyzer is unable to resolve a path in
+a `use` declaration.
+
+
+
+
+#### unresolved-macro-call
+
+Source: [unresolved_macro_call.rs](crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs#3)
+
+
+This diagnostic is triggered if rust-analyzer is unable to resolve the path
+to a macro in a macro invocation.
+
+
+
+
+#### unresolved-method
+
+Source: [unresolved_method.rs](crates/ide-diagnostics/src/handlers/unresolved_method.rs#15)
+
+
+This diagnostic is triggered if a method does not exist on a given type.
+
+
+
+
+#### unresolved-module
+
+Source: [unresolved_module.rs](crates/ide-diagnostics/src/handlers/unresolved_module.rs#8)
+
+
+This diagnostic is triggered if rust-analyzer is unable to discover referred module.
+
+
+
+
+#### unused-mut
+
+Source: [mutability_errors.rs](crates/ide-diagnostics/src/handlers/mutability_errors.rs#62)
+
+
+This diagnostic is triggered when a mutable variable isn't actually mutated.
+
+
+
+
+#### unused-variables
+
+Source: [unused_variables.rs](crates/ide-diagnostics/src/handlers/unused_variables.rs#13)
+
+
+This diagnostic is triggered when a local variable is not used.
+
+
diff --git a/docs/book/src/features_generated.md b/docs/book/src/features_generated.md
new file mode 100644
index 0000000000..2c5829b1f5
--- /dev/null
+++ b/docs/book/src/features_generated.md
@@ -0,0 +1,940 @@
+//! Generated by `cargo xtask codegen feature-docs`, do not edit by hand.
+
+### Annotations
+**Source:** [annotations.rs](crates/ide/src/annotations.rs#19)
+
+Provides user with annotations above items for looking up references or impl blocks
+and running/debugging binaries.
+
+![Annotations](https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png)
+
+
+### Auto Import
+**Source:** [auto_import.rs](crates/ide-assists/src/handlers/auto_import.rs#15)
+
+Using the `auto-import` assist it is possible to insert missing imports for unresolved items.
+When inserting an import it will do so in a structured manner by keeping imports grouped,
+separated by a newline in the following order:
+
+- `std` and `core`
+- External Crates
+- Current Crate, paths prefixed by `crate`
+- Current Module, paths prefixed by `self`
+- Super Module, paths prefixed by `super`
+
+Example:
+```rust
+use std::fs::File;
+
+use itertools::Itertools;
+use syntax::ast;
+
+use crate::utils::insert_use;
+
+use self::auto_import;
+
+use super::AssistContext;
+```
+
+#### Import Granularity
+
+It is possible to configure how use-trees are merged with the `imports.granularity.group` setting.
+It has the following configurations:
+
+- `crate`: Merge imports from the same crate into a single use statement. This kind of
+ nesting is only supported in Rust versions later than 1.24.
+- `module`: Merge imports from the same module into a single use statement.
+- `item`: Don't merge imports at all, creating one import per item.
+- `preserve`: Do not change the granularity of any imports. For auto-import this has the same
+ effect as `item`.
+- `one`: Merge all imports into a single use statement as long as they have the same visibility
+ and attributes.
+
+In `VS Code` the configuration for this is `rust-analyzer.imports.granularity.group`.
+
+#### Import Prefix
+
+The style of imports in the same crate is configurable through the `imports.prefix` setting.
+It has the following configurations:
+
+- `crate`: This setting will force paths to be always absolute, starting with the `crate`
+ prefix, unless the item is defined outside of the current crate.
+- `self`: This setting will force paths that are relative to the current module to always
+ start with `self`. This will result in paths that always start with either `crate`, `self`,
+ `super` or an extern crate identifier.
+- `plain`: This setting does not impose any restrictions in imports.
+
+In `VS Code` the configuration for this is `rust-analyzer.imports.prefix`.
+
+![Auto Import](https://user-images.githubusercontent.com/48062697/113020673-b85be580-917a-11eb-9022-59585f35d4f8.gif)
+
+
+### Completion With Autoimport
+**Source:** [flyimport.rs](crates/ide-completion/src/completions/flyimport.rs#20)
+
+When completing names in the current scope, proposes additional imports from other modules or crates,
+if they can be qualified in the scope, and their name contains all symbols from the completion input.
+
+To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
+If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the containing is checked case-insensitively.
+
+```
+fn main() {
+ pda$0
+}
+# pub mod std { pub mod marker { pub struct PhantomData { } } }
+```
+->
+```
+use std::marker::PhantomData;
+
+fn main() {
+ PhantomData
+}
+# pub mod std { pub mod marker { pub struct PhantomData { } } }
+```
+
+Also completes associated items, that require trait imports.
+If any unresolved and/or partially-qualified path precedes the input, it will be taken into account.
+Currently, only the imports with their import path ending with the whole qualifier will be proposed
+(no fuzzy matching for qualifier).
+
+```
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::Item::TEST_A$0
+}
+```
+->
+```
+use foo::bar;
+
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::Item::TEST_ASSOC
+}
+```
+
+NOTE: currently, if an assoc item comes from a trait that's not currently imported, and it also has an unresolved and/or partially-qualified path,
+no imports will be proposed.
+
+#### Fuzzy search details
+
+To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
+(i.e. in `HashMap` in the `std::collections::HashMap` path).
+For the same reasons, avoids searching for any path imports for inputs with their length less than 2 symbols
+(but shows all associated items for any input length).
+
+#### Import configuration
+
+It is possible to configure how use-trees are merged with the `imports.granularity.group` setting.
+Mimics the corresponding behavior of the `Auto Import` feature.
+
+#### LSP and performance implications
+
+The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
+(case-sensitive) resolve client capability in its client capabilities.
+This way the server is able to defer the costly computations, doing them for a selected completion item only.
+For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
+which might be slow ergo the feature is automatically disabled.
+
+#### Feature toggle
+
+The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.autoimport.enable` flag.
+Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corresponding
+capability enabled.
+
+
+### Debug ItemTree
+**Source:** [view_item_tree.rs](crates/ide/src/view_item_tree.rs#5)
+
+Displays the ItemTree of the currently open file, for debugging.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Debug ItemTree** |
+
+
+### Expand Macro Recursively
+**Source:** [expand_macro.rs](crates/ide/src/expand_macro.rs#18)
+
+Shows the full macro expansion of the macro at the current caret position.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Expand macro recursively at caret** |
+
+![Expand Macro Recursively](https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif)
+
+
+### Expand and Shrink Selection
+**Source:** [extend_selection.rs](crates/ide/src/extend_selection.rs#15)
+
+Extends or shrinks the current selection to the encompassing syntactic construct
+(expression, statement, item, module, etc). It works with multiple cursors.
+
+| Editor | Shortcut |
+|---------|----------|
+| VS Code | <kbd>Alt+Shift+→</kbd>, <kbd>Alt+Shift+←</kbd> |
+
+![Expand and Shrink Selection](https://user-images.githubusercontent.com/48062697/113020651-b42fc800-917a-11eb-8a4f-cf1a07859fac.gif)
+
+
+### File Structure
+**Source:** [file_structure.rs](crates/ide/src/file_structure.rs#26)
+
+Provides a tree of the symbols defined in the file. Can be used to
+
+* fuzzy search symbol in a file (super useful)
+* draw breadcrumbs to describe the context around the cursor
+* draw outline of the file
+
+| Editor | Shortcut |
+|---------|----------|
+| VS Code | <kbd>Ctrl+Shift+O</kbd> |
+
+![File Structure](https://user-images.githubusercontent.com/48062697/113020654-b42fc800-917a-11eb-8388-e7dc4d92b02e.gif)
+
+
+### Find All References
+**Source:** [references.rs](crates/ide/src/references.rs#42)
+
+Shows all references of the item at the cursor location
+
+| Editor | Shortcut |
+|---------|----------|
+| VS Code | <kbd>Shift+Alt+F12</kbd> |
+
+![Find All References](https://user-images.githubusercontent.com/48062697/113020670-b7c34f00-917a-11eb-8003-370ac5f2b3cb.gif)
+
+
+### Folding
+**Source:** [folding_ranges.rs](crates/ide/src/folding_ranges.rs#36)
+
+Defines folding regions for curly braced blocks, runs of consecutive use, mod, const or static
+items, and `region` / `endregion` comment markers.
+
+
+### Format String Completion
+**Source:** [format_like.rs](crates/ide-completion/src/completions/postfix/format_like.rs#0)
+
+`"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`.
+
+The following postfix snippets are available:
+
+* `format` -> `format!(...)`
+* `panic` -> `panic!(...)`
+* `println` -> `println!(...)`
+* `log`:
+** `logd` -> `log::debug!(...)`
+** `logt` -> `log::trace!(...)`
+** `logi` -> `log::info!(...)`
+** `logw` -> `log::warn!(...)`
+** `loge` -> `log::error!(...)`
+
+![Format String Completion](https://user-images.githubusercontent.com/48062697/113020656-b560f500-917a-11eb-87de-02991f61beb8.gif)
+
+
+### Go to Declaration
+**Source:** [goto_declaration.rs](crates/ide/src/goto_declaration.rs#13)
+
+Navigates to the declaration of an identifier.
+
+This is the same as `Go to Definition` with the following exceptions:
+- outline modules will navigate to the `mod name;` item declaration
+- trait assoc items will navigate to the assoc item of the trait declaration as opposed to the trait impl
+- fields in patterns will navigate to the field declaration of the struct, union or variant
+
+
+### Go to Definition
+**Source:** [goto_definition.rs](crates/ide/src/goto_definition.rs#28)
+
+Navigates to the definition of an identifier.
+
+For outline modules, this will navigate to the source file of the module.
+
+| Editor | Shortcut |
+|---------|----------|
+| VS Code | <kbd>F12</kbd> |
+
+![Go to Definition](https://user-images.githubusercontent.com/48062697/113065563-025fbe00-91b1-11eb-83e4-a5a703610b23.gif)
+
+
+### Go to Implementation
+**Source:** [goto_implementation.rs](crates/ide/src/goto_implementation.rs#11)
+
+Navigates to the impl items of types.
+
+| Editor | Shortcut |
+|---------|----------|
+| VS Code | <kbd>Ctrl+F12</kbd>
+
+![Go to Implementation](https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif)
+
+
+### Go to Type Definition
+**Source:** [goto_type_definition.rs](crates/ide/src/goto_type_definition.rs#7)
+
+Navigates to the type of an identifier.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **Go to Type Definition** |
+
+![Go to Type Definition](https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif)
+
+
+### Highlight Related
+**Source:** [highlight_related.rs](crates/ide/src/highlight_related.rs#42)
+
+Highlights constructs related to the thing under the cursor:
+
+1. if on an identifier, highlights all references to that identifier in the current file
+ * additionally, if the identifier is a trait in a where clause, type parameter trait bound or use item, highlights all references to that trait's assoc items in the corresponding scope
+1. if on an `async` or `await` token, highlights all yield points for that async context
+1. if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context
+1. if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
+1. if on a `move` or `|` token that belongs to a closure, highlights all captures of the closure.
+
+Note: `?`, `|` and `->` do not currently trigger this behavior in the VSCode editor.
+
+
+### Hover
+**Source:** [hover.rs](crates/ide/src/hover.rs#116)
+
+Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code.
+Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
+
+![Hover](https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif)
+
+
+### Inlay Hints
+**Source:** [inlay_hints.rs](crates/ide/src/inlay_hints.rs#41)
+
+rust-analyzer shows additional information inline with the source code.
+Editors usually render this using read-only virtual text snippets interspersed with code.
+
+rust-analyzer by default shows hints for
+
+* types of local variables
+* names of function arguments
+* names of const generic parameters
+* types of chained expressions
+
+Optionally, one can enable additional hints for
+
+* return types of closure expressions
+* elided lifetimes
+* compiler inserted reborrows
+* names of generic type and lifetime parameters
+
+Note: inlay hints for function argument names are heuristically omitted to reduce noise and will not appear if
+any of the
+[following criteria](https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L92-L99)
+are met:
+
+* the parameter name is a suffix of the function's name
+* the argument is a qualified constructing or call expression where the qualifier is an ADT
+* exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
+ of argument with _ splitting it off
+* the parameter name starts with `ra_fixture`
+* the parameter name is a
+[well known name](https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L200)
+in a unary function
+* the parameter name is a
+[single character](https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L201)
+in a unary function
+
+![Inlay hints](https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png)
+
+
+### Interpret A Function, Static Or Const.
+**Source:** [interpret.rs](crates/ide/src/interpret.rs#8)
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Interpret** |
+
+
+### Join Lines
+**Source:** [join_lines.rs](crates/ide/src/join_lines.rs#20)
+
+Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces.
+
+See [this gif](https://user-images.githubusercontent.com/1711539/124515923-4504e800-dde9-11eb-8d58-d97945a1a785.gif) for the cases handled specially by joined lines.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Join lines** |
+
+![Join Lines](https://user-images.githubusercontent.com/48062697/113020661-b6922200-917a-11eb-87c4-b75acc028f11.gif)
+
+
+### Magic Completions
+**Source:** [lib.rs](crates/ide-completion/src/lib.rs#78)
+
+In addition to usual reference completion, rust-analyzer provides some ✨magic✨
+completions as well:
+
+Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
+is placed at the appropriate position. Even though `if` is easy to type, you
+still want to complete it, to get ` { }` for free! `return` is inserted with a
+space or `;` depending on the return type of the function.
+
+When completing a function call, `()` are automatically inserted. If a function
+takes arguments, the cursor is positioned inside the parenthesis.
+
+There are postfix completions, which can be triggered by typing something like
+`foo().if`. The word after `.` determines postfix completion. Possible variants are:
+
+- `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
+- `expr.match` -> `match expr {}`
+- `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
+- `expr.ref` -> `&expr`
+- `expr.refm` -> `&mut expr`
+- `expr.let` -> `let $0 = expr;`
+- `expr.lete` -> `let $1 = expr else { $0 };`
+- `expr.letm` -> `let mut $0 = expr;`
+- `expr.not` -> `!expr`
+- `expr.dbg` -> `dbg!(expr)`
+- `expr.dbgr` -> `dbg!(&expr)`
+- `expr.call` -> `(expr)`
+
+There also snippet completions:
+
+#### Expressions
+
+- `pd` -> `eprintln!(" = {:?}", );`
+- `ppd` -> `eprintln!(" = {:#?}", );`
+
+#### Items
+
+- `tfn` -> `#[test] fn feature(){}`
+- `tmod` ->
+```rust
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_name() {}
+}
+```
+
+And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities.
+Those are the additional completion options with automatic `use` import and options from all project importable items,
+fuzzy matched against the completion input.
+
+![Magic Completions](https://user-images.githubusercontent.com/48062697/113020667-b72ab880-917a-11eb-8778-716cf26a0eb3.gif)
+
+
+### Matching Brace
+**Source:** [matching_brace.rs](crates/ide/src/matching_brace.rs#6)
+
+If the cursor is on any brace (`<>(){}[]||`) which is a part of a brace-pair,
+moves cursor to the matching brace. It uses the actual parser to determine
+braces, so it won't confuse generics with comparisons.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Find matching brace** |
+
+![Matching Brace](https://user-images.githubusercontent.com/48062697/113065573-04298180-91b1-11eb-8dec-d4e2a202f304.gif)
+
+
+### Memory Usage
+**Source:** [apply_change.rs](crates/ide-db/src/apply_change.rs#43)
+
+Clears rust-analyzer's internal database and prints memory usage statistics.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Memory Usage (Clears Database)**
+
+
+### Move Item
+**Source:** [move_item.rs](crates/ide/src/move_item.rs#16)
+
+Move item under cursor or selection up and down.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Move item up**
+| VS Code | **rust-analyzer: Move item down**
+
+![Move Item](https://user-images.githubusercontent.com/48062697/113065576-04298180-91b1-11eb-91ce-4505e99ed598.gif)
+
+
+### On Enter
+**Source:** [on_enter.rs](crates/ide/src/typing/on_enter.rs#17)
+
+rust-analyzer can override <kbd>Enter</kbd> key to make it smarter:
+
+- <kbd>Enter</kbd> inside triple-slash comments automatically inserts `///`
+- <kbd>Enter</kbd> in the middle or after a trailing space in `//` inserts `//`
+- <kbd>Enter</kbd> inside `//!` doc comments automatically inserts `//!`
+- <kbd>Enter</kbd> after `{` indents contents and closing `}` of single-line block
+
+This action needs to be assigned to shortcut explicitly.
+
+Note that, depending on the other installed extensions, this feature can visibly slow down typing.
+Similarly, if rust-analyzer crashes or stops responding, `Enter` might not work.
+In that case, you can still press `Shift-Enter` to insert a newline.
+
+#### VS Code
+
+Add the following to `keybindings.json`:
+```json
+{
+ "key": "Enter",
+ "command": "rust-analyzer.onEnter",
+ "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust"
+}
+````
+
+When using the Vim plugin:
+```json
+{
+ "key": "Enter",
+ "command": "rust-analyzer.onEnter",
+ "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust && vim.mode == 'Insert'"
+}
+````
+
+![On Enter](https://user-images.githubusercontent.com/48062697/113065578-04c21800-91b1-11eb-82b8-22b8c481e645.gif)
+
+
+### On Typing Assists
+**Source:** [typing.rs](crates/ide/src/typing.rs#42)
+
+Some features trigger on typing certain characters:
+
+- typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
+- typing `=` between two expressions adds `;` when in statement position
+- typing `=` to turn an assignment into an equality comparison removes `;` when in expression position
+- typing `.` in a chain method call auto-indents
+- typing `{` or `(` in front of an expression inserts a closing `}` or `)` after the expression
+- typing `{` in a use item adds a closing `}` in the right place
+- typing `>` to complete a return type `->` will insert a whitespace after it
+
+#### VS Code
+
+Add the following to `settings.json`:
+```json
+"editor.formatOnType": true,
+```
+
+![On Typing Assists](https://user-images.githubusercontent.com/48062697/113166163-69758500-923a-11eb-81ee-eb33ec380399.gif)
+![On Typing Assists](https://user-images.githubusercontent.com/48062697/113171066-105c2000-923f-11eb-87ab-f4a263346567.gif)
+
+
+### Open Docs
+**Source:** [doc_links.rs](crates/ide/src/doc_links.rs#118)
+
+Retrieve a links to documentation for the given symbol.
+
+The simplest way to use this feature is via the context menu. Right-click on
+the selected item. The context menu opens. Select **Open Docs**.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Open Docs** |
+
+
+### Parent Module
+**Source:** [parent_module.rs](crates/ide/src/parent_module.rs#14)
+
+Navigates to the parent module of the current module.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Locate parent module** |
+
+![Parent Module](https://user-images.githubusercontent.com/48062697/113065580-04c21800-91b1-11eb-9a32-00086161c0bd.gif)
+
+
+### Related Tests
+**Source:** [runnables.rs](crates/ide/src/runnables.rs#202)
+
+Provides a sneak peek of all tests where the current item is used.
+
+The simplest way to use this feature is via the context menu. Right-click on
+the selected item. The context menu opens. Select **Peek Related Tests**.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Peek Related Tests** |
+
+
+### Rename
+**Source:** [rename.rs](crates/ide/src/rename.rs#70)
+
+Renames the item below the cursor and all of its references
+
+| Editor | Shortcut |
+|---------|----------|
+| VS Code | <kbd>F2</kbd> |
+
+![Rename](https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif)
+
+
+### Run
+**Source:** [runnables.rs](crates/ide/src/runnables.rs#116)
+
+Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
+location**. Super useful for repeatedly running just a single test. Do bind this
+to a shortcut!
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Run** |
+
+![Run](https://user-images.githubusercontent.com/48062697/113065583-055aae80-91b1-11eb-958f-d67efcaf6a2f.gif)
+
+
+### Semantic Syntax Highlighting
+**Source:** [syntax_highlighting.rs](crates/ide/src/syntax_highlighting.rs#68)
+
+rust-analyzer highlights the code semantically.
+For example, `Bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait.
+rust-analyzer does not specify colors directly, instead it assigns a tag (like `struct`) and a set of modifiers (like `declaration`) to each token.
+It's up to the client to map those to specific colors.
+
+The general rule is that a reference to an entity gets colored the same way as the entity itself.
+We also give special modifier for `mut` and `&mut` local variables.
+
+
+#### Token Tags
+
+Rust-analyzer currently emits the following token tags:
+
+- For items:
+
+| | |
+|-----------|--------------------------------|
+| attribute | Emitted for attribute macros. |
+|enum| Emitted for enums. |
+|function| Emitted for free-standing functions. |
+|derive| Emitted for derive macros. |
+|macro| Emitted for function-like macros. |
+|method| Emitted for associated functions, also knowns as methods. |
+|namespace| Emitted for modules. |
+|struct| Emitted for structs.|
+|trait| Emitted for traits.|
+|typeAlias| Emitted for type aliases and `Self` in `impl`s.|
+|union| Emitted for unions.|
+
+- For literals:
+
+| | |
+|-----------|--------------------------------|
+| boolean| Emitted for the boolean literals `true` and `false`.|
+| character| Emitted for character literals.|
+| number| Emitted for numeric literals.|
+| string| Emitted for string literals.|
+| escapeSequence| Emitted for escaped sequences inside strings like `\n`.|
+| formatSpecifier| Emitted for format specifiers `{:?}` in `format!`-like macros.|
+
+- For operators:
+
+| | |
+|-----------|--------------------------------|
+|operator| Emitted for general operators.|
+|arithmetic| Emitted for the arithmetic operators `+`, `-`, `*`, `/`, `+=`, `-=`, `*=`, `/=`.|
+|bitwise| Emitted for the bitwise operators `|`, `&`, `!`, `^`, `|=`, `&=`, `^=`.|
+|comparison| Emitted for the comparison oerators `>`, `<`, `==`, `>=`, `<=`, `!=`.|
+|logical| Emitted for the logical operatos `||`, `&&`, `!`.|
+
+- For punctuation:
+
+| | |
+|-----------|--------------------------------|
+|punctuation| Emitted for general punctuation.|
+|attributeBracket| Emitted for attribute invocation brackets, that is the `#[` and `]` tokens.|
+|angle| Emitted for `<>` angle brackets.|
+|brace| Emitted for `{}` braces.|
+|bracket| Emitted for `[]` brackets.|
+|parenthesis| Emitted for `()` parentheses.|
+|colon| Emitted for the `:` token.|
+|comma| Emitted for the `,` token.|
+|dot| Emitted for the `.` token.|
+|semi| Emitted for the `;` token.|
+|macroBang| Emitted for the `!` token in macro calls.|
+
+-
+
+| | |
+|-----------|--------------------------------|
+|builtinAttribute| Emitted for names to builtin attributes in attribute path, the `repr` in `#[repr(u8)]` for example.|
+|builtinType| Emitted for builtin types like `u32`, `str` and `f32`.|
+|comment| Emitted for comments.|
+|constParameter| Emitted for const parameters.|
+|deriveHelper| Emitted for derive helper attributes.|
+|enumMember| Emitted for enum variants.|
+|generic| Emitted for generic tokens that have no mapping.|
+|keyword| Emitted for keywords.|
+|label| Emitted for labels.|
+|lifetime| Emitted for lifetimes.|
+|parameter| Emitted for non-self function parameters.|
+|property| Emitted for struct and union fields.|
+|selfKeyword| Emitted for the self function parameter and self path-specifier.|
+|selfTypeKeyword| Emitted for the Self type parameter.|
+|toolModule| Emitted for tool modules.|
+|typeParameter| Emitted for type parameters.|
+|unresolvedReference| Emitted for unresolved references, names that rust-analyzer can't find the definition of.|
+|variable| Emitted for locals, constants and statics.|
+
+
+#### Token Modifiers
+
+Token modifiers allow to style some elements in the source code more precisely.
+
+Rust-analyzer currently emits the following token modifiers:
+
+| | |
+|-----------|--------------------------------|
+|async| Emitted for async functions and the `async` and `await` keywords.|
+|attribute| Emitted for tokens inside attributes.|
+|callable| Emitted for locals whose types implements one of the `Fn*` traits.|
+|constant| Emitted for const.|
+|consuming| Emitted for locals that are being consumed when use in a function call.|
+|controlFlow| Emitted for control-flow related tokens, this includes th `?` operator.|
+|crateRoot| Emitted for crate names, like `serde` and `crate.|
+|declaration| Emitted for names of definitions, like `foo` in `fn foo(){}`.|
+|defaultLibrary| Emitted for items from built-in crates (std, core, allc, test and proc_macro).|
+|documentation| Emitted for documentation comment.|
+|injected| Emitted for doc-string injected highlighting like rust source blocks in documentation.|
+|intraDocLink| Emitted for intra doc links in doc-string.|
+|library| Emitted for items that are defined outside of the current crae.|
+|macro| Emitted for tokens inside macro call.|
+|mutable| Emitted for mutable locals and statics as well as functions taking `&mut self`.|
+|public| Emitted for items that are from the current crate and are `pub.|
+|reference| Emitted for locals behind a reference and functions taking self` by reference.|
+|static| Emitted for "static" functions, also known as functions that d not take a `self` param, as well as statics and consts.|
+|trait| Emitted for associated trait item.|
+|unsafe| Emitted for unsafe operations, like unsafe function calls, as ell as the `unsafe` token.|
+
+![Semantic Syntax Highlighting](https://user-images.githubusercontent.com/48062697/113164457-06cfb980-9239-11eb-819b-0f93e646acf8.png)
+![Semantic Syntax Highlighting](https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png)
+
+
+### Show Dependency Tree
+**Source:** [fetch_crates.rs](crates/ide/src/fetch_crates.rs#13)
+
+Shows a view tree with all the dependencies of this project
+
+| Editor | Panel Name |
+|---------|------------|
+| VS Code | **Rust Dependencies** |
+
+![Show Dependency Tree](https://user-images.githubusercontent.com/5748995/229394139-2625beab-f4c9-484b-84ed-ad5dee0b1e1a.png)
+
+
+### Show Syntax Tree
+**Source:** [view_syntax_tree.rs](crates/ide/src/view_syntax_tree.rs#14)
+
+Shows a tree view with the syntax tree of the current file
+
+| Editor | Panel Name |
+|---------|-------------|
+| VS Code | **Rust Syntax Tree** |
+
+
+### Status
+**Source:** [status.rs](crates/ide/src/status.rs#28)
+
+Shows internal statistic about memory usage of rust-analyzer.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: Status** |
+
+![Status](https://user-images.githubusercontent.com/48062697/113065584-05f34500-91b1-11eb-98cc-5c196f76be7f.gif)
+
+
+### Structural Search and Replace
+**Source:** [lib.rs](crates/ide-ssr/src/lib.rs#6)
+
+Search and replace with named wildcards that will match any expression, type, path, pattern or item.
+The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
+A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement.
+Within a macro call, a placeholder will match up until whatever token follows the placeholder.
+
+All paths in both the search pattern and the replacement template must resolve in the context
+in which this command is invoked. Paths in the search pattern will then match the code if they
+resolve to the same item, even if they're written differently. For example if we invoke the
+command in the module `foo` with a pattern of `Bar`, then code in the parent module that refers
+to `foo::Bar` will match.
+
+Paths in the replacement template will be rendered appropriately for the context in which the
+replacement occurs. For example if our replacement template is `foo::Bar` and we match some
+code in the `foo` module, we'll insert just `Bar`.
+
+Inherent method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will
+match `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. When a
+placeholder is the receiver of a method call in the search pattern (e.g. `$s.foo()`), but not in
+the replacement template (e.g. `bar($s)`), then *, & and &mut will be added as needed to mirror
+whatever autoderef and autoref was happening implicitly in the matched code.
+
+The scope of the search / replace will be restricted to the current selection if any, otherwise
+it will apply to the whole workspace.
+
+Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`.
+
+Supported constraints:
+
+| Constraint | Restricts placeholder |
+|---------------|------------------------|
+| kind(literal) | Is a literal (e.g. `42` or `"forty two"`) |
+| not(a) | Negates the constraint `a` |
+
+Available via the command `rust-analyzer.ssr`.
+
+```rust
+// Using structural search replace command [foo($a, $b) ==>> ($a).foo($b)]
+
+// BEFORE
+String::from(foo(y + 5, z))
+
+// AFTER
+String::from((y + 5).foo(z))
+```
+
+| Editor | Action Name |
+|---------|--------------|
+| VS Code | **rust-analyzer: Structural Search Replace** |
+
+Also available as an assist, by writing a comment containing the structural
+search and replace rule. You will only see the assist if the comment can
+be parsed as a valid structural search and replace rule.
+
+```rust
+// Place the cursor on the line below to see the assist 💡.
+// foo($a, $b) ==>> ($a).foo($b)
+```
+
+
+### User Snippet Completions
+**Source:** [snippet.rs](crates/ide-completion/src/snippet.rs#5)
+
+rust-analyzer allows the user to define custom (postfix)-snippets that may depend on items to be accessible for the current scope to be applicable.
+
+A custom snippet can be defined by adding it to the `rust-analyzer.completion.snippets.custom` object respectively.
+
+```json
+{
+ "rust-analyzer.completion.snippets.custom": {
+ "thread spawn": {
+ "prefix": ["spawn", "tspawn"],
+ "body": [
+ "thread::spawn(move || {",
+ "\t$0",
+ "});",
+ ],
+ "description": "Insert a thread::spawn call",
+ "requires": "std::thread",
+ "scope": "expr",
+ }
+ }
+}
+```
+
+In the example above:
+
+* `"thread spawn"` is the name of the snippet.
+
+* `prefix` defines one or more trigger words that will trigger the snippets completion.
+Using `postfix` will instead create a postfix snippet.
+
+* `body` is one or more lines of content joined via newlines for the final output.
+
+* `description` is an optional description of the snippet, if unset the snippet name will be used.
+
+* `requires` is an optional list of item paths that have to be resolvable in the current crate where the completion is rendered.
+
+
+### View Crate Graph
+**Source:** [view_crate_graph.rs](crates/ide/src/view_crate_graph.rs#8)
+
+Renders the currently loaded crate graph as an SVG graphic. Requires the `dot` tool, which
+is part of graphviz, to be installed.
+
+Only workspace crates are included, no crates.io dependencies or sysroot crates.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: View Crate Graph** |
+
+
+### View Hir
+**Source:** [view_hir.rs](crates/ide/src/view_hir.rs#5)
+
+| Editor | Action Name |
+|---------|--------------|
+| VS Code | **rust-analyzer: View Hir**
+
+![View Hir](https://user-images.githubusercontent.com/48062697/113065588-068bdb80-91b1-11eb-9a78-0b4ef1e972fb.gif)
+
+
+### View Memory Layout
+**Source:** [view_memory_layout.rs](crates/ide/src/view_memory_layout.rs#74)
+
+Displays the recursive memory layout of a datatype.
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: View Memory Layout** |
+
+
+### View Mir
+**Source:** [view_mir.rs](crates/ide/src/view_mir.rs#5)
+
+| Editor | Action Name |
+|---------|-------------|
+| VS Code | **rust-analyzer: View Mir**
+
+
+### Workspace Symbol
+**Source:** [symbol_index.rs](crates/ide-db/src/symbol_index.rs#174)
+
+Uses fuzzy-search to find types, modules and functions by name across your
+project and dependencies. This is **the** most useful feature, which improves code
+navigation tremendously. It mostly works on top of the built-in LSP
+functionality, however `#` and `*` symbols can be used to narrow down the
+search. Specifically,
+
+- `Foo` searches for `Foo` type in the current workspace
+- `foo#` searches for `foo` function in the current workspace
+- `Foo*` searches for `Foo` type among dependencies, including `stdlib`
+- `foo#*` searches for `foo` function among dependencies
+
+That is, `#` switches from "types" to all symbols, `*` switches from the current
+workspace to dependencies.
+
+Note that filtering does not currently work in VSCode due to the editor never
+sending the special symbols to the language server. Instead, you can configure
+the filtering via the `rust-analyzer.workspace.symbol.search.scope` and
+`rust-analyzer.workspace.symbol.search.kind` settings. Symbols prefixed
+with `__` are hidden from the search results unless configured otherwise.
+
+| Editor | Shortcut |
+|---------|-----------|
+| VS Code | <kbd>Ctrl+T</kbd>
diff --git a/docs/book/src/installation.md b/docs/book/src/installation.md
index 5b697e9bc3..3a4c0cf227 100644
--- a/docs/book/src/installation.md
+++ b/docs/book/src/installation.md
@@ -1,19 +1,19 @@
# Installation
-In theory, one should be able to just install the [`rust-analyzer`
-binary](#rust-analyzer-language-server-binary) and have it automatically
-work with any editor. We are not there yet, so some editor specific
-setup is required.
+To use rust-analyzer, you need a `rust-analyzer` binary, a text editor
+that supports LSP, and the source code of the Rust standard library.
-Additionally, rust-analyzer needs the sources of the standard library.
-If the source code is not present, rust-analyzer will attempt to install
-it automatically.
+If you're [using VS Code](./vs_code.html), the extension bundles a
+copy of the `rust-analyzer` binary. For other editors, you'll need to
+[install the binary](./rust_analyzer_binary.html) and [configure your
+editor](./other_editors.html).
-To add the sources manually, run the following command:
+## Rust Standard Library
- $ rustup component add rust-src
+rust-analyzer will attempt to install the standard library source code
+automatically. You can also install it manually with `rustup`.
-## Toolchain
+ $ rustup component add rust-src
Only the latest stable standard library source is officially supported
for use with rust-analyzer. If you are using an older toolchain or have
@@ -25,620 +25,16 @@ If you are using an override in your project, you can still force
rust-analyzer to use the stable toolchain via the environment variable
`RUSTUP_TOOLCHAIN`. For example, with VS Code or coc-rust-analyzer:
- { "rust-analyzer.server.extraEnv": { "RUSTUP_TOOLCHAIN": "stable" } }
-
-## VS Code
-
-This is the best supported editor at the moment. The rust-analyzer
-plugin for VS Code is maintained [in
-tree](https://github.com/rust-lang/rust-analyzer/tree/master/editors/code).
-
-You can install the latest release of the plugin from [the
-marketplace](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).
-
-Note that the plugin may cause conflicts with the [previous official
-Rust
-plugin](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust).
-The latter is no longer maintained and should be uninstalled.
-
-The server binary is stored in the extension install directory, which
-starts with `rust-lang.rust-analyzer-` and is located under:
-
-- Linux: `~/.vscode/extensions`
-
-- Linux (Remote, such as WSL): `~/.vscode-server/extensions`
-
-- macOS: `~/.vscode/extensions`
-
-- Windows: `%USERPROFILE%\.vscode\extensions`
-
-As an exception, on NixOS, the extension makes a copy of the server and
-stores it under
-`~/.config/Code/User/globalStorage/rust-lang.rust-analyzer`.
-
-Note that we only support the two most recent versions of VS Code.
-
-### Updates
-
-The extension will be updated automatically as new versions become
-available. It will ask your permission to download the matching language
-server version binary if needed.
-
-#### Nightly
-
-We ship nightly releases for VS Code. To help us out by testing the
-newest code, you can enable pre-release versions in the Code extension
-page.
-
-### Manual installation
-
-Alternatively, download a VSIX corresponding to your platform from the
-[releases](https://github.com/rust-lang/rust-analyzer/releases) page.
-
-Install the extension with the `Extensions: Install from VSIX` command
-within VS Code, or from the command line via:
-
- $ code --install-extension /path/to/rust-analyzer.vsix
-
-If you are running an unsupported platform, you can install
-`rust-analyzer-no-server.vsix` and compile or obtain a server binary.
-Copy the server anywhere, then add the path to your settings.json, for
-example:
-
- { "rust-analyzer.server.path": "~/.local/bin/rust-analyzer-linux" }
-
-### Building From Source
-
-Both the server and the Code plugin can be installed from source:
-
- $ git clone https://github.com/rust-lang/rust-analyzer.git && cd rust-analyzer
- $ cargo xtask install
-
-You’ll need Cargo, nodejs (matching a supported version of VS Code) and
-npm for this.
-
-Note that installing via `xtask install` does not work for VS Code
-Remote, instead you’ll need to install the `.vsix` manually.
-
-If you’re not using Code, you can compile and install only the LSP
-server:
-
- $ cargo xtask install --server
-
-Make sure that `.cargo/bin` is in `$PATH` and precedes paths where
-`rust-analyzer` may also be installed. Specifically, `rustup` includes a
-proxy called `rust-analyzer`, which can cause problems if you’re
-planning to use a source build or even a downloaded binary.
-
-## rust-analyzer Language Server Binary
-
-Other editors generally require the `rust-analyzer` binary to be in
-`$PATH`. You can download pre-built binaries from the
-[releases](https://github.com/rust-lang/rust-analyzer/releases) page.
-You will need to uncompress and rename the binary for your platform,
-e.g. from `rust-analyzer-aarch64-apple-darwin.gz` on Mac OS to
-`rust-analyzer`, make it executable, then move it into a directory in
-your `$PATH`.
-
-On Linux to install the `rust-analyzer` binary into `~/.local/bin`,
-these commands should work:
-
- $ mkdir -p ~/.local/bin
- $ curl -L https://github.com/rust-lang/rust-analyzer/releases/latest/download/rust-analyzer-x86_64-unknown-linux-gnu.gz | gunzip -c - > ~/.local/bin/rust-analyzer
- $ chmod +x ~/.local/bin/rust-analyzer
-
-Make sure that `~/.local/bin` is listed in the `$PATH` variable and use
-the appropriate URL if you’re not on a `x86-64` system.
-
-You don’t have to use `~/.local/bin`, any other path like `~/.cargo/bin`
-or `/usr/local/bin` will work just as well.
-
-Alternatively, you can install it from source using the command below.
-You’ll need the latest stable version of the Rust toolchain.
-
- $ git clone https://github.com/rust-lang/rust-analyzer.git && cd rust-analyzer
- $ cargo xtask install --server
-
-If your editor can’t find the binary even though the binary is on your
-`$PATH`, the likely explanation is that it doesn’t see the same `$PATH`
-as the shell, see [this
-issue](https://github.com/rust-lang/rust-analyzer/issues/1811). On Unix,
-running the editor from a shell or changing the `.desktop` file to set
-the environment should help.
-
-### rustup
-
-`rust-analyzer` is available in `rustup`:
-
- $ rustup component add rust-analyzer
-
-### Arch Linux
-
-The `rust-analyzer` binary can be installed from the repos or AUR (Arch
-User Repository):
-
-- [`rust-analyzer`](https://www.archlinux.org/packages/extra/x86_64/rust-analyzer/)
- (built from latest tagged source)
-
-- [`rust-analyzer-git`](https://aur.archlinux.org/packages/rust-analyzer-git)
- (latest Git version)
-
-Install it with pacman, for example:
-
- $ pacman -S rust-analyzer
-
-### Gentoo Linux
-
-`rust-analyzer` is installed when the `rust-analyzer` use flag is set for dev-lang/rust or dev-lang/rust-bin. You also need to set the `rust-src` use flag.
-
-### macOS
-
-The `rust-analyzer` binary can be installed via
-[Homebrew](https://brew.sh/).
-
- $ brew install rust-analyzer
-
-### Windows
-
-It is recommended to install the latest Microsoft Visual C++ Redistributable prior to installation.
-Download links can be found
-[here](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist).
-
-## VS Code or VSCodium in Flatpak
-
-Setting up `rust-analyzer` with a Flatpak version of Code is not trivial
-because of the Flatpak sandbox. While the sandbox can be disabled for
-some directories, `/usr/bin` will always be mounted under
-`/run/host/usr/bin`. This prevents access to the system’s C compiler, a
-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`.
-
-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`)
-
-If you want to use Flatpak in combination with `rustup`, the following
-steps might help:
-
-- both Rust and `rustup` have to be installed using
- <https://rustup.rs>. Distro packages *will not* work.
-
-- you need to launch Code, open a terminal and run `echo $PATH`
-
-- using
- [Flatseal](https://flathub.org/apps/details/com.github.tchx84.Flatseal),
- you must add an environment variable called `PATH`. Set its value to
- the output from above, appending `:~/.cargo/bin`, where `~` is the
- path to your home directory. You must replace `~`, as it won’t be
- expanded otherwise.
-
-- while Flatseal is open, you must enable access to "All user files"
-
-A C compiler should already be available via `org.freedesktop.Sdk`. Any
-other tools or libraries you will need to acquire from Flatpak.
-
-## Emacs
-
-Prerequisites: You have installed the [`rust-analyzer`
-binary](#rust-analyzer-language-server-binary).
-
-To use `rust-analyzer`, you need to install and enable one of the two
-popular LSP client implementations for Emacs,
-[Eglot](https://github.com/joaotavora/eglot) or [LSP
-Mode](https://github.com/emacs-lsp/lsp-mode). Both enable
-`rust-analyzer` by default in rust buffers if it is available.
-
-### Eglot
-
-Eglot is the more minimalistic and lightweight LSP client for Emacs,
-integrates well with existing Emacs functionality and is built into
-Emacs starting from release 29.
-
-After installing Eglot, e.g. via `M-x package-install` (not needed from
-Emacs 29), you can enable it via the `M-x eglot` command or load it
-automatically in `rust-mode` via
-
- (add-hook 'rust-mode-hook 'eglot-ensure)
-
-To enable clippy, you will need to configure the initialization options
-to pass the `check.command` setting.
-
- (add-to-list 'eglot-server-programs
- '((rust-ts-mode rust-mode) .
- ("rust-analyzer" :initializationOptions (:check (:command "clippy")))))
-
-For more detailed instructions and options see the [Eglot
-manual](https://joaotavora.github.io/eglot) (also available from Emacs
-via `M-x info`) and the [Eglot
-readme](https://github.com/joaotavora/eglot/blob/master/README.md).
-
-Eglot does not support the rust-analyzer extensions to the
-language-server protocol and does not aim to do so in the future. The
-[eglot-x](https://github.com/nemethf/eglot-x#rust-analyzer-extensions)
-package adds experimental support for those LSP extensions.
-
-### LSP Mode
-
-LSP-mode is the original LSP-client for emacs. Compared to Eglot it has
-a larger codebase and supports more features, like LSP protocol
-extensions. With extension packages like [LSP
-UI](https://github.com/emacs-lsp/lsp-mode) it offers a lot of visual
-eyecandy. Further it integrates well with [DAP
-mode](https://github.com/emacs-lsp/dap-mode) for support of the Debug
-Adapter Protocol.
-
-You can install LSP-mode via `M-x package-install` and then run it via
-the `M-x lsp` command or load it automatically in rust buffers with
-
- (add-hook 'rust-mode-hook 'lsp-deferred)
-
-For more information on how to set up LSP mode and its extension package
-see the instructions in the [LSP mode
-manual](https://emacs-lsp.github.io/lsp-mode/page/installation). Also
-see the [rust-analyzer
-section](https://emacs-lsp.github.io/lsp-mode/page/lsp-rust-analyzer/)
-for `rust-analyzer` specific options and commands, which you can
-optionally bind to keys.
-
-Note the excellent
-[guide](https://robert.kra.hn/posts/2021-02-07_rust-with-emacs/) from
-[@rksm](https://github.com/rksm) on how to set-up Emacs for Rust
-development with LSP mode and several other packages.
-
-## Vim/Neovim
-
-Prerequisites: You have installed the [`rust-analyzer`
-binary](#rust-analyzer-language-server-binary). Not needed if the
-extension can install/update it on its own, coc-rust-analyzer is one
-example.
-
-There are several LSP client implementations for Vim or Neovim:
-
-### coc-rust-analyzer
-
-1. Install coc.nvim by following the instructions at
- [coc.nvim](https://github.com/neoclide/coc.nvim) (Node.js required)
-
-2. Run `:CocInstall coc-rust-analyzer` to install
- [coc-rust-analyzer](https://github.com/fannheyward/coc-rust-analyzer),
- this extension implements *most* of the features supported in the
- VSCode extension:
-
- - automatically install and upgrade stable/nightly releases
-
- - same configurations as VSCode extension,
- `rust-analyzer.server.path`, `rust-analyzer.cargo.features` etc.
-
- - same commands too, `rust-analyzer.analyzerStatus`,
- `rust-analyzer.ssr` etc.
-
- - inlay hints for variables and method chaining, *Neovim Only*
-
-Note: for code actions, use `coc-codeaction-cursor` and
-`coc-codeaction-selected`; `coc-codeaction` and `coc-codeaction-line`
-are unlikely to be useful.
-
-### LanguageClient-neovim
-
-1. Install LanguageClient-neovim by following the instructions
- [here](https://github.com/autozimu/LanguageClient-neovim)
-
- - The GitHub project wiki has extra tips on configuration
-
-2. Configure by adding this to your Vim/Neovim config file (replacing
- the existing Rust-specific line if it exists):
-
- let g:LanguageClient_serverCommands = {
- \ 'rust': ['rust-analyzer'],
- \ }
-
-### YouCompleteMe
-
-Install YouCompleteMe by following the instructions
-[here](https://github.com/ycm-core/YouCompleteMe#installation).
-
-rust-analyzer is the default in ycm, it should work out of the box.
-
-### ALE
-
-To use the LSP server in [ale](https://github.com/dense-analysis/ale):
-
- let g:ale_linters = {'rust': ['analyzer']}
-
-### nvim-lsp
-
-Neovim 0.5 has built-in language server support. For a quick start
-configuration of rust-analyzer, use
-[neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig#rust_analyzer).
-Once `neovim/nvim-lspconfig` is installed, use
-`lua require'lspconfig'.rust_analyzer.setup({})` in your `init.vim`.
-
-You can also pass LSP settings to the server:
-
- lua << EOF
- local lspconfig = require'lspconfig'
-
- local on_attach = function(client)
- require'completion'.on_attach(client)
- end
-
- lspconfig.rust_analyzer.setup({
- on_attach = on_attach,
- settings = {
- ["rust-analyzer"] = {
- imports = {
- granularity = {
- group = "module",
- },
- prefix = "self",
- },
- cargo = {
- buildScripts = {
- enable = true,
- },
- },
- procMacro = {
- enable = true
- },
- }
- }
- })
- EOF
-
-If you're running Neovim 0.10 or later, you can enable inlay hints via `on_attach`:
-
-```vim
-lspconfig.rust_analyzer.setup({
- on_attach = function(client, bufnr)
- vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
- end
-})
+```json
+{ "rust-analyzer.server.extraEnv": { "RUSTUP_TOOLCHAIN": "stable" } }
```
-Note that the hints are only visible after `rust-analyzer` has finished loading **and** you have to
-edit the file to trigger a re-render.
-
-See <https://sharksforarms.dev/posts/neovim-rust/> for more tips on
-getting started.
-
-Check out <https://github.com/mrcjkb/rustaceanvim> for a batteries
-included rust-analyzer setup for Neovim.
-
-### vim-lsp
-
-vim-lsp is installed by following [the plugin
-instructions](https://github.com/prabirshrestha/vim-lsp). It can be as
-simple as adding this line to your `.vimrc`:
-
- Plug 'prabirshrestha/vim-lsp'
-
-Next you need to register the `rust-analyzer` binary. If it is avim.lspvailable
-in `$PATH`, you may want to add this to your `.vimrc`:
-
- if executable('rust-analyzer')
- au User lsp_setup call lsp#register_server({
- \ 'name': 'Rust Language Server',
- \ 'cmd': {server_info->['rust-analyzer']},
- \ 'whitelist': ['rust'],
- \ })
- endif
-
-There is no dedicated UI for the server configuration, so you would need
-to send any options as a value of the `initialization_options` field, as
-described in the [Configuration](#configuration) section. Here is an
-example of how to enable the proc-macro support:
-
- if executable('rust-analyzer')
- au User lsp_setup call lsp#register_server({
- \ 'name': 'Rust Language Server',
- \ 'cmd': {server_info->['rust-analyzer']},
- \ 'whitelist': ['rust'],
- \ 'initialization_options': {
- \ 'cargo': {
- \ 'buildScripts': {
- \ 'enable': v:true,
- \ },
- \ },
- \ 'procMacro': {
- \ 'enable': v:true,
- \ },
- \ },
- \ })
- endif
-
-## Sublime Text
-
-### Sublime Text 4:
-
-- Follow the instructions in
- [LSP-rust-analyzer](https://github.com/sublimelsp/LSP-rust-analyzer).
-
-Install
-[LSP-file-watcher-chokidar](https://packagecontrol.io/packages/LSP-file-watcher-chokidar)
-to enable file watching (`workspace/didChangeWatchedFiles`).
-
-### Sublime Text 3:
-
-- Install the [`rust-analyzer`
- binary](#rust-analyzer-language-server-binary).
-
-- Install the [LSP package](https://packagecontrol.io/packages/LSP).
-
-- From the command palette, run `LSP: Enable Language Server Globally`
- and select `rust-analyzer`.
-
-If it worked, you should see "rust-analyzer, Line X, Column Y" on the
-left side of the status bar, and after waiting a bit, functionalities
-like tooltips on hovering over variables should become available.
-
-If you get an error saying `No such file or directory: 'rust-analyzer'`,
-see the [`rust-analyzer` binary](#rust-analyzer-language-server-binary)
-section on installing the language server binary.
-
-## GNOME Builder
-
-GNOME Builder 3.37.1 and newer has native `rust-analyzer` support. If
-the LSP binary is not available, GNOME Builder can install it when
-opening a Rust file.
-
-## Eclipse IDE
-
-Support for Rust development in the Eclipse IDE is provided by [Eclipse
-Corrosion](https://github.com/eclipse/corrosion). If available in PATH
-or in some standard location, `rust-analyzer` is detected and powers
-editing of Rust files without further configuration. If `rust-analyzer`
-is not detected, Corrosion will prompt you for configuration of your
-Rust toolchain and language server with a link to the *Window &gt;
-Preferences &gt; Rust* preference page; from here a button allows to
-download and configure `rust-analyzer`, but you can also reference
-another installation. You’ll need to close and reopen all .rs and Cargo
-files, or to restart the IDE, for this change to take effect.
-
-## Kate Text Editor
-
-Support for the language server protocol is built into Kate through the
-LSP plugin, which is included by default. It is preconfigured to use
-rust-analyzer for Rust sources since Kate 21.12.
-
-To change rust-analyzer config options, start from the following example
-and put it into Kate’s "User Server Settings" tab (located under the LSP
-Client settings):
-
- {
- "servers": {
- "rust": {
- "initializationOptions": {
- "cachePriming": {
- "enable": false
- },
- "check": {
- "allTargets": false
- },
- "checkOnSave": false
- }
- }
- }
- }
-
-Then click on apply, and restart the LSP server for your rust project.
-
-## juCi++
-
-[juCi++](https://gitlab.com/cppit/jucipp) has built-in support for the
-language server protocol, and since version 1.7.0 offers installation of
-both Rust and rust-analyzer when opening a Rust file.
-
-## Kakoune
-
-[Kakoune](https://kakoune.org/) supports LSP with the help of
-[`kak-lsp`](https://github.com/kak-lsp/kak-lsp). Follow the
-[instructions](https://github.com/kak-lsp/kak-lsp#installation) to
-install `kak-lsp`. To configure `kak-lsp`, refer to the [configuration
-section](https://github.com/kak-lsp/kak-lsp#configuring-kak-lsp) which
-is basically about copying the [configuration
-file](https://github.com/kak-lsp/kak-lsp/blob/master/kak-lsp.toml) in
-the right place (latest versions should use `rust-analyzer` by default).
-
-Finally, you need to configure Kakoune to talk to `kak-lsp` (see [Usage
-section](https://github.com/kak-lsp/kak-lsp#usage)). A basic
-configuration will only get you LSP but you can also activate inlay
-diagnostics and auto-formatting on save. The following might help you
-get all of this.
-
- eval %sh{kak-lsp --kakoune -s $kak_session} # Not needed if you load it with plug.kak.
- hook global WinSetOption filetype=rust %{
- # Enable LSP
- lsp-enable-window
-
- # Auto-formatting on save
- hook window BufWritePre .* lsp-formatting-sync
-
- # Configure inlay hints (only on save)
- hook window -group rust-inlay-hints BufWritePost .* rust-analyzer-inlay-hints
- hook -once -always window WinSetOption filetype=.* %{
- remove-hooks window rust-inlay-hints
- }
- }
-
-## Helix
-
-[Helix](https://docs.helix-editor.com/) supports LSP by default.
-However, it won’t install `rust-analyzer` automatically. You can follow
-instructions for installing [`rust-analyzer`
-binary](#rust-analyzer-language-server-binary).
-
-## Visual Studio 2022
-
-There are multiple rust-analyzer extensions for Visual Studio 2022 on
-Windows:
-
-### rust-analyzer.vs
-
-(License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0
-International)
-
-[Visual Studio
-Marketplace](https://marketplace.visualstudio.com/items?itemName=kitamstudios.RustAnalyzer)
-
-[GitHub](https://github.com/kitamstudios/rust-analyzer/)
-
-Support for Rust development in the Visual Studio IDE is enabled by the
-[rust-analyzer](https://marketplace.visualstudio.com/items?itemName=kitamstudios.RustAnalyzer)
-package. Either click on the download link or install from IDE’s
-extension manager. For now [Visual Studio
-2022](https://visualstudio.microsoft.com/downloads/) is required. All
-editions are supported viz. Community, Professional & Enterprise. The
-package aims to provide 0-friction installation and therefore comes
-loaded with most things required including rust-analyzer binary. If
-anything it needs is missing, appropriate errors / warnings will guide
-the user. E.g. cargo.exe needs to be in path and the package will tell
-you as much. This package is under rapid active development. So if you
-encounter any issues please file it at
-[rust-analyzer.vs](https://github.com/kitamstudios/rust-analyzer/).
-
-### VS\_RustAnalyzer
-
-(License: GPL)
-
-[Visual Studio
-Marketplace](https://marketplace.visualstudio.com/items?itemName=cchharris.vsrustanalyzer)
-
-[GitHub](https://github.com/cchharris/VS-RustAnalyzer)
-
-### SourceGear Rust
-
-(License: closed source)
-
-[Visual Studio
-Marketplace](https://marketplace.visualstudio.com/items?itemName=SourceGear.SourceGearRust)
-
-[GitHub (docs, issues,
-discussions)](https://github.com/sourcegear/rust-vs-extension)
-
-- Free (no-cost)
-
-- Supports all editions of Visual Studio 2022 on Windows: Community,
- Professional, or Enterprise
-
-## Lapce
-
-[Lapce](https://lapce.dev/) has a Rust plugin which you can install
-directly. Unfortunately, it downloads an old version of `rust-analyzer`,
-but you can set the server path under Settings.
-
## Crates
There is a package named `ra_ap_rust_analyzer` available on
-[crates.io](https://crates.io/crates/ra_ap_rust-analyzer), for someone
-who wants to use it programmatically.
+[crates.io](https://crates.io/crates/ra_ap_rust-analyzer), for people
+who want to use rust-analyzer programmatically.
For more details, see [the publish
workflow](https://github.com/rust-lang/rust-analyzer/blob/master/.github/workflows/autopublish.yaml).
-## Zed
-
-[Zed](https://zed.dev) has native `rust-analyzer` support. If the LSP
-binary is not available, Zed can install it when opening a Rust file.
diff --git a/docs/book/src/other_editors.md b/docs/book/src/other_editors.md
new file mode 100644
index 0000000000..0a9a453e01
--- /dev/null
+++ b/docs/book/src/other_editors.md
@@ -0,0 +1,423 @@
+# Other Editors
+
+rust-analyzer works with any editor that supports the [Language Server
+Protocol](https://microsoft.github.io/language-server-protocol/).
+
+This page assumes that you have already [installed the rust-analyzer
+binary](./rust_analyzer_binary.html).
+
+## Emacs
+
+To use `rust-analyzer`, you need to install and enable one of the two
+popular LSP client implementations for Emacs,
+[Eglot](https://github.com/joaotavora/eglot) or [LSP
+Mode](https://github.com/emacs-lsp/lsp-mode). Both enable
+`rust-analyzer` by default in Rust buffers if it is available.
+
+### Eglot
+
+Eglot is the more minimalistic and lightweight LSP client for Emacs,
+integrates well with existing Emacs functionality and is built into
+Emacs starting from release 29.
+
+After installing Eglot, e.g. via `M-x package-install` (not needed from
+Emacs 29), you can enable it via the `M-x eglot` command or load it
+automatically in `rust-mode` via
+
+```
+(add-hook 'rust-mode-hook 'eglot-ensure)
+```
+
+To enable clippy, you will need to configure the initialization options
+to pass the `check.command` setting.
+
+```
+(add-to-list 'eglot-server-programs
+ '((rust-ts-mode rust-mode) .
+ ("rust-analyzer" :initializationOptions (:check (:command "clippy")))))
+```
+
+For more detailed instructions and options see the [Eglot
+manual](https://joaotavora.github.io/eglot) (also available from Emacs
+via `M-x info`) and the [Eglot
+readme](https://github.com/joaotavora/eglot/blob/master/README.md).
+
+Eglot does not support the rust-analyzer extensions to the
+language-server protocol and does not aim to do so in the future. The
+[eglot-x](https://github.com/nemethf/eglot-x#rust-analyzer-extensions)
+package adds experimental support for those LSP extensions.
+
+### LSP Mode
+
+LSP-mode is the original LSP-client for emacs. Compared to Eglot it has
+a larger codebase and supports more features, like LSP protocol
+extensions. With extension packages like [LSP
+UI](https://github.com/emacs-lsp/lsp-mode) it offers a lot of visual
+eyecandy. Further it integrates well with [DAP
+mode](https://github.com/emacs-lsp/dap-mode) for support of the Debug
+Adapter Protocol.
+
+You can install LSP-mode via `M-x package-install` and then run it via
+the `M-x lsp` command or load it automatically in rust buffers with
+
+```
+(add-hook 'rust-mode-hook 'lsp-deferred)
+```
+
+For more information on how to set up LSP mode and its extension package
+see the instructions in the [LSP mode
+manual](https://emacs-lsp.github.io/lsp-mode/page/installation). Also
+see the [rust-analyzer
+section](https://emacs-lsp.github.io/lsp-mode/page/lsp-rust-analyzer/)
+for `rust-analyzer` specific options and commands, which you can
+optionally bind to keys.
+
+Note the excellent
+[guide](https://robert.kra.hn/posts/2021-02-07_rust-with-emacs/) from
+[@rksm](https://github.com/rksm) on how to set-up Emacs for Rust
+development with LSP mode and several other packages.
+
+## Vim/Neovim
+
+There are several LSP client implementations for Vim or Neovim:
+
+### coc-rust-analyzer
+
+1. Install coc.nvim by following the instructions at
+ [coc.nvim](https://github.com/neoclide/coc.nvim) (Node.js required)
+
+2. Run `:CocInstall coc-rust-analyzer` to install
+ [coc-rust-analyzer](https://github.com/fannheyward/coc-rust-analyzer),
+ this extension implements *most* of the features supported in the
+ VSCode extension:
+
+ - automatically install and upgrade stable/nightly releases
+
+ - same configurations as VSCode extension,
+ `rust-analyzer.server.path`, `rust-analyzer.cargo.features` etc.
+
+ - same commands too, `rust-analyzer.analyzerStatus`,
+ `rust-analyzer.ssr` etc.
+
+ - inlay hints for variables and method chaining, *Neovim Only*
+
+Note: coc-rust-analyzer is capable of installing or updating the
+rust-analyzer binary on its own.
+
+Note: for code actions, use `coc-codeaction-cursor` and
+`coc-codeaction-selected`; `coc-codeaction` and `coc-codeaction-line`
+are unlikely to be useful.
+
+### LanguageClient-neovim
+
+1. Install LanguageClient-neovim by following the instructions
+ [here](https://github.com/autozimu/LanguageClient-neovim)
+
+ - The GitHub project wiki has extra tips on configuration
+
+2. Configure by adding this to your Vim/Neovim config file (replacing
+ the existing Rust-specific line if it exists):
+
+ let g:LanguageClient_serverCommands = {
+ \ 'rust': ['rust-analyzer'],
+ \ }
+
+### YouCompleteMe
+
+Install YouCompleteMe by following the instructions
+[here](https://github.com/ycm-core/YouCompleteMe#installation).
+
+rust-analyzer is the default in ycm, it should work out of the box.
+
+### ALE
+
+To use the LSP server in [ale](https://github.com/dense-analysis/ale):
+
+ let g:ale_linters = {'rust': ['analyzer']}
+
+### nvim-lsp
+
+Neovim 0.5 has built-in language server support. For a quick start
+configuration of rust-analyzer, use
+[neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig#rust_analyzer).
+Once `neovim/nvim-lspconfig` is installed, use
+`lua require'lspconfig'.rust_analyzer.setup({})` in your `init.vim`.
+
+You can also pass LSP settings to the server:
+
+```lua
+lua << EOF
+local lspconfig = require'lspconfig'
+
+local on_attach = function(client)
+ require'completion'.on_attach(client)
+end
+
+lspconfig.rust_analyzer.setup({
+ on_attach = on_attach,
+ settings = {
+ ["rust-analyzer"] = {
+ imports = {
+ granularity = {
+ group = "module",
+ },
+ prefix = "self",
+ },
+ cargo = {
+ buildScripts = {
+ enable = true,
+ },
+ },
+ procMacro = {
+ enable = true
+ },
+ }
+ }
+})
+EOF
+```
+
+If you're running Neovim 0.10 or later, you can enable inlay hints via `on_attach`:
+
+```lua
+lspconfig.rust_analyzer.setup({
+ on_attach = function(client, bufnr)
+ vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
+ end
+})
+```
+
+Note that the hints are only visible after `rust-analyzer` has finished loading **and** you have to
+edit the file to trigger a re-render.
+
+See <https://sharksforarms.dev/posts/neovim-rust/> for more tips on
+getting started.
+
+Check out <https://github.com/mrcjkb/rustaceanvim> for a batteries
+included rust-analyzer setup for Neovim.
+
+### vim-lsp
+
+vim-lsp is installed by following [the plugin
+instructions](https://github.com/prabirshrestha/vim-lsp). It can be as
+simple as adding this line to your `.vimrc`:
+
+ Plug 'prabirshrestha/vim-lsp'
+
+Next you need to register the `rust-analyzer` binary. If it is avim.lspvailable
+in `$PATH`, you may want to add this to your `.vimrc`:
+
+ if executable('rust-analyzer')
+ au User lsp_setup call lsp#register_server({
+ \ 'name': 'Rust Language Server',
+ \ 'cmd': {server_info->['rust-analyzer']},
+ \ 'whitelist': ['rust'],
+ \ })
+ endif
+
+There is no dedicated UI for the server configuration, so you would need
+to send any options as a value of the `initialization_options` field, as
+described in the [Configuration](#configuration) section. Here is an
+example of how to enable the proc-macro support:
+
+ if executable('rust-analyzer')
+ au User lsp_setup call lsp#register_server({
+ \ 'name': 'Rust Language Server',
+ \ 'cmd': {server_info->['rust-analyzer']},
+ \ 'whitelist': ['rust'],
+ \ 'initialization_options': {
+ \ 'cargo': {
+ \ 'buildScripts': {
+ \ 'enable': v:true,
+ \ },
+ \ },
+ \ 'procMacro': {
+ \ 'enable': v:true,
+ \ },
+ \ },
+ \ })
+ endif
+
+## Sublime Text
+
+### Sublime Text 4:
+
+- Follow the instructions in
+ [LSP-rust-analyzer](https://github.com/sublimelsp/LSP-rust-analyzer).
+
+Install
+[LSP-file-watcher-chokidar](https://packagecontrol.io/packages/LSP-file-watcher-chokidar)
+to enable file watching (`workspace/didChangeWatchedFiles`).
+
+### Sublime Text 3:
+
+- Install the [LSP package](https://packagecontrol.io/packages/LSP).
+
+- From the command palette, run `LSP: Enable Language Server Globally`
+ and select `rust-analyzer`.
+
+If it worked, you should see "rust-analyzer, Line X, Column Y" on the
+left side of the status bar, and after waiting a bit, functionalities
+like tooltips on hovering over variables should become available.
+
+If you get an error saying `No such file or directory: 'rust-analyzer'`,
+see the [rust-analyzer binary installation](./rust_analyzer_binary.html) section.
+
+## GNOME Builder
+
+GNOME Builder 3.37.1 and newer has native `rust-analyzer` support. If
+the LSP binary is not available, GNOME Builder can install it when
+opening a Rust file.
+
+## Eclipse IDE
+
+Support for Rust development in the Eclipse IDE is provided by [Eclipse
+Corrosion](https://github.com/eclipse/corrosion). If available in PATH
+or in some standard location, `rust-analyzer` is detected and powers
+editing of Rust files without further configuration. If `rust-analyzer`
+is not detected, Corrosion will prompt you for configuration of your
+Rust toolchain and language server with a link to the *Window &gt;
+Preferences &gt; Rust* preference page; from here a button allows to
+download and configure `rust-analyzer`, but you can also reference
+another installation. You’ll need to close and reopen all .rs and Cargo
+files, or to restart the IDE, for this change to take effect.
+
+## Kate Text Editor
+
+Support for the language server protocol is built into Kate through the
+LSP plugin, which is included by default. It is preconfigured to use
+rust-analyzer for Rust sources since Kate 21.12.
+
+To change rust-analyzer config options, start from the following example
+and put it into Kate’s "User Server Settings" tab (located under the LSP
+Client settings):
+
+```json
+{
+ "servers": {
+ "rust": {
+ "initializationOptions": {
+ "cachePriming": {
+ "enable": false
+ },
+ "check": {
+ "allTargets": false
+ },
+ "checkOnSave": false
+ }
+ }
+ }
+}
+```
+
+Then click on apply, and restart the LSP server for your rust project.
+
+## juCi++
+
+[juCi++](https://gitlab.com/cppit/jucipp) has built-in support for the
+language server protocol, and since version 1.7.0 offers installation of
+both Rust and rust-analyzer when opening a Rust file.
+
+## Kakoune
+
+[Kakoune](https://kakoune.org/) supports LSP with the help of
+[`kak-lsp`](https://github.com/kak-lsp/kak-lsp). Follow the
+[instructions](https://github.com/kak-lsp/kak-lsp#installation) to
+install `kak-lsp`. To configure `kak-lsp`, refer to the [configuration
+section](https://github.com/kak-lsp/kak-lsp#configuring-kak-lsp) which
+is basically about copying the [configuration
+file](https://github.com/kak-lsp/kak-lsp/blob/master/kak-lsp.toml) in
+the right place (latest versions should use `rust-analyzer` by default).
+
+Finally, you need to configure Kakoune to talk to `kak-lsp` (see [Usage
+section](https://github.com/kak-lsp/kak-lsp#usage)). A basic
+configuration will only get you LSP but you can also activate inlay
+diagnostics and auto-formatting on save. The following might help you
+get all of this.
+
+ eval %sh{kak-lsp --kakoune -s $kak_session} # Not needed if you load it with plug.kak.
+ hook global WinSetOption filetype=rust %{
+ # Enable LSP
+ lsp-enable-window
+
+ # Auto-formatting on save
+ hook window BufWritePre .* lsp-formatting-sync
+
+ # Configure inlay hints (only on save)
+ hook window -group rust-inlay-hints BufWritePost .* rust-analyzer-inlay-hints
+ hook -once -always window WinSetOption filetype=.* %{
+ remove-hooks window rust-inlay-hints
+ }
+ }
+
+## Helix
+
+[Helix](https://docs.helix-editor.com/) supports LSP by default.
+However, it won’t install `rust-analyzer` automatically. You can follow
+instructions for [installing the rust-analyzer
+binary](./rust_analyzer_binary.html).
+
+## Visual Studio 2022
+
+There are multiple rust-analyzer extensions for Visual Studio 2022 on
+Windows:
+
+### rust-analyzer.vs
+
+(License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0
+International)
+
+[Visual Studio
+Marketplace](https://marketplace.visualstudio.com/items?itemName=kitamstudios.RustAnalyzer)
+
+[GitHub](https://github.com/kitamstudios/rust-analyzer/)
+
+Support for Rust development in the Visual Studio IDE is enabled by the
+[rust-analyzer](https://marketplace.visualstudio.com/items?itemName=kitamstudios.RustAnalyzer)
+package. Either click on the download link or install from IDE’s
+extension manager. For now [Visual Studio
+2022](https://visualstudio.microsoft.com/downloads/) is required. All
+editions are supported viz. Community, Professional & Enterprise. The
+package aims to provide 0-friction installation and therefore comes
+loaded with most things required including rust-analyzer binary. If
+anything it needs is missing, appropriate errors / warnings will guide
+the user. E.g. cargo.exe needs to be in path and the package will tell
+you as much. This package is under rapid active development. So if you
+encounter any issues please file it at
+[rust-analyzer.vs](https://github.com/kitamstudios/rust-analyzer/).
+
+### VS RustAnalyzer
+
+(License: GPL)
+
+[Visual Studio
+Marketplace](https://marketplace.visualstudio.com/items?itemName=cchharris.vsrustanalyzer)
+
+[GitHub](https://github.com/cchharris/VS-RustAnalyzer)
+
+### SourceGear Rust
+
+(License: closed source)
+
+[Visual Studio
+Marketplace](https://marketplace.visualstudio.com/items?itemName=SourceGear.SourceGearRust)
+
+[GitHub (docs, issues,
+discussions)](https://github.com/sourcegear/rust-vs-extension)
+
+- Free (no-cost)
+
+- Supports all editions of Visual Studio 2022 on Windows: Community,
+ Professional, or Enterprise
+
+## Lapce
+
+[Lapce](https://lapce.dev/) has a Rust plugin which you can install
+directly. Unfortunately, it downloads an old version of `rust-analyzer`,
+but you can set the server path under Settings.
+
+## Zed
+
+[Zed](https://zed.dev) has native `rust-analyzer` support. If the
+rust-analyzer binary is not available, Zed can install it when opening
+a Rust file.
diff --git a/docs/book/src/rust_analyzer_binary.md b/docs/book/src/rust_analyzer_binary.md
new file mode 100644
index 0000000000..c7ac3087ce
--- /dev/null
+++ b/docs/book/src/rust_analyzer_binary.md
@@ -0,0 +1,74 @@
+# rust-analyzer Binary
+
+Text editors require the `rust-analyzer` binary to be in
+`$PATH`. You can download pre-built binaries from the
+[releases](https://github.com/rust-lang/rust-analyzer/releases) page.
+You will need to uncompress and rename the binary for your platform,
+e.g. from `rust-analyzer-aarch64-apple-darwin.gz` on Mac OS to
+`rust-analyzer`, make it executable, then move it into a directory in
+your `$PATH`.
+
+On Linux to install the `rust-analyzer` binary into `~/.local/bin`,
+these commands should work:
+
+ $ mkdir -p ~/.local/bin
+ $ curl -L https://github.com/rust-lang/rust-analyzer/releases/latest/download/rust-analyzer-x86_64-unknown-linux-gnu.gz | gunzip -c - > ~/.local/bin/rust-analyzer
+ $ chmod +x ~/.local/bin/rust-analyzer
+
+Make sure that `~/.local/bin` is listed in the `$PATH` variable and use
+the appropriate URL if you’re not on a `x86-64` system.
+
+You don’t have to use `~/.local/bin`, any other path like `~/.cargo/bin`
+or `/usr/local/bin` will work just as well.
+
+Alternatively, you can install it from source using the command below.
+You’ll need the latest stable version of the Rust toolchain.
+
+ $ git clone https://github.com/rust-lang/rust-analyzer.git && cd rust-analyzer
+ $ cargo xtask install --server
+
+If your editor can’t find the binary even though the binary is on your
+`$PATH`, the likely explanation is that it doesn’t see the same `$PATH`
+as the shell, see [this
+issue](https://github.com/rust-lang/rust-analyzer/issues/1811). On Unix,
+running the editor from a shell or changing the `.desktop` file to set
+the environment should help.
+
+### rustup
+
+`rust-analyzer` is available in `rustup`:
+
+ $ rustup component add rust-analyzer
+
+### Arch Linux
+
+The `rust-analyzer` binary can be installed from the repos or AUR (Arch
+User Repository):
+
+- [`rust-analyzer`](https://www.archlinux.org/packages/extra/x86_64/rust-analyzer/)
+ (built from latest tagged source)
+
+- [`rust-analyzer-git`](https://aur.archlinux.org/packages/rust-analyzer-git)
+ (latest Git version)
+
+Install it with pacman, for example:
+
+ $ pacman -S rust-analyzer
+
+### Gentoo Linux
+
+`rust-analyzer` is installed when the `rust-analyzer` use flag is set for dev-lang/rust or dev-lang/rust-bin. You also need to set the `rust-src` use flag.
+
+### macOS
+
+The `rust-analyzer` binary can be installed via
+[Homebrew](https://brew.sh/).
+
+ $ brew install rust-analyzer
+
+### Windows
+
+It is recommended to install the latest Microsoft Visual C++ Redistributable prior to installation.
+Download links can be found
+[here](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist).
+
diff --git a/docs/book/src/vs_code.md b/docs/book/src/vs_code.md
new file mode 100644
index 0000000000..233b862d2c
--- /dev/null
+++ b/docs/book/src/vs_code.md
@@ -0,0 +1,121 @@
+# VS Code
+
+This is the best supported editor at the moment. The rust-analyzer
+plugin for VS Code is maintained [in
+tree](https://github.com/rust-lang/rust-analyzer/tree/master/editors/code).
+
+You can install the latest release of the plugin from [the
+marketplace](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).
+
+Note that the plugin may cause conflicts with the [previous official
+Rust
+plugin](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust).
+The latter is no longer maintained and should be uninstalled.
+
+The server binary is stored in the extension install directory, which
+starts with `rust-lang.rust-analyzer-` and is located under:
+
+- Linux: `~/.vscode/extensions`
+
+- Linux (Remote, such as WSL): `~/.vscode-server/extensions`
+
+- macOS: `~/.vscode/extensions`
+
+- Windows: `%USERPROFILE%\.vscode\extensions`
+
+As an exception, on NixOS, the extension makes a copy of the server and
+stores it under
+`~/.config/Code/User/globalStorage/rust-lang.rust-analyzer`.
+
+Note that we only support the two most recent versions of VS Code.
+
+### Updates
+
+The extension will be updated automatically as new versions become
+available. It will ask your permission to download the matching language
+server version binary if needed.
+
+#### Nightly
+
+We ship nightly releases for VS Code. To help us out by testing the
+newest code, you can enable pre-release versions in the Code extension
+page.
+
+### Manual installation
+
+Alternatively, download a VSIX corresponding to your platform from the
+[releases](https://github.com/rust-lang/rust-analyzer/releases) page.
+
+Install the extension with the `Extensions: Install from VSIX` command
+within VS Code, or from the command line via:
+
+ $ code --install-extension /path/to/rust-analyzer.vsix
+
+If you are running an unsupported platform, you can install
+`rust-analyzer-no-server.vsix` and compile or obtain a server binary.
+Copy the server anywhere, then add the path to your settings.json, for
+example:
+
+```json
+{ "rust-analyzer.server.path": "~/.local/bin/rust-analyzer-linux" }
+```
+
+### Building From Source
+
+Both the server and the Code plugin can be installed from source:
+
+ $ git clone https://github.com/rust-lang/rust-analyzer.git && cd rust-analyzer
+ $ cargo xtask install
+
+You’ll need Cargo, nodejs (matching a supported version of VS Code) and
+npm for this.
+
+Note that installing via `xtask install` does not work for VS Code
+Remote, instead you’ll need to install the `.vsix` manually.
+
+If you’re not using Code, you can compile and install only the LSP
+server:
+
+ $ cargo xtask install --server
+
+Make sure that `.cargo/bin` is in `$PATH` and precedes paths where
+`rust-analyzer` may also be installed. Specifically, `rustup` includes a
+proxy called `rust-analyzer`, which can cause problems if you’re
+planning to use a source build or even a downloaded binary.
+
+## VS Code or VSCodium in Flatpak
+
+Setting up `rust-analyzer` with a Flatpak version of Code is not trivial
+because of the Flatpak sandbox. While the sandbox can be disabled for
+some directories, `/usr/bin` will always be mounted under
+`/run/host/usr/bin`. This prevents access to the system’s C compiler, a
+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`.
+
+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`)
+
+If you want to use Flatpak in combination with `rustup`, the following
+steps might help:
+
+- both Rust and `rustup` have to be installed using
+ <https://rustup.rs>. Distro packages *will not* work.
+
+- you need to launch Code, open a terminal and run `echo $PATH`
+
+- using
+ [Flatseal](https://flathub.org/apps/details/com.github.tchx84.Flatseal),
+ you must add an environment variable called `PATH`. Set its value to
+ the output from above, appending `:~/.cargo/bin`, where `~` is the
+ path to your home directory. You must replace `~`, as it won’t be
+ expanded otherwise.
+
+- while Flatseal is open, you must enable access to "All user files"
+
+A C compiler should already be available via `org.freedesktop.Sdk`. Any
+other tools or libraries you will need to acquire from Flatpak.
+
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 6027f81331..86a066454a 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -23,7 +23,7 @@
"@typescript-eslint/parser": "^6.0.0",
"@vscode/test-electron": "^2.3.8",
"@vscode/vsce": "^3.0.0",
- "esbuild": "^0.18.12",
+ "esbuild": "^0.25.0",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"ovsx": "^0.8.2",
@@ -256,356 +256,429 @@
"node": ">=16"
}
},
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
+ "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/android-arm": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.12.tgz",
- "integrity": "sha512-LIxaNIQfkFZbTLb4+cX7dozHlAbAshhFE5PKdro0l+FnCpx1GDJaQ2WMcqm+ToXKMt8p8Uojk/MFRuGyz3V5Sw==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
+ "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.12.tgz",
- "integrity": "sha512-BMAlczRqC/LUt2P97E4apTBbkvS9JTJnp2DKFbCwpZ8vBvXVbNdqmvzW/OsdtI/+mGr+apkkpqGM8WecLkPgrA==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
+ "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.12.tgz",
- "integrity": "sha512-zU5MyluNsykf5cOJ0LZZZjgAHbhPJ1cWfdH1ZXVMXxVMhEV0VZiZXQdwBBVvmvbF28EizeK7obG9fs+fpmS0eQ==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
+ "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.12.tgz",
- "integrity": "sha512-zUZMep7YONnp6954QOOwEBwFX9svlKd3ov6PkxKd53LGTHsp/gy7vHaPGhhjBmEpqXEXShi6dddjIkmd+NgMsA==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
+ "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.12.tgz",
- "integrity": "sha512-ohqLPc7i67yunArPj1+/FeeJ7AgwAjHqKZ512ADk3WsE3FHU9l+m5aa7NdxXr0HmN1bjDlUslBjWNbFlD9y12Q==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
+ "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.12.tgz",
- "integrity": "sha512-GIIHtQXqgeOOqdG16a/A9N28GpkvjJnjYMhOnXVbn3EDJcoItdR58v/pGN31CHjyXDc8uCcRnFWmqaJt24AYJg==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
+ "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.12.tgz",
- "integrity": "sha512-zK0b9a1/0wZY+6FdOS3BpZcPc1kcx2G5yxxfEJtEUzVxI6n/FrC2Phsxj/YblPuBchhBZ/1wwn7AyEBUyNSa6g==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
+ "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.12.tgz",
- "integrity": "sha512-y75OijvrBE/1XRrXq1jtrJfG26eHeMoqLJ2dwQNwviwTuTtHGCojsDO6BJNF8gU+3jTn1KzJEMETytwsFSvc+Q==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
+ "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.12.tgz",
- "integrity": "sha512-JKgG8Q/LL/9sw/iHHxQyVMoQYu3rU3+a5Z87DxC+wAu3engz+EmctIrV+FGOgI6gWG1z1+5nDDbXiRMGQZXqiw==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
+ "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.12.tgz",
- "integrity": "sha512-yoRIAqc0B4lDIAAEFEIu9ttTRFV84iuAl0KNCN6MhKLxNPfzwCBvEMgwco2f71GxmpBcTtn7KdErueZaM2rEvw==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
+ "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
"cpu": [
"ia32"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.12.tgz",
- "integrity": "sha512-qYgt3dHPVvf/MgbIBpJ4Sup/yb9DAopZ3a2JgMpNKIHUpOdnJ2eHBo/aQdnd8dJ21X/+sS58wxHtA9lEazYtXQ==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
+ "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
"cpu": [
"loong64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.12.tgz",
- "integrity": "sha512-wHphlMLK4ufNOONqukELfVIbnGQJrHJ/mxZMMrP2jYrPgCRZhOtf0kC4yAXBwnfmULimV1qt5UJJOw4Kh13Yfg==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
+ "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
"cpu": [
"mips64el"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.12.tgz",
- "integrity": "sha512-TeN//1Ft20ZZW41+zDSdOI/Os1bEq5dbvBvYkberB7PHABbRcsteeoNVZFlI0YLpGdlBqohEpjrn06kv8heCJg==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
+ "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
"cpu": [
"ppc64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.12.tgz",
- "integrity": "sha512-AgUebVS4DoAblBgiB2ACQ/8l4eGE5aWBb8ZXtkXHiET9mbj7GuWt3OnsIW/zX+XHJt2RYJZctbQ2S/mDjbp0UA==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
+ "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
"cpu": [
"riscv64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.12.tgz",
- "integrity": "sha512-dJ3Rb3Ei2u/ysSXd6pzleGtfDdc2MuzKt8qc6ls8vreP1G3B7HInX3i7gXS4BGeVd24pp0yqyS7bJ5NHaI9ing==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
+ "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
"cpu": [
"s390x"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.12.tgz",
- "integrity": "sha512-OrNJMGQbPaVyHHcDF8ybNSwu7TDOfX8NGpXCbetwOSP6txOJiWlgQnRymfC9ocR1S0Y5PW0Wb1mV6pUddqmvmQ==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
+ "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
+ "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.12.tgz",
- "integrity": "sha512-55FzVCAiwE9FK8wWeCRuvjazNRJ1QqLCYGZVB6E8RuQuTeStSwotpSW4xoRGwp3a1wUsaVCdYcj5LGCASVJmMg==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
+ "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
+ "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.12.tgz",
- "integrity": "sha512-qnluf8rfb6Y5Lw2tirfK2quZOBbVqmwxut7GPCIJsM8lc4AEUj9L8y0YPdLaPK0TECt4IdyBdBD/KRFKorlK3g==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
+ "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.12.tgz",
- "integrity": "sha512-+RkKpVQR7bICjTOPUpkTBTaJ4TFqQBX5Ywyd/HSdDkQGn65VPkTsR/pL4AMvuMWy+wnXgIl4EY6q4mVpJal8Kg==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
+ "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.12.tgz",
- "integrity": "sha512-GNHuciv0mFM7ouzsU0+AwY+7eV4Mgo5WnbhfDCQGtpvOtD1vbOiRjPYG6dhmMoFyBjj+pNqQu2X+7DKn0KQ/Gw==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
+ "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.12.tgz",
- "integrity": "sha512-kR8cezhYipbbypGkaqCTWIeu4zID17gamC8YTPXYtcN3E5BhhtTnwKBn9I0PJur/T6UVwIEGYzkffNL0lFvxEw==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
+ "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
"cpu": [
"ia32"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.12.tgz",
- "integrity": "sha512-O0UYQVkvfM/jO8a4OwoV0mAKSJw+mjWTAd1MJd/1FCX6uiMdLmMRPK/w6e9OQ0ob2WGxzIm9va/KG0Ja4zIOgg==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
+ "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": {
@@ -2521,40 +2594,44 @@
}
},
"node_modules/esbuild": {
- "version": "0.18.12",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.12.tgz",
- "integrity": "sha512-XuOVLDdtsDslXStStduT41op21Ytmf4/BDS46aa3xPJ7X5h2eMWBF1oAe3QjUH3bDksocNXgzGUZ7XHIBya6Tg==",
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
+ "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
"dev": true,
"hasInstallScript": true,
+ "license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"optionalDependencies": {
- "@esbuild/android-arm": "0.18.12",
- "@esbuild/android-arm64": "0.18.12",
- "@esbuild/android-x64": "0.18.12",
- "@esbuild/darwin-arm64": "0.18.12",
- "@esbuild/darwin-x64": "0.18.12",
- "@esbuild/freebsd-arm64": "0.18.12",
- "@esbuild/freebsd-x64": "0.18.12",
- "@esbuild/linux-arm": "0.18.12",
- "@esbuild/linux-arm64": "0.18.12",
- "@esbuild/linux-ia32": "0.18.12",
- "@esbuild/linux-loong64": "0.18.12",
- "@esbuild/linux-mips64el": "0.18.12",
- "@esbuild/linux-ppc64": "0.18.12",
- "@esbuild/linux-riscv64": "0.18.12",
- "@esbuild/linux-s390x": "0.18.12",
- "@esbuild/linux-x64": "0.18.12",
- "@esbuild/netbsd-x64": "0.18.12",
- "@esbuild/openbsd-x64": "0.18.12",
- "@esbuild/sunos-x64": "0.18.12",
- "@esbuild/win32-arm64": "0.18.12",
- "@esbuild/win32-ia32": "0.18.12",
- "@esbuild/win32-x64": "0.18.12"
+ "@esbuild/aix-ppc64": "0.25.0",
+ "@esbuild/android-arm": "0.25.0",
+ "@esbuild/android-arm64": "0.25.0",
+ "@esbuild/android-x64": "0.25.0",
+ "@esbuild/darwin-arm64": "0.25.0",
+ "@esbuild/darwin-x64": "0.25.0",
+ "@esbuild/freebsd-arm64": "0.25.0",
+ "@esbuild/freebsd-x64": "0.25.0",
+ "@esbuild/linux-arm": "0.25.0",
+ "@esbuild/linux-arm64": "0.25.0",
+ "@esbuild/linux-ia32": "0.25.0",
+ "@esbuild/linux-loong64": "0.25.0",
+ "@esbuild/linux-mips64el": "0.25.0",
+ "@esbuild/linux-ppc64": "0.25.0",
+ "@esbuild/linux-riscv64": "0.25.0",
+ "@esbuild/linux-s390x": "0.25.0",
+ "@esbuild/linux-x64": "0.25.0",
+ "@esbuild/netbsd-arm64": "0.25.0",
+ "@esbuild/netbsd-x64": "0.25.0",
+ "@esbuild/openbsd-arm64": "0.25.0",
+ "@esbuild/openbsd-x64": "0.25.0",
+ "@esbuild/sunos-x64": "0.25.0",
+ "@esbuild/win32-arm64": "0.25.0",
+ "@esbuild/win32-ia32": "0.25.0",
+ "@esbuild/win32-x64": "0.25.0"
}
},
"node_modules/escalade": {
diff --git a/editors/code/package.json b/editors/code/package.json
index 0a60376770..3f09033051 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -59,7 +59,7 @@
"@typescript-eslint/parser": "^6.0.0",
"@vscode/test-electron": "^2.3.8",
"@vscode/vsce": "^3.0.0",
- "esbuild": "^0.18.12",
+ "esbuild": "^0.25.0",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"ovsx": "^0.8.2",
@@ -1473,8 +1473,8 @@
{
"title": "files",
"properties": {
- "rust-analyzer.files.excludeDirs": {
- "markdownDescription": "These directories will be ignored by rust-analyzer. They are\nrelative to the workspace root, and globs are not supported. You may\nalso need to add the folders to Code's `files.watcherExclude`.",
+ "rust-analyzer.files.exclude": {
+ "markdownDescription": "These paths (file/directories) will be ignored by rust-analyzer. They are\nrelative to the workspace root, and globs are not supported. You may\nalso need to add the folders to Code's `files.watcherExclude`.",
"default": [],
"type": "array",
"items": {
diff --git a/editors/code/walkthrough-setup-tips.md b/editors/code/walkthrough-setup-tips.md
index fda4ac8002..aabe0dd662 100644
--- a/editors/code/walkthrough-setup-tips.md
+++ b/editors/code/walkthrough-setup-tips.md
@@ -5,6 +5,7 @@ Add the following to settings.json to mark Rust library sources as read-only:
```json
"files.readonlyInclude": {
"**/.cargo/registry/src/**/*.rs": true,
+ "**/.cargo/git/checkouts/**/*.rs": true,
"**/lib/rustlib/src/rust/library/**/*.rs": true,
},
```
diff --git a/rust-version b/rust-version
index f414893b6c..4006d06e4f 100644
--- a/rust-version
+++ b/rust-version
@@ -1 +1 @@
-d9a4a47b8b3dc0bdff83360cea2013200d60d49c
+273465e1f2932a30a5b56ac95859cdc86f3f33fa
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs
index 18f49643dc..e84c259e97 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -117,7 +117,7 @@ impl fmt::Display for Location {
let path = self.file.strip_prefix(project_root()).unwrap().display().to_string();
let path = path.replace('\\', "/");
let name = self.file.file_name().unwrap();
- write!(f, " [{}]({}#{}) ", name.to_str().unwrap(), path, self.line)
+ write!(f, " [{}](/{}#{}) ", name.to_str().unwrap(), path, self.line)
}
}