From 2ec4b9bb37d0c0fffcf92134a7ddd1a156e95d36 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 29 Jun 2026 03:34:38 +0300 Subject: [PATCH 01/38] Avoid type unification errors in term search --- src/tools/rust-analyzer/crates/hir/src/term_search.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/crates/hir/src/term_search.rs b/src/tools/rust-analyzer/crates/hir/src/term_search.rs index 1cc6766bfb3b4..6006982cda68c 100644 --- a/src/tools/rust-analyzer/crates/hir/src/term_search.rs +++ b/src/tools/rust-analyzer/crates/hir/src/term_search.rs @@ -175,6 +175,7 @@ impl<'db> LookupTable<'db> { /// transitive. For example `Vec` and `FxHashSet` both unify with `Iterator`, /// but they clearly do not unify themselves. fn insert(&mut self, ty: Type<'db>, exprs: impl Iterator>) { + let ty = ty.instantiate_with_errors(); match self.data.get_mut(&ty) { Some(it) => { it.extend_with_threshold(self.many_threshold, exprs); From 41d005f05805b1ff8ad7d53e248c63b24bdb5f64 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 17 Aug 2026 03:36:55 +0300 Subject: [PATCH 02/38] Fix parsing of `self::` in fn param list --- .../crates/parser/src/grammar/params.rs | 39 ++++-- .../parser/test_data/generated/runner.rs | 8 ++ .../inline/err/non_isolated_self_err.rast | 131 ++++++++++++++++++ .../inline/err/non_isolated_self_err.rs | 3 + .../parser/inline/ok/non_isolated_self.rast | 109 +++++++++++++++ .../parser/inline/ok/non_isolated_self.rs | 3 + 6 files changed, 283 insertions(+), 10 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/non_isolated_self_err.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/non_isolated_self_err.rs create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/non_isolated_self.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/non_isolated_self.rs diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs index 51ffcd070694c..49abd6267d528 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs @@ -165,7 +165,23 @@ fn variadic_param(p: &mut Parser<'_>) -> bool { // fn e(mut self) {} // } fn opt_self_param(p: &mut Parser<'_>, m: Marker) -> Result<(), Marker> { - if p.at(T![self]) || p.at(T![mut]) && p.nth(1) == T![self] { + let is_isolated_self = |p: &mut Parser<'_>, n| { + // test non_isolated_self + // fn f(self::S: S) {} + // fn g(&self::S: &S) {} + // fn h(&mut self::S: &mut S) {} + + // test_err non_isolated_self_err + // fn f(mut self::S: S) {} + // fn g(&'l self::S: &S) {} + // fn h(&'l mut self::S: &mut S) {} + p.nth_at(n, T![self]) && !p.nth_at(n + 1, T![::]) + }; + let mut self_pos = 0; + if p.at(T![mut]) { + self_pos += 1; + } + if is_isolated_self(p, self_pos) { p.eat(T![mut]); self_as_name(p); // test arb_self_types @@ -177,17 +193,20 @@ fn opt_self_param(p: &mut Parser<'_>, m: Marker) -> Result<(), Marker> { types::ascription(p); } } else { - let la1 = p.nth(1); - let la2 = p.nth(2); - let la3 = p.nth(3); - if !matches!( - (p.current(), la1, la2, la3), - (T![&], T![self], _, _) - | (T![&], T![mut] | LIFETIME_IDENT, T![self], _) - | (T![&], LIFETIME_IDENT, T![mut], T![self]) - ) { + if !p.at(T![&]) { return Err(m); } + let mut self_pos = 1; + if p.nth_at(self_pos, LIFETIME_IDENT) { + self_pos += 1; + } + if p.nth_at(self_pos, T![mut]) { + self_pos += 1; + } + if !is_isolated_self(p, self_pos) { + return Err(m); + } + p.bump(T![&]); if p.at(LIFETIME_IDENT) { lifetime(p); diff --git a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs index 520e736e302bb..9e3de7517f227 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs +++ b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs @@ -492,6 +492,10 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/nocontentexpr_after_item.rs"); } #[test] + fn non_isolated_self() { + run_and_expect_no_errors("test_data/parser/inline/ok/non_isolated_self.rs"); + } + #[test] fn not_null_pat() { run_and_expect_no_errors("test_data/parser/inline/ok/not_null_pat.rs"); } #[test] fn offset_of_parens() { @@ -928,6 +932,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/missing_static_type.rs"); } #[test] + fn non_isolated_self_err() { + run_and_expect_errors("test_data/parser/inline/err/non_isolated_self_err.rs"); + } + #[test] fn path_item_without_excl() { run_and_expect_errors("test_data/parser/inline/err/path_item_without_excl.rs"); } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/non_isolated_self_err.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/non_isolated_self_err.rast new file mode 100644 index 0000000000000..e75e641fedcc0 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/non_isolated_self_err.rast @@ -0,0 +1,131 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "f" + PARAM_LIST + L_PAREN "(" + PARAM + IDENT_PAT + MUT_KW "mut" + WHITESPACE " " + ERROR + SELF_KW "self" + COLON ":" + ERROR + COLON ":" + PARAM + IDENT_PAT + NAME + IDENT "S" + COLON ":" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "g" + PARAM_LIST + L_PAREN "(" + PARAM + REF_PAT + AMP "&" + ERROR + LIFETIME_IDENT "'l" + WHITESPACE " " + PARAM + PATH_PAT + PATH + PATH + PATH_SEGMENT + NAME_REF + SELF_KW "self" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "S" + COLON ":" + WHITESPACE " " + REF_TYPE + AMP "&" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "h" + PARAM_LIST + L_PAREN "(" + PARAM + REF_PAT + AMP "&" + ERROR + LIFETIME_IDENT "'l" + WHITESPACE " " + PARAM + IDENT_PAT + MUT_KW "mut" + WHITESPACE " " + ERROR + SELF_KW "self" + COLON ":" + ERROR + COLON ":" + PARAM + IDENT_PAT + NAME + IDENT "S" + COLON ":" + WHITESPACE " " + REF_TYPE + AMP "&" + MUT_KW "mut" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" +error 9: expected a name +error 14: expected type +error 15: expected `,` +error 30: expected pattern +error 32: missing type for function parameter +error 32: expected `,` +error 55: expected pattern +error 57: missing type for function parameter +error 57: expected `,` +error 62: expected a name +error 67: expected type +error 68: expected `,` diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/non_isolated_self_err.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/non_isolated_self_err.rs new file mode 100644 index 0000000000000..d36479c660bdd --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/non_isolated_self_err.rs @@ -0,0 +1,3 @@ +fn f(mut self::S: S) {} +fn g(&'l self::S: &S) {} +fn h(&'l mut self::S: &mut S) {} diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/non_isolated_self.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/non_isolated_self.rast new file mode 100644 index 0000000000000..fd4735452ba90 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/non_isolated_self.rast @@ -0,0 +1,109 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "f" + PARAM_LIST + L_PAREN "(" + PARAM + PATH_PAT + PATH + PATH + PATH_SEGMENT + NAME_REF + SELF_KW "self" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "S" + COLON ":" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "g" + PARAM_LIST + L_PAREN "(" + PARAM + REF_PAT + AMP "&" + PATH_PAT + PATH + PATH + PATH_SEGMENT + NAME_REF + SELF_KW "self" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "S" + COLON ":" + WHITESPACE " " + REF_TYPE + AMP "&" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "h" + PARAM_LIST + L_PAREN "(" + PARAM + REF_PAT + AMP "&" + MUT_KW "mut" + WHITESPACE " " + PATH_PAT + PATH + PATH + PATH_SEGMENT + NAME_REF + SELF_KW "self" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "S" + COLON ":" + WHITESPACE " " + REF_TYPE + AMP "&" + MUT_KW "mut" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/non_isolated_self.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/non_isolated_self.rs new file mode 100644 index 0000000000000..29ee4ce4c2f6a --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/non_isolated_self.rs @@ -0,0 +1,3 @@ +fn f(self::S: S) {} +fn g(&self::S: &S) {} +fn h(&mut self::S: &mut S) {} From 95a793fbb3dba0a01e53cf040bf8deb2d6109f30 Mon Sep 17 00:00:00 2001 From: Aditya-PS-05 Date: Fri, 28 Aug 2026 21:00:41 +0530 Subject: [PATCH 03/38] fix: accept Self as non-leading path segment in attribute paths --- .../rust-analyzer/crates/hir-expand/src/mod_path.rs | 1 + .../src/handlers/unresolved_macro_call.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs index 4cc194452fbcd..e329e36dde8cf 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs @@ -316,6 +316,7 @@ fn convert_path( let name = match segment.kind()? { ast::PathSegmentKind::Name(name) => name.as_name(), ast::PathSegmentKind::SelfKw => continue, + ast::PathSegmentKind::SelfTypeKw => Name::new_symbol_root(sym::Self_), _ => return None, }; mod_path.segments.push(name); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs index 9be7ef6fe7181..8f36f1e583f31 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs @@ -105,4 +105,16 @@ fn foo() { "#, ); } + + #[test] + fn tool_attribute_with_keyword_segment() { + check_diagnostics( + r#" +#[diagnostic::Self(message = "foo")] +trait Foo {} +#[clippy::Self(message = "foo")] +trait Bar {} + "#, + ); + } } From fd6ad1768f8db6af64197c825bb2235132867e25 Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 20 Aug 2026 22:37:45 +0200 Subject: [PATCH 04/38] Install cargo tools with locked dependencies Installing cargo tools (`cargo install`) without locked dependencies exposes users to supply-chain attacks to all the dependencies of the tool (https://blog.rust-lang.org/2026/08/20/supply-chain-attack-on-arrayref/). Using `cargo install --locked` reduces this risk to a compromise of the tool itself, while using the locked and hashed version of the dependencies. I went through all `rg "cargo install"` hits in the repository and added `--locked` to all but explanatory examples (such as cargo's docs on `cargo install` itself). I validated that those tools publish functioning `Cargo.lock`s with https://gist.github.com/konstin/bcb1169c1c1120c259dca64e777a64d0. --- src/tools/rust-analyzer/.github/workflows/autopublish.yaml | 2 +- src/tools/rust-analyzer/.github/workflows/ci.yaml | 2 +- src/tools/rust-analyzer/.github/workflows/fuzz.yml | 2 +- src/tools/rust-analyzer/.github/workflows/publish-libs.yaml | 2 +- src/tools/rust-analyzer/docs/book/README.md | 2 +- src/tools/rust-analyzer/xtask/src/main.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml index abb9b521f1a50..0f510ecf4723d 100644 --- a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml +++ b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml @@ -28,7 +28,7 @@ jobs: run: rustup update --no-self-update stable - name: Install cargo-workspaces - run: cargo install cargo-workspaces --version "0.3.6" + run: cargo install --locked cargo-workspaces --version "0.3.6" - name: Publish Crates env: diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index 14582f8405f55..d227ec2e41b6f 100644 --- a/src/tools/rust-analyzer/.github/workflows/ci.yaml +++ b/src/tools/rust-analyzer/.github/workflows/ci.yaml @@ -50,7 +50,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Install rustup-toolchain-install-master - run: cargo install rustup-toolchain-install-master@1.11.0 + run: cargo install --locked rustup-toolchain-install-master@1.11.0 # Install a pinned rustc commit to avoid surprises - name: Install Rust toolchain diff --git a/src/tools/rust-analyzer/.github/workflows/fuzz.yml b/src/tools/rust-analyzer/.github/workflows/fuzz.yml index af0e03598ecf2..1c3ad1cd22211 100644 --- a/src/tools/rust-analyzer/.github/workflows/fuzz.yml +++ b/src/tools/rust-analyzer/.github/workflows/fuzz.yml @@ -38,6 +38,6 @@ jobs: - name: Build fuzzers run: | - cargo install cargo-fuzz + cargo install --locked cargo-fuzz cd crates/syntax cargo +nightly fuzz build diff --git a/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml b/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml index 762b7bda871c0..ff04f1b830666 100644 --- a/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml +++ b/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml @@ -22,7 +22,7 @@ jobs: run: rustup update --no-self-update stable - name: Install cargo-workspaces - run: cargo install cargo-workspaces --version "0.3.6" + run: cargo install --locked cargo-workspaces --version "0.3.6" - name: Publish Crates env: diff --git a/src/tools/rust-analyzer/docs/book/README.md b/src/tools/rust-analyzer/docs/book/README.md index cd4d8783a4d20..229180226999d 100644 --- a/src/tools/rust-analyzer/docs/book/README.md +++ b/src/tools/rust-analyzer/docs/book/README.md @@ -7,7 +7,7 @@ The rust analyzer manual uses [mdbook](https://rust-lang.github.io/mdBook/). To run the documentation site locally: ```bash -cargo install mdbook +cargo install --locked mdbook cargo xtask codegen cd docs/book mdbook serve diff --git a/src/tools/rust-analyzer/xtask/src/main.rs b/src/tools/rust-analyzer/xtask/src/main.rs index 764cc09ded6c4..dbfc39534c673 100644 --- a/src/tools/rust-analyzer/xtask/src/main.rs +++ b/src/tools/rust-analyzer/xtask/src/main.rs @@ -72,7 +72,7 @@ fn run_fuzzer(sh: &Shell) -> anyhow::Result<()> { let _d = sh.push_dir("./crates/syntax"); let _e = sh.push_env("RUSTUP_TOOLCHAIN", "nightly"); if cmd!(sh, "cargo fuzz --help").read().is_err() { - cmd!(sh, "cargo install cargo-fuzz").run()?; + cmd!(sh, "cargo install --locked cargo-fuzz").run()?; }; // Expecting nightly rustc From 207b861f314b939ad6bca63c8fd1ad3caf7f6ea3 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 23 Aug 2026 10:40:20 +0300 Subject: [PATCH 05/38] Represent doc comments using their own node and not as COMMENT trivia For two reasons: - This simplifies my work to fix https://github.com/rust-lang/rust-analyzer/issues/23088; to fix that issue, macros must have to be able to return doc comments (and not just desugared doc comments), and code in `syntax-bridge` doesn't expect macros to return trivia. Making doc comments non-trivia solves that. - It should simplify the work to attach trivia to tokens; doc comments have no obvious place to attach (for example, when between two attributes we must attach them to either the preceding `]` or the following `#`, both will complicate code handling them). Furthermore, arguably doc comments are really not a trivia: it's an error to put them in an unexpected place, and reason 2 above reveals that they're more like a kind of an attribute than a comment. This touches a lot of places (especially assists etc.) subtly; I fixed what I found and the tests helped reveal more, but it's certainly possible some places are still not handling them correctly now. --- .../crates/hir-def/src/attrs/docs.rs | 37 ++- .../src/handlers/convert_comment_block.rs | 51 ++-- .../convert_comment_from_or_to_doc.rs | 79 ++---- .../src/handlers/desugar_doc_comment.rs | 40 ++- .../src/handlers/extract_function.rs | 2 +- .../src/handlers/extract_module.rs | 5 +- .../extract_struct_from_enum_variant.rs | 2 +- .../src/handlers/extract_variable.rs | 6 +- .../src/handlers/fix_visibility.rs | 1 + .../src/handlers/generate_derive.rs | 4 +- .../generate_documentation_template.rs | 16 +- .../src/handlers/generate_trait_from_impl.rs | 6 +- .../crates/ide-assists/src/utils.rs | 9 +- .../src/completions/item_list/trait_impl.rs | 5 +- .../crates/ide-db/src/imports/insert_use.rs | 2 +- .../crates/ide-ssr/src/from_comment.rs | 2 +- .../rust-analyzer/crates/ide/src/doc_links.rs | 37 ++- .../crates/ide/src/doc_links/tests.rs | 2 +- .../crates/ide/src/extend_selection.rs | 11 +- .../crates/ide/src/folding_ranges.rs | 60 ++--- .../crates/ide/src/goto_definition.rs | 4 +- .../rust-analyzer/crates/ide/src/hover.rs | 6 +- .../crates/ide/src/inlay_hints/chaining.rs | 4 +- .../crates/ide/src/join_lines.rs | 11 +- .../rust-analyzer/crates/ide/src/moniker.rs | 4 +- .../crates/ide/src/static_index.rs | 19 +- .../crates/ide/src/syntax_highlighting.rs | 2 +- .../ide/src/syntax_highlighting/highlight.rs | 12 +- .../crates/ide/src/typing/on_enter.rs | 8 +- .../crates/parser/src/grammar/attributes.rs | 14 +- .../parser/src/grammar/generic_params.rs | 4 +- .../crates/parser/src/grammar/items/adt.rs | 4 +- .../crates/parser/src/grammar/params.rs | 6 +- .../crates/parser/src/lexed_str.rs | 14 +- .../parser/src/syntax_kind/generated.rs | 8 + ...closed_nested_block_comment_partially.rast | 2 +- .../lexer/ok/single_line_comments.rast | 12 +- .../test_data/parser/ok/0035_weird_exprs.rast | 12 +- .../parser/test_data/parser/ok/0037_mod.rast | 3 +- .../test_data/parser/ok/0045_block_attrs.rast | 9 +- .../ok/0046_extern_inner_attributes.rast | 3 +- .../0053_outer_attribute_on_macro_rules.rast | 3 +- .../parser/ok/0065_comment_newline.rast | 3 +- .../crates/syntax-bridge/src/lib.rs | 51 ++-- .../rust-analyzer/crates/syntax/rust.ungram | 154 ++++++----- .../rust-analyzer/crates/syntax/src/ast.rs | 40 +-- .../crates/syntax/src/ast/edit.rs | 4 +- .../crates/syntax/src/ast/generated/nodes.rs | 252 +++++++----------- .../crates/syntax/src/ast/node_ext.rs | 57 +++- .../crates/syntax/src/ast/token_ext.rs | 164 +++++++----- .../crates/syntax/src/ast/traits.rs | 79 +++--- .../crates/syntax/src/parsing/reparsing.rs | 8 - .../validation/0031_block_inner_attrs.rast | 15 +- .../xtask/src/codegen/grammar.rs | 39 +-- 54 files changed, 696 insertions(+), 711 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs index f91140ee8f934..ac8ca70ccc4b9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs @@ -22,8 +22,8 @@ use hir_expand::{ }; use span::AstIdMap; use syntax::{ - AstNode, AstToken, SyntaxNode, - ast::{self, AttrDocCommentIter, IsString}, + AstNode, SyntaxNode, + ast::{self, IsString}, }; use thin_vec::ThinVec; use tt::{TextRange, TextSize}; @@ -213,15 +213,10 @@ impl Docs { )); } - fn extend_with_doc_comment(&mut self, comment: ast::Comment, indent: &mut Indent) { - let Some((doc, offset)) = comment.doc_comment() else { return }; - let offset = comment.syntax().text_range().start() + offset; - self.extend_with_doc_str( - doc, - offset, - DocCommentKind::Sugared(comment.kind().shape), - indent, - ); + fn extend_with_doc_comment(&mut self, comment: ast::DocComment, indent: &mut Indent) { + let doc = comment.text(); + let offset = comment.syntax().text_range().start() + ast::DocComment::PREFIX_LEN; + self.extend_with_doc_str(doc, offset, DocCommentKind::Sugared(comment.shape()), indent); } fn extend_with_doc_attr(&mut self, value: ast::String, indent: &mut Indent) { @@ -662,13 +657,13 @@ fn extend_with_attrs<'a, 'db>( let mut expander = None; expand_cfg_attr_with_doc_comments::<_, Infallible>( - AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { - Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, - Either::Right(comment) => comment - .kind() - .doc - .is_some_and(|kind| (kind == ast::CommentPlacement::Inner) == expect_inner_attrs), - }), + node.children() + .filter_map(ast::AnyAttr::cast) + .filter(|attr| attr.kind().is_inner() == expect_inner_attrs) + .map(|attr| match attr { + ast::AnyAttr::Attr(it) => Either::Left(it), + ast::AnyAttr::DocComment(it) => Either::Right(it), + }), || *cfg_options.get_or_insert_with(get_cfg_options), |attr| { match attr { @@ -795,7 +790,7 @@ pub(crate) fn extract_docs<'a, 'db>( mod tests { use expect_test::expect; use hir_expand::InFile; - use syntax::{AstToken, ast}; + use syntax::{AstNode, ast}; use test_fixture::WithFixture; use thin_vec::ThinVec; use tt::{TextRange, TextSize}; @@ -1016,8 +1011,8 @@ mod tests { let comment = syntax::SourceFile::parse(source, span::Edition::CURRENT) .syntax_node() .descendants_with_tokens() - .filter_map(|it| it.into_token()) - .find_map(ast::Comment::cast) + .filter_map(|it| it.into_node()) + .find_map(ast::DocComment::cast) .expect("no comment in the fixture"); let mut docs = Docs { docs: String::new(), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs index d950b6df214a3..ace5fc3d757b5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use syntax::{ - AstToken, Direction, SyntaxElement, TextRange, - ast::{self, Comment, CommentKind, CommentShape, Whitespace, edit::IndentLevel}, + AstToken, SyntaxToken, TextRange, + ast::{self, CommentKind, CommentShape, Whitespace, edit::IndentLevel}, }; use crate::{AssistContext, AssistId, Assists}; @@ -22,19 +22,19 @@ use crate::{AssistContext, AssistId, Assists}; // */ // ``` pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> { - let comment = ctx.find_token_at_offset::()?; + let comment = ctx.find_token_at_offset::()?; // Only allow comments which are alone on their line if let Some(prev) = comment.syntax().prev_token() { Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?; } - match comment.kind().shape { + match comment.shape() { ast::CommentShape::Block => block_to_line(acc, comment), ast::CommentShape::Line => line_to_block(acc, comment), } } -fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { +fn block_to_line(acc: &mut Assists, comment: ast::AnyComment) -> Option<()> { let target = comment.syntax().text_range(); acc.add( @@ -45,9 +45,7 @@ fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { let indentation = IndentLevel::from_token(comment.syntax()); let line_prefix = CommentKind { shape: CommentShape::Line, ..comment.kind() }.prefix(); - let text = comment.text(); - let text = &text[comment.prefix().len()..(text.len() - "*/".len())].trim(); - + let text = comment.text().trim(); let lines = text.lines().peekable(); let indent_spaces = indentation.to_string(); @@ -69,7 +67,7 @@ fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { ) } -fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { +fn line_to_block(acc: &mut Assists, comment: ast::AnyComment) -> Option<()> { // Find all the comments we'll be collapsing into a block let comments = relevant_line_comments(&comment); @@ -109,37 +107,26 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { /// The line -> block assist can be invoked from anywhere within a sequence of line comments. /// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will /// be joined. -pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { - // The prefix identifies the kind of comment we're dealing with - let prefix = comment.prefix(); - let same_prefix = |c: &ast::Comment| c.prefix() == prefix; +pub(crate) fn relevant_line_comments(comment: &ast::AnyComment) -> Vec { + let expected_kind = comment.kind(); + let same_kind = |c: &ast::AnyComment| c.kind() == expected_kind; // These tokens are allowed to exist between comments - let skippable = |not: &SyntaxElement| { - not.clone() - .into_token() - .and_then(Whitespace::cast) - .map(|w| !w.spans_multiple_lines()) - .unwrap_or(false) + let skippable = |not: &SyntaxToken| { + Whitespace::cast(not.clone()).map(|w| !w.spans_multiple_lines()).unwrap_or(false) }; // Find all preceding comments (in reverse order) that have the same prefix - let prev_comments = comment - .syntax() - .siblings_with_tokens(Direction::Prev) + let prev_comments = std::iter::successors(Some(comment.syntax().clone()), |it| it.prev_token()) .filter(|s| !skippable(s)) - .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) - .take_while(|opt_com| opt_com.is_some()) - .flatten() + .map_while(ast::AnyComment::cast) + .take_while(same_kind) .skip(1); // skip the first element so we don't duplicate it in next_comments - let next_comments = comment - .syntax() - .siblings_with_tokens(Direction::Next) + let next_comments = std::iter::successors(Some(comment.syntax().clone()), |it| it.next_token()) .filter(|s| !skippable(s)) - .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) - .take_while(|opt_com| opt_com.is_some()) - .flatten(); + .map_while(ast::AnyComment::cast) + .take_while(same_kind); let mut comments: Vec<_> = prev_comments.collect(); comments.reverse(); @@ -161,7 +148,7 @@ pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { // */ // // But since such comments aren't idiomatic we're okay with this. -pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String { +pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::AnyComment) -> String { let text = comm.text(); let contents_without_prefix = text.strip_prefix(comm.prefix()).unwrap_or(text); let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs index 11a3c64188d49..ae2b98423d819 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs @@ -1,10 +1,12 @@ use itertools::Itertools; use syntax::{ - AstToken, Direction, SyntaxElement, TextRange, - ast::{self, Comment, CommentPlacement, Whitespace, edit::IndentLevel}, + AstToken, TextRange, + ast::{self, AttrKind, Whitespace, edit::IndentLevel}, }; -use crate::{AssistContext, AssistId, Assists}; +use crate::{ + AssistContext, AssistId, Assists, handlers::convert_comment_block::relevant_line_comments, +}; // Assist: comment_to_doc // @@ -23,7 +25,7 @@ pub(crate) fn convert_comment_from_or_to_doc( acc: &mut Assists, ctx: &AssistContext<'_, '_>, ) -> Option<()> { - let comment = ctx.find_token_at_offset::()?; + let comment = ctx.find_token_at_offset::()?; match comment.kind().doc { Some(_) => doc_to_comment(acc, comment), @@ -31,7 +33,7 @@ pub(crate) fn convert_comment_from_or_to_doc( } } -fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { +fn doc_to_comment(acc: &mut Assists, comment: ast::AnyComment) -> Option<()> { let target = if comment.kind().shape.is_line() { line_comments_text_range(&comment)? } else { @@ -52,7 +54,7 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { let prefix = format!("{indentation}//"); relevant_line_comments(&comment) .iter() - .map(|comment| comment.text()) + .map(|comment| comment.text_with_markers()) .flat_map(|text| text.lines()) .map(|line| line.replacen(line_start, &prefix, 1)) .join("\n") @@ -60,7 +62,7 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { ast::CommentShape::Block => { let block_start = comment.prefix(); comment - .text() + .text_with_markers() .lines() .enumerate() .map(|(idx, line)| { @@ -78,7 +80,7 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { ) } -fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacement) -> Option<()> { +fn comment_to_doc(acc: &mut Assists, comment: ast::AnyComment, style: AttrKind) -> Option<()> { let target = if comment.kind().shape.is_line() { line_comments_text_range(&comment)? } else { @@ -96,23 +98,23 @@ fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacem ast::CommentShape::Line => { let indentation = IndentLevel::from_token(comment.syntax()); let line_start = match style { - CommentPlacement::Inner => format!("{indentation}//!"), - CommentPlacement::Outer => format!("{indentation}///"), + AttrKind::Inner => format!("{indentation}//!"), + AttrKind::Outer => format!("{indentation}///"), }; relevant_line_comments(&comment) .iter() - .map(|comment| comment.text()) + .map(|comment| comment.text_with_markers()) .flat_map(|text| text.lines()) .map(|line| line.replacen("//", &line_start, 1)) .join("\n") } ast::CommentShape::Block => { let block_start = match style { - CommentPlacement::Inner => "/*!", - CommentPlacement::Outer => "/**", + AttrKind::Inner => "/*!", + AttrKind::Outer => "/**", }; comment - .text() + .text_with_markers() .lines() .enumerate() .map(|(idx, line)| { @@ -176,7 +178,7 @@ fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacem /// // Modules only normally get inner documentation when they are defined as a separate file. /// } /// ``` -fn can_be_doc_comment(comment: &ast::Comment) -> Option { +fn can_be_doc_comment(comment: &ast::AnyComment) -> Option { use syntax::SyntaxKind::*; // if the comment is not on its own line, then we do not propose anything. @@ -186,7 +188,7 @@ fn can_be_doc_comment(comment: &ast::Comment) -> Option { Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?; } // There is no previous token, this is the start of the file. - None => return Some(CommentPlacement::Inner), + None => return Some(AttrKind::Inner), } // check if comment is followed by: `struct`, `trait`, `mod`, `fn`, `type`, `extern crate`, @@ -194,51 +196,10 @@ fn can_be_doc_comment(comment: &ast::Comment) -> Option { let parent = comment.syntax().parent(); let par_kind = parent.as_ref().map(|parent| parent.kind()); matches!(par_kind, Some(STRUCT | TRAIT | MODULE | FN | TYPE_ALIAS | EXTERN_CRATE | USE | CONST)) - .then_some(CommentPlacement::Outer) -} - -/// The line -> block assist can be invoked from anywhere within a sequence of line comments. -/// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will -/// be joined. -pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { - // The prefix identifies the kind of comment we're dealing with - let prefix = comment.prefix(); - let same_prefix = |c: &ast::Comment| c.prefix() == prefix; - - // These tokens are allowed to exist between comments - let skippable = |not: &SyntaxElement| { - not.clone() - .into_token() - .and_then(Whitespace::cast) - .map(|w| !w.spans_multiple_lines()) - .unwrap_or(false) - }; - - // Find all preceding comments (in reverse order) that have the same prefix - let prev_comments = comment - .syntax() - .siblings_with_tokens(Direction::Prev) - .filter(|s| !skippable(s)) - .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) - .take_while(|opt_com| opt_com.is_some()) - .flatten() - .skip(1); // skip the first element so we don't duplicate it in next_comments - - let next_comments = comment - .syntax() - .siblings_with_tokens(Direction::Next) - .filter(|s| !skippable(s)) - .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) - .take_while(|opt_com| opt_com.is_some()) - .flatten(); - - let mut comments: Vec<_> = prev_comments.collect(); - comments.reverse(); - comments.extend(next_comments); - comments + .then_some(AttrKind::Outer) } -fn line_comments_text_range(comment: &ast::Comment) -> Option { +fn line_comments_text_range(comment: &ast::AnyComment) -> Option { let comments = relevant_line_comments(comment); let first = comments.first()?; let indentation = IndentLevel::from_token(first.syntax()); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs index e6784a0c3b397..b0657ef434095 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs @@ -1,8 +1,8 @@ use either::Either; use itertools::Itertools; use syntax::{ - AstToken, TextRange, - ast::{self, CommentPlacement, Whitespace, edit::IndentLevel}, + AstNode, AstToken, TextRange, + ast::{self, AttrKind, Whitespace, edit::IndentLevel}, }; use crate::{ @@ -25,22 +25,22 @@ use crate::{ // comment"] // ``` pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> { - let comment = ctx.find_token_at_offset::()?; + let comment = ctx.find_node_at_offset::()?; // Only allow doc comments - let placement = comment.kind().doc?; + let placement = comment.kind(); // Only allow comments which are alone on their line - if let Some(prev) = comment.syntax().prev_token() { + if let Some(prev) = comment.syntax().first_token().and_then(|it| it.prev_token()) { Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?; } - let indentation = IndentLevel::from_token(comment.syntax()).to_string(); + let indentation = IndentLevel::from_node(comment.syntax()).to_string(); - let (target, comments) = match comment.kind().shape { + let (target, comments) = match comment.shape() { ast::CommentShape::Block => (comment.syntax().text_range(), Either::Left(comment)), ast::CommentShape::Line => { // Find all the comments we'll be desugaring - let comments = relevant_line_comments(&comment); + let comments = relevant_line_comments(&comment.token()); // Establish the target of our edit based on the comments we found ( @@ -59,26 +59,22 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_, '_> target, |edit| { let text = match comments { - Either::Left(comment) => { - let text = comment.text(); - text[comment.prefix().len()..(text.len() - "*/".len())] - .trim() - .lines() - .map(|l| l.strip_prefix(&indentation).unwrap_or(l)) - .join("\n") - } - Either::Right(comments) => comments - .into_iter() - .map(|cm| line_comment_text(IndentLevel(0), cm)) - .collect::>() + Either::Left(comment) => comment + .text() + .trim() + .lines() + .map(|l| l.strip_prefix(&indentation).unwrap_or(l)) .join("\n"), + Either::Right(comments) => { + comments.into_iter().map(|cm| line_comment_text(IndentLevel(0), cm)).join("\n") + } }; let hashes = "#".repeat(required_hashes(&text)); let prefix = match placement { - CommentPlacement::Inner => "#!", - CommentPlacement::Outer => "#", + AttrKind::Inner => "#!", + AttrKind::Outer => "#", }; let output = format!(r#"{prefix}[doc = r{hashes}"{text}"{hashes}]"#); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index 46333ed726388..038d100e621ec 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -76,7 +76,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - return None; } - if node.kind() == COMMENT { + if ast::AnyComment::can_cast(node.kind()) { cov_mark::hit!(extract_function_in_comment_is_not_applicable); return None; } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs index 60a1c7ab44eb0..9ff07d8dd6d2c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs @@ -469,7 +469,10 @@ impl Module { syntax.children_with_tokens().find(|nt| { !matches!( nt.kind(), - SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE + SyntaxKind::COMMENT + | SyntaxKind::DOC_COMMENT + | SyntaxKind::ATTR + | SyntaxKind::WHITESPACE ) }) }) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs index c1ac4f1724893..8d0b9da112c39 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs @@ -365,7 +365,7 @@ fn collect_variant_comments( for child in node.children_with_tokens() { match child.kind() { - COMMENT => { + COMMENT | DOC_COMMENT => { after_comment = true; to_insert.push(child.clone()); to_delete.push(child); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs index e514e8be4233a..e2ff8fa2fc6d5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs @@ -89,11 +89,15 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - } } else { match ctx.covering_element() { - NodeOrToken::Node(it) => it, + NodeOrToken::Node(it) if it.kind() == SyntaxKind::DOC_COMMENT => { + cov_mark::hit!(extract_var_in_comment_is_not_applicable); + return None; + } NodeOrToken::Token(it) if it.kind() == SyntaxKind::COMMENT => { cov_mark::hit!(extract_var_in_comment_is_not_applicable); return None; } + NodeOrToken::Node(it) => it, NodeOrToken::Token(it) => it.parent()?, } }; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs index d0f5c7c5003d4..54784fa9903c6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs @@ -91,6 +91,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_, ' it.kind(), syntax::SyntaxKind::WHITESPACE | syntax::SyntaxKind::COMMENT + | syntax::SyntaxKind::DOC_COMMENT | syntax::SyntaxKind::ATTR ) }) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs index ba6bb5c70bcb1..b24ba4b9abcfd 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs @@ -1,5 +1,5 @@ use syntax::{ - SyntaxKind::{ATTR, COMMENT, WHITESPACE}, + SyntaxKind::{ATTR, COMMENT, DOC_COMMENT, WHITESPACE}, T, ast::{self, AstNode, HasAttrs, edit::IndentLevel}, syntax_editor::{Element, Position}, @@ -55,7 +55,7 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> let after_attrs_and_comments = nominal .syntax() .children_with_tokens() - .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) + .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | DOC_COMMENT | ATTR)) .map_or(Position::first_child_of(nominal.syntax()), Position::before); editor.insert_all( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs index 89adda93866f0..72971e55222cc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs @@ -3,9 +3,9 @@ use ide_db::assists::AssistId; use itertools::Itertools; use stdx::{format_to, to_lower_snake_case}; use syntax::{ - AstNode, AstToken, Edition, + AstNode, Edition, algo::skip_whitespace_token, - ast::{self, HasDocComments, HasGenericArgs, HasName, edit::IndentLevel}, + ast::{self, HasAttrs, HasGenericArgs, HasName, edit::IndentLevel}, match_ast, }; @@ -96,11 +96,13 @@ pub(crate) fn generate_documentation_template( // pub fn add(a: i32, b: i32) -> i32 { a + b } // ``` pub(crate) fn generate_doc_example(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> { - let tok: ast::Comment = ctx.find_token_at_offset()?; - let node = tok.syntax().parent()?; - let last_doc_token = - ast::AnyHasDocComments::cast(node.clone())?.doc_comments().last()?.syntax().clone(); - let next_token = skip_whitespace_token(last_doc_token.next_token()?, syntax::Direction::Next)?; + let doc_at_cursor: ast::DocComment = ctx.find_node_at_offset()?; + let node = doc_at_cursor.syntax().parent()?; + let last_doc_comment = ast::AnyHasAttrs::cast(node.clone())?.doc_comments().last()?; + let next_token = skip_whitespace_token( + last_doc_comment.syntax().last_token()?.next_token()?, + syntax::Direction::Next, + )?; let example = match_ast! { match node { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index 354447cf3356e..79254044a9608 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -1,9 +1,9 @@ use crate::assist_context::{AssistContext, Assists}; use ide_db::{assists::AssistId, defs::Definition, search::SearchScope}; use syntax::{ - AstNode, AstToken, SyntaxKind, T, + AstNode, SyntaxKind, T, ast::{ - self, HasDocComments, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, + self, HasAttrs, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, syntax_factory::SyntaxFactory, }, syntax_editor::{Position, SyntaxEditor}, @@ -226,7 +226,7 @@ fn remove_items_visibility(editor: &SyntaxEditor, item: &ast::AssocItem) { fn remove_doc_comments(editor: &SyntaxEditor, item: &ast::AssocItem) { for doc in item.doc_comments() { - if let Some(next) = doc.syntax().next_token() + if let Some(next) = doc.syntax().last_token().and_then(|it| it.next_token()) && next.kind() == SyntaxKind::WHITESPACE { editor.delete(next); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 0f12ec79ee6e8..e5e735faf6f93 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -57,12 +57,7 @@ pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option TextSize { node.children_with_tokens() - .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) + .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | DOC_COMMENT | ATTR)) .map(|it| it.text_range().start()) .unwrap_or_else(|| node.text_range().start()) } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs index ee6788b16e45c..1523d0ad43b8b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -112,7 +112,10 @@ fn complete_trait_impl_name( .find(|child| { !matches!( child.kind(), - SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR + SyntaxKind::COMMENT + | SyntaxKind::DOC_COMMENT + | SyntaxKind::WHITESPACE + | SyntaxKind::ATTR ) }) .unwrap_or_else(|| SyntaxElement::Node(real_file_item.clone())); diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs index 0235389763080..bf7aa771c2afa 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs @@ -573,5 +573,5 @@ fn insert_use_with_editor_( } fn is_inner_attribute(node: SyntaxNode) -> bool { - ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) + ast::AnyAttr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) } diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs index 83b8c3dc81ea6..c3089262ecc1d 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs @@ -22,7 +22,7 @@ pub fn ssr_from_comment( let file = file_id.parse(db); file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast) }?; - let comment_text_without_prefix = comment.text().strip_prefix(comment.prefix()).unwrap(); + let comment_text_without_prefix = comment.text_without_markers(); let ssr_rule = comment_text_without_prefix.parse().ok()?; let lookup_context = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs index 2f29fc31f8a84..c152d7e9cc964 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -277,19 +277,28 @@ pub(crate) struct DocCommentToken { } pub(crate) fn token_as_doc_comment(doc_token: &SyntaxToken) -> Option { - (match_ast! { - match doc_token { - ast::Comment(comment) => TextSize::try_from(comment.prefix().len()).ok(), - ast::String(string) => { - doc_token.parent_ancestors().find_map(ast::Attr::cast).filter(|attr| attr.simple_name().as_deref() == Some("doc"))?; - if doc_token.parent_ancestors().find_map(ast::MacroCall::cast).filter(|mac| mac.path().and_then(|p| p.segment()?.name_ref()).as_ref().map(|n| n.text()) == Some("include_str")).is_some() { - return None; - } - string.open_quote_text_range().map(|it| it.len()) - }, - _ => None, + let prefix_len = if matches!(doc_token.kind(), INNER_DOC_COMMENT | OUTER_DOC_COMMENT) { + ast::DocComment::PREFIX_LEN + } else { + let string = ast::String::cast(doc_token.clone())?; + doc_token + .parent_ancestors() + .find_map(ast::Attr::cast) + .filter(|attr| attr.simple_name().as_deref() == Some("doc"))?; + if doc_token + .parent_ancestors() + .find_map(ast::MacroCall::cast) + .filter(|mac| { + mac.path().and_then(|p| p.segment()?.name_ref()).as_ref().map(|n| n.text()) + == Some("include_str") + }) + .is_some() + { + return None; } - }).map(|prefix_len| DocCommentToken { prefix_len, doc_token: doc_token.clone() }) + string.open_quote_text_range()?.len() + }; + Some(DocCommentToken { prefix_len, doc_token: doc_token.clone() }) } impl DocCommentToken { @@ -308,8 +317,8 @@ impl DocCommentToken { sema.descend_into_macros(doc_token).into_iter().find_map(|t| { let (node, descended_prefix_len, is_inner) = match_ast!{ match t { - ast::Comment(comment) => { - (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?, comment.is_inner()) + ast::AnyComment(comment) => { + (t.parent()?.parent()?, TextSize::try_from(comment.prefix().len()).ok()?, comment.is_inner()) }, ast::String(string) => { let attr = t.parent_ancestors().find_map(ast::Attr::cast)?; diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs index 720528d0b52f2..f5755da13bdbe 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs @@ -449,7 +449,7 @@ fn doc_links_items_simple() { check_doc_links( r#" //- /main.rs crate:main deps:krate -/// [`krate`] +//! [`krate`] //! [`Trait`] //! [`function`] //! [`CONST`] diff --git a/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs index 2926384c40786..079f8426cf5af 100644 --- a/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs +++ b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs @@ -35,7 +35,8 @@ fn try_extend_selection( ) -> Option { let range = frange.range; - let string_kinds = [COMMENT, STRING, BYTE_STRING, C_STRING]; + let string_kinds = + [COMMENT, INNER_DOC_COMMENT, OUTER_DOC_COMMENT, STRING, BYTE_STRING, C_STRING]; let list_kinds = [ RECORD_PAT_FIELD_LIST, MATCH_ARM_LIST, @@ -81,7 +82,7 @@ fn try_extend_selection( if token.text_range() != range { return Some(token.text_range()); } - if let Some(comment) = ast::Comment::cast(token.clone()) + if let Some(comment) = ast::AnyComment::cast(token.clone()) && let Some(range) = extend_comments(comment) { return Some(range); @@ -292,7 +293,7 @@ fn extend_list_item(node: &SyntaxNode) -> Option { None } -fn extend_comments(comment: ast::Comment) -> Option { +fn extend_comments(comment: ast::AnyComment) -> Option { let prev = adj_comments(&comment, Direction::Prev); let next = adj_comments(&comment, Direction::Next); if prev != next { @@ -302,14 +303,14 @@ fn extend_comments(comment: ast::Comment) -> Option { } } -fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment { +fn adj_comments(comment: &ast::AnyComment, dir: Direction) -> ast::AnyComment { let mut res = comment.clone(); for element in comment.syntax().siblings_with_tokens(dir) { let token = match element.as_token() { None => break, Some(token) => token, }; - if let Some(c) = ast::Comment::cast(token.clone()) { + if let Some(c) = ast::AnyComment::cast(token.clone()) { res = c } else if token.kind() != WHITESPACE || token.text().contains("\n\n") { break; diff --git a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs index ae006d152a386..4cac61d9f6cef 100644 --- a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs +++ b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs @@ -10,8 +10,8 @@ use syntax::{ use std::hash::Hash; -const REGION_START: &str = "// region:"; -const REGION_END: &str = "// endregion"; +const REGION_START: &str = "region:"; +const REGION_END: &str = "endregion"; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldKind { @@ -109,7 +109,7 @@ pub(crate) fn folding_ranges(file: &SourceFile, add_collapsed_text: bool) -> Vec match element { NodeOrToken::Token(token) => { // Fold groups of comments - if let Some(comment) = ast::Comment::cast(token) { + if let Some(comment) = ast::AnyComment::cast(token) { if visited_comments.contains(&comment) { continue; } @@ -200,7 +200,7 @@ fn fold_kind( } match element.kind() { - COMMENT => Some(FoldKind::Comment), + COMMENT | INNER_DOC_COMMENT | OUTER_DOC_COMMENT => Some(FoldKind::Comment), ARG_LIST | PARAM_LIST | GENERIC_ARG_LIST | GENERIC_PARAM_LIST => Some(FoldKind::ArgList), ARRAY_EXPR => Some(FoldKind::Array), RET_TYPE => Some(FoldKind::ReturnType), @@ -391,8 +391,8 @@ fn eq_visibility(vis0: Option, vis1: Option) - } fn contiguous_range_for_comment( - first: ast::Comment, - visited: &mut FxHashSet, + first: ast::AnyComment, + visited: &mut FxHashSet, ) -> Option { visited.insert(first.clone()); @@ -403,33 +403,29 @@ fn contiguous_range_for_comment( } let mut last = first.clone(); - for element in first.syntax().siblings_with_tokens(Direction::Next) { - match element { - NodeOrToken::Token(token) => { - if let Some(ws) = ast::Whitespace::cast(token.clone()) - && !ws.spans_multiple_lines() - { - // Ignore whitespace without blank lines - continue; - } - if let Some(c) = ast::Comment::cast(token) - && c.kind() == group_kind - { - let text = c.text().trim_start(); - // regions are not real comments - if !(text.starts_with(REGION_START) || text.starts_with(REGION_END)) { - visited.insert(c.clone()); - last = c; - continue; - } - } - // The comment group ends because either: - // * An element of a different kind was reached - // * A comment of a different flavor was reached - break; + let next_comments = std::iter::successors(Some(first.syntax().clone()), |it| it.next_token()); + for token in next_comments { + if let Some(ws) = ast::Whitespace::cast(token.clone()) + && !ws.spans_multiple_lines() + { + // Ignore whitespace without blank lines + continue; + } + if let Some(c) = ast::AnyComment::cast(token) + && c.kind() == group_kind + { + let text = c.text().trim_start(); + // regions are not real comments + if !(text.starts_with(REGION_START) || text.starts_with(REGION_END)) { + visited.insert(c.clone()); + last = c; + continue; } - NodeOrToken::Node(_) => break, - }; + } + // The comment group ends because either: + // * An element of a different kind was reached + // * A comment of a different flavor was reached + break; } if first != last { diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index 6947f00e21870..033de7dcc20b4 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -71,7 +71,9 @@ pub(crate) fn goto_definition( | T![super] | T![crate] | T![Self] - | COMMENT => 4, + | COMMENT + | INNER_DOC_COMMENT + | OUTER_DOC_COMMENT => 4, // index and prefix ops T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3, kind if kind.is_keyword(edition) => 2, diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 3d73b5b0f24d4..92473de4e634f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -182,8 +182,12 @@ fn hover_offset( _ => 1, })?; - if let Some(doc_comment) = token_as_doc_comment(&original_token) { + if ast::Comment::can_cast(original_token.kind()) { cov_mark::hit!(no_highlight_on_comment_hover); + return None; + } + + if let Some(doc_comment) = token_as_doc_comment(&original_token) { return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| { let res = hover_for_definition( sema, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs index 4b06f83971b25..e008a7b940662 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs @@ -34,7 +34,9 @@ pub(super) fn hints( .filter_map(NodeOrToken::into_token) .filter(|t| match t.kind() { SyntaxKind::WHITESPACE if !t.text().contains('\n') => false, - SyntaxKind::COMMENT => false, + SyntaxKind::COMMENT | SyntaxKind::OUTER_DOC_COMMENT | SyntaxKind::INNER_DOC_COMMENT => { + false + } _ => true, }); diff --git a/src/tools/rust-analyzer/crates/ide/src/join_lines.rs b/src/tools/rust-analyzer/crates/ide/src/join_lines.rs index a946559c35455..4c648d3cb9a0f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/join_lines.rs +++ b/src/tools/rust-analyzer/crates/ide/src/join_lines.rs @@ -186,9 +186,10 @@ fn remove_newline( } } + // We can't use `prev` and `next`, since `DOC_COMMENT` has only one token, so `token` has no siblings. if let (Some(_), Some(next)) = ( - prev.as_token().cloned().and_then(ast::Comment::cast), - next.as_token().cloned().and_then(ast::Comment::cast), + token.prev_token().and_then(ast::AnyComment::cast), + token.next_token().and_then(ast::AnyComment::cast), ) { // Removes: newline (incl. surrounding whitespace), start of the next comment edit.delete(TextRange::new( @@ -674,14 +675,14 @@ fn foo() { fn test_join_lines_doc_comments() { check_join_lines( r" +/// Hello$0 +/// world! fn foo() { - /// Hello$0 - /// world! } ", r" +/// Hello$0 world! fn foo() { - /// Hello$0 world! } ", ); diff --git a/src/tools/rust-analyzer/crates/ide/src/moniker.rs b/src/tools/rust-analyzer/crates/ide/src/moniker.rs index c92f2bbace84a..dd1c7379e7906 100644 --- a/src/tools/rust-analyzer/crates/ide/src/moniker.rs +++ b/src/tools/rust-analyzer/crates/ide/src/moniker.rs @@ -154,7 +154,9 @@ pub(crate) fn moniker( | T![super] | T![crate] | T![Self] - | COMMENT => 2, + | COMMENT + | INNER_DOC_COMMENT + | OUTER_DOC_COMMENT => 2, kind if kind.is_trivia() => 0, _ => 1, })?; diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index 9e8d772cb3d12..7a01eb9054bfa 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -12,7 +12,7 @@ use ide_db::{ famous_defs::FamousDefs, ra_fixture::RaFixtureConfig, }; -use syntax::{AstNode, AstToken, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, ast}; +use syntax::{AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange}; use crate::navigation_target::UpmappingResult; use crate::{ @@ -349,15 +349,20 @@ fn definition_range_excluding_trivia( } fn is_leading_trivia_excluding_docs(token: &SyntaxToken) -> bool { - match token.kind() { - SyntaxKind::WHITESPACE => true, - SyntaxKind::COMMENT => ast::Comment::cast(token.clone()).is_none_or(|it| !it.is_outer()), - _ => false, - } + matches!( + token.kind(), + SyntaxKind::WHITESPACE | SyntaxKind::COMMENT | SyntaxKind::INNER_DOC_COMMENT + ) } fn is_trailing_trivia(token: &SyntaxToken) -> bool { - matches!(token.kind(), SyntaxKind::WHITESPACE | SyntaxKind::COMMENT) + matches!( + token.kind(), + SyntaxKind::WHITESPACE + | SyntaxKind::COMMENT + | SyntaxKind::INNER_DOC_COMMENT + | SyntaxKind::OUTER_DOC_COMMENT + ) } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index 9fd3f005ec70a..c29f180101632 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -534,7 +534,7 @@ fn descend_token( sema: &Semantics<'_, RootDatabase>, token: InRealFile, ) -> InFile> { - if token.value.kind() == COMMENT { + if ast::AnyComment::can_cast(token.value.kind()) { return token.map(NodeOrToken::Token).into(); } let ranker = Ranker::from_token(&token.value); diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs index 92daacd6d314f..07b191231807c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs @@ -11,7 +11,7 @@ use ide_db::{ }; use span::Edition; use syntax::{ - AstNode, AstPtr, AstToken, NodeOrToken, + AstNode, AstPtr, NodeOrToken, SyntaxKind::{self, *}, SyntaxNode, SyntaxNodePtr, SyntaxToken, T, ast, match_ast, }; @@ -28,15 +28,9 @@ pub(super) fn token( is_unsafe_node: &impl Fn(AstPtr>) -> bool, in_tt: bool, ) -> Option { - if let Some(comment) = ast::Comment::cast(token.clone()) { - let h = HlTag::Comment; - return Some(match comment.kind().doc { - Some(_) => h | HlMod::Documentation, - None => h.into(), - }); - } - let h = match token.kind() { + COMMENT => HlTag::Comment.into(), + INNER_DOC_COMMENT | OUTER_DOC_COMMENT => HlTag::Comment | HlMod::Documentation, STRING | BYTE_STRING | C_STRING => HlTag::StringLiteral.into(), INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), BYTE => HlTag::ByteLiteral.into(), diff --git a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs index 4e3c491418754..c4f53337b3910 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs @@ -55,7 +55,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option Option Option { @@ -159,7 +159,7 @@ fn brace_contents_on_same_line(l_curly: &SyntaxToken) -> Option<(SyntaxToken, St } } -fn followed_by_comment(comment: &ast::Comment) -> bool { +fn followed_by_comment(comment: &ast::AnyComment) -> bool { let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { Some(it) => it, None => return false, @@ -167,7 +167,7 @@ fn followed_by_comment(comment: &ast::Comment) -> bool { if ws.spans_multiple_lines() { return false; } - ws.syntax().next_token().and_then(ast::Comment::cast).is_some() + ws.syntax().next_token().and_then(ast::AnyComment::cast).is_some() } fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs index 2eeaa25257dba..acce7afb54e9b 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs @@ -1,23 +1,29 @@ use super::*; -pub(super) const ATTRIBUTE_FIRST: TokenSet = TokenSet::new(&[T![#]]); +pub(super) const OUTER_ATTR_FIRST: TokenSet = TokenSet::new(&[T![#], OUTER_DOC_COMMENT]); pub(super) fn inner_attrs(p: &mut Parser<'_>) { - while p.at(T![#]) && p.nth(1) == T![!] { + while p.at(INNER_DOC_COMMENT) || (p.at(T![#]) && p.nth_at(1, T![!])) { attr(p, true); } } pub(super) fn outer_attrs(p: &mut Parser<'_>) { - while p.at(T![#]) { + while p.at_ts(OUTER_ATTR_FIRST) { attr(p, false); } } fn attr(p: &mut Parser<'_>, inner: bool) { - assert!(p.at(T![#])); + if (inner && p.at(INNER_DOC_COMMENT)) || (!inner && p.at(OUTER_DOC_COMMENT)) { + let m = p.start(); + p.bump_any(); + m.complete(p, DOC_COMMENT); + return; + } let attr = p.start(); + p.bump(T![#]); if inner { diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs index d419817e5cd70..661ab0562e70e 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs @@ -1,4 +1,4 @@ -use crate::grammar::attributes::ATTRIBUTE_FIRST; +use crate::grammar::attributes::OUTER_ATTR_FIRST; use super::*; @@ -22,7 +22,7 @@ pub(super) fn generic_param_list(p: &mut Parser<'_>) { T![>], T![,], || "expected generic parameter".into(), - GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST), + GENERIC_PARAM_FIRST.union(OUTER_ATTR_FIRST), |p| { // test generic_param_attribute // fn foo<#[lt_attr] 'a, #[t_attr] T>() {} diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs index 33e19f5725b36..ec4e595398788 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs @@ -1,4 +1,4 @@ -use crate::grammar::attributes::ATTRIBUTE_FIRST; +use crate::grammar::attributes::OUTER_ATTR_FIRST; use super::*; @@ -180,7 +180,7 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) { } const TUPLE_FIELD_FIRST: TokenSet = - types::TYPE_FIRST.union(ATTRIBUTE_FIRST).union(VISIBILITY_FIRST); + types::TYPE_FIRST.union(OUTER_ATTR_FIRST).union(VISIBILITY_FIRST); // test_err tuple_field_list_recovery // struct S(struct S; diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs index 51ffcd070694c..4f1c744c1d0f3 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs @@ -1,4 +1,4 @@ -use crate::grammar::attributes::ATTRIBUTE_FIRST; +use crate::grammar::attributes::OUTER_ATTR_FIRST; use super::*; @@ -64,7 +64,7 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) { } }; - if !p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) { + if !p.at_ts(PARAM_FIRST.union(OUTER_ATTR_FIRST)) { p.error("expected value parameter"); m.abandon(p); if p.eat(T![,]) { @@ -74,7 +74,7 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) { } param(p, m, flavor); if !p.eat(T![,]) { - if p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) { + if p.at_ts(PARAM_FIRST.union(OUTER_ATTR_FIRST)) { p.error("expected `,`"); } else { break; diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs index d7eec6cde8c01..ec994b731bf95 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -195,6 +195,14 @@ impl<'a> Converter<'a> { } } + fn comment_kind(doc_style: Option) -> SyntaxKind { + match doc_style { + Some(rustc_lexer::DocStyle::Outer) => OUTER_DOC_COMMENT, + Some(rustc_lexer::DocStyle::Inner) => INNER_DOC_COMMENT, + None => COMMENT, + } + } + fn extend_token(&mut self, kind: &rustc_lexer::TokenKind, mut token_text: &str) { // A note on an intended tradeoff: // We drop some useful information here (see patterns with double dots `..`) @@ -204,14 +212,14 @@ impl<'a> Converter<'a> { let syntax_kind = { match kind { - rustc_lexer::TokenKind::LineComment { doc_style: _ } => COMMENT, - rustc_lexer::TokenKind::BlockComment { doc_style: _, terminated } => { + rustc_lexer::TokenKind::LineComment { doc_style } => Self::comment_kind(*doc_style), + rustc_lexer::TokenKind::BlockComment { doc_style, terminated } => { if !terminated { errors.push( "Missing trailing `*/` symbols to terminate the block comment".into(), ); } - COMMENT + Self::comment_kind(*doc_style) } rustc_lexer::TokenKind::Frontmatter { diff --git a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs index 5604da5026e90..81a3b423e4436 100644 --- a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs +++ b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs @@ -162,8 +162,10 @@ pub enum SyntaxKind { ERROR, FRONTMATTER, IDENT, + INNER_DOC_COMMENT, LIFETIME_IDENT, NEWLINE, + OUTER_DOC_COMMENT, SHEBANG, WHITESPACE, ABI, @@ -204,6 +206,7 @@ pub enum SyntaxKind { CONST_PARAM, CONTINUE_EXPR, DEREF_PAT, + DOC_COMMENT, DYN_TRAIT_TYPE, ENUM, EXPR_STMT, @@ -391,6 +394,7 @@ impl SyntaxKind { | CONST_PARAM | CONTINUE_EXPR | DEREF_PAT + | DOC_COMMENT | DYN_TRAIT_TYPE | ENUM | EXPR_STMT @@ -526,8 +530,10 @@ impl SyntaxKind { | ERROR | FRONTMATTER | IDENT + | INNER_DOC_COMMENT | LIFETIME_IDENT | NEWLINE + | OUTER_DOC_COMMENT | SHEBANG | WHITESPACE => panic!("no text for these `SyntaxKind`s"), DOLLAR => "$", @@ -1226,6 +1232,8 @@ macro_rules ! T_ { [string] => { $ crate :: SyntaxKind :: STRING }; [shebang] => { $ crate :: SyntaxKind :: SHEBANG }; [frontmatter] => { $ crate :: SyntaxKind :: FRONTMATTER }; + [inner_doc_comment] => { $ crate :: SyntaxKind :: INNER_DOC_COMMENT }; + [outer_doc_comment] => { $ crate :: SyntaxKind :: OUTER_DOC_COMMENT }; } impl ::core::marker::Copy for SyntaxKind {} diff --git a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unclosed_nested_block_comment_partially.rast b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unclosed_nested_block_comment_partially.rast index e9b74ee7f8276..54341df49c869 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unclosed_nested_block_comment_partially.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unclosed_nested_block_comment_partially.rast @@ -1 +1 @@ -COMMENT "/** /*! /* comment */ */\n" error: Missing trailing `*/` symbols to terminate the block comment +OUTER_DOC_COMMENT "/** /*! /* comment */ */\n" error: Missing trailing `*/` symbols to terminate the block comment diff --git a/src/tools/rust-analyzer/crates/parser/test_data/lexer/ok/single_line_comments.rast b/src/tools/rust-analyzer/crates/parser/test_data/lexer/ok/single_line_comments.rast index c4e531b449f7b..17ef356156f7e 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/lexer/ok/single_line_comments.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/lexer/ok/single_line_comments.rast @@ -1,21 +1,21 @@ SHEBANG "#!/usr/bin/env bash\n" COMMENT "// hello" WHITESPACE "\n" -COMMENT "//! World" +INNER_DOC_COMMENT "//! World" WHITESPACE "\n" -COMMENT "//!! Inner line doc" +INNER_DOC_COMMENT "//!! Inner line doc" WHITESPACE "\n" -COMMENT "/// Outer line doc" +OUTER_DOC_COMMENT "/// Outer line doc" WHITESPACE "\n" COMMENT "//// Just a comment" WHITESPACE "\n\n" COMMENT "//" WHITESPACE "\n" -COMMENT "//!" +INNER_DOC_COMMENT "//!" WHITESPACE "\n" -COMMENT "//!!" +INNER_DOC_COMMENT "//!!" WHITESPACE "\n" -COMMENT "///" +OUTER_DOC_COMMENT "///" WHITESPACE "\n" COMMENT "////" WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0035_weird_exprs.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0035_weird_exprs.rast index 15ce6c70bea72..b96711c613ca2 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0035_weird_exprs.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0035_weird_exprs.rast @@ -1,11 +1,15 @@ SOURCE_FILE - COMMENT "//! Adapted from a `rustc` test, which can be found at " + DOC_COMMENT + INNER_DOC_COMMENT "//! Adapted from a `rustc` test, which can be found at " WHITESPACE "\n" - COMMENT "//! https://github.com/rust-lang/rust/blob/6d34ec18c7d7e574553f6347ecf08e1e1c45c13d/src/test/run-pass/weird-exprs.rs." + DOC_COMMENT + INNER_DOC_COMMENT "//! https://github.com/rust-lang/rust/blob/6d34ec18c7d7e574553f6347ecf08e1e1c45c13d/src/test/run-pass/weird-exprs.rs." WHITESPACE "\n" - COMMENT "//! " + DOC_COMMENT + INNER_DOC_COMMENT "//! " WHITESPACE "\n" - COMMENT "//! Reported to rust-analyzer in https://github.com/rust-lang/rust-analyzer/issues/290" + DOC_COMMENT + INNER_DOC_COMMENT "//! Reported to rust-analyzer in https://github.com/rust-lang/rust-analyzer/issues/290" WHITESPACE "\n\n" ATTR POUND "#" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0037_mod.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0037_mod.rast index b4a3fc6292e96..59c33eba25c10 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0037_mod.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0037_mod.rast @@ -1,7 +1,8 @@ SOURCE_FILE COMMENT "// https://github.com/rust-lang/rust-analyzer/issues/357" WHITESPACE "\n\n" - COMMENT "//! docs" + DOC_COMMENT + INNER_DOC_COMMENT "//! docs" WHITESPACE "\n" MODULE COMMENT "// non-docs" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0045_block_attrs.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0045_block_attrs.rast index f26bb85df2927..2dc22d12370ee 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0045_block_attrs.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0045_block_attrs.rast @@ -27,7 +27,8 @@ SOURCE_FILE R_PAREN ")" R_BRACK "]" WHITESPACE "\n " - COMMENT "//! As are ModuleDoc style comments" + DOC_COMMENT + INNER_DOC_COMMENT "//! As are ModuleDoc style comments" WHITESPACE "\n " EXPR_STMT BLOCK_EXPR @@ -64,7 +65,8 @@ SOURCE_FILE R_PAREN ")" R_BRACK "]" WHITESPACE "\n " - COMMENT "//! As are ModuleDoc style comments" + DOC_COMMENT + INNER_DOC_COMMENT "//! As are ModuleDoc style comments" WHITESPACE "\n " R_CURLY "}" SEMICOLON ";" @@ -88,7 +90,8 @@ SOURCE_FILE R_PAREN ")" R_BRACK "]" WHITESPACE "\n " - COMMENT "//! As are ModuleDoc style comments" + DOC_COMMENT + INNER_DOC_COMMENT "//! As are ModuleDoc style comments" WHITESPACE "\n " R_CURLY "}" WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast index 3d33eb4ff73ce..8338f1416c044 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast @@ -8,7 +8,8 @@ SOURCE_FILE EXTERN_ITEM_LIST L_CURLY "{" WHITESPACE "\n " - COMMENT "//! This is a doc comment" + DOC_COMMENT + INNER_DOC_COMMENT "//! This is a doc comment" WHITESPACE "\n " ATTR POUND "#" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast index c300b7af50589..c9eeda6582afc 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast @@ -1,6 +1,7 @@ SOURCE_FILE MACRO_RULES - COMMENT "/// Some docs" + DOC_COMMENT + OUTER_DOC_COMMENT "/// Some docs" WHITESPACE "\n" ATTR POUND "#" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0065_comment_newline.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0065_comment_newline.rast index 3ffcb48f5e424..e95ed7d38c90a 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0065_comment_newline.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0065_comment_newline.rast @@ -1,6 +1,7 @@ SOURCE_FILE FN - COMMENT "/// Example" + DOC_COMMENT + OUTER_DOC_COMMENT "/// Example" WHITESPACE "\n\n" FN_KW "fn" WHITESPACE " " diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs index 181f9a14e764d..3e6e5f804e263 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs @@ -12,10 +12,10 @@ use rustc_hash::{FxHashMap, FxHashSet}; use span::{Edition, Span, SpanAnchor, SpanMap, SyntaxContext}; use stdx::{format_to, never}; use syntax::{ - AstToken, Parse, PreorderWithTokens, SmolStr, SyntaxElement, + Parse, PreorderWithTokens, SmolStr, SyntaxElement, SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, SyntaxTreeBuilder, T, TextRange, TextSize, WalkEvent, - ast::{self, make::tokens::doc_comment}, + ast::make::tokens::doc_comment, format_smolstr, }; use tt::{Punct, buffer::Cursor, token_to_literal}; @@ -250,9 +250,9 @@ where Some(leaf) => leaf.clone(), None => match token.kind(conv) { // Desugar doc comments into doc attributes - COMMENT => { + kind @ (INNER_DOC_COMMENT | OUTER_DOC_COMMENT) => { let span = conv.span_for(abs_range); - conv.convert_doc_comment(&token, span, &mut builder); + conv.convert_doc_comment(&token, kind == INNER_DOC_COMMENT, span, &mut builder); continue; } kind if kind.is_punct() && kind != UNDERSCORE => { @@ -419,13 +419,11 @@ pub fn desugar_doc_comment_text(text: &str, mode: DocCommentDesugarMode) -> (Sym fn convert_doc_comment( token: &syntax::SyntaxToken, + is_inner: bool, span: Span, mode: DocCommentDesugarMode, builder: &mut tt::TopSubtreeBuilder, ) { - let Some(comment) = ast::Comment::cast(token.clone()) else { return }; - let Some(doc) = comment.kind().doc else { return }; - let mk_ident = |s: &str| { tt::Leaf::from(tt::Ident { sym: Symbol::intern(s), span, is_raw: tt::IdentIsRaw::No }) }; @@ -433,14 +431,11 @@ fn convert_doc_comment( let mk_punct = |c: char| tt::Leaf::from(tt::Punct { char: c, spacing: tt::Spacing::Alone, span }); - let mk_doc_literal = |comment: &ast::Comment| { - let prefix_len = comment.prefix().len(); - let mut text = &comment.text()[prefix_len..]; + let mk_doc_literal = |token: &SyntaxToken| { + let text = token.text(); + let from_end = if text.starts_with("/*") && text.ends_with("*/") { 2 } else { 0 }; + let text = &text[3..text.len() - from_end]; - // Remove ending "*/" - if comment.kind().shape == ast::CommentShape::Block { - text = &text[0..text.len() - 2]; - } let (text, kind) = desugar_doc_comment_text(text, mode); let lit = tt::Literal { text_and_suffix: text, span, kind, suffix_len: 0 }; @@ -448,11 +443,11 @@ fn convert_doc_comment( }; // Make `doc="\" Comments\"" - let meta_tkns = [mk_ident("doc"), mk_punct('='), mk_doc_literal(&comment)]; + let meta_tkns = [mk_ident("doc"), mk_punct('='), mk_doc_literal(token)]; // Make `#![]` builder.push(mk_punct('#')); - if let ast::CommentPlacement::Inner = doc { + if is_inner { builder.push(mk_punct('!')); } builder.open(tt::DelimiterKind::Bracket, span); @@ -494,6 +489,7 @@ trait TokenConverter: Sized { fn convert_doc_comment( &self, token: &Self::Token, + is_inner: bool, span: Span, builder: &mut tt::TopSubtreeBuilder, ); @@ -538,9 +534,15 @@ impl SrcToken> for usize { impl TokenConverter for RawConverter<'_> { type Token = usize; - fn convert_doc_comment(&self, &token: &usize, span: Span, builder: &mut tt::TopSubtreeBuilder) { + fn convert_doc_comment( + &self, + &token: &usize, + is_inner: bool, + span: Span, + builder: &mut tt::TopSubtreeBuilder, + ) { let text = self.lexed.text(token); - convert_doc_comment(&doc_comment(text), span, self.mode, builder); + convert_doc_comment(&doc_comment(text), is_inner, span, self.mode, builder); } fn bump(&mut self) -> Option<(Self::Token, TextRange)> { @@ -574,9 +576,15 @@ impl TokenConverter for RawConverter<'_> { impl TokenConverter for StaticRawConverter<'_> { type Token = usize; - fn convert_doc_comment(&self, &token: &usize, span: Span, builder: &mut tt::TopSubtreeBuilder) { + fn convert_doc_comment( + &self, + &token: &usize, + is_inner: bool, + span: Span, + builder: &mut tt::TopSubtreeBuilder, + ) { let text = self.lexed.text(token); - convert_doc_comment(&doc_comment(text), span, self.mode, builder); + convert_doc_comment(&doc_comment(text), is_inner, span, self.mode, builder); } fn bump(&mut self) -> Option<(Self::Token, TextRange)> { @@ -752,10 +760,11 @@ where fn convert_doc_comment( &self, token: &Self::Token, + is_inner: bool, span: Span, builder: &mut tt::TopSubtreeBuilder, ) { - convert_doc_comment(token.token(), span, self.mode, builder); + convert_doc_comment(token.token(), is_inner, span, self.mode, builder); } fn bump(&mut self) -> Option<(Self::Token, TextRange)> { diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram index 7a24b32c87cfe..91387041a1116 100644 --- a/src/tools/rust-analyzer/crates/syntax/rust.ungram +++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram @@ -87,15 +87,15 @@ GenericParam = | TypeParam TypeParam = - Attr* Name (':' TypeBoundList?)? + AnyAttr* Name (':' TypeBoundList?)? ('=' default_type:Type)? ConstParam = - Attr* 'const' Name ':' Type + AnyAttr* 'const' Name ':' Type ('=' default_val:ConstArg)? LifetimeParam = - Attr* Lifetime (':' TypeBoundList?)? + AnyAttr* Lifetime (':' TypeBoundList?)? WhereClause = 'where' predicates:(WherePred (',' WherePred)* ','?) @@ -109,7 +109,7 @@ WherePred = //*************************// MacroCall = - Attr* Path '!' TokenTree ';'? + AnyAttr* Path '!' TokenTree ';'? TokenTree = '(' ')' @@ -123,9 +123,15 @@ MacroStmts = statements:Stmt* Expr? +AnyAttr = + Attr | DocComment + Attr = '#' '!'? '[' Meta ']' +DocComment = + '#inner_doc_comment' | '#outer_doc_comment' + CfgAttrMeta = 'cfg_attr' '(' CfgPredicate ',' (Meta (',' Meta)* ','?) ')' @@ -169,7 +175,7 @@ TokenTreeMeta = SourceFile = '#shebang'? '#frontmatter'? - Attr* + AnyAttr* Item* Item = @@ -192,32 +198,32 @@ Item = | AsmExpr MacroRules = - Attr* Visibility? + AnyAttr* Visibility? 'macro_rules' '!' Name TokenTree MacroDef = - Attr* Visibility? + AnyAttr* Visibility? 'macro' Name args:TokenTree? body:TokenTree Module = - Attr* Visibility? + AnyAttr* Visibility? 'mod' Name (ItemList | ';') ItemList = - '{' Attr* Item* '}' + '{' AnyAttr* Item* '}' ExternCrate = - Attr* Visibility? + AnyAttr* Visibility? 'extern' 'crate' NameRef Rename? ';' Rename = 'as' (Name | '_') Use = - Attr* Visibility? + AnyAttr* Visibility? 'use' UseTree ';' UseTree = @@ -228,7 +234,7 @@ UseTreeList = '{' (UseTree (',' UseTree)* ','?)? '}' Fn = - Attr* Visibility? + AnyAttr* Visibility? 'default'? 'const'? 'async'? 'gen'? 'unsafe'? 'safe'? Abi? 'fn' Name GenericParamList? ParamList RetType? WhereClause? (body:BlockExpr | ';') @@ -244,13 +250,13 @@ ParamList = | '|' (Param (',' Param)* ','?)? '|' SelfParam = - Attr* ( + AnyAttr* ( ('&' Lifetime?)? 'mut'? Name | 'mut'? Name ':' Type ) Param = - Attr* ( + AnyAttr* ( Pat (':' Type)? | Type | '...' @@ -260,13 +266,13 @@ RetType = '->' Type TypeAlias = - Attr* Visibility? + AnyAttr* Visibility? 'default'? 'type' Name GenericParamList? (':' TypeBoundList?)? WhereClause? ('=' Type)? ';' Struct = - Attr* Visibility? + AnyAttr* Visibility? 'struct' Name GenericParamList? ( WhereClause? (RecordFieldList | ';') | TupleFieldList WhereClause? ';' @@ -276,7 +282,7 @@ RecordFieldList = '{' fields:(RecordField (',' RecordField)* ','?)? '}' RecordField = - Attr* Visibility? 'unsafe'? + AnyAttr* Visibility? 'unsafe'? MutRestriction? Name ':' Type ('=' default_val:ConstArg)? @@ -284,7 +290,7 @@ TupleFieldList = '(' fields:(TupleField (',' TupleField)* ','?)? ')' TupleField = - Attr* Visibility? + AnyAttr* Visibility? MutRestriction? Type @@ -296,7 +302,7 @@ MutRestriction = 'mut' VisibilityInner Enum = - Attr* Visibility? + AnyAttr* Visibility? 'enum' Name GenericParamList? WhereClause? VariantList @@ -304,11 +310,11 @@ VariantList = '{' (Variant (',' Variant)* ','?)? '}' Variant = - Attr* Visibility? + AnyAttr* Visibility? (Name | '_') FieldList? ('=' ConstArg)? Union = - Attr* Visibility? + AnyAttr* Visibility? 'union' Name GenericParamList? WhereClause? RecordFieldList @@ -326,7 +332,7 @@ VariantDef = | Variant Const = - Attr* Visibility? + AnyAttr* Visibility? 'default'? 'type'? 'const' (Name | '_') GenericParamList? ':' Type @@ -334,13 +340,13 @@ Const = WhereClause? ';' Static = - Attr* Visibility? + AnyAttr* Visibility? 'unsafe'? 'safe'? 'static' 'mut'? Name ':' Type ('=' body:Expr)? ';' Trait = - Attr* Visibility? + AnyAttr* Visibility? 'unsafe'? 'auto'? ImplRestriction? 'trait' Name GenericParamList? @@ -351,7 +357,7 @@ ImplRestriction = 'impl' VisibilityInner AssocItemList = - '{' Attr* AssocItem* '}' + '{' AnyAttr* AssocItem* '}' AssocItem = Const @@ -360,16 +366,16 @@ AssocItem = | TypeAlias Impl = - Attr* Visibility? + AnyAttr* Visibility? 'default'? 'unsafe'? 'impl' GenericParamList? ('const'? '!'? trait:Type 'for')? self_ty:Type WhereClause? AssocItemList ExternBlock = - Attr* 'unsafe'? Abi ExternItemList + AnyAttr* 'unsafe'? Abi ExternItemList ExternItemList = - '{' Attr* ExternItem* '}' + '{' AnyAttr* ExternItem* '}' ExternItem = Fn @@ -394,7 +400,7 @@ Stmt = | LetStmt LetStmt = - Attr* 'super'? 'let' Pat (':' Type)? + AnyAttr* 'super'? 'let' Pat (':' Type)? '=' initializer:Expr LetElse? ';' @@ -450,13 +456,13 @@ IncludeBytesExpr = 'builtin' '#' 'include_bytes' OffsetOfExpr = - Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')' + AnyAttr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')' // asm := "asm!(" format_string *("," format_string) *("," operand) [","] ")" // global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")" // format_string := STRING_LITERAL / RAW_STRING_LITERAL AsmExpr = - Attr* 'builtin' '#' ( 'asm' | 'global_asm' | 'naked_asm' ) + AnyAttr* 'builtin' '#' ( 'asm' | 'global_asm' | 'naked_asm' ) '(' template:(Expr (',' Expr)*) (AsmPiece (',' AsmPiece)*)? ','? ')' // operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_" @@ -468,21 +474,21 @@ AsmRegSpec = '@string' | NameRef // reg_operand := [ident "="] dir_spec "(" reg_spec ")" operand_expr AsmRegOperand = AsmDirSpec '(' AsmRegSpec ')' AsmOperandExpr // clobber_abi := "clobber_abi(" *("," ) [","] ")" -AsmClobberAbi = Attr* 'clobber_abi' '(' ('@string' (',' '@string')* ','?) ')' +AsmClobberAbi = AnyAttr* 'clobber_abi' '(' ('@string' (',' '@string')* ','?) ')' // option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nostack" / "att_syntax" / "raw" AsmOption = 'pure' | 'nomem' | 'readonly' | 'preserves_flags' | 'noreturn' | 'nostack' | 'att_syntax' | 'raw' | 'may_unwind' // options := "options(" option *("," option) [","] ")" -AsmOptions = Attr* 'options' '(' (AsmOption (',' AsmOption)*) ','? ')' +AsmOptions = AnyAttr* 'options' '(' (AsmOption (',' AsmOption)*) ','? ')' AsmLabel = 'label' BlockExpr AsmSym = 'sym' Path AsmConst = 'const' Expr // operand := reg_operand / clobber_abi / options AsmOperand = AsmRegOperand | AsmLabel | AsmSym | AsmConst -AsmOperandNamed = Attr* (Name '=')? AsmOperand +AsmOperandNamed = AnyAttr* (Name '=')? AsmOperand AsmPiece = AsmOperandNamed | AsmClobberAbi | AsmOptions FormatArgsExpr = - Attr* 'builtin' '#' 'format_args' '(' + AnyAttr* 'builtin' '#' 'format_args' '(' template:Expr (',' args:(FormatArgsArg (',' FormatArgsArg)* ','?)? )? ')' @@ -494,7 +500,7 @@ MacroExpr = MacroCall Literal = - Attr* value:( + AnyAttr* value:( '@int_number' | '@float_number' | '@string' | '@byte_string' @@ -504,32 +510,32 @@ Literal = ) PathExpr = - Attr* Path + AnyAttr* Path StmtList = '{' - Attr* + AnyAttr* statements:Stmt* tail_expr:Expr? '}' RefExpr = - Attr* '&' (('raw' 'const'?)| ('raw'? 'mut') ) Expr + AnyAttr* '&' (('raw' 'const'?)| ('raw'? 'mut') ) Expr TryExpr = - Attr* Expr '?' + AnyAttr* Expr '?' TryBlockModifier = 'try' ('bikeshed' Type)? BlockExpr = - Attr* Label? (TryBlockModifier | 'unsafe' | ('async' 'move'?) | ('gen' 'move'?) | 'const') StmtList + AnyAttr* Label? (TryBlockModifier | 'unsafe' | ('async' 'move'?) | ('gen' 'move'?) | 'const') StmtList PrefixExpr = - Attr* op:('-' | '!' | '*') Expr + AnyAttr* op:('-' | '!' | '*') Expr BinExpr = - Attr* + AnyAttr* lhs:Expr op:( '||' | '&&' @@ -540,118 +546,118 @@ BinExpr = rhs:Expr CastExpr = - Attr* Expr 'as' Type + AnyAttr* Expr 'as' Type ParenExpr = - Attr* '(' Attr* Expr ')' + AnyAttr* '(' AnyAttr* Expr ')' ArrayExpr = - Attr* '[' Attr* ( + AnyAttr* '[' AnyAttr* ( (Expr (',' Expr)* ','?)? | Expr ';' Expr ) ']' IndexExpr = - Attr* base:Expr '[' index:Expr ']' + AnyAttr* base:Expr '[' index:Expr ']' TupleExpr = - Attr* '(' Attr* fields:(Expr (',' Expr)* ','?)? ')' + AnyAttr* '(' AnyAttr* fields:(Expr (',' Expr)* ','?)? ')' RecordExpr = Path RecordExprFieldList RecordExprFieldList = '{' - Attr* + AnyAttr* fields:(RecordExprField (',' RecordExprField)* ','?)? ('..' spread:Expr?)? '}' RecordExprField = - Attr* (NameRef ':')? Expr + AnyAttr* (NameRef ':')? Expr CallExpr = - Attr* Expr ArgList + AnyAttr* Expr ArgList ArgList = '(' args:(Expr (',' Expr)* ','?)? ')' MethodCallExpr = - Attr* receiver:Expr '.' NameRef GenericArgList? ArgList + AnyAttr* receiver:Expr '.' NameRef GenericArgList? ArgList FieldExpr = - Attr* Expr '.' NameRef + AnyAttr* Expr '.' NameRef ClosureExpr = - Attr* ForBinder? 'const'? 'static'? 'async'? 'gen'? 'move'? ParamList RetType? + AnyAttr* ForBinder? 'const'? 'static'? 'async'? 'gen'? 'move'? ParamList RetType? body:Expr ForBinder = 'for' GenericParamList IfExpr = - Attr* 'if' condition:Expr then_branch:BlockExpr + AnyAttr* 'if' condition:Expr then_branch:BlockExpr ('else' else_branch:(IfExpr | BlockExpr))? LoopExpr = - Attr* Label? 'loop' + AnyAttr* Label? 'loop' loop_body:BlockExpr ForExpr = - Attr* Label? 'for' Pat 'in' iterable:Expr + AnyAttr* Label? 'for' Pat 'in' iterable:Expr loop_body:BlockExpr WhileExpr = - Attr* Label? 'while' condition:Expr + AnyAttr* Label? 'while' condition:Expr loop_body:BlockExpr Label = Lifetime ':' BreakExpr = - Attr* 'break' Lifetime? Expr? + AnyAttr* 'break' Lifetime? Expr? ContinueExpr = - Attr* 'continue' Lifetime? + AnyAttr* 'continue' Lifetime? RangeExpr = - Attr* start:Expr? op:('..' | '..=') end:Expr? + AnyAttr* start:Expr? op:('..' | '..=') end:Expr? MatchExpr = - Attr* 'match' Expr MatchArmList + AnyAttr* 'match' Expr MatchArmList MatchArmList = '{' - Attr* + AnyAttr* arms:MatchArm* '}' MatchArm = - Attr* Pat guard:MatchGuard? '=>' Expr ','? + AnyAttr* Pat guard:MatchGuard? '=>' Expr ','? MatchGuard = 'if' condition:Expr ReturnExpr = - Attr* 'return' Expr? + AnyAttr* 'return' Expr? BecomeExpr = - Attr* 'become' Expr + AnyAttr* 'become' Expr YieldExpr = - Attr* 'yield' Expr? + AnyAttr* 'yield' Expr? YeetExpr = - Attr* 'do' 'yeet' Expr? + AnyAttr* 'do' 'yeet' Expr? LetExpr = - Attr* 'let' Pat '=' Expr + AnyAttr* 'let' Pat '=' Expr UnderscoreExpr = - Attr* '_' + AnyAttr* '_' AwaitExpr = - Attr* Expr '.' 'await' + AnyAttr* Expr '.' 'await' //*************************// // Types // @@ -768,7 +774,7 @@ LiteralPat = '-'? Literal IdentPat = - Attr* 'ref'? 'mut'? Name ('@' Pat)? + AnyAttr* 'ref'? 'mut'? Name ('@' Pat)? WildcardPat = '_' @@ -794,7 +800,7 @@ RecordPatFieldList = '}' RecordPatField = - Attr* (NameRef ':')? Pat + AnyAttr* (NameRef ':')? Pat TupleStructPat = Path '(' fields:(Pat (',' Pat)* ','?)? ')' @@ -818,7 +824,7 @@ BoxPat = 'box' Pat RestPat = - Attr* '..' + AnyAttr* '..' MacroPat = MacroCall diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast.rs b/src/tools/rust-analyzer/crates/syntax/src/ast.rs index 855b5a80a5f6d..2f1fad2dc7e31 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast.rs @@ -29,13 +29,11 @@ pub use self::{ TypeOrConstParam, VisibilityKind, }, operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}, - token_ext::{ - AnyString, CommentKind, CommentPlacement, CommentShape, IsString, QuoteOffsets, Radix, - }, + token_ext::{AnyComment, AnyString, CommentKind, CommentShape, IsString, QuoteOffsets, Radix}, traits::{ - AttrDocCommentIter, DocCommentIter, HasArgList, HasAttrs, HasDocComments, HasGenericArgs, - HasGenericParams, HasLoopBody, HasModuleItem, HasName, HasTypeBounds, HasVisibility, - attrs_including_inner, + AttrsIter, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, + HasModuleItem, HasName, HasTypeBounds, HasVisibility, attrs_including_inner, + attrs_with_doc_including_inner, }, }; @@ -170,6 +168,14 @@ mod support { } } +#[cfg(test)] +fn doc_comment_text(owner: impl HasAttrs) -> Option { + use itertools::Itertools; + + let docs = owner.doc_comments().map(|comment| comment.text().to_owned()).join("\n"); + if docs.is_empty() { None } else { Some(docs) } +} + #[test] fn assert_ast_is_dyn_compatible() { fn _f(_: &dyn AstNode, _: &dyn HasName) {} @@ -187,7 +193,7 @@ fn test_doc_comment_none() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert!(module.doc_comments().doc_comment_text().is_none()); + assert!(doc_comment_text(module).is_none()); } #[test] @@ -203,7 +209,7 @@ fn test_outer_doc_comment_of_items() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" doc", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" doc", doc_comment_text(module).unwrap()); } #[test] @@ -219,7 +225,7 @@ fn test_inner_doc_comment_of_items() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert!(module.doc_comments().doc_comment_text().is_none()); + assert!(doc_comment_text(module).is_none()); } #[test] @@ -234,7 +240,7 @@ fn test_doc_comment_of_statics() { .ok() .unwrap(); let st = file.syntax().descendants().find_map(Static::cast).unwrap(); - assert_eq!(" Number of levels", st.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" Number of levels", doc_comment_text(st).unwrap()); } #[test] @@ -256,7 +262,7 @@ fn test_doc_comment_preserves_indents() { let module = file.syntax().descendants().find_map(Module::cast).unwrap(); assert_eq!( " doc1\n ```\n fn foo() {\n // ...\n }\n ```", - module.doc_comments().doc_comment_text().unwrap() + doc_comment_text(module).unwrap() ); } @@ -275,7 +281,7 @@ fn test_doc_comment_preserves_newlines() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" this\n is\n mod\n foo", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" this\n is\n mod\n foo", doc_comment_text(module).unwrap()); } #[test] @@ -290,7 +296,7 @@ fn test_doc_comment_single_line_block_strips_suffix() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" this is mod foo", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" this is mod foo", doc_comment_text(module).unwrap()); } #[test] @@ -305,7 +311,7 @@ fn test_doc_comment_single_line_block_strips_suffix_whitespace() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" this is mod foo ", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" this is mod foo ", doc_comment_text(module).unwrap()); } #[test] @@ -326,7 +332,7 @@ fn test_doc_comment_multi_line_block_strips_suffix() { let module = file.syntax().descendants().find_map(Module::cast).unwrap(); assert_eq!( "\n this\n is\n mod foo\n ", - module.doc_comments().doc_comment_text().unwrap() + doc_comment_text(module).unwrap() ); } @@ -340,7 +346,7 @@ fn test_comments_preserve_trailing_whitespace() { let def = file.syntax().descendants().find_map(Struct::cast).unwrap(); assert_eq!( " Representation of a Realm. \n In the specification these are called Realm Records.", - def.doc_comments().doc_comment_text().unwrap() + doc_comment_text(def).unwrap() ); } @@ -357,7 +363,7 @@ fn test_four_slash_line_comment() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" doc comment", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" doc comment", doc_comment_text(module).unwrap()); } #[test] diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs index 852b13fc7a3d2..e34cfa7460735 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs @@ -1,7 +1,7 @@ //! This module contains functions for editing syntax trees. As the trees are //! immutable, all function here return a fresh copy of the tree, instead of //! doing an in-place modification. -use parser::T; +use parser::{SyntaxKind::DOC_COMMENT, T}; use std::{ fmt, iter::{self, once}, @@ -165,7 +165,7 @@ pub trait AttrsOwnerEdit: ast::HasAttrs { let mut remove_next_ws = false; for child in self.syntax().children_with_tokens() { match child.kind() { - ATTR | COMMENT => { + ATTR | COMMENT | DOC_COMMENT => { remove_next_ws = true; editor.delete(child); continue; diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs index 5fa56cf33c5d8..ad52d82787278 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs @@ -468,7 +468,6 @@ pub struct Const { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Const {} -impl ast::HasDocComments for Const {} impl ast::HasGenericParams for Const {} impl ast::HasName for Const {} impl ast::HasVisibility for Const {} @@ -552,6 +551,19 @@ impl DerefPat { #[inline] pub fn deref_token(&self) -> Option { support::token(&self.syntax, T![deref]) } } +pub struct DocComment { + pub(crate) syntax: SyntaxNode, +} +impl DocComment { + #[inline] + pub fn inner_doc_comment_token(&self) -> Option { + support::token(&self.syntax, T![inner_doc_comment]) + } + #[inline] + pub fn outer_doc_comment_token(&self) -> Option { + support::token(&self.syntax, T![outer_doc_comment]) + } +} pub struct DynTraitType { pub(crate) syntax: SyntaxNode, } @@ -565,7 +577,6 @@ pub struct Enum { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Enum {} -impl ast::HasDocComments for Enum {} impl ast::HasGenericParams for Enum {} impl ast::HasName for Enum {} impl ast::HasVisibility for Enum {} @@ -588,7 +599,6 @@ pub struct ExternBlock { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for ExternBlock {} -impl ast::HasDocComments for ExternBlock {} impl ExternBlock { #[inline] pub fn abi(&self) -> Option { support::child(&self.syntax) } @@ -601,7 +611,6 @@ pub struct ExternCrate { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for ExternCrate {} -impl ast::HasDocComments for ExternCrate {} impl ast::HasVisibility for ExternCrate {} impl ExternCrate { #[inline] @@ -643,7 +652,6 @@ pub struct Fn { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Fn {} -impl ast::HasDocComments for Fn {} impl ast::HasGenericParams for Fn {} impl ast::HasName for Fn {} impl ast::HasVisibility for Fn {} @@ -805,7 +813,6 @@ pub struct Impl { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Impl {} -impl ast::HasDocComments for Impl {} impl ast::HasGenericParams for Impl {} impl ast::HasVisibility for Impl {} impl Impl { @@ -1002,7 +1009,6 @@ pub struct MacroCall { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for MacroCall {} -impl ast::HasDocComments for MacroCall {} impl MacroCall { #[inline] pub fn path(&self) -> Option { support::child(&self.syntax) } @@ -1017,7 +1023,6 @@ pub struct MacroDef { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for MacroDef {} -impl ast::HasDocComments for MacroDef {} impl ast::HasName for MacroDef {} impl ast::HasVisibility for MacroDef {} impl MacroDef { @@ -1047,7 +1052,6 @@ pub struct MacroRules { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for MacroRules {} -impl ast::HasDocComments for MacroRules {} impl ast::HasName for MacroRules {} impl ast::HasVisibility for MacroRules {} impl MacroRules { @@ -1141,7 +1145,6 @@ pub struct Module { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Module {} -impl ast::HasDocComments for Module {} impl ast::HasName for Module {} impl ast::HasVisibility for Module {} impl Module { @@ -1472,7 +1475,6 @@ pub struct RecordField { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for RecordField {} -impl ast::HasDocComments for RecordField {} impl ast::HasName for RecordField {} impl ast::HasVisibility for RecordField {} impl RecordField { @@ -1667,7 +1669,6 @@ pub struct SourceFile { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for SourceFile {} -impl ast::HasDocComments for SourceFile {} impl ast::HasModuleItem for SourceFile {} impl SourceFile { #[inline] @@ -1681,7 +1682,6 @@ pub struct Static { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Static {} -impl ast::HasDocComments for Static {} impl ast::HasName for Static {} impl ast::HasVisibility for Static {} impl Static { @@ -1720,7 +1720,6 @@ pub struct Struct { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Struct {} -impl ast::HasDocComments for Struct {} impl ast::HasGenericParams for Struct {} impl ast::HasName for Struct {} impl ast::HasVisibility for Struct {} @@ -1762,7 +1761,6 @@ pub struct Trait { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Trait {} -impl ast::HasDocComments for Trait {} impl ast::HasGenericParams for Trait {} impl ast::HasName for Trait {} impl ast::HasTypeBounds for Trait {} @@ -1822,7 +1820,6 @@ pub struct TupleField { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for TupleField {} -impl ast::HasDocComments for TupleField {} impl ast::HasVisibility for TupleField {} impl TupleField { #[inline] @@ -1880,7 +1877,6 @@ pub struct TypeAlias { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for TypeAlias {} -impl ast::HasDocComments for TypeAlias {} impl ast::HasGenericParams for TypeAlias {} impl ast::HasName for TypeAlias {} impl ast::HasTypeBounds for TypeAlias {} @@ -1979,7 +1975,6 @@ pub struct Union { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Union {} -impl ast::HasDocComments for Union {} impl ast::HasGenericParams for Union {} impl ast::HasName for Union {} impl ast::HasVisibility for Union {} @@ -2006,7 +2001,6 @@ pub struct Use { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Use {} -impl ast::HasDocComments for Use {} impl ast::HasVisibility for Use {} impl Use { #[inline] @@ -2059,7 +2053,6 @@ pub struct Variant { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Variant {} -impl ast::HasDocComments for Variant {} impl ast::HasName for Variant {} impl ast::HasVisibility for Variant {} impl Variant { @@ -2171,11 +2164,16 @@ pub enum Adt { Union(Union), } impl ast::HasAttrs for Adt {} -impl ast::HasDocComments for Adt {} impl ast::HasGenericParams for Adt {} impl ast::HasName for Adt {} impl ast::HasVisibility for Adt {} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum AnyAttr { + Attr(Attr), + DocComment(DocComment), +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum AsmOperand { AsmConst(AsmConst), @@ -2200,7 +2198,6 @@ pub enum AssocItem { TypeAlias(TypeAlias), } impl ast::HasAttrs for AssocItem {} -impl ast::HasDocComments for AssocItem {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CfgPredicate { @@ -2257,7 +2254,6 @@ pub enum ExternItem { TypeAlias(TypeAlias), } impl ast::HasAttrs for ExternItem {} -impl ast::HasDocComments for ExternItem {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum FieldList { @@ -2374,7 +2370,6 @@ pub enum VariantDef { Variant(Variant), } impl ast::HasAttrs for VariantDef {} -impl ast::HasDocComments for VariantDef {} impl ast::HasName for VariantDef {} impl ast::HasVisibility for VariantDef {} pub struct AnyHasArgList { @@ -2395,15 +2390,6 @@ impl AnyHasAttrs { AnyHasAttrs { syntax: node.syntax().clone() } } } -pub struct AnyHasDocComments { - pub(crate) syntax: SyntaxNode, -} -impl AnyHasDocComments { - #[inline] - pub fn new(node: T) -> AnyHasDocComments { - AnyHasDocComments { syntax: node.syntax().clone() } - } -} pub struct AnyHasGenericArgs { pub(crate) syntax: SyntaxNode, } @@ -3683,6 +3669,38 @@ impl fmt::Debug for DerefPat { f.debug_struct("DerefPat").field("syntax", &self.syntax).finish() } } +impl AstNode for DocComment { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + DOC_COMMENT + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == DOC_COMMENT } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for DocComment { + fn hash(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for DocComment {} +impl PartialEq for DocComment { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for DocComment { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for DocComment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DocComment").field("syntax", &self.syntax).finish() + } +} impl AstNode for DynTraitType { #[inline] fn kind() -> SyntaxKind @@ -7909,6 +7927,34 @@ impl AstNode for Adt { } } } +impl From for AnyAttr { + #[inline] + fn from(node: Attr) -> AnyAttr { AnyAttr::Attr(node) } +} +impl From for AnyAttr { + #[inline] + fn from(node: DocComment) -> AnyAttr { AnyAttr::DocComment(node) } +} +impl AstNode for AnyAttr { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, ATTR | DOC_COMMENT) } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + ATTR => AnyAttr::Attr(Attr { syntax }), + DOC_COMMENT => AnyAttr::DocComment(DocComment { syntax }), + _ => return None, + }; + Some(res) + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + match self { + AnyAttr::Attr(it) => &it.syntax, + AnyAttr::DocComment(it) => &it.syntax, + } + } +} impl From for AsmOperand { #[inline] fn from(node: AsmConst) -> AsmOperand { AsmOperand::AsmConst(node) } @@ -9455,136 +9501,6 @@ impl From for AnyHasAttrs { #[inline] fn from(node: YieldExpr) -> AnyHasAttrs { AnyHasAttrs { syntax: node.syntax } } } -impl ast::HasDocComments for AnyHasDocComments {} -impl AstNode for AnyHasDocComments { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - matches!( - kind, - CONST - | ENUM - | EXTERN_BLOCK - | EXTERN_CRATE - | FN - | IMPL - | MACRO_CALL - | MACRO_DEF - | MACRO_RULES - | MODULE - | RECORD_FIELD - | SOURCE_FILE - | STATIC - | STRUCT - | TRAIT - | TUPLE_FIELD - | TYPE_ALIAS - | UNION - | USE - | VARIANT - ) - } - #[inline] - fn cast(syntax: SyntaxNode) -> Option { - Self::can_cast(syntax.kind()).then_some(AnyHasDocComments { syntax }) - } - #[inline] - fn syntax(&self) -> &SyntaxNode { &self.syntax } -} -impl hash::Hash for AnyHasDocComments { - fn hash(&self, state: &mut H) { self.syntax.hash(state); } -} -impl Eq for AnyHasDocComments {} -impl PartialEq for AnyHasDocComments { - fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } -} -impl Clone for AnyHasDocComments { - fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } -} -impl fmt::Debug for AnyHasDocComments { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AnyHasDocComments").field("syntax", &self.syntax).finish() - } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Const) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Enum) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: ExternBlock) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: ExternCrate) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Fn) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Impl) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: MacroCall) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: MacroDef) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: MacroRules) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Module) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: RecordField) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: SourceFile) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Static) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Struct) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Trait) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: TupleField) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: TypeAlias) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Union) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Use) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Variant) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} impl ast::HasGenericArgs for AnyHasGenericArgs {} impl AstNode for AnyHasGenericArgs { #[inline] @@ -10066,6 +9982,11 @@ impl std::fmt::Display for Adt { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for AnyAttr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for AsmOperand { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -10336,6 +10257,11 @@ impl std::fmt::Display for DerefPat { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for DocComment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for DynTraitType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs index 672e2fd233e48..eef314175ae1c 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs @@ -7,14 +7,14 @@ use std::{fmt, iter::successors}; use itertools::Itertools; use parser::SyntaxKind; -use rowan::{GreenNodeData, GreenTokenData}; +use rowan::{GreenNodeData, GreenTokenData, TextSize}; use smallvec::{SmallVec, smallvec}; use crate::{ NodeOrToken, SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxToken, T, ast::{ - self, AstNode, AstToken, HasAttrs, HasGenericArgs, HasGenericParams, HasName, - HasTypeBounds, SyntaxNode, support, + self, AnyComment, AstNode, AstToken, CommentShape, HasAttrs, HasGenericArgs, + HasGenericParams, HasName, HasTypeBounds, SyntaxNode, support, }, syntax_editor::SyntaxEditor, }; @@ -271,6 +271,55 @@ impl ast::Attr { } } +impl ast::DocComment { + // `///` or `/**` or `//!` or `/*!`, all are 3 chars. + pub const PREFIX_LEN: TextSize = TextSize::new(3); + + pub fn kind(&self) -> AttrKind { + match self.inner_doc_comment_token() { + Some(_) => AttrKind::Inner, + None => AttrKind::Outer, + } + } + + pub fn token(&self) -> AnyComment { + self.syntax + .first_token() + .and_then(ast::AnyComment::cast) + .expect("`ast::DocComment` must have a comment token") + } + + pub fn shape(&self) -> CommentShape { + CommentShape::from_text(self.text_with_markers()) + } + + /// Returns the text with the `/**...*/` or `/*!...*/` or `///...` or `//!...` markers. + pub fn text_with_markers(&self) -> &str { + text_of_first_token(&self.syntax) + } + + /// Returns the textual content of a doc comment node as a single string with prefix and suffix removed. + pub fn text(&self) -> &str { + let shape = self.shape(); + let text = &self.text_with_markers()[Self::PREFIX_LEN.into()..]; + if shape == CommentShape::Block { + // The `*/` may not exist because of recovery. + text.strip_suffix("*/").unwrap_or(text) + } else { + text + } + } +} + +impl ast::AnyAttr { + pub fn kind(&self) -> AttrKind { + match self { + ast::AnyAttr::Attr(it) => it.kind(), + ast::AnyAttr::DocComment(it) => it.kind(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum PathSegmentKind { Name(ast::NameRef), @@ -1129,8 +1178,6 @@ impl ast::HasLoopBody for ast::WhileExpr { } } -impl ast::HasAttrs for ast::AnyHasDocComments {} - impl From for ast::Item { fn from(it: ast::Adt) -> Self { match it { diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs index bb0f53db24739..07240265da193 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs @@ -3,6 +3,7 @@ use std::ops::Range; use std::{borrow::Cow, num::ParseIntError}; +use parser::SyntaxKind; use rustc_literal_escaper::{ EscapeError, MixedUnit, unescape_byte, unescape_byte_str, unescape_c_str, unescape_char, unescape_str, @@ -10,47 +11,23 @@ use rustc_literal_escaper::{ use stdx::always; use crate::{ - TextRange, TextSize, - ast::{self, AstToken}, + SyntaxToken, TextRange, TextSize, + ast::{self, AstToken, AttrKind}, }; impl ast::Comment { - pub fn kind(&self) -> CommentKind { - CommentKind::from_text(self.text()) - } - - pub fn is_doc(&self) -> bool { - self.kind().doc.is_some() - } - - pub fn is_inner(&self) -> bool { - self.kind().doc == Some(CommentPlacement::Inner) - } - - pub fn is_outer(&self) -> bool { - self.kind().doc == Some(CommentPlacement::Outer) - } - - pub fn prefix(&self) -> &'static str { - self.kind().prefix() + pub fn shape(&self) -> CommentShape { + CommentShape::from_text(self.text()) } - /// Returns the textual content of a doc comment node as a single string with prefix and suffix - /// removed, plus the offset of the returned string from the beginning of the comment. - pub fn doc_comment(&self) -> Option<(&str, TextSize)> { - let kind = self.kind(); - match kind { - CommentKind { shape, doc: Some(_) } => { - let prefix = kind.prefix(); - let text = &self.text()[prefix.len()..]; - let text = if shape == CommentShape::Block { - text.strip_suffix("*/").unwrap_or(text) - } else { - text - }; - Some((text, TextSize::of(prefix))) - } - _ => None, + /// Returns the text without the `//` or `/*...*/` markers. + pub fn text_without_markers(&self) -> &str { + let text = self.text(); + let shape = CommentShape::from_text(text); + let text = &text[2..]; + match shape { + CommentShape::Block => text.strip_suffix("*/").unwrap_or(text), + CommentShape::Line => text, } } } @@ -58,7 +35,20 @@ impl ast::Comment { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct CommentKind { pub shape: CommentShape, - pub doc: Option, + pub doc: Option, +} + +impl CommentKind { + pub fn prefix(&self) -> &'static str { + match (self.shape, self.doc) { + (CommentShape::Line, None) => "//", + (CommentShape::Line, Some(AttrKind::Inner)) => "//!", + (CommentShape::Line, Some(AttrKind::Outer)) => "///", + (CommentShape::Block, None) => "/*", + (CommentShape::Block, Some(AttrKind::Inner)) => "/*!", + (CommentShape::Block, Some(AttrKind::Outer)) => "/**", + } + } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -68,6 +58,11 @@ pub enum CommentShape { } impl CommentShape { + #[inline] + pub fn from_text(text: &str) -> CommentShape { + if text.starts_with("/*") { CommentShape::Block } else { CommentShape::Line } + } + pub fn is_line(self) -> bool { self == CommentShape::Line } @@ -77,37 +72,80 @@ impl CommentShape { } } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum CommentPlacement { - Inner, - Outer, +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AnyComment { + syntax: SyntaxToken, } -impl CommentKind { - const BY_PREFIX: [(&'static str, CommentKind); 9] = [ - ("/**/", CommentKind { shape: CommentShape::Block, doc: None }), - ("/***", CommentKind { shape: CommentShape::Block, doc: None }), - ("////", CommentKind { shape: CommentShape::Line, doc: None }), - ("///", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Outer) }), - ("//!", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Inner) }), - ("/**", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Outer) }), - ("/*!", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Inner) }), - ("//", CommentKind { shape: CommentShape::Line, doc: None }), - ("/*", CommentKind { shape: CommentShape::Block, doc: None }), - ]; - - pub(crate) fn from_text(text: &str) -> CommentKind { - let &(_prefix, kind) = CommentKind::BY_PREFIX - .iter() - .find(|&(prefix, _kind)| text.starts_with(prefix)) - .unwrap(); - kind +impl AstToken for AnyComment { + fn can_cast(kind: SyntaxKind) -> bool + where + Self: Sized, + { + matches!( + kind, + SyntaxKind::COMMENT | SyntaxKind::INNER_DOC_COMMENT | SyntaxKind::OUTER_DOC_COMMENT + ) + } + + fn cast(syntax: SyntaxToken) -> Option + where + Self: Sized, + { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + + fn syntax(&self) -> &SyntaxToken { + &self.syntax + } +} + +impl AnyComment { + pub fn shape(&self) -> CommentShape { + CommentShape::from_text(self.text_with_markers()) + } + + pub fn doc_kind(&self) -> Option { + match self.syntax.kind() { + SyntaxKind::COMMENT => None, + SyntaxKind::INNER_DOC_COMMENT => Some(AttrKind::Inner), + SyntaxKind::OUTER_DOC_COMMENT => Some(AttrKind::Outer), + _ => unreachable!(), + } + } + + pub fn kind(&self) -> CommentKind { + CommentKind { shape: self.shape(), doc: self.doc_kind() } } pub fn prefix(&self) -> &'static str { - let &(prefix, _) = - CommentKind::BY_PREFIX.iter().rev().find(|(_, kind)| kind == self).unwrap(); - prefix + self.kind().prefix() + } + + pub fn is_inner(&self) -> bool { + self.doc_kind() == Some(AttrKind::Inner) + } + + pub fn is_outer(&self) -> bool { + self.doc_kind() == Some(AttrKind::Outer) + } + + /// Returns the text with the `/*...*/` or `//...` or `/**...*/` or `/*!...*/` or `///...` or `//!...` markers. + pub fn text_with_markers(&self) -> &str { + self.syntax.text() + } + + /// Returns the textual content of a doc comment node as a single string with prefix and suffix removed. + pub fn text(&self) -> &str { + let shape = self.shape(); + let prefix_len = if self.doc_kind().is_some() { 3 } else { 2 }; + let text = &self.text_with_markers()[prefix_len..]; + if shape == CommentShape::Block { + // The `*/` may not exist because of recovery. + text.strip_suffix("*/").unwrap_or(text) + } else { + text + } } } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/traits.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/traits.rs index 6fe5abb84e22b..59e1835b02d60 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/traits.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/traits.rs @@ -4,10 +4,9 @@ use either::Either; use crate::{ - SyntaxElement, SyntaxNode, SyntaxToken, T, - ast::{self, AstChildren, AstNode, AstToken, support}, + SyntaxNode, SyntaxToken, T, + ast::{self, AstChildren, AstNode, support}, match_ast, - syntax_node::SyntaxElementChildren, }; pub trait HasName: AstNode { @@ -74,6 +73,14 @@ pub trait HasAttrs: AstNode { support::children(self.syntax()) } + fn doc_comments(&self) -> AstChildren { + support::children(self.syntax()) + } + + fn attrs_with_doc(&self) -> AstChildren { + support::children(self.syntax()) + } + /// This may return the same node as called with (with `SourceFile`). The caller has the responsibility /// to avoid duplicate attributes. fn inner_attributes_node(&self) -> Option { @@ -102,68 +109,42 @@ pub trait HasAttrs: AstNode { /// Returns all attributes of this node, including inner attributes that may not be directly under this node /// but under a child. -pub fn attrs_including_inner(owner: &dyn HasAttrs) -> impl Iterator + Clone { - owner.attrs().filter(|attr| attr.kind().is_outer()).chain( +pub fn attrs_with_doc_including_inner( + owner: &dyn HasAttrs, +) -> impl Iterator + Clone { + owner.attrs_with_doc().filter(|attr| attr.kind().is_outer()).chain( owner .inner_attributes_node() .into_iter() - .flat_map(|node| support::children::(&node)) + .flat_map(|node| support::children::(&node)) .filter(|attr| attr.kind().is_inner()), ) } -pub trait HasDocComments: HasAttrs { - fn doc_comments(&self) -> DocCommentIter { - DocCommentIter { iter: self.syntax().children_with_tokens() } - } -} - -impl DocCommentIter { - pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> DocCommentIter { - DocCommentIter { iter: syntax_node.children_with_tokens() } - } - - #[cfg(test)] - pub fn doc_comment_text(self) -> Option { - let docs = itertools::Itertools::join( - &mut self.filter_map(|comment| comment.doc_comment().map(|it| it.0.to_owned())), - "\n", - ); - if docs.is_empty() { None } else { Some(docs) } - } +pub fn attrs_including_inner(owner: &dyn HasAttrs) -> impl Iterator + Clone { + AttrsIter::new(attrs_with_doc_including_inner(owner)) } -pub struct DocCommentIter { - iter: SyntaxElementChildren, +#[derive(Clone)] +pub struct AttrsIter { + inner: I, } -impl Iterator for DocCommentIter { - type Item = ast::Comment; - fn next(&mut self) -> Option { - self.iter.by_ref().find_map(|el| { - el.into_token().and_then(ast::Comment::cast).filter(ast::Comment::is_doc) - }) +impl> AttrsIter { + #[inline] + pub fn new(inner: I) -> Self { + Self { inner } } } -pub struct AttrDocCommentIter { - iter: SyntaxElementChildren, -} +impl> Iterator for AttrsIter { + type Item = ast::Attr; -impl AttrDocCommentIter { - pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> AttrDocCommentIter { - AttrDocCommentIter { iter: syntax_node.children_with_tokens() } - } -} - -impl Iterator for AttrDocCommentIter { - type Item = Either; + #[inline] fn next(&mut self) -> Option { - self.iter.find_map(|el| match el { - SyntaxElement::Node(node) => ast::Attr::cast(node).map(Either::Left), - SyntaxElement::Token(tok) => { - ast::Comment::cast(tok).filter(ast::Comment::is_doc).map(Either::Right) - } + self.inner.find_map(|attr| match attr { + ast::AnyAttr::Attr(it) => Some(it), + ast::AnyAttr::DocComment(_) => None, }) } } diff --git a/src/tools/rust-analyzer/crates/syntax/src/parsing/reparsing.rs b/src/tools/rust-analyzer/crates/syntax/src/parsing/reparsing.rs index 5f193f01bc73b..df1d6a1713d19 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/parsing/reparsing.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/parsing/reparsing.rs @@ -384,14 +384,6 @@ fn baz $0$0 () {} " \t\t\n\n", 2, ); - do_check( - r" -/// foo $0$0omment -mod { } -", - "c", - 14, - ); do_check( r#" fn -> &str { "Hello$0$0" } diff --git a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast index f56af1a5c05b4..8952e88e70023 100644 --- a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast +++ b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast @@ -40,7 +40,8 @@ SOURCE_FILE@0..611 R_PAREN@81..82 ")" R_BRACK@82..83 "]" WHITESPACE@83..92 "\n " - COMMENT@92..122 "//! Nor are ModuleDoc ..." + DOC_COMMENT@92..122 + INNER_DOC_COMMENT@92..122 "//! Nor are ModuleDoc ..." WHITESPACE@122..127 "\n " R_CURLY@127..128 "}" SEMICOLON@128..129 ";" @@ -86,7 +87,8 @@ SOURCE_FILE@0..611 R_PAREN@210..211 ")" R_BRACK@211..212 "]" WHITESPACE@212..221 "\n " - COMMENT@221..251 "//! Nor are ModuleDoc ..." + DOC_COMMENT@221..251 + INNER_DOC_COMMENT@221..251 "//! Nor are ModuleDoc ..." WHITESPACE@251..256 "\n " R_CURLY@256..257 "}" WHITESPACE@257..262 "\n " @@ -116,7 +118,8 @@ SOURCE_FILE@0..611 R_PAREN@300..301 ")" R_BRACK@301..302 "]" WHITESPACE@302..311 "\n " - COMMENT@311..341 "//! Nor are ModuleDoc ..." + DOC_COMMENT@311..341 + INNER_DOC_COMMENT@311..341 "//! Nor are ModuleDoc ..." WHITESPACE@341..346 "\n " R_CURLY@346..347 "}" WHITESPACE@347..353 "\n " @@ -143,7 +146,8 @@ SOURCE_FILE@0..611 R_PAREN@428..429 ")" R_BRACK@429..430 "]" WHITESPACE@430..439 "\n " - COMMENT@439..468 "//! So are ModuleDoc ..." + DOC_COMMENT@439..468 + INNER_DOC_COMMENT@439..468 "//! So are ModuleDoc ..." WHITESPACE@468..473 "\n " R_CURLY@473..474 "}" WHITESPACE@474..479 "\n " @@ -181,7 +185,8 @@ SOURCE_FILE@0..611 R_PAREN@562..563 ")" R_BRACK@563..564 "]" WHITESPACE@564..573 "\n " - COMMENT@573..602 "//! So are ModuleDoc ..." + DOC_COMMENT@573..602 + INNER_DOC_COMMENT@573..602 "//! So are ModuleDoc ..." WHITESPACE@602..607 "\n " R_CURLY@607..608 "}" WHITESPACE@608..609 "\n" diff --git a/src/tools/rust-analyzer/xtask/src/codegen/grammar.rs b/src/tools/rust-analyzer/xtask/src/codegen/grammar.rs index 257429c42661f..0553234a5c898 100644 --- a/src/tools/rust-analyzer/xtask/src/codegen/grammar.rs +++ b/src/tools/rust-analyzer/xtask/src/codegen/grammar.rs @@ -672,6 +672,8 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String { [string] => { $crate::SyntaxKind::STRING }; [shebang] => { $crate::SyntaxKind::SHEBANG }; [frontmatter] => { $crate::SyntaxKind::FRONTMATTER }; + [inner_doc_comment] => { $crate::SyntaxKind::INNER_DOC_COMMENT }; + [outer_doc_comment] => { $crate::SyntaxKind::OUTER_DOC_COMMENT }; } impl ::core::marker::Copy for SyntaxKind {} @@ -938,7 +940,13 @@ fn lower_rule(acc: &mut Vec, grammar: &Grammar, label: Option<&String>, r Rule::Rep(inner) => { if let Rule::Node(node) = &**inner { let ty = grammar[*node].name.clone(); - let name = label.cloned().unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty))); + let name = label.cloned().unwrap_or_else(|| { + if ty == "AnyAttr" { + "attrs".to_owned() + } else { + pluralize(&to_lower_snake_case(&ty)) + } + }); let field = Field::Node { name, ty, cardinality: Cardinality::Many }; acc.push(field); return; @@ -1089,35 +1097,6 @@ fn extract_struct_traits(ast: &mut AstSrc) { extract_struct_trait(node, name, methods); } } - - let nodes_with_doc_comments = [ - "SourceFile", - "Fn", - "Struct", - "Union", - "RecordField", - "TupleField", - "Enum", - "Variant", - "Trait", - "Module", - "Static", - "Const", - "TypeAlias", - "Impl", - "ExternBlock", - "ExternCrate", - "MacroCall", - "MacroRules", - "MacroDef", - "Use", - ]; - - for node in &mut ast.nodes { - if nodes_with_doc_comments.contains(&&*node.name) { - node.traits.push("HasDocComments".into()); - } - } } fn extract_struct_trait(node: &mut AstNodeSrc, trait_name: &str, methods: &[&str]) { From adbeb5e931287225d610a85f7d7c23e2377fc100 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 31 Aug 2026 22:59:51 +0200 Subject: [PATCH 06/38] rename `StoredProjection::lookup` to `StoredProjection::as_slice` --- src/tools/rust-analyzer/crates/hir-ty/src/mir.rs | 5 ++--- src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs | 2 +- src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs | 2 +- src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs index fc71e716d90f6..cd132e6012872 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs @@ -267,13 +267,12 @@ impl<'db> std::ops::Deref for Projection<'db> { } impl StoredProjection { - // FIXME: rename to as_slice - pub fn lookup(&self) -> &[PlaceElem] { + pub fn as_slice(&self) -> &[PlaceElem] { self.as_ref().as_slice() } pub fn is_empty(&self) -> bool { - self.lookup().is_empty() + self.as_slice().is_empty() } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index e513f13ed85a5..51b94929dcd2e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -792,7 +792,7 @@ impl<'a, 'db> Evaluator<'a, 'db> { let mut addr = locals.ptr[p.local].addr; let mut ty = PlaceTy::from_ty(locals.body.locals[p.local].ty.as_ref()); let mut metadata: Option = None; // locals are always sized - for proj in p.projection.lookup() { + for proj in p.projection.as_slice() { let prev_ty = ty; ty = self.projected_ty(ty, *proj); match proj { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index ab0c25201a925..48dd56b869ffd 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -2152,7 +2152,7 @@ pub fn mir_body_for_closure_query<'db>( } let mut err = None; ctx.result.walk_places(|mir_place| { - let mir_projections = mir_place.projection.lookup(); + let mir_projections = mir_place.projection.as_slice(); if let Some(hir_places) = upvar_map.get(&mir_place.local) { let projections = hir_places.iter().find_map(|hir_place| { let iter = mir_projections diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs index 4a51b5113a436..67f88e9503d64 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs @@ -398,7 +398,7 @@ impl<'a, 'db> MirPrettyCtx<'a, 'db> { } } } - f(self, p.local, p.projection.lookup()); + f(self, p.local, p.projection.as_slice()); } fn operand(&mut self, r: &Operand) { From 911ae19bfa3c6361fb5bde9aac4ad164bd1ab9f6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 31 Aug 2026 23:00:13 +0200 Subject: [PATCH 07/38] rename `Place` to `StoredPlace` --- .../rust-analyzer/crates/hir-ty/src/mir.rs | 38 +++++++++---------- .../crates/hir-ty/src/mir/eval.rs | 20 ++++++---- .../crates/hir-ty/src/mir/lower.rs | 20 +++++----- .../crates/hir-ty/src/mir/pretty.rs | 4 +- 4 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs index cd132e6012872..297a236333db3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs @@ -92,7 +92,7 @@ pub enum OperandKind { /// /// Before drop elaboration, the type of the place must be `Copy`. After drop elaboration there /// is no such requirement. - Copy(Place), + Copy(StoredPlace), /// Creates a value by performing loading the place, just like the `Copy` operand. /// @@ -101,7 +101,7 @@ pub enum OperandKind { /// place without first re-initializing it. /// /// [UCG#188]: https://github.com/rust-lang/unsafe-code-guidelines/issues/188 - Move(Place), + Move(StoredPlace), /// Constants are already semantically values, and remain unchanged. Constant { konst: StoredConst, @@ -284,12 +284,12 @@ pub struct PlaceRef<'db> { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Place { +pub struct StoredPlace { pub local: LocalId, pub projection: StoredProjection, } -impl Place { +impl StoredPlace { pub fn as_ref<'db>(&self) -> PlaceRef<'db> { PlaceRef { local: self.local, projection: self.projection.as_ref() } } @@ -314,8 +314,8 @@ impl<'db> PlaceRef<'db> { PlaceRef { local: self.local, projection: self.projection.project(projection) } } - pub fn store(&self) -> Place { - Place { local: self.local, projection: self.projection.store() } + pub fn store(&self) -> StoredPlace { + StoredPlace { local: self.local, projection: self.projection.store() } } pub fn ty( &self, @@ -493,7 +493,7 @@ pub enum TerminatorKind { /// > The drop glue is executed if, among all statements executed within this `Body`, an assignment to /// > the place or one of its "parents" occurred more recently than a move out of it. This does not /// > consider indirect assignments. - Drop { place: Place, target: BasicBlockId, unwind: Option }, + Drop { place: StoredPlace, target: BasicBlockId, unwind: Option }, /// Drops the place and assigns a new value to it. /// @@ -526,7 +526,7 @@ pub enum TerminatorKind { /// /// Disallowed after drop elaboration. DropAndReplace { - place: Place, + place: StoredPlace, value: Operand, target: BasicBlockId, unwind: Option, @@ -551,7 +551,7 @@ pub enum TerminatorKind { /// reused across function calls without duplicating the contents. args: Box<[Operand]>, /// Where the returned value will be written - destination: Place, + destination: StoredPlace, /// Where to go after this call returns. If none, the call necessarily diverges. target: Option, /// Cleanups to be done if the call unwinds. @@ -596,7 +596,7 @@ pub enum TerminatorKind { /// Where to resume to. resume: BasicBlockId, /// The place to store the resume argument in. - resume_arg: Place, + resume_arg: StoredPlace, /// Cleanup to be done if the coroutine is dropped at this suspend point. drop: Option, }, @@ -888,7 +888,7 @@ pub enum Rvalue { /// exactly what the behavior of this operation should be. /// /// `Shallow` borrows are disallowed after drop lowering. - Ref(BorrowKind, Place), + Ref(BorrowKind, StoredPlace), /// Creates a pointer/reference to the given thread local. /// @@ -919,7 +919,7 @@ pub enum Rvalue { /// If the type of the place is an array, this is the array length. For slices (`[T]`, not /// `&[T]`) this accesses the place's metadata to determine the length. This rvalue is /// ill-formed for places of other types. - Len(Place), + Len(StoredPlace), /// Performs essentially all of the casts that can be performed via `as`. /// @@ -980,7 +980,7 @@ pub enum Rvalue { /// variant index; use `discriminant_for_variant` to convert. /// /// [#91095]: https://github.com/rust-lang/rust/issues/91095 - Discriminant(Place), + Discriminant(StoredPlace), /// Creates an aggregate value, like a tuple or struct. /// @@ -1000,18 +1000,18 @@ pub enum Rvalue { /// read never happened and just projects further. This allows simplifying various MIR /// optimizations and codegen backends that previously had to handle deref operations anywhere /// in a place. - CopyForDeref(Place), + CopyForDeref(StoredPlace), } #[derive(Debug, PartialEq, Eq, Clone)] pub enum StatementKind { - Assign(Place, Rvalue), - FakeRead(Place), + Assign(StoredPlace, Rvalue), + FakeRead(StoredPlace), //SetDiscriminant { // place: Box, // variant_index: VariantIdx, //}, - Deinit(Place), + Deinit(StoredPlace), StorageLive(LocalId), StorageDead(LocalId), //Retag(RetagKind, Box), @@ -1072,8 +1072,8 @@ impl MirBody<'_> { self.binding_locals.iter().map(|(it, y)| (*y, it)).collect() } - fn walk_places(&mut self, mut f: impl FnMut(&mut Place)) { - fn for_operand(op: &mut Operand, f: &mut impl FnMut(&mut Place)) { + fn walk_places(&mut self, mut f: impl FnMut(&mut StoredPlace)) { + fn for_operand(op: &mut Operand, f: &mut impl FnMut(&mut StoredPlace)) { match &mut op.kind { OperandKind::Copy(p) | OperandKind::Move(p) => { f(p); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index 51b94929dcd2e..14aaef13e7645 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -57,8 +57,8 @@ use crate::{ use super::{ AggregateKind, BasicBlockId, BinOp, CastKind, LocalId, MirBody, MirLowerError, MirSpan, - Operand, OperandKind, Place, PlaceElem, PlaceRef, PlaceTy, ProjectionElem, Rvalue, - StatementKind, TerminatorKind, UnOp, return_slot, + Operand, OperandKind, PlaceElem, PlaceRef, PlaceTy, ProjectionElem, Rvalue, StatementKind, + StoredPlace, TerminatorKind, UnOp, return_slot, }; mod shim; @@ -713,11 +713,11 @@ impl<'a, 'db> Evaluator<'a, 'db> { self.infcx.interner.lang_items() } - fn place_addr(&self, p: &Place, locals: &Locals<'a, 'db>) -> Result<'db, Address> { + fn place_addr(&self, p: &StoredPlace, locals: &Locals<'a, 'db>) -> Result<'db, Address> { Ok(self.place_addr_and_ty_and_metadata(p, locals)?.0) } - fn place_interval(&self, p: &Place, locals: &Locals<'a, 'db>) -> Result<'db, Interval> { + fn place_interval(&self, p: &StoredPlace, locals: &Locals<'a, 'db>) -> Result<'db, Interval> { let place_addr_and_ty = self.place_addr_and_ty_and_metadata(p, locals)?; Ok(Interval { addr: place_addr_and_ty.0, @@ -786,7 +786,7 @@ impl<'a, 'db> Evaluator<'a, 'db> { fn place_addr_and_ty_and_metadata<'b>( &'b self, - p: &Place, + p: &StoredPlace, locals: &'b Locals<'a, 'db>, ) -> Result<'db, (Address, Ty<'db>, Option)> { let mut addr = locals.ptr[p.local].addr; @@ -908,7 +908,11 @@ impl<'a, 'db> Evaluator<'a, 'db> { self.layout(Ty::new_adt(self.interner(), adt, subst)) } - fn place_ty<'b>(&'b self, p: &Place, locals: &'b Locals<'a, 'db>) -> Result<'db, Ty<'db>> { + fn place_ty<'b>( + &'b self, + p: &StoredPlace, + locals: &'b Locals<'a, 'db>, + ) -> Result<'db, Ty<'db>> { Ok(self.place_addr_and_ty_and_metadata(p, locals)?.1) } @@ -2176,7 +2180,7 @@ impl<'a, 'db> Evaluator<'a, 'db> { Ok(Interval::new(addr, size)) } - fn eval_place(&mut self, p: &Place, locals: &Locals<'a, 'db>) -> Result<'db, Interval> { + fn eval_place(&mut self, p: &StoredPlace, locals: &Locals<'a, 'db>) -> Result<'db, Interval> { let addr = self.place_addr(p, locals)?; Ok(Interval::new( addr, @@ -3081,7 +3085,7 @@ impl<'a, 'db> Evaluator<'a, 'db> { fn drop_place( &mut self, - place: &Place, + place: &StoredPlace, locals: &mut Locals<'a, 'db>, span: MirSpan, ) -> Result<'db, ()> { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index 48dd56b869ffd..4bccc37fb431c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -46,8 +46,8 @@ use crate::{ mir::{ AggregateKind, Arena, BasicBlock, BasicBlockId, BinOp, BorrowKind, CastKind, Expr, FieldIndex, GenericArgs, Idx, InferenceResult, Local, LocalId, MemoryMap, MirBody, MirSpan, - Mutability, Operand, Place, PlaceElem, PointerCast, Projection, ProjectionElem, Rvalue, - Statement, StatementKind, SwitchTargets, Terminator, TerminatorKind, Ty, UnOp, VariantId, + Mutability, Operand, PlaceElem, PointerCast, Projection, ProjectionElem, Rvalue, Statement, + StatementKind, StoredPlace, SwitchTargets, Terminator, TerminatorKind, Ty, UnOp, VariantId, return_slot, }, next_solver::{ @@ -69,7 +69,7 @@ struct LoopBlocks { begin: BasicBlockId, /// `None` for loops that are not terminating end: Option, - place: Place, + place: StoredPlace, drop_scope_index: usize, } @@ -85,7 +85,7 @@ struct MirLowerCtx<'a, 'db> { store_owner: ExpressionStoreOwnerId, current_loop_blocks: Option, labeled_loop_blocks: FxHashMap, - discr_temp: Option, + discr_temp: Option, db: &'db dyn HirDatabase, store: &'a ExpressionStore, infer: &'a InferenceResult<'db>, @@ -123,7 +123,7 @@ pub enum MirLowerError<'db> { LangItemNotFound, MutatingRvalue, UnresolvedLabel, - UnresolvedUpvar(Place), + UnresolvedUpvar(StoredPlace), InaccessibleLocal, // monomorphization errors: @@ -1199,7 +1199,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { else { not_supported!("non-local capture"); }; - Ok(Place { + Ok(StoredPlace { local: this.binding_local(local)?, projection: Projection::new_from_iter(convert_closure_capture_projections( self.db, place, @@ -2119,12 +2119,14 @@ pub fn mir_body_for_closure_query<'db>( projections.push(ProjectionElem::Deref); } projections.push(ProjectionElem::Field(FieldIndex(capture_idx as u32))); - let capture_param_place = Place { + let capture_param_place = StoredPlace { local: closure_local, projection: Projection::new_from_slice(&projections).store(), }; - let capture_local_place = - Place { local: capture_local, projection: Projection::new_from_slice(&[]).store() }; + let capture_local_place = StoredPlace { + local: capture_local, + projection: Projection::new_from_slice(&[]).store(), + }; let capture_local_rvalue = Rvalue::Use(Operand { kind: OperandKind::Move(capture_param_place), span: None }); ctx.push_assignment( diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs index 67f88e9503d64..3498d88b4f756 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs @@ -25,7 +25,7 @@ use crate::{ use super::{ AggregateKind, BasicBlockId, BorrowKind, LocalId, MirBody, MutBorrowKind, Operand, OperandKind, - Place, Rvalue, UnOp, + Rvalue, StoredPlace, UnOp, }; macro_rules! w { @@ -319,7 +319,7 @@ impl<'a, 'db> MirPrettyCtx<'a, 'db> { } } - fn place(&mut self, p: &Place) { + fn place(&mut self, p: &StoredPlace) { fn f<'db>(this: &mut MirPrettyCtx<'_, 'db>, local: LocalId, projections: &[PlaceElem]) { let Some((last, head)) = projections.split_last() else { // no projection From 69fba7f3a78cf8961630a8ca0d848b68a3f8abf8 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 31 Aug 2026 23:05:34 +0200 Subject: [PATCH 08/38] rename `PlaceRef` to `Place` --- .../rust-analyzer/crates/hir-ty/src/mir.rs | 27 +++++++------- .../crates/hir-ty/src/mir/eval.rs | 8 ++--- .../crates/hir-ty/src/mir/lower.rs | 36 +++++++++---------- .../crates/hir-ty/src/mir/lower/as_place.rs | 18 +++++----- .../hir-ty/src/mir/lower/pattern_matching.rs | 22 ++++++------ 5 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs index 297a236333db3..433fc274ea955 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs @@ -276,9 +276,8 @@ impl StoredProjection { } } -// FIXME: would be nicer to rename PlaceRef -> Place, Place -> StoredPlace, but I didn't want to blow up the diff #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct PlaceRef<'db> { +pub struct Place<'db> { pub local: LocalId, pub projection: Projection<'db>, } @@ -290,28 +289,28 @@ pub struct StoredPlace { } impl StoredPlace { - pub fn as_ref<'db>(&self) -> PlaceRef<'db> { - PlaceRef { local: self.local, projection: self.projection.as_ref() } + pub fn as_ref<'db>(&self) -> Place<'db> { + Place { local: self.local, projection: self.projection.as_ref() } } } -impl<'db> PlaceRef<'db> { - fn is_parent(&self, child: PlaceRef<'db>) -> bool { +impl<'db> Place<'db> { + fn is_parent(&self, child: Place<'db>) -> bool { self.local == child.local && child.projection.as_slice().starts_with(self.projection.as_slice()) } /// The place itself is not included - fn iterate_over_parents<'a>(&'a self) -> impl Iterator> + 'a { + fn iterate_over_parents<'a>(&'a self) -> impl Iterator> + 'a { let projection = self.projection.as_slice(); - (0..projection.len()).map(move |x| PlaceRef { + (0..projection.len()).map(move |x| Place { local: self.local, projection: Projection::new_from_slice(&projection[0..x]), }) } - fn project(&self, projection: PlaceElem) -> PlaceRef<'db> { - PlaceRef { local: self.local, projection: self.projection.project(projection) } + fn project(&self, projection: PlaceElem) -> Place<'db> { + Place { local: self.local, projection: self.projection.project(projection) } } pub fn store(&self) -> StoredPlace { @@ -331,10 +330,10 @@ impl<'db> PlaceRef<'db> { } } -impl<'db> From for PlaceRef<'db> { +impl<'db> From for Place<'db> { fn from(local: LocalId) -> Self { let empty: &[PlaceElem] = &[]; - PlaceRef { local, projection: Projection::new_from_slice(empty) } + Place { local, projection: Projection::new_from_slice(empty) } } } @@ -1194,13 +1193,13 @@ impl From<&ExprId> for MirSpan { } } -impl<'tcx> PlaceRef<'tcx> { +impl<'tcx> Place<'tcx> { /// If this place represents a local variable like `_X` with no /// projections, return `Some(_X)`. #[inline] pub fn as_local(&self) -> Option { match *self { - PlaceRef { local, projection } if projection.as_slice().is_empty() => Some(local), + Place { local, projection } if projection.as_slice().is_empty() => Some(local), _ => None, } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index 14aaef13e7645..df3debf807e1a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -57,7 +57,7 @@ use crate::{ use super::{ AggregateKind, BasicBlockId, BinOp, CastKind, LocalId, MirBody, MirLowerError, MirSpan, - Operand, OperandKind, PlaceElem, PlaceRef, PlaceTy, ProjectionElem, Rvalue, StatementKind, + Operand, OperandKind, Place, PlaceElem, PlaceTy, ProjectionElem, Rvalue, StatementKind, StoredPlace, TerminatorKind, UnOp, return_slot, }; @@ -563,11 +563,11 @@ type Result<'db, T> = std::result::Result>; #[derive(Debug, Default)] struct DropFlags<'db> { - need_drop: FxHashSet>, + need_drop: FxHashSet>, } impl<'db> DropFlags<'db> { - fn add_place(&mut self, p: PlaceRef<'db>) { + fn add_place(&mut self, p: Place<'db>) { if p.iterate_over_parents().any(|it| self.need_drop.contains(&it)) { return; } @@ -575,7 +575,7 @@ impl<'db> DropFlags<'db> { self.need_drop.insert(p); } - fn remove_place(&mut self, p: PlaceRef<'db>) -> bool { + fn remove_place(&mut self, p: Place<'db>) -> bool { // FIXME: replace parents with parts if let Some(parent) = p.iterate_over_parents().find(|it| self.need_drop.contains(it)) { self.need_drop.remove(&parent); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index 4bccc37fb431c..6e179176a9496 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -57,7 +57,7 @@ use crate::{ }, }; -use super::{OperandKind, PlaceRef}; +use super::{OperandKind, Place}; mod as_place; mod pattern_matching; @@ -379,7 +379,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { fn lower_expr_to_place_with_adjust( &mut self, expr_id: ExprId, - place: PlaceRef<'db>, + place: Place<'db>, current: BasicBlockId, adjustments: &[Adjustment], ) -> Result<'db, Option> { @@ -439,7 +439,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { fn lower_expr_to_place_with_borrow_adjust( &mut self, expr_id: ExprId, - place: PlaceRef<'db>, + place: Place<'db>, current: BasicBlockId, rest: &[Adjustment], m: Mutability, @@ -457,7 +457,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { fn lower_expr_to_place( &mut self, expr_id: ExprId, - place: PlaceRef<'db>, + place: Place<'db>, prev_block: BasicBlockId, ) -> Result<'db, Option> { if let Some(adjustments) = self.infer.expr_adjustments.get(&expr_id) { @@ -469,7 +469,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { fn lower_expr_to_place_without_adjust( &mut self, expr_id: ExprId, - place: PlaceRef<'db>, + place: Place<'db>, mut current: BasicBlockId, ) -> Result<'db, Option> { match &self.store[expr_id] { @@ -1328,7 +1328,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { fn push_field_projection( &mut self, - place: &mut PlaceRef<'db>, + place: &mut Place<'db>, expr_id: ExprId, ) -> Result<'db, ()> { if let Expr::Field { expr, name } = &self.store[expr_id] { @@ -1461,7 +1461,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { &mut self, const_id: GeneralConstId<'db>, prev_block: BasicBlockId, - place: PlaceRef<'db>, + place: Place<'db>, subst: GenericArgs<'db>, span: MirSpan, ) -> Result<'db, ()> { @@ -1494,7 +1494,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { fn write_bytes_to_place( &mut self, prev_block: BasicBlockId, - place: PlaceRef<'db>, + place: Place<'db>, cv: Box<[u8]>, ty: Ty<'db>, span: MirSpan, @@ -1507,7 +1507,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { &mut self, variant_id: EnumVariantId, prev_block: BasicBlockId, - place: PlaceRef<'db>, + place: Place<'db>, ty: Ty<'db>, fields: Box<[Operand]>, span: MirSpan, @@ -1529,7 +1529,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { &mut self, func: Operand, args: impl Iterator, - place: PlaceRef<'db>, + place: Place<'db>, mut current: BasicBlockId, is_uninhabited: bool, span: MirSpan, @@ -1554,7 +1554,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { &mut self, func: Operand, args: Box<[Operand]>, - place: PlaceRef<'db>, + place: Place<'db>, current: BasicBlockId, is_uninhabited: bool, span: MirSpan, @@ -1605,27 +1605,27 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { self.result.basic_blocks[block].statements.push(statement); } - fn push_fake_read(&mut self, block: BasicBlockId, p: PlaceRef<'db>, span: MirSpan) { + fn push_fake_read(&mut self, block: BasicBlockId, p: Place<'db>, span: MirSpan) { self.push_statement(block, StatementKind::FakeRead(p.store()).with_span(span)); } fn push_assignment( &mut self, block: BasicBlockId, - place: PlaceRef<'db>, + place: Place<'db>, rvalue: Rvalue, span: MirSpan, ) { self.push_statement(block, StatementKind::Assign(place.store(), rvalue).with_span(span)); } - fn discr_temp_place(&mut self, current: BasicBlockId) -> PlaceRef<'db> { + fn discr_temp_place(&mut self, current: BasicBlockId) -> Place<'db> { match &self.discr_temp { Some(it) => it.as_ref(), None => { // FIXME: rustc's ty is dependent on the adt type, maybe we need to do that as well let discr_ty = Ty::new_int(self.interner(), rustc_type_ir::IntTy::I128); - let tmp: PlaceRef<'_> = self + let tmp: Place<'_> = self .temp(discr_ty, current, MirSpan::Unknown) .expect("discr_ty is never unsized") .into(); @@ -1638,7 +1638,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { fn lower_loop( &mut self, prev_block: BasicBlockId, - place: PlaceRef<'db>, + place: Place<'db>, label: Option, span: MirSpan, f: impl FnOnce(&mut MirLowerCtx<'_, 'db>, BasicBlockId) -> Result<'db, ()>, @@ -1749,7 +1749,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { statements: &[hir_def::hir::Statement], mut current: BasicBlockId, tail: Option, - place: PlaceRef<'db>, + place: Place<'db>, span: MirSpan, ) -> Result<'db, Option>> { let scope = self.push_drop_scope(); @@ -2002,7 +2002,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { self.set_terminator( prev, TerminatorKind::Drop { - place: PlaceRef::from(l).store(), + place: Place::from(l).store(), target: *current, unwind: None, }, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs index 71014eb661742..beb1d317d1101 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs @@ -14,7 +14,7 @@ impl<'db> MirLowerCtx<'_, 'db> { &mut self, expr_id: ExprId, prev_block: BasicBlockId, - ) -> Result<'db, Option<(PlaceRef<'db>, BasicBlockId)>> { + ) -> Result<'db, Option<(Place<'db>, BasicBlockId)>> { let ty = self.expr_ty_without_adjust(expr_id); let place = self.temp(ty, prev_block, expr_id.into())?.into(); let Some(current) = self.lower_expr_to_place_without_adjust(expr_id, place, prev_block)? @@ -29,7 +29,7 @@ impl<'db> MirLowerCtx<'_, 'db> { expr_id: ExprId, prev_block: BasicBlockId, adjustments: &[Adjustment], - ) -> Result<'db, Option<(PlaceRef<'db>, BasicBlockId)>> { + ) -> Result<'db, Option<(Place<'db>, BasicBlockId)>> { let ty = adjustments .last() .map(|it| it.target.as_ref()) @@ -49,7 +49,7 @@ impl<'db> MirLowerCtx<'_, 'db> { expr_id: ExprId, upgrade_rvalue: bool, adjustments: &[Adjustment], - ) -> Result<'db, Option<(PlaceRef<'db>, BasicBlockId)>> { + ) -> Result<'db, Option<(Place<'db>, BasicBlockId)>> { let try_rvalue = |this: &mut MirLowerCtx<'_, 'db>| { if !upgrade_rvalue { return Err(MirLowerError::MutatingRvalue); @@ -107,7 +107,7 @@ impl<'db> MirLowerCtx<'_, 'db> { current: BasicBlockId, expr_id: ExprId, upgrade_rvalue: bool, - ) -> Result<'db, Option<(PlaceRef<'db>, BasicBlockId)>> { + ) -> Result<'db, Option<(Place<'db>, BasicBlockId)>> { match self.infer.expr_adjustments.get(&expr_id) { Some(a) => self.lower_expr_as_place_with_adjust(current, expr_id, upgrade_rvalue, a), None => self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue), @@ -119,7 +119,7 @@ impl<'db> MirLowerCtx<'_, 'db> { current: BasicBlockId, expr_id: ExprId, upgrade_rvalue: bool, - ) -> Result<'db, Option<(PlaceRef<'db>, BasicBlockId)>> { + ) -> Result<'db, Option<(Place<'db>, BasicBlockId)>> { let try_rvalue = |this: &mut MirLowerCtx<'_, 'db>| { if !upgrade_rvalue { return Err(MirLowerError::MutatingRvalue); @@ -262,13 +262,13 @@ impl<'db> MirLowerCtx<'_, 'db> { fn lower_overloaded_index( &mut self, current: BasicBlockId, - place: PlaceRef<'db>, + place: Place<'db>, base_ty: Ty<'db>, result_ty: Ty<'db>, index_operand: Operand, span: MirSpan, index_fn: (FunctionId, GenericArgs<'db>), - ) -> Result<'db, Option<(PlaceRef<'db>, BasicBlockId)>> { + ) -> Result<'db, Option<(Place<'db>, BasicBlockId)>> { let mutability = match base_ty.as_reference() { Some((_, _, mutability)) => mutability, None => Mutability::Not, @@ -302,12 +302,12 @@ impl<'db> MirLowerCtx<'_, 'db> { fn lower_overloaded_deref( &mut self, current: BasicBlockId, - place: PlaceRef<'db>, + place: Place<'db>, source_ty: Ty<'db>, target_ty: Ty<'db>, span: MirSpan, mutability: bool, - ) -> Result<'db, Option<(PlaceRef<'db>, BasicBlockId)>> { + ) -> Result<'db, Option<(Place<'db>, BasicBlockId)>> { let lang_items = self.lang_items(); let (mutability, deref_fn, borrow_kind) = if !mutability { (Mutability::Not, lang_items.Deref_deref, BorrowKind::Shared) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs index 44f410408a40a..682ae827db67d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -9,7 +9,7 @@ use rustc_type_ir::inherent::{IntoKind, Ty as _}; use crate::{ BindingMode, ByRef, mir::{ - FieldIndex, LocalId, MutBorrowKind, Operand, OperandKind, PlaceRef, Projection, + FieldIndex, LocalId, MutBorrowKind, Operand, OperandKind, Place, Projection, lower::{ BasicBlockId, BinOp, BindingId, BorrowKind, Expr, Idx, MemoryMap, MirLowerCtx, MirLowerError, MirSpan, Pat, PatId, PlaceElem, ProjectionElem, ResolveValueResult, @@ -67,7 +67,7 @@ impl<'db> MirLowerCtx<'_, 'db> { &mut self, current: BasicBlockId, current_else: Option, - cond_place: PlaceRef<'db>, + cond_place: Place<'db>, pattern: PatId, ) -> Result<'db, (BasicBlockId, Option)> { let (current, current_else) = self.pattern_match_inner( @@ -90,7 +90,7 @@ impl<'db> MirLowerCtx<'_, 'db> { pub(super) fn pattern_match_assignment( &mut self, current: BasicBlockId, - value: PlaceRef<'db>, + value: Place<'db>, pattern: PatId, ) -> Result<'db, BasicBlockId> { let (current, _) = @@ -118,7 +118,7 @@ impl<'db> MirLowerCtx<'_, 'db> { &mut self, mut current: BasicBlockId, mut current_else: Option, - mut cond_place: PlaceRef<'db>, + mut cond_place: Place<'db>, pattern: PatId, mode: MatchingMode, ) -> Result<'db, (BasicBlockId, Option)> { @@ -527,7 +527,7 @@ impl<'db> MirLowerCtx<'_, 'db> { &mut self, id: BindingId, mode: BindingMode, - cond_place: PlaceRef<'db>, + cond_place: Place<'db>, span: MirSpan, current: BasicBlockId, current_else: Option, @@ -543,7 +543,7 @@ impl<'db> MirLowerCtx<'_, 'db> { current: BasicBlockId, target_place: LocalId, mode: BindingMode, - cond_place: PlaceRef<'db>, + cond_place: Place<'db>, span: MirSpan, ) { self.push_assignment( @@ -570,7 +570,7 @@ impl<'db> MirLowerCtx<'_, 'db> { current_else: Option, current: BasicBlockId, c: Operand, - cond_place: PlaceRef<'db>, + cond_place: Place<'db>, pattern: Idx, ) -> Result<'db, (BasicBlockId, Option)> { let then_target = self.new_basic_block(); @@ -600,7 +600,7 @@ impl<'db> MirLowerCtx<'_, 'db> { fn pattern_matching_variant( &mut self, - cond_place: PlaceRef<'db>, + cond_place: Place<'db>, variant: VariantId, mut current: BasicBlockId, span: MirSpan, @@ -671,7 +671,7 @@ impl<'db> MirLowerCtx<'_, 'db> { v: VariantId, current: BasicBlockId, current_else: Option, - cond_place: &PlaceRef<'db>, + cond_place: &Place<'db>, mode: MatchingMode, ) -> Result<'db, (BasicBlockId, Option)> { let downcast_place = if matches!(v, VariantId::EnumVariantId(_)) { @@ -718,7 +718,7 @@ impl<'db> MirLowerCtx<'_, 'db> { mut current: BasicBlockId, mut current_else: Option, args: impl Iterator, - cond_place: &PlaceRef<'db>, + cond_place: &Place<'db>, mode: MatchingMode, ) -> Result<'db, (BasicBlockId, Option)> { for (proj, arg) in args { @@ -736,7 +736,7 @@ impl<'db> MirLowerCtx<'_, 'db> { args: &[PatId], ellipsis: Option, fields: impl DoubleEndedIterator + Clone, - cond_place: &PlaceRef<'db>, + cond_place: &Place<'db>, mode: MatchingMode, ) -> Result<'db, (BasicBlockId, Option)> { let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); From 8cc2b1b79b42e2909aaef1a0cd35d5dfc01c9b23 Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Tue, 1 Sep 2026 12:50:04 +0200 Subject: [PATCH 09/38] render const value in completions label details --- .../src/completions/format_string.rs | 13 ++++-- .../crates/ide-completion/src/item.rs | 25 +++++++++++- .../crates/ide-completion/src/render.rs | 7 +++- .../ide-completion/src/render/const_.rs | 3 +- .../ide-completion/src/tests/expression.rs | 24 +++++------ .../ide-completion/src/tests/flyimport.rs | 18 ++++----- .../crates/ide-completion/src/tests/item.rs | 40 ++++++++++++++++++- .../ide-completion/src/tests/pattern.rs | 24 +++++------ .../ide-completion/src/tests/special.rs | 34 ++++++++-------- .../ide-completion/src/tests/type_pos.rs | 38 +++++++++--------- .../ide-completion/src/tests/use_tree.rs | 2 +- 11 files changed, 148 insertions(+), 80 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs index 1e5240218571a..e779cf8e9dd62 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs @@ -37,19 +37,24 @@ pub(crate) fn format_string( }); ctx.scope.process_all_names(&mut |name, scope| { if let ScopeDef::ModuleDef(module_def) = scope { + let mut const_value = None; let symbol_kind = match module_def { - ModuleDef::Const(..) => SymbolKind::Const, + ModuleDef::Const(c) => { + const_value = Some(c); + SymbolKind::Const + } ModuleDef::Static(..) => SymbolKind::Static, _ => return, }; - CompletionItem::new( + let mut builder = CompletionItem::new( CompletionItemKind::SymbolKind(symbol_kind), source_range, name.display_no_db(ctx.edition).to_smolstr(), ctx.edition, - ) - .add_to(acc, ctx.db); + ); + builder.const_value(const_value, ctx.db, ctx.display_target); + builder.add_to(acc, ctx.db); } }); } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs index 675ffac040293..89796901f4b16 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs @@ -2,7 +2,8 @@ use std::{fmt, mem}; -use hir::Mutability; +use hir::db::HirDatabase; +use hir::{DisplayTarget, Mutability}; use ide_db::text_edit::TextEdit; use ide_db::{ RootDatabase, SnippetCap, SymbolKind, documentation::Documentation, @@ -12,7 +13,7 @@ use itertools::Itertools; use macros::UpmapFromRaFixture; use smallvec::SmallVec; use stdx::{format_to, impl_from, never}; -use syntax::{Edition, SmolStr, TextRange, TextSize, format_smolstr}; +use syntax::{Edition, SmolStr, TextRange, TextSize, ToSmolStr, format_smolstr}; use crate::{ context::{CompletionContext, PathCompletionCtx}, @@ -497,6 +498,7 @@ impl CompletionItem { imports_to_add: Default::default(), doc_aliases: vec![], adds_text: None, + const_value: None, edition, } } @@ -534,6 +536,7 @@ pub(crate) struct Builder { trait_name: Option, doc_aliases: Vec, adds_text: Option, + const_value: Option, label: SmolStr, insert_text: Option, is_snippet: bool, @@ -603,6 +606,9 @@ impl Builder { lookup = format_smolstr!("{lookup}{lookup_doc_aliases}"); } } + if let Some(const_value) = self.const_value { + to_detail_left(format_args!(" = {}", const_value.trim())); + } if let Some(adds_text) = self.adds_text { to_detail_left(format_args!("(adds {})", adds_text.trim())); } @@ -681,6 +687,21 @@ impl Builder { self.adds_text = Some(adds_text); self } + pub(crate) fn const_value( + &mut self, + const_value: Option, + db: &dyn HirDatabase, + display_target: DisplayTarget, + ) -> &mut Builder { + if let Some(const_value) = const_value { + if let Ok(evaluated_value) = const_value.eval(db) { + self.const_value = Some(evaluated_value.render(db, display_target).to_smolstr()); + } else if let Some(written_value) = const_value.value(db) { + self.const_value = Some(written_value.to_smolstr()); + } + } + self + } pub(crate) fn insert_text(&mut self, insert_text: impl Into) -> &mut Builder { self.insert_text = Some(insert_text.into()); self diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs index de0a9a9174314..f5c54832a12ce 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -546,6 +546,9 @@ fn render_resolution_simple_<'db>( local_name.as_str().to_smolstr(), ctx.completion.edition, ); + if let ScopeDef::ModuleDef(ModuleDef::Const(konst)) = resolution { + item.const_value(Some(konst), db, ctx.completion.display_target); + } item.set_relevance(ctx.completion_relevance()) .set_documentation(scope_def_docs(db, resolution)) .set_deprecated(scope_def_is_deprecated(&ctx, resolution)); @@ -1941,7 +1944,9 @@ fn main() { A$0 } [ CompletionItem { label: "A", - detail_left: None, + detail_left: Some( + " = 0", + ), detail_right: Some( "i32", ), diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs index c14fc1704c5ba..4922c72468ae1 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs @@ -26,6 +26,7 @@ fn render(ctx: RenderContext<'_, '_>, const_: hir::Const) -> Option, const_: hir::Const) -> Option i32 "#]], ); @@ -1435,7 +1435,7 @@ fn function() { } "#, expect![[r#" - ct FooConst (use module::FooConst) + ct FooConst = () (use module::FooConst) st FooStruct (use module::FooStruct) "#]], ); diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs index bb79af7e98df1..86fcea5983b55 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs @@ -355,7 +355,7 @@ fn bar() { } "#, expect![[r#" - ct CONST Unit + ct CONST = Unit Unit en Enum Enum fn bar() fn() fn foo() fn() @@ -422,7 +422,7 @@ macro_rules! foo { foo!(f$0); "#, expect![[r#" - ct BAR u8 + ct BAR = f u8 fn foo() fn() -> u8 ma foo!(…) macro_rules! foo bt u32 u32 @@ -443,6 +443,42 @@ foo!(f$0); ); } +#[test] +fn const_eval_label_details() { + check( + r#" +pub const MAX: u32 = !0; +pub const MIN: u32 = 0; +pub const MNOEVAL: u32 = unknown(); + +fn main() { + let x = M$0 +} + "#, + expect![[r#" + ct MAX = 4294967295 u32 + ct MIN = 0 u32 + ct MNOEVAL = unknown() u32 + fn main() fn() + bt u32 u32 + kw const + kw crate:: + kw false + kw for + kw if + kw if let + kw loop + kw match + kw return + kw self:: + kw true + kw unsafe + kw while + kw while let + "#]], + ); +} + #[test] fn completes_variant_through_hidden_enum_alias() { check( diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs index 0d85f2e9ad627..d1e78bb069688 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs @@ -115,7 +115,7 @@ fn foo() { } "#, expect![[r#" - ct CONST + ct CONST = Unit en Enum ma makro!(…) macro_rules! makro md module @@ -342,10 +342,10 @@ fn func() { } "#, expect![[r#" - ct ASSOC_CONST const ASSOC_CONST: () - bn RecordV {…} RecordV { field$1 }$0 - bn TupleV(…) TupleV($1)$0 - bn UnitV UnitV$0 + ct ASSOC_CONST = () const ASSOC_CONST: () + bn RecordV {…} RecordV { field$1 }$0 + bn TupleV(…) TupleV($1)$0 + bn UnitV UnitV$0 "#]], ); } @@ -360,7 +360,7 @@ fn func() { } "#, expect![[r#" - ct CONST + ct CONST = Unit en Enum ma makro!(…) macro_rules! makro md module @@ -662,7 +662,7 @@ fn f(t: Ty) { } "#, expect![[r#" - ct ABC const ABC: Self + ct ABC = Ty(0) const ABC: Self "#]], ); @@ -683,8 +683,8 @@ fn f(e: MyEnum) { } "#, expect![[r#" - ct A pub const A: i32 - ct B pub const B: i32 + ct A = 123 pub const A: i32 + ct B = 456 pub const B: i32 "#]], ); @@ -708,8 +708,8 @@ fn f(u: U) { } "#, expect![[r#" - ct C pub const C: i32 - ct D pub const D: i32 + ct C = 123 pub const C: i32 + ct D = 456 pub const D: i32 "#]], ); @@ -729,7 +729,7 @@ fn f(v: u32) { } "#, expect![[r#" - ct MIN pub const MIN: Self + ct MIN = 0 pub const MIN: Self "#]], ); } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs index 6c2502a4eb670..51ece1b63cc9e 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs @@ -206,9 +206,9 @@ impl S { fn foo() { let _ = lib::S::$0 } "#, expect![[r#" - ct PUBLIC_CONST pub const PUBLIC_CONST: u32 - fn public_method() fn() - ta PublicType pub type PublicType = u32 + ct PUBLIC_CONST = 1 pub const PUBLIC_CONST: u32 + fn public_method() fn() + ta PublicType pub type PublicType = u32 "#]], ); } @@ -337,14 +337,14 @@ impl Sub for Wrap { } "#, expect![[r#" - ct C2 (as Sub) const C2: () - ct CONST (as Super) const CONST: u8 - fn func() (as Super) fn() - fn subfunc() (as Sub) fn() - me method(…) (as Super) fn(&self) - me submethod(…) (as Sub) fn(&self) - ta SubTy (as Sub) type SubTy - ta Ty (as Super) type Ty + ct C2 = () (as Sub) const C2: () + ct CONST = 0 (as Super) const CONST: u8 + fn func() (as Super) fn() + fn subfunc() (as Sub) fn() + me method(…) (as Super) fn(&self) + me submethod(…) (as Sub) fn(&self) + ta SubTy (as Sub) type SubTy + ta Ty (as Super) type Ty "#]], ); } @@ -427,9 +427,9 @@ mod p { } "#, expect![[r#" - ct RIGHT_CONST u32 - fn right_fn() fn() - st RightType WrongType + ct RIGHT_CONST = 1 u32 + fn right_fn() fn() + st RightType WrongType "#]], ); @@ -823,8 +823,8 @@ impl u8 { } "#, expect![[r#" - ct MAX pub const MAX: Self - me func(…) fn(self) + ct MAX = 255 pub const MAX: Self + me func(…) fn(self) "#]], ); } @@ -1054,7 +1054,7 @@ fn main() { } "#, expect![[r#" - ct by_macro (as MyTrait) pub const by_macro: u8 + ct by_macro = 1 (as MyTrait) pub const by_macro: u8 "#]], ) } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs index 419b15ed868b3..8fa2d361829de 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs @@ -1077,7 +1077,7 @@ trait MyTrait { fn f(t: impl MyTrait = ()>>() {} "#, expect![[r#" - ct CONST Unit - ct X usize + ct CONST = Unit Unit + ct X = 0 usize ma makro!(…) macro_rules! makro kw crate:: kw dyn @@ -1316,8 +1316,8 @@ fn completes_const_and_type_generics_separately() { } "#, expect![[r#" - ct CONST Unit - ct X usize + ct CONST = Unit Unit + ct X = 0 usize ma makro!(…) macro_rules! makro kw crate:: kw dyn @@ -1339,8 +1339,8 @@ fn completes_const_and_type_generics_separately() { } "#, expect![[r#" - ct CONST Unit - ct X usize + ct CONST = Unit Unit + ct X = 0 usize ma makro!(…) macro_rules! makro kw crate:: kw dyn @@ -1361,8 +1361,8 @@ fn completes_const_and_type_generics_separately() { } "#, expect![[r#" - ct CONST Unit - ct X usize + ct CONST = Unit Unit + ct X = 0 usize ma makro!(…) macro_rules! makro kw crate:: kw dyn @@ -1381,8 +1381,8 @@ fn completes_const_and_type_generics_separately() { impl Foo<(), $0> for () {} "#, expect![[r#" - ct CONST Unit - ct X usize + ct CONST = Unit Unit + ct X = 0 usize ma makro!(…) macro_rules! makro kw crate:: kw dyn @@ -1403,8 +1403,8 @@ fn completes_const_and_type_generics_separately() { fn foo>() {} "#, expect![[r#" - ct CONST Unit - ct X usize + ct CONST = Unit Unit + ct X = 0 usize ma makro!(…) macro_rules! makro kw crate:: kw dyn @@ -1422,7 +1422,7 @@ struct S<'a, 'b, const C: usize, T>(core::marker::PhantomData<&'a &'b T>); fn foo<'a>() { S::; } "#, expect![[r#" - ct CONST Unit + ct CONST = Unit Unit ma makro!(…) macro_rules! makro kw crate:: kw dyn @@ -1439,7 +1439,7 @@ struct S<'a, 'b, const C: usize, T>(core::marker::PhantomData<&'a &'b T>); fn foo<'a>() { S::<'static, 'static, F$0, _>; } "#, expect![[r#" - ct CONST Unit + ct CONST = Unit Unit ma makro!(…) macro_rules! makro kw crate:: kw dyn diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs index 593b1edde5cc5..98e44b3bb3a46 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs @@ -252,7 +252,7 @@ mod a { } "#, expect![[r#" - ct A usize + ct A = 0 usize md b kw super:: "#]], From 75dd8b343d04bf3e2e54fa8d64c2cf12ef8c5047 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 1 Sep 2026 12:57:27 +0200 Subject: [PATCH 10/38] misc: add an intra-doc link `body` is no longer a neighbouring module, but rather a child of `expr_store` --- src/tools/rust-analyzer/crates/hir-def/src/hir.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs index 0ac315ea50fc6..44c32d16f2197 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs @@ -10,7 +10,9 @@ //! names refer to. //! 4. Desugared. There's no `if let`. //! -//! See also a neighboring `body` module. +//! See also a neighboring [`body`] module. +//! +//! [`body`]: crate::expr_store::body pub mod format_args; pub mod generics; From 8ba5a181c3320ef8887f1dcafc50f42d4689a56f Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 1 Sep 2026 14:17:50 +0300 Subject: [PATCH 11/38] Fix handling of `#[unsafe()]` attrs without inner meta The `continue` was aimed at the incorrect loop. I thought this will cause an infinite loop but it doesn't seem to, still it's incorrect. --- src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs index f5e581e8cf472..185298d5f937f 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs @@ -114,7 +114,7 @@ pub fn expand_cfg_attr_with_doc_comments<'a, DocComment, BreakValue>( mut callback: impl FnMut(Either<(ast::Meta, ast::Attr), DocComment>) -> ControlFlow, ) -> Option { let mut stack = SmallVec::<[_; 1]>::new(); - loop { + 'process_attrs: loop { let (mut meta, top_attr) = if let Some(it) = stack.pop() { it } else { @@ -134,7 +134,7 @@ pub fn expand_cfg_attr_with_doc_comments<'a, DocComment, BreakValue>( }; while let ast::Meta::UnsafeMeta(unsafe_meta) = &meta { - let Some(inner) = unsafe_meta.meta() else { continue }; + let Some(inner) = unsafe_meta.meta() else { continue 'process_attrs }; meta = inner; } From 95347610cba1794a0b2549455a83852eeda1a286 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 1 Sep 2026 12:07:58 +0200 Subject: [PATCH 12/38] merge `hir_def::hir::Expr::Unsafe` into `Expr::Block` A lot of callsites handled these variants similarly, so this ended up simplifying things. --- .../crates/hir-def/src/expr_store.rs | 3 +-- .../crates/hir-def/src/expr_store/lower.rs | 20 +++++++++++++++---- .../src/expr_store/lower/format_args.rs | 12 +++++++++-- .../crates/hir-def/src/expr_store/pretty.rs | 13 ++++++------ .../crates/hir-def/src/expr_store/scope.rs | 5 +---- .../rust-analyzer/crates/hir-def/src/hir.rs | 14 ++++++------- .../crates/hir-ty/src/diagnostics/expr.rs | 15 +++++--------- .../hir-ty/src/diagnostics/unsafe_check.rs | 9 ++++++--- .../closure/analysis/expr_use_visitor.rs | 3 +-- .../crates/hir-ty/src/infer/expr.rs | 6 +----- .../crates/hir-ty/src/infer/mutability.rs | 3 +-- .../crates/hir-ty/src/mir/lower.rs | 5 +---- .../rust-analyzer/crates/hir/src/semantics.rs | 4 ++-- 13 files changed, 59 insertions(+), 53 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs index e952390268753..ec8f2bfb6686f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs @@ -689,8 +689,7 @@ impl ExpressionStore { visitor.on_pat(*pat); visitor.on_expr(*expr); } - Expr::Block { statements, tail, id: _, label: _ } - | Expr::Unsafe { statements, tail, id: _ } => { + Expr::Block { statements, tail, id: _, label: _, unsafe_: _ } => { for stmt in statements { match stmt { Statement::Let { initializer, else_branch, pat, type_ref } => { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index 08cecb52570eb..c6f07f3037616 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -52,7 +52,7 @@ use crate::{ Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind, CoroutineKind, CoroutineSource, Expr, ExprId, Item, Label, LabelId, Literal, LoopSource, MatchArm, Movability, OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, RecordSpread, - Statement, generics::GenericParams, + Statement, Unsafe, generics::GenericParams, }, item_scope::BuiltinShadowMode, lang_item::{LangItemTarget, LangItems}, @@ -1188,7 +1188,13 @@ impl<'db> ExprCollector<'db> { statements: Box<[Statement]>, tail: Option, ) -> Expr { - let block = self.alloc_expr_desugared(Expr::Block { label: None, id, statements, tail }); + let block = self.alloc_expr_desugared(Expr::Block { + label: None, + id, + statements, + tail, + unsafe_: Unsafe::No, + }); Expr::Closure { args: Box::default(), arg_types: Box::default(), @@ -1390,10 +1396,12 @@ impl<'db> ExprCollector<'db> { self.desugar_try_block(e, result_type) } Some(ast::BlockModifier::Unsafe(_)) => { - self.collect_block_(e, |_, id, statements, tail| Expr::Unsafe { + self.collect_block_(e, |_, id, statements, tail| Expr::Block { id, statements, tail, + label: None, + unsafe_: Unsafe::Yes, }) } Some(ast::BlockModifier::Label(label)) => { @@ -1405,6 +1413,7 @@ impl<'db> ExprCollector<'db> { statements, tail, label: Some(label_id), + unsafe_: Unsafe::No, }) }) } @@ -2247,7 +2256,7 @@ impl<'db> ExprCollector<'db> { let mut btail = None; let block = this.collect_block_(e, |_, id, statements, tail| { btail = tail; - Expr::Block { id, statements, tail, label: Some(label) } + Expr::Block { id, statements, tail, label: Some(label), unsafe_: Unsafe::No } }); (btail, block) }); @@ -2296,6 +2305,7 @@ impl<'db> ExprCollector<'db> { }]), tail: Some(tail_expr), label: None, + unsafe_: Unsafe::No, }, ptr, ) @@ -2429,6 +2439,7 @@ impl<'db> ExprCollector<'db> { statements: Box::default(), tail: Some(loop_inner), label: None, + unsafe_: Unsafe::No, }, syntax_ptr, ); @@ -2727,6 +2738,7 @@ impl<'db> ExprCollector<'db> { statements, tail, label: None, + unsafe_: Unsafe::No, }) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs index ad548c6758c16..d6f0aa60978af 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs @@ -12,7 +12,7 @@ use syntax::{ use crate::{ expr_store::{HygieneId, lower::ExprCollector, path::Path}, hir::{ - Array, BindingAnnotation, Expr, ExprId, Literal, Pat, Statement, + Array, BindingAnnotation, Expr, ExprId, Literal, Pat, Statement, Unsafe, format_args::{ self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatSign, FormatTrait, @@ -187,6 +187,7 @@ impl<'db> ExprCollector<'db> { .collect(), tail: Some(from_str), label: None, + unsafe_: Unsafe::No, }, syntax_ptr, ) @@ -378,7 +379,13 @@ impl<'db> ExprCollector<'db> { self.alloc_expr_desugared(Expr::Call { callee: new, args: Box::new([template, args]) }) }; let call = self.alloc_expr( - Expr::Unsafe { id: None, statements: Box::new([]), tail: Some(call) }, + Expr::Block { + id: None, + statements: Box::new([]), + tail: Some(call), + label: None, + unsafe_: Unsafe::Yes, + }, syntax_ptr, ); @@ -402,6 +409,7 @@ impl<'db> ExprCollector<'db> { statements: statements.into_boxed_slice(), tail: Some(call), label: None, + unsafe_: Unsafe::No, }, syntax_ptr, ) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs index 558693ee8adad..7d7b948d34149 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/pretty.rs @@ -19,7 +19,7 @@ use crate::{ expr_store::path::{GenericArg, GenericArgs}, hir::{ Array, BindingAnnotation, CaptureBy, ClosureKind, CoroutineKind, Literal, Movability, - RecordSpread, Statement, + RecordSpread, Statement, Unsafe, generics::{GenericParams, WherePredicate}, }, lang_item::LangItemTarget, @@ -843,14 +843,11 @@ impl Printer<'_> { w!(self, "]"); } Expr::Literal(lit) => self.print_literal(lit), - Expr::Block { id: _, statements, tail, label } => { + Expr::Block { id: _, statements, tail, label, unsafe_ } => { let label = label.map(|lbl| { format!("{}: ", self.store[lbl].name.display(self.db, self.edition)) }); - self.print_block(label.as_deref(), statements, tail); - } - Expr::Unsafe { id: _, statements, tail } => { - self.print_block(Some("unsafe "), statements, tail); + self.print_block(label.as_deref(), *unsafe_, statements, tail); } Expr::Const(id) => { w!(self, "const {{ /* {id:?} */ }}"); @@ -870,6 +867,7 @@ impl Printer<'_> { fn print_block( &mut self, label: Option<&str>, + unsafe_: Unsafe, statements: &[Statement], tail: &Option>, ) { @@ -877,6 +875,9 @@ impl Printer<'_> { if let Some(lbl) = label { w!(self, "{}", lbl); } + if unsafe_ == Unsafe::Yes { + w!(self, "unsafe "); + } w!(self, "{{"); if !statements.is_empty() || tail.is_some() { self.indented(|p| { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs index f0a4b5ab6128c..24baa0f8c8d89 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs @@ -335,12 +335,9 @@ impl StoreVisitor for ExprScopeVisitor<'_> { fn on_expr(&mut self, expr: ExprId) { self.scopes.set_scope(expr, self.scope); match &self.store[expr] { - Expr::Block { statements, tail, id, label } => { + Expr::Block { statements, tail, id, label, unsafe_: _ } => { self.visit_block(expr, *id, statements, *tail, *label); } - Expr::Unsafe { id, statements, tail } => { - self.visit_block(expr, *id, statements, *tail, None); - } Expr::Loop { body, label, source: _ } => { let scope = self.scopes.new_labeled_scope(self.scope, *label); self.with_scope(scope, |this| this.on_expr(*body)); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs index 44c32d16f2197..5785a546513c9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs @@ -271,6 +271,12 @@ pub enum RecordSpread { Expr(ExprId), } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Unsafe { + Yes, + No, +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum Expr { /// This is produced if the syntax tree does not have a required expression piece. @@ -290,14 +296,9 @@ pub enum Expr { statements: Box<[Statement]>, tail: Option, label: Option, + unsafe_: Unsafe, }, Const(ExprId), - // FIXME: Fold this into Block with an unsafe flag? - Unsafe { - id: Option, - statements: Box<[Statement]>, - tail: Option, - }, Loop { body: ExprId, label: Option, @@ -406,7 +407,6 @@ impl Expr { Expr::Array(_) | Expr::InlineAsm(_) | Expr::Block { .. } - | Expr::Unsafe { .. } | Expr::Const(_) | Expr::If { .. } | Expr::Literal(_) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs index 0947f456adeae..94b1ec331f87d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs @@ -151,8 +151,8 @@ impl<'db> ExprValidator<'db> { Expr::If { .. } => { self.check_for_unnecessary_else(id, expr); } - Expr::Block { .. } | Expr::Unsafe { .. } => { - self.validate_block(expr); + Expr::Block { statements, .. } => { + self.validate_block(statements); } _ => {} } @@ -314,13 +314,10 @@ impl<'db> ExprValidator<'db> { } } - fn validate_block(&mut self, expr: &Expr) { - let (Expr::Block { statements, .. } | Expr::Unsafe { statements, .. }) = expr else { - return; - }; + fn validate_block(&mut self, statements: &[Statement]) { let pattern_arena = Arena::new(); let cx = MatchCheckCtx::new(self.owner.module(self.db()), &self.infcx, self.env); - for stmt in &**statements { + for stmt in statements { match *stmt { Statement::Expr { expr: stmt_expr, has_semi: true } if self.validate_lints => { let mut diags = Vec::new(); @@ -417,9 +414,7 @@ impl<'db> ExprValidator<'db> { // `expr`; branching containers (`if`/`match`) recurse on each arm. loop { match &self.body[expr] { - Expr::Block { tail: Some(tail), .. } - | Expr::Unsafe { tail: Some(tail), .. } - | Expr::Const(tail) => expr = *tail, + Expr::Block { tail: Some(tail), .. } | Expr::Const(tail) => expr = *tail, Expr::If { then_branch, else_branch, .. } => { self.check_unused_must_use(*then_branch, acc); if let Some(else_branch) = else_branch { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs index 58598980707b3..ca843c8690f28 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -8,7 +8,10 @@ use hir_def::{ AdtId, CallableDefId, DefWithBodyId, ExpressionStoreOwnerId, FieldId, FunctionId, GenericDefId, VariantId, expr_store::{Body, ExpressionStore, path::Path}, - hir::{AsmOperand, Expr, ExprId, ExprOrPatId, InlineAsmKind, Pat, PatId, Statement, UnaryOp}, + hir::{ + AsmOperand, Expr, ExprId, ExprOrPatId, InlineAsmKind, Pat, PatId, Statement, UnaryOp, + Unsafe, + }, resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs}, signatures::{FunctionSignature, StaticFlags, StaticSignature}, type_ref::Rawness, @@ -391,7 +394,7 @@ impl<'db> UnsafeVisitor<'db> { self.on_unsafe_op(current.into(), UnsafetyReason::UnionField); } } - Expr::Unsafe { statements, .. } => { + Expr::Block { unsafe_: Unsafe::Yes, statements, .. } => { self.with_inside_unsafe_block(InsideUnsafeBlock::Yes, |this| { this.walk_pats_top( statements.iter().filter_map(|statement| match statement { @@ -404,7 +407,7 @@ impl<'db> UnsafeVisitor<'db> { }); return; } - Expr::Block { statements, .. } => { + Expr::Block { unsafe_: Unsafe::No, statements, .. } => { self.walk_pats_top( statements.iter().filter_map(|statement| match statement { &Statement::Let { pat, .. } => Some(pat), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs index 78940783bd9bf..da9c5ab10fc46 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs @@ -631,8 +631,7 @@ impl<'a, 'db, D: Delegate<'db>> ExprUseVisitor<'a, 'db, D> { self.consume_expr(rhs)?; } - Expr::Block { ref statements, tail, .. } - | Expr::Unsafe { ref statements, tail, .. } => { + Expr::Block { ref statements, tail, .. } => { for stmt in statements { self.walk_stmt(stmt)?; } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index fc53d64a2f984..2ad12b8a374b4 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -265,7 +265,6 @@ impl<'db> InferenceContext<'db> { | Expr::Assignment { .. } | Expr::Yield { .. } | Expr::Cast { .. } - | Expr::Unsafe { .. } | Expr::Await { .. } | Expr::Ref { .. } | Expr::RecordLit { .. } @@ -391,12 +390,9 @@ impl<'db> InferenceContext<'db> { ); self.types.types.bool } - Expr::Block { statements, tail, label, id: _ } => { + Expr::Block { statements, tail, label, id: _, unsafe_: _ } => { self.infer_block(tgt_expr, statements, *tail, *label, expected) } - Expr::Unsafe { id: _, statements, tail } => { - self.infer_block(tgt_expr, statements, *tail, None, expected) - } Expr::Const(id) => { self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { this.infer_expr(*id, expected, ExprIsRead::Yes) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs index 9a6414ee2ba11..09b0d3c03d5b8 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs @@ -86,8 +86,7 @@ impl<'db> InferenceContext<'db> { self.infer_mut_expr(*id, Mutability::Not); } Expr::Let { pat, expr } => self.infer_mut_expr(*expr, self.pat_bound_mutability(*pat)), - Expr::Block { id: _, statements, tail, label: _ } - | Expr::Unsafe { id: _, statements, tail } => { + Expr::Block { id: _, statements, tail, label: _, unsafe_: _ } => { for st in statements.iter() { match st { Statement::Let { pat, type_ref: _, initializer, else_branch } => { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index 6e179176a9496..da631c0d595ea 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -649,10 +649,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { } Ok(self.merge_blocks(Some(then_target), else_target, expr_id.into())) } - Expr::Unsafe { id: _, statements, tail } => { - self.lower_block_to_place(statements, current, *tail, place, expr_id.into()) - } - Expr::Block { id: _, statements, tail, label } => { + Expr::Block { id: _, statements, tail, label, unsafe_: _ } => { if let Some(label) = label { self.lower_loop(current, place, Some(*label), expr_id.into(), |this, begin| { if let Some(current) = this.lower_block_to_place( diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index f298e25489a59..4c4167a41de65 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -17,7 +17,7 @@ use hir_def::{ StructId, TraitId, VariantId, attrs::parse_extra_crate_attrs, expr_store::{Body, ExprOrPatSource, ExpressionStore, HygieneId, path::Path}, - hir::{BindingId, Expr, ExprId, ExprOrPatId}, + hir::{BindingId, Expr, ExprId, ExprOrPatId, Unsafe}, nameres::{ModuleOrigin, crate_def_map}, resolver::{self, HasResolver, Resolver, TypeNs, ValueNs}, type_ref::Mutability, @@ -2427,7 +2427,7 @@ impl<'db> SemanticsImpl<'db> { if let Some(parent) = ast::Expr::cast(parent.clone()) && let Some(ExprOrPatId::ExprId(expr_id)) = source_map.node_expr(InFile { file_id, value: &parent }) - && let Expr::Unsafe { .. } = body[expr_id] + && let Expr::Block { unsafe_: Unsafe::Yes, .. } = body[expr_id] { break true; } From 1aa60870554b502a56c1aaea39f40eacf46e9507 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 1 Sep 2026 20:18:11 +0800 Subject: [PATCH 13/38] minor: add space after comma in prettify macro expansion Example --- ```diff -fn method(&mut self,params: ::Output) {} +fn method(&mut self, params: ::Output) {} ``` --- .../src/handlers/add_missing_impl_members.rs | 4 +-- .../src/completions/item_list/trait_impl.rs | 6 ++-- .../src/prettify_macro_expansion.rs | 32 ++++++++++++++----- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 8b129acad3319..9f9bb1d131548 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -2117,7 +2117,7 @@ macro_rules! define_method { } trait AnotherTrait { define_method!(); } impl AnotherTrait for () { - $0fn method(&mut self,params: ::Output) { + $0fn method(&mut self, params: ::Output) { todo!() } } @@ -2154,7 +2154,7 @@ macro_rules! define_method { } trait AnotherTrait { define_method!(T); } impl AnotherTrait for () { - $0fn method(&mut self,params: ::Output) { + $0fn method(&mut self, params: ::Output) { todo!() } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs index ee6788b16e45c..afc551107d08e 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -1365,7 +1365,7 @@ noop! { struct Test; impl Foo for Test { - fn foo(&mut self,bar: i64,baz: &mut u32) -> Result<(),u32> { + fn foo(&mut self, bar: i64, baz: &mut u32) -> Result<(), u32> { $0 } } @@ -1440,7 +1440,7 @@ macro_rules! define_method { } trait AnotherTrait { define_method!(); } impl AnotherTrait for () { - fn method(&mut self,params: ::Output) { + fn method(&mut self, params: ::Output) { $0 } } @@ -1478,7 +1478,7 @@ macro_rules! define_method { } trait AnotherTrait { define_method!(T); } impl AnotherTrait for () { - fn method(&mut self,params: ::Output) { + fn method(&mut self, params: ::Output) { $0 } } diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs index 001c920c9b824..c5e4aca95ee1f 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs @@ -147,6 +147,14 @@ pub fn prettify_macro_expansion( T![!] if is_last(|it| it == MACRO_RULES_KW, false) && is_next(is_text, false) => { mods.push(do_ws(after, tok)); } + T![,] if tok.parent().is_some_and(|it| it.kind() != MATCH_ARM) => { + if is_next( + |it| !matches!(it, R_BRACK | R_PAREN | R_CURLY | T![,] | WHITESPACE), + false, + ) { + mods.push(do_ws(after, tok)); + } + } _ => (), } @@ -229,8 +237,8 @@ mod tests { macro_rules! foo { () => { $crate::foo::bar!(); - (1..2,1..=2); - (a==b,a!=b,a<=b,a>=b,x+=2,x<<=2); + (1..2, 1..=2); + (a==b, a!=b, a<=b, a>=b, x+=2, x<<=2); }; } "#]], @@ -280,11 +288,11 @@ mod tests { let mut y = 3; let ref mut z@0..5 = 4; let ref mut t@0..=5 = 4; - let (x,ref y) = (5,6); + let (x, ref y) = (5, 6); let (Foo { - x,y - },Bar(z,t)); - let (&mut x,(y|y)); + x, y + }, Bar(z, t)); + let (&mut x, (y|y)); match (){} }; "#]], @@ -324,6 +332,9 @@ mod tests { fn foo() {} struct Foo {} struct Foo; + struct Bar { + x: i32, + } enum Foo {} impl Foo {} const _: () = {}; @@ -341,6 +352,9 @@ mod tests { struct Foo {} struct Foo; + struct Bar { + x: i32, + } enum Foo {} impl Foo {} const _: () = {}; @@ -352,7 +366,7 @@ mod tests { type X = 2; use a; use b::{ - c,d + c, d }; macro_rules! foo { () => {}; @@ -373,6 +387,7 @@ mod tests { let _ = async move {}; let _ = x.await; let _ = (1..2, 1..=2); + let _ = (3,); 'lab: for _ in 0..5 { loop { } break 'lab expr; @@ -394,7 +409,8 @@ mod tests { let _ = async move||{}; let _ = async move {}; let _ = x.await; - let _ = (1..2,1..=2); + let _ = (1..2, 1..=2); + let _ = (3,); 'lab: for _ in 0..5 { loop {} break 'lab expr; From 58efe61921b08b2db11f59aa0b7b34900931c4a3 Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Tue, 1 Sep 2026 17:44:40 +0200 Subject: [PATCH 14/38] Fix typos, update typos-cli --- src/tools/rust-analyzer/.github/workflows/ci.yaml | 2 +- src/tools/rust-analyzer/.typos.toml | 2 ++ src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs | 2 +- src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index 7ea734e7ef1c0..77f35340c61e6 100644 --- a/src/tools/rust-analyzer/.github/workflows/ci.yaml +++ b/src/tools/rust-analyzer/.github/workflows/ci.yaml @@ -317,7 +317,7 @@ jobs: timeout-minutes: 10 env: FORCE_COLOR: 1 - TYPOS_VERSION: v1.38.1 + TYPOS_VERSION: v1.50.0 steps: - name: download typos run: curl -LsSf https://github.com/crate-ci/typos/releases/download/$TYPOS_VERSION/typos-$TYPOS_VERSION-x86_64-unknown-linux-musl.tar.gz | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin diff --git a/src/tools/rust-analyzer/.typos.toml b/src/tools/rust-analyzer/.typos.toml index 873daa3bf3b83..97789f0abc669 100644 --- a/src/tools/rust-analyzer/.typos.toml +++ b/src/tools/rust-analyzer/.typos.toml @@ -40,5 +40,7 @@ inh = "inh" anc = "anc" datas = "datas" impl_froms = "impl_froms" +implicits = "implicits" selfs = "selfs" taits = "taits" +verifys = "verifys" diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs index ec8f2bfb6686f..7d6b191c17ad0 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs @@ -125,7 +125,7 @@ struct ExpressionOnlyStore { /// A map from an variable usages to their hygiene ID. /// - /// Expressions (and destructuing patterns) that can be recorded here are single segment path, although not all single segments path refer + /// Expressions (and destructuring patterns) that can be recorded here are single segment path, although not all single segments path refer /// to variables and have hygiene (some refer to items, we don't know at this stage). ident_hygiene: FxHashMap, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs index 3e78ae09983ec..9375e25cbdfe7 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs @@ -11773,7 +11773,7 @@ This feature has no tracking issue, and is therefore likely internal to the comp label: "nvptx_target_feature", description: r##"# `nvptx_target_feature` -Target feaures on nvptx. +Target features on nvptx. The tracking issue for this feature is: [#150254] From e21bd35bdeff8ec401b3bff06858eb5cea648ef3 Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Tue, 1 Sep 2026 18:00:03 +0200 Subject: [PATCH 15/38] Remove FIXME, update docs --- src/tools/rust-analyzer/crates/base-db/Cargo.toml | 2 +- src/tools/rust-analyzer/crates/base-db/src/input.rs | 9 +++++---- src/tools/rust-analyzer/crates/base-db/src/lib.rs | 5 +++-- .../docs/book/src/contributing/architecture.md | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/base-db/Cargo.toml b/src/tools/rust-analyzer/crates/base-db/Cargo.toml index 7425856b30e6a..9bf41b3aa7160 100644 --- a/src/tools/rust-analyzer/crates/base-db/Cargo.toml +++ b/src/tools/rust-analyzer/crates/base-db/Cargo.toml @@ -2,7 +2,7 @@ name = "base-db" version = "0.0.0" repository.workspace = true -description = "Basic database traits for rust-analyzer. The concrete DB is defined by `ide` (aka `ra_ap_ide`)." +description = "Basic database trait and infra for rust-analyzer's crate and source root inputs. The concrete DB is defined by `ide` (aka `ra_ap_ide`)." authors.workspace = true edition.workspace = true diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index 1e32ee69804c7..230b7cbed680f 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -257,9 +257,10 @@ impl fmt::Display for LangCrateOrigin { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CrateDisplayName { - // The name we use to display various paths (with `_`). + /// The name we use to display various paths (with `_`). crate_name: CrateName, - // The name as specified in Cargo.toml (with `-`). + /// The name as self-declared by the crate. For example, the name declared in the manifest of + /// the crate. This may contain dashes `-`. canonical_name: Symbol, } @@ -936,8 +937,8 @@ impl<'a> IntoIterator for &'a Env { /// /// ## dev-dependencies /// -/// Note that it's actually legal for a cargo package (i.e. a thing -/// with a Cargo.toml) to depend on itself in dev-dependencies. This +/// Note that it's actually legal for a Cargo package (i.e. a thing +/// with a `Cargo.toml`) to depend on itself in dev-dependencies. This /// can enable additional features, and is typically used when a /// project wants features to be enabled in tests. Dev-dependencies /// are not propagated, so they aren't visible to package that depend diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index 4346a2a8c8294..0da1faba676c0 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -1,5 +1,6 @@ -//! base_db defines basic database traits. The concrete DB is defined by ide. -// FIXME: Rename this crate, base db is non descriptive +//! This crate defines the basic database trait for interacting with source code using [`salsa`]. +//! +//! The concrete implementation DB is defined by ide. #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] diff --git a/src/tools/rust-analyzer/docs/book/src/contributing/architecture.md b/src/tools/rust-analyzer/docs/book/src/contributing/architecture.md index 50f60bcdccc1b..8b6ea56c85165 100644 --- a/src/tools/rust-analyzer/docs/book/src/contributing/architecture.md +++ b/src/tools/rust-analyzer/docs/book/src/contributing/architecture.md @@ -143,7 +143,7 @@ Reading the docs of the `base_db::input` module should be useful: everything els **Architecture Invariant:** particularities of the build system are *not* the part of the ground state. In particular, `base-db` knows nothing about cargo. -For example, `cfg` flags are a part of `base_db`, but `feature`s are not. +For example, `cfg` flags are a part of `base-db`, but `feature`s are not. A `foo` feature is a Cargo-level concept, which is lowered by Cargo to `--cfg feature=foo` argument on the command line. The `CrateGraph` structure is used to represent the dependencies between the crates abstractly. From 90e8beb06b6f1b6cc0b4c7eb765e2ed8d1022a0a Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 2 Sep 2026 10:59:42 +0800 Subject: [PATCH 16/38] fix: hover `1f64` use float instead of integer Example --- ```rust fn main() { $01f64; } ``` **Before this PR** ```rust value of literal: ` 1 (0x1|0b1) ` ``` **After this PR** ```rust value of literal: ` 1 (bits: 0x3FF0000000000000) ` ``` --- .../crates/ide/src/hover/render.rs | 49 ++++++++++--------- .../crates/ide/src/hover/tests.rs | 16 ++++++ 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index f70783fd3c0c4..1897168821863 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -842,6 +842,29 @@ pub(super) fn literal( } .original; + let parse_float = |text: &str| { + if ty.as_builtin().map(|it| it.is_f16()).unwrap_or(false) { + match text.parse::() { + Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), + Err(e) => Err(e.0.to_owned()), + } + } else if ty.as_builtin().map(|it| it.is_f32()).unwrap_or(false) { + match text.parse::() { + Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), + Err(e) => Err(e.to_string()), + } + } else if ty.as_builtin().map(|it| it.is_f128()).unwrap_or(false) { + match text.parse::() { + Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), + Err(e) => Err(e.0.to_owned()), + } + } else { + match text.parse::() { + Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), + Err(e) => Err(e.to_string()), + } + } + }; let value = match_ast! { match token { ast::String(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string), @@ -849,29 +872,9 @@ pub(super) fn literal( ast::CString(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(|it| std::str::from_utf8(it).map_or_else(|e| format!("{e:?}"), ToOwned::to_owned)), ast::Char(char) => char .value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string), ast::Byte(byte) => byte .value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("0x{it:X}")), - ast::FloatNumber(num) => { - let text = num.value_string(); - if ty.as_builtin().map(|it| it.is_f16()).unwrap_or(false) { - match text.parse::() { - Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), - Err(e) => Err(e.0.to_owned()), - } - } else if ty.as_builtin().map(|it| it.is_f32()).unwrap_or(false) { - match text.parse::() { - Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), - Err(e) => Err(e.to_string()), - } - } else if ty.as_builtin().map(|it| it.is_f128()).unwrap_or(false) { - match text.parse::() { - Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), - Err(e) => Err(e.0.to_owned()), - } - } else { - match text.parse::() { - Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), - Err(e) => Err(e.to_string()), - } - } + ast::FloatNumber(num) => parse_float(&num.value_string()), + ast::IntNumber(num) if matches!(num.suffix(), Some("f16" | "f32" | "f64" | "f128")) => { + parse_float(&num.value_string()) }, ast::IntNumber(num) => match num.value() { Ok(num) => Ok(format!("{num} (0x{num:X}|0b{num:b})")), diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index 66ccd1924639e..c8aa8a2221640 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -9139,6 +9139,22 @@ fn main() { ); check( r#" +fn main() { + $01f64; +} +"#, + expect![[r#" + *1f64* + ```rust + f64 + ``` + --- + + value of literal: ` 1 (bits: 0x3FF0000000000000) ` + "#]], + ); + check( + r#" fn main() { $00.1ea123; } From 13c017d344c6ea6d0218a37e3281e8e52d65439e Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Wed, 2 Sep 2026 14:47:50 +0000 Subject: [PATCH 17/38] Prepare for merging from rust-lang/rust This updates the rust-version file to 59dabe56f7b78d9dd427645cd7f8e7fd426724cc. --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index 9ff8b0c27d19c..46cba50173768 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -f7d782a3be46d6bb4b9792fe69a61db389ba1769 +59dabe56f7b78d9dd427645cd7f8e7fd426724cc From 8ef799591b13ce2a2cb780b436bd59f6c4c84946 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 3 Sep 2026 13:33:13 +0800 Subject: [PATCH 18/38] internal: remove redundant disable 'unused_variables' --- .../src/handlers/explicit_drop_method_use.rs | 16 +++------------- .../handlers/fru_in_destructuring_assignment.rs | 6 ++---- .../src/handlers/incorrect_case.rs | 6 ++---- .../ide-diagnostics/src/handlers/invalid_cast.rs | 2 +- .../src/handlers/missing_unsafe.rs | 1 - 5 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/explicit_drop_method_use.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/explicit_drop_method_use.rs index b02bccf5a533c..5ab30fffe76da 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/explicit_drop_method_use.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/explicit_drop_method_use.rs @@ -127,9 +127,7 @@ fn fix_path( #[cfg(test)] mod tests { - use crate::tests::{ - check_diagnostics, check_diagnostics_with_disabled, check_fix, check_fix_with_disabled, - }; + use crate::tests::{check_diagnostics, check_fix}; #[test] fn method_call_diagnostic() { @@ -289,7 +287,7 @@ fn main(mut a: A) { #[test] fn path_diagnostic() { - check_diagnostics_with_disabled( + check_diagnostics( r#" //- minicore: drop struct A; @@ -301,10 +299,6 @@ fn main(mut a: A) { d(&mut a); } "#, - // Because of the error, the code isn't analyzed further (?), and so `d` is warned on as unused. - // Arguably a bug in r-a (rustc doesn't emit a warning in this case) - // FIXME: remove this once r-a no longer warns - &["unused_variables"], ); } @@ -312,7 +306,7 @@ fn main(mut a: A) { // NOTE: Here, the fix is not completely correct, as it doesn't replace `d(&mut a)` with `d(a)`. // Oh well, rustc doesn't either fn path_fix() { - check_fix_with_disabled( + check_fix( r#" //- minicore: drop struct A; @@ -332,10 +326,6 @@ fn main(mut a: A) { d(&mut a); } "#, - // Because of the error, the code isn't analyzed further (?), and so `d` is warned on as unused. - // Arguably a bug in r-a (rustc doesn't emit a warning in this case) - // FIXME: remove this once r-a no longer warns - &["unused_variables"], ); } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/fru_in_destructuring_assignment.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/fru_in_destructuring_assignment.rs index f8d3d80d62f6b..270b27a8e37cc 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/fru_in_destructuring_assignment.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/fru_in_destructuring_assignment.rs @@ -18,11 +18,11 @@ pub(crate) fn fru_in_destructuring_assignment( #[cfg(test)] mod tests { - use crate::tests::{check_diagnostics, check_diagnostics_with_disabled}; + use crate::tests::check_diagnostics; #[test] fn spread_variable() { - check_diagnostics_with_disabled( + check_diagnostics( r#" struct Foo { bar: u32, baz: u32 } fn test(f: Foo, g: Foo, mut bar: u32, mut baz: u32) { @@ -34,8 +34,6 @@ fn test(f: Foo, g: Foo, mut bar: u32, mut baz: u32) { // ^ error: functional record updates are not allowed in destructuring assignments } "#, - // We don't end up using neither `bar` nor `baz` - &["unused_variables"], ); } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs index 888f55cea777e..3716e8d66e9ca 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -61,7 +61,7 @@ fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &hir::IncorrectCase) -> Option() { //^^^^^^^^^^^^^ error: cannot cast `usize` to a fat pointer `*const T` } "#, - &["E0308", "unused_variables"], + &["E0308"], ); } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs index 4b55995a058df..abdee7da3fb6c 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs @@ -425,7 +425,6 @@ fn main() { check_diagnostics( r#" //- minicore: index, slice -#![allow(unused_variables)] fn main() { From 1770debdb552d3ce7710ae9b646b339c76101214 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 19 Aug 2026 02:40:48 +0300 Subject: [PATCH 19/38] Refactor collection of diagnostics in `hir` The new code is more consistent, easier to maintain and not forget to cover all cases when we add new things (e.g. new queries creating anon consts), and fixes a few missing edges (see the changed diagnostics in incorrect_case that now emit a diagnostic they haven't previously). --- .../rust-analyzer/crates/hir-ty/src/db.rs | 77 +- .../crates/hir-ty/src/display.rs | 5 + .../rust-analyzer/crates/hir-ty/src/tests.rs | 2 +- .../crates/hir/src/diagnostics.rs | 776 ++++++++++++++++- src/tools/rust-analyzer/crates/hir/src/lib.rs | 822 +----------------- .../rust-analyzer/crates/hir/src/semantics.rs | 10 +- .../src/handlers/incorrect_case.rs | 7 +- 7 files changed, 823 insertions(+), 876 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs index f42a428bbdd21..76c33d3cae7b2 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -25,7 +25,7 @@ use triomphe::Arc; use crate::{ FieldType, GenericDefaultsRef, GenericPredicates, ImplTraitId, InferBodyId, TyDefId, - TyLoweringResult, ValueTyDefId, + TyLoweringDiagnostic, TyLoweringResult, ValueTyDefId, consteval::ConstEvalError, dyn_compatibility::DynCompatibilityViolation, layout::{Layout, LayoutError}, @@ -506,42 +506,53 @@ impl HasResolver for AnonConstId<'_> { } } +pub fn signature_anon_consts_and_diagnostics<'db>( + db: &'db dyn HirDatabase, + def: GenericDefId, +) -> ArrayVec<(&'db [AnonConstId<'db>], &'db [TyLoweringDiagnostic]), 5> { + let mut result = ArrayVec::new(); + + // Queries common to all generic defs: + push(&mut result, db.generic_defaults_with_diagnostics(def)); + push(&mut result, GenericPredicates::query_with_diagnostics(db, def)); + push(&mut result, db.const_param_types_with_diagnostics(def)); + + match def { + GenericDefId::ImplId(id) => { + push(&mut result, db.impl_self_ty_with_diagnostics(id)); + if let Some(trait_ref) = db.impl_trait_with_diagnostics(id) { + push(&mut result, trait_ref); + } + } + GenericDefId::TypeAliasId(id) => { + push(&mut result, db.type_for_type_alias_with_diagnostics(id)); + push(&mut result, db.type_alias_bounds_with_diagnostics(id)); + } + GenericDefId::FunctionId(id) => push(&mut result, db.fn_sig_for_fn_with_diagnostics(id)), + GenericDefId::ConstId(def) => push(&mut result, db.type_for_const_with_diagnostics(def)), + GenericDefId::StaticId(def) => push(&mut result, db.type_for_static_with_diagnostics(def)), + GenericDefId::TraitId(_) | GenericDefId::AdtId(_) => {} + } + + return result; + + fn push<'db, T>( + result: &mut ArrayVec<(&'db [AnonConstId<'db>], &'db [TyLoweringDiagnostic]), 5>, + item: &'db TyLoweringResult<'db, T>, + ) { + result.push((item.defined_anon_consts(), item.diagnostics())); + } +} + impl<'db> AnonConstId<'db> { pub fn all_from_signature( db: &'db dyn HirDatabase, def: GenericDefId, - ) -> ArrayVec<&'db [Self], 5> { - let mut result = ArrayVec::new(); - - // Queries common to all generic defs: - result.push(db.generic_defaults_with_diagnostics(def).defined_anon_consts()); - result.push(GenericPredicates::query_with_diagnostics(db, def).defined_anon_consts()); - result.push(db.const_param_types_with_diagnostics(def).defined_anon_consts()); - - match def { - GenericDefId::ImplId(id) => { - result.push(db.impl_self_ty_with_diagnostics(id).defined_anon_consts()); - if let Some(trait_ref) = db.impl_trait_with_diagnostics(id) { - result.push(trait_ref.defined_anon_consts()); - } - } - GenericDefId::TypeAliasId(id) => { - result.push(db.type_for_type_alias_with_diagnostics(id).defined_anon_consts()); - result.push(db.type_alias_bounds_with_diagnostics(id).defined_anon_consts()); - } - GenericDefId::FunctionId(id) => { - result.push(db.fn_sig_for_fn_with_diagnostics(id).defined_anon_consts()) - } - GenericDefId::ConstId(def) => { - result.push(db.type_for_const_with_diagnostics(def).defined_anon_consts()) - } - GenericDefId::StaticId(def) => { - result.push(db.type_for_static_with_diagnostics(def).defined_anon_consts()) - } - GenericDefId::TraitId(_) | GenericDefId::AdtId(_) => {} - } - - result + ) -> impl Iterator> { + signature_anon_consts_and_diagnostics(db, def) + .into_iter() + .flat_map(|(anon_consts, _)| anon_consts) + .copied() } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index dfa088830543f..83bcf77003eee 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -520,6 +520,11 @@ impl DisplayTarget { let edition = krate.data(db).edition; Self { krate, edition } } + + pub fn from_crate_and_edition(db: &dyn HirDatabase, krate: Crate, edition: Edition) -> Self { + let _ = db; + Self { krate, edition } + } } #[derive(Clone, Copy)] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs index c19a1f27af96e..2e433e8be8909 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs @@ -527,7 +527,7 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { if store.expr_roots().next().is_none() { continue; } - for &anon_const in AnonConstId::all_from_signature(&db, def).into_iter().flatten() { + for anon_const in AnonConstId::all_from_signature(&db, def) { let infer = InferenceResult::of(&db, anon_const); infer_def(infer, store, source_map, None, krate); } diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index 9a2f1fd11d6a4..c342effa06aff 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -3,36 +3,64 @@ //! //! This probably isn't the best way to do this -- ideally, diagnostics should //! be expressed in terms of hir types themselves. +use std::mem::discriminant; + use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_def::{ - DefWithBodyId, GenericParamId, HasModule, SyntheticSyntax, + AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, GenericDefId, GenericParamId, ImplId, + Lookup, MacroId, ModuleDefId, ModuleId, StaticId, SyntheticSyntax, TraitId, + attrs::AttrFlags, expr_store::{ - ExprOrPatPtr, ExpressionStoreSourceMap, hir_assoc_type_binding_to_ast, - hir_generic_arg_to_ast, hir_segment_to_ast_segment, + Body, ExprOrPatPtr, ExpressionStore, ExpressionStoreDiagnostics, ExpressionStoreSourceMap, + hir_assoc_type_binding_to_ast, hir_generic_arg_to_ast, hir_segment_to_ast_segment, }, hir::{ExprId, ExprOrPatId, PatId}, + nameres::{ + DefMap, + assoc::{ImplItems, TraitItems}, + diagnostics::{DefDiagnosticKind, DefDiagnostics}, + }, + signatures::{ + ConstSignature, FunctionSignature, ImplFlags, ImplSignature, StaticSignature, TraitFlags, + TraitSignature, TypeAliasSignature, + }, type_ref::TypeRefId, + unstable_features::UnstableFeatures, +}; +use hir_expand::{ + HirFileId, InFile, MacroCallId, MacroCallKind, MacroKind, RenderedExpandError, ValueResult, + mod_path::ModPath, name::Name, }; -use hir_expand::{HirFileId, InFile, mod_path::ModPath, name::Name}; use hir_ty::{ - CastError, ExplicitDropMethodUseKind, InferenceDiagnostic, InferenceTyDiagnosticSource, - PathGenericsSource, PathLoweringDiagnostic, TyLoweringDiagnostic, - db::HirDatabase, + CastError, ExplicitDropMethodUseKind, InferBodyId, InferenceDiagnostic, InferenceResult, + InferenceTyDiagnosticSource, ParamEnvAndCrate, PathGenericsSource, PathLoweringDiagnostic, + TyLoweringDiagnostic, check_orphan_rules, + db::{AnonConstId, HirDatabase, signature_anon_consts_and_diagnostics}, diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, display::{DisplayTarget, HirDisplay}, - next_solver::{DbInterner, EarlyBinder}, + method_resolution::TraitImpls, + next_solver::{ + DbInterner, EarlyBinder, TyKind, TypingMode, + infer::{DbInternerInferExt, InferCtxt}, + }, solver_errors::SolverDiagnosticKind, + traits::{is_inherent_impl_coherent, structurally_normalize_ty}, }; +use rustc_type_ir::inherent::IntoKind as _; +use span::Edition; use stdx::{impl_from, never}; use syntax::{ AstNode, AstPtr, SyntaxError, SyntaxNodePtr, TextRange, - ast::{self, HasGenericArgs}, + ast::{self, HasGenericArgs, HasName}, match_ast, }; use triomphe::Arc; -use crate::{AssocItem, Field, Function, GenericDef, Trait, Type, TypeOwnerId, Variant}; +use crate::{ + AnyFunctionId, AssocItem, Field, Function, GenericDef, Trait, Type, TypeOwnerId, Variant, + struct_tail_raw, +}; pub use hir_def::VariantId; pub use hir_ty::{ @@ -681,8 +709,717 @@ pub struct ReturnOutsideFunction { pub kind: ReturnKind, } +pub(crate) struct DiagnosticsCollector<'a, 'db> { + db: &'db dyn HirDatabase, + krate: base_db::Crate, + edition: Edition, + style_lints: bool, + acc: &'a mut Vec>, +} + +fn precise_macro_call_location( + ast: &MacroCallKind, + db: &dyn HirDatabase, + krate: base_db::Crate, +) -> InFile { + // FIXME: maybe we actually want slightly different ranges for the different macro diagnostics + // - e.g. the full attribute for macro errors, but only the name for name resolution + match ast { + MacroCallKind::FnLike { ast_id, .. } => { + let node = ast_id.to_node(db); + let range = node + .path() + .and_then(|it| it.segment()) + .and_then(|it| it.name_ref()) + .map(|it| it.syntax().text_range()); + let range = range.unwrap_or_else(|| node.syntax().text_range()); + ast_id.with_value(range) + } + MacroCallKind::Derive { ast_id, derive_attr_index, derive_index, .. } => { + let range = derive_attr_index.find_derive_range(db, krate, *ast_id, *derive_index); + ast_id.with_value(range) + } + MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { + let attr_range = + attr_ids.invoc_attr().find_attr_range(db, krate, *ast_id).1.syntax().text_range(); + ast_id.with_value(attr_range) + } + } +} + +impl<'a, 'db> DiagnosticsCollector<'a, 'db> { + pub(crate) fn collect( + db: &'db dyn HirDatabase, + module: ModuleId, + acc: &'a mut Vec>, + style_lints: bool, + ) { + let krate = module.krate(db); + DiagnosticsCollector { db, krate, edition: krate.data(db).edition, style_lints, acc } + .collect_module(module); + } + + fn emit_def_diagnostic(&mut self, diag: &DefDiagnosticKind) { + match diag { + DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates } => { + let decl = declaration.to_ptr(self.db); + self.acc.push( + UnresolvedModule { + decl: InFile::new(declaration.file_id, decl), + candidates: candidates.clone(), + } + .into(), + ) + } + DefDiagnosticKind::UnresolvedExternCrate { ast } => { + let item = ast.to_ptr(self.db); + self.acc + .push(UnresolvedExternCrate { decl: InFile::new(ast.file_id, item) }.into()); + } + + DefDiagnosticKind::MacroError { ast, path, err } => { + let item = ast.to_ptr(self.db); + let RenderedExpandError { message, error, kind } = err.render_to_string(self.db); + self.acc.push( + MacroError { + range: InFile::new(ast.file_id, item.text_range()), + message: format!("{}: {message}", path.display(self.db, self.edition)), + error, + kind, + } + .into(), + ) + } + DefDiagnosticKind::UnresolvedImport { id, index } => { + let file_id = id.file_id; + + let use_tree = hir_def::src::use_tree_to_ast(self.db, *id, *index); + self.acc.push( + UnresolvedImport { decl: InFile::new(file_id, AstPtr::new(&use_tree)) }.into(), + ); + } + + DefDiagnosticKind::UnconfiguredCode { ast_id, cfg, opts } => { + let ast_id_map = ast_id.file_id.ast_id_map(self.db); + let ptr = ast_id_map.get_erased(ast_id.value); + self.acc.push( + InactiveCode { + node: InFile::new(ast_id.file_id, ptr), + cfg: cfg.clone(), + opts: opts.clone(), + } + .into(), + ); + } + DefDiagnosticKind::UnresolvedMacroCall { ast, path } => { + let location = precise_macro_call_location(ast, self.db, self.krate); + self.acc.push( + UnresolvedMacroCall { + range: location, + path: path.clone(), + is_bang: matches!(ast, MacroCallKind::FnLike { .. }), + } + .into(), + ); + } + DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => { + let node = ast.to_node(self.db); + // Must have a name, otherwise we wouldn't emit it. + let name = node.name().expect("unimplemented builtin macro with no name"); + self.acc.push( + UnimplementedBuiltinMacro { + node: ast.with_value(SyntaxNodePtr::from(AstPtr::new(&name))), + } + .into(), + ); + } + DefDiagnosticKind::InvalidDeriveTarget { ast, id } => { + let (_, attr) = id.find_attr_range(self.db, self.krate, *ast); + let derive = attr + .path() + .map(|path| path.syntax().text_range()) + .unwrap_or_else(|| attr.syntax().text_range()); + self.acc.push(InvalidDeriveTarget { range: ast.with_value(derive) }.into()); + } + DefDiagnosticKind::MalformedDerive { ast, id } => { + let derive = id.find_attr_range(self.db, self.krate, *ast).1.syntax().text_range(); + self.acc.push(MalformedDerive { range: ast.with_value(derive) }.into()); + } + DefDiagnosticKind::MacroDefError { ast, message } => { + let node = ast.to_node(self.db); + self.acc.push( + MacroDefError { + node: InFile::new(ast.file_id, AstPtr::new(&node)), + name: node.name().map(|it| it.syntax().text_range()), + message: message.clone(), + } + .into(), + ); + } + } + } + + fn emit_def_diagnostics(&mut self, diagnostics: &DefDiagnostics) { + diagnostics.iter().for_each(|diag| self.emit_def_diagnostic(&diag.kind)); + } + + fn emit_case_diagnostics(&mut self, def: ModuleDefId) { + self.acc + .extend(hir_ty::diagnostics::incorrect_case(self.db, def).into_iter().map(Into::into)); + } + + fn collect_macro_call(&mut self, macro_call_id: MacroCallId) { + let Some(e) = macro_call_id.parse_macro_expansion_error(self.db) else { + return; + }; + let ValueResult { value: parse_errors, err } = e; + if let Some(err) = err { + let loc = macro_call_id.loc(self.db); + let file_id = loc.kind.file_id(); + let mut range = precise_macro_call_location(&loc.kind, self.db, loc.krate); + let RenderedExpandError { message, error, kind } = err.render_to_string(self.db); + if Some(err.span().anchor.file_id) + == file_id.file_id().map(|it| it.span_file_id(self.db)) + { + range.value = err.span().range + + file_id + .ast_id_map(self.db) + .get_erased(err.span().anchor.ast_id) + .text_range() + .start(); + } + self.acc.push(MacroError { range, message, error, kind }.into()); + } + + if !parse_errors.is_empty() { + let loc = macro_call_id.loc(self.db); + let range = precise_macro_call_location(&loc.kind, self.db, loc.krate); + self.acc.push(MacroExpansionParseError { range, errors: parse_errors.clone() }.into()) + } + } + + fn collect_assoc_items(&mut self, defs: &[(Name, AssocItemId)], def_map: &DefMap) { + for &(_, def) in defs { + self.collect_module_def(def.into(), def_map); + } + } + + fn collect_trait(&mut self, def: TraitId, def_map: &DefMap) { + let (signature, source_map) = TraitSignature::with_source_map(self.db, def); + let items = TraitItems::query_with_diagnostics(self.db, def); + + self.collect_generic_def(&signature.store, source_map, def.into()); + self.emit_def_diagnostics(&items.1); + items.0.macro_calls().for_each(|(_, call)| self.collect_macro_call(call)); + self.collect_assoc_items(&items.0.items, def_map); + } + + fn collect_impl( + &mut self, + def: ImplId, + infcx: &InferCtxt<'db>, + def_map: &'db DefMap, + impl_assoc_items_scratch: &mut Vec<(Name, AssocItemId)>, + ) { + let (impl_signature, source_map) = ImplSignature::with_source_map(self.db, def); + let impl_items = ImplItems::of(self.db, def); + + self.collect_generic_def(&impl_signature.store, source_map, def.into()); + self.emit_def_diagnostics(&impl_items.1); + impl_items.0.macro_calls().for_each(|(_, call)| self.collect_macro_call(call)); + self.collect_assoc_items(&impl_items.0.items, def_map); + + let loc = def.lookup(self.db); + + let file_id = loc.id.file_id; + if file_id.macro_file().is_some_and(|it| it.kind(self.db) == MacroKind::DeriveBuiltIn) { + // these expansion come from us, diagnosing them is a waste of resources + // FIXME: Once we diagnose the inputs to builtin derives, we should at least extract those diagnostics somehow + return; + } + + let ast_id_map = file_id.ast_id_map(self.db); + + let trait_impl = impl_signature.target_trait.is_some(); + if !trait_impl && !is_inherent_impl_coherent(self.db, def_map, def) { + self.acc.push(IncoherentImpl { impl_: ast_id_map.get(loc.id.value), file_id }.into()) + } + + if trait_impl && !check_orphan_rules(self.db, def) { + self.acc.push(TraitImplOrphan { impl_: ast_id_map.get(loc.id.value), file_id }.into()) + } + + let trait_ = trait_impl + .then(|| self.db.impl_trait(def)) + .flatten() + .map(|trait_ref| trait_ref.instantiate_identity().skip_norm_wip().def_id.0); + let mut trait_is_unsafe = trait_.is_some_and(|trait_| { + TraitSignature::of(self.db, trait_).flags.contains(TraitFlags::UNSAFE) + }); + let impl_is_negative = impl_signature.is_negative(); + let impl_is_unsafe = impl_signature.flags.contains(ImplFlags::UNSAFE); + + let trait_is_unresolved = trait_.is_none() && trait_impl; + if trait_is_unresolved { + // Ignore trait safety errors when the trait is unresolved, as otherwise we'll treat it as safe, + // which may not be correct. + trait_is_unsafe = impl_is_unsafe; + } + + let drop_maybe_dangle = (|| { + let trait_ = trait_?; + let drop_trait = infcx.interner.lang_items().Drop?; + if drop_trait != trait_ { + return None; + } + let parent = def.into(); + let (lifetimes_attrs, type_and_consts_attrs) = + AttrFlags::query_generic_params(self.db, parent); + let res = lifetimes_attrs.values().any(|it| it.contains(AttrFlags::MAY_DANGLE)) + || type_and_consts_attrs.values().any(|it| it.contains(AttrFlags::MAY_DANGLE)); + Some(res) + })() + .unwrap_or(false); + + match (impl_is_unsafe, trait_is_unsafe, impl_is_negative, drop_maybe_dangle) { + // unsafe negative impl + (true, _, true, _) | + // unsafe impl for safe trait + (true, false, _, false) => self.acc.push(TraitImplIncorrectSafety { impl_: ast_id_map.get(loc.id.value), file_id, should_be_safe: true }.into()), + // safe impl for unsafe trait + (false, true, false, _) | + // safe impl of dangling drop + (false, false, _, true) => self.acc.push(TraitImplIncorrectSafety { impl_: ast_id_map.get(loc.id.value), file_id, should_be_safe: false }.into()), + _ => (), + }; + + // Negative impls can't have items, don't emit missing items diagnostic for them + if let (false, Some(trait_)) = (impl_is_negative, trait_) { + let trait_items = &trait_.trait_items(self.db).items; + let required_items = trait_items.iter().filter(|&(_, assoc)| match *assoc { + AssocItemId::FunctionId(it) => !FunctionSignature::of(self.db, it).has_body(), + AssocItemId::ConstId(id) => !ConstSignature::of(self.db, id).has_body(), + AssocItemId::TypeAliasId(it) => TypeAliasSignature::of(self.db, it).ty.is_none(), + }); + impl_assoc_items_scratch.extend(impl_items.0.items.iter().cloned()); + + let redundant = impl_assoc_items_scratch + .iter() + .filter(|(name, id)| { + !trait_items.iter().any(|(impl_name, impl_item)| { + discriminant(impl_item) == discriminant(id) && impl_name == name + }) + }) + .map(|(name, item)| (name.clone(), AssocItem::from(*item))); + for (name, assoc_item) in redundant { + self.acc.push( + TraitImplRedundantAssocItems { + trait_: trait_.into(), + file_id, + impl_: ast_id_map.get(loc.id.value), + assoc_item: (name, assoc_item), + } + .into(), + ) + } + + let mut missing: Vec<_> = required_items + .filter(|(name, id)| { + !impl_assoc_items_scratch.iter().any(|(impl_name, impl_item)| { + discriminant(impl_item) == discriminant(id) && impl_name == name + }) + }) + .map(|(name, item)| (name.clone(), AssocItem::from(*item))) + .collect(); + + if !missing.is_empty() { + let env = ParamEnvAndCrate { + param_env: self.db.trait_environment(def.into()), + krate: self.krate, + }; + let self_ty = self.db.impl_self_ty(def).instantiate_identity().skip_norm_wip(); + let self_ty = structurally_normalize_ty(infcx, self_ty, env.param_env); + let tail_ty = struct_tail_raw(self.db, infcx.interner, self_ty, |ty| { + structurally_normalize_ty(infcx, ty, env.param_env) + }); + let self_ty_is_guaranteed_unsized = + matches!(tail_ty.kind(), TyKind::Dynamic(..) | TyKind::Slice(..) | TyKind::Str); + if self_ty_is_guaranteed_unsized { + missing.retain(|(_, assoc_item)| { + let assoc_item = match *assoc_item { + AssocItem::Function(it) => match it.id { + AnyFunctionId::FunctionId(id) => id.into(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => { + never!("should not have an `AnyFunctionId::BuiltinDeriveImplMethod` here"); + return false; + }, + }, + AssocItem::Const(it) => it.id.into(), + AssocItem::TypeAlias(it) => it.id.into(), + }; + !hir_ty::dyn_compatibility::generics_require_sized_self(self.db, assoc_item) + }); + } + } + + // HACK: When specialization is enabled in the current crate, and there exists + // *any* blanket impl that provides a default implementation for the missing item, + // suppress the missing associated item diagnostic. + // This can lead to false negatives when the impl in question does not actually + // specialize that blanket impl, but determining the exact specialization + // relationship here would be significantly more expensive. + if !missing.is_empty() { + let features = UnstableFeatures::query(self.db, self.krate); + if features.specialization || features.min_specialization { + missing.retain(|(assoc_name, assoc_item)| { + let AssocItem::Function(_) = assoc_item else { + return true; + }; + + for &impl_ in + TraitImpls::for_crate(self.db, self.krate).blanket_impls(trait_) + { + if impl_ == def { + continue; + } + + for (name, item) in &impl_.impl_items(self.db).items { + let AssocItemId::FunctionId(fn_) = item else { + continue; + }; + if name != assoc_name { + continue; + } + + if FunctionSignature::of(self.db, *fn_).is_default() { + return false; + } + } + } + + true + }); + } + } + + if !missing.is_empty() { + self.acc.push( + TraitImplMissingAssocItems { + impl_: ast_id_map.get(loc.id.value), + file_id, + missing, + } + .into(), + ) + } + impl_assoc_items_scratch.clear(); + } + } + + fn collect_module(&mut self, def: ModuleId) { + let _p = tracing::info_span!("diagnostics", name = ?def.name(self.db)).entered(); + + let def_map = def.def_map(self.db); + let scope = &def_map[def].scope; + + for diag in def_map.diagnostics() { + if diag.in_module != def { + // FIXME: This is accidentally quadratic. + continue; + } + self.emit_def_diagnostic(&diag.kind); + } + + if !def.is_block_module(self.db) { + // These are reported by the body of block modules + scope.all_macro_calls().for_each(|call| self.collect_macro_call(call)); + } + + scope + .declarations() + .chain(scope.unnamed_consts().map(ModuleDefId::ConstId)) + .for_each(|def| self.collect_module_def(def, def_map)); + + scope.legacy_macros().flat_map(|(_, it)| it).for_each(|&def| { + self.emit_case_diagnostics(def.into()); + self.collect_macro_def(def); + }); + + let interner = DbInterner::new_with(self.db, self.krate); + let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); + let mut impl_assoc_items_scratch = Vec::new(); + scope.impls().for_each(|def| { + impl_assoc_items_scratch.clear(); + self.collect_impl(def, &infcx, def_map, &mut impl_assoc_items_scratch) + }); + } + + fn collect_macro_def(&mut self, def: MacroId) { + let id = def.definition(self.db); + if let hir_expand::MacroDefKind::Declarative(ast, _) = id.kind + && let expander = ast.decl_macro_expander(self.db, id.krate) + && let Some(e) = expander.mac.err() + { + self.emit_def_diagnostic(&DefDiagnosticKind::MacroDefError { + ast, + message: e.to_string(), + }); + } + } + + fn collect_anon_const(&mut self, source_map: &ExpressionStoreSourceMap, def: AnonConstId<'db>) { + self.emit_inference_errors(def.into(), source_map, None, def.into()); + } + + fn collect_static(&mut self, def: StaticId) { + let (signature, signature_source_map) = StaticSignature::with_source_map(self.db, def); + self.collect_expr_store(&signature.store, signature_source_map); + + self.collect_def_with_body( + Some(signature_source_map), + def.into(), + TypeOwnerId::NoParams(self.krate), + ); + } + + fn collect_enum(&mut self, def: EnumId) { + self.collect_only_generic_def(def); + + let variants = def.enum_variants_with_diagnostics(self.db); + variants.0.variants.values().for_each(|&(def, _)| self.collect_enum_variant(def)); + + let file = def.lookup(self.db).id.file_id; + let ast_id_map = file.ast_id_map(self.db); + for diag in &variants.1 { + self.acc.push( + InactiveCode { + node: InFile::new(file, ast_id_map.get(diag.ast_id).syntax_node_ptr()), + cfg: diag.cfg.clone(), + opts: diag.opts.clone(), + } + .into(), + ); + } + } + + fn collect_enum_variant(&mut self, def: EnumVariantId) { + self.collect_def_with_body( + None, + def.into(), + TypeOwnerId::GenericDefId(def.loc(self.db).parent.into()), + ); + self.collect_variant(def.into()); + } + + fn collect_anon_consts_and_ty_diagnostics( + &mut self, + source_map: &ExpressionStoreSourceMap, + anon_consts: &[AnonConstId<'db>], + diagnostics: &[TyLoweringDiagnostic], + ) { + anon_consts.iter().for_each(|&anon_const| self.collect_anon_const(source_map, anon_const)); + + diagnostics + .iter() + .filter_map(|diag| AnyDiagnostic::ty_diagnostic(diag, source_map, self.db)) + .for_each(|diag| self.acc.push(diag)); + } + + fn collect_generic_def( + &mut self, + store: &ExpressionStore, + source_map: &ExpressionStoreSourceMap, + def: GenericDefId, + ) { + self.collect_expr_store(store, source_map); + for (anon_consts, diagnostics) in signature_anon_consts_and_diagnostics(self.db, def) { + self.collect_anon_consts_and_ty_diagnostics(source_map, anon_consts, diagnostics); + } + } + + fn collect_def_with_body( + &mut self, + sig_map: Option<&ExpressionStoreSourceMap>, + def: DefWithBodyId, + type_owner: TypeOwnerId<'db>, + ) { + let (body, source_map) = Body::with_source_map(self.db, def); + + self.collect_expr_store(body, source_map); + self.emit_inference_errors(def.into(), source_map, sig_map, type_owner); + + // FIXME: Missing unsafe and body validation should be defined for any `InferBodyId`. + let missing_unsafe = hir_ty::diagnostics::missing_unsafe(self.db, def); + for (node, reason) in missing_unsafe.unsafe_exprs { + match source_map.expr_or_pat_syntax(node) { + Ok(node) => self.acc.push( + MissingUnsafe { + node, + lint: if missing_unsafe.fn_is_unsafe { + UnsafeLint::UnsafeOpInUnsafeFn + } else { + UnsafeLint::HardError + }, + reason, + } + .into(), + ), + Err(SyntheticSyntax) => { + // FIXME: Here and elsewhere in this file, the `expr` was + // desugared, report or assert that this doesn't happen. + } + } + } + for node in missing_unsafe.deprecated_safe_calls { + match source_map.expr_syntax(node) { + Ok(node) => self.acc.push( + MissingUnsafe { + node, + lint: UnsafeLint::DeprecatedSafe2024, + reason: UnsafetyReason::UnsafeFnCall, + } + .into(), + ), + Err(SyntheticSyntax) => never!("synthetic DeprecatedSafe2024"), + } + } + + for diagnostic in BodyValidationDiagnostic::collect(self.db, def, self.style_lints) { + self.acc + .extend(AnyDiagnostic::body_validation_diagnostic(self.db, diagnostic, source_map)); + } + } + + fn emit_inference_errors( + &mut self, + def: InferBodyId<'db>, + source_map: &ExpressionStoreSourceMap, + sig_map: Option<&ExpressionStoreSourceMap>, + type_owner: TypeOwnerId<'db>, + ) { + let infer = InferenceResult::of(self.db, def); + + self.acc.extend(infer.diagnostics().iter().filter_map(|diag| { + AnyDiagnostic::inference_diagnostic( + self.db, + self.krate, + self.edition, + diag, + source_map, + sig_map, + type_owner, + ) + })); + } + + fn collect_variant(&mut self, def: VariantId) { + let (fields, source_map) = def.fields_with_source_map(self.db); + self.collect_expr_store(&fields.store, source_map); + + let lowering = self.db.field_types_with_diagnostics(def); + self.collect_anon_consts_and_ty_diagnostics( + source_map, + lowering.defined_anon_consts(), + lowering.diagnostics(), + ); + } + + fn collect_generic_def_with_body( + &mut self, + def: impl Into + Into + Copy, + ) { + let generic_def: GenericDefId = def.into(); + let (signature_store, signature_source_map) = + ExpressionStore::with_source_map(self.db, generic_def.into()); + self.collect_generic_def(signature_store, signature_source_map, generic_def); + + let def_with_body: DefWithBodyId = def.into(); + self.collect_def_with_body(Some(signature_source_map), def_with_body, generic_def.into()); + } + + fn collect_generic_variant(&mut self, def: impl Into + Into + Copy) { + let generic_def: GenericDefId = def.into(); + let (store, source_map) = ExpressionStore::with_source_map(self.db, generic_def.into()); + self.collect_generic_def(store, source_map, generic_def); + self.collect_variant(def.into()); + } + + fn collect_only_generic_def(&mut self, def: impl Into) { + let generic_def: GenericDefId = def.into(); + let (store, source_map) = ExpressionStore::with_source_map(self.db, generic_def.into()); + self.collect_generic_def(store, source_map, generic_def); + } + + fn collect_module_def(&mut self, def: ModuleDefId, def_map: &DefMap) { + self.emit_case_diagnostics(def); + + match def { + ModuleDefId::ModuleId(def) => { + // Only add diagnostics from inline modules + if def_map[def].origin.is_inline() { + self.collect_module(def); + } + } + ModuleDefId::TraitId(def) => self.collect_trait(def, def_map), + ModuleDefId::MacroId(def) => self.collect_macro_def(def), + ModuleDefId::FunctionId(def) => self.collect_generic_def_with_body(def), + ModuleDefId::ConstId(def) => self.collect_generic_def_with_body(def), + ModuleDefId::StaticId(def) => self.collect_static(def), + ModuleDefId::EnumVariantId(def) => self.collect_enum_variant(def), + ModuleDefId::AdtId(AdtId::StructId(def)) => self.collect_generic_variant(def), + ModuleDefId::AdtId(AdtId::UnionId(def)) => self.collect_generic_variant(def), + ModuleDefId::AdtId(AdtId::EnumId(def)) => self.collect_enum(def), + ModuleDefId::TypeAliasId(def) => self.collect_only_generic_def(def), + ModuleDefId::BuiltinType(_) => {} + } + } + + fn collect_expr_store( + &mut self, + store: &ExpressionStore, + source_map: &ExpressionStoreSourceMap, + ) { + for (_, def_map) in store.blocks(self.db) { + self.collect_module(def_map.root_module_id()); + } + + for diag in source_map.diagnostics() { + self.acc.push(match diag { + ExpressionStoreDiagnostics::InactiveCode { node, cfg, opts } => { + InactiveCode { node: *node, cfg: cfg.clone(), opts: opts.clone() }.into() + } + ExpressionStoreDiagnostics::UnresolvedMacroCall { node, path } => { + UnresolvedMacroCall { + range: node.map(|ptr| ptr.text_range()), + path: path.clone(), + is_bang: true, + } + .into() + } + ExpressionStoreDiagnostics::AwaitOutsideOfAsync { node, location } => { + AwaitOutsideOfAsync { node: *node, location: location.clone() }.into() + } + ExpressionStoreDiagnostics::UnreachableLabel { node, name } => { + UnreachableLabel { node: *node, name: name.clone() }.into() + } + ExpressionStoreDiagnostics::UndeclaredLabel { node, name } => { + UndeclaredLabel { node: *node, name: name.clone() }.into() + } + ExpressionStoreDiagnostics::PatternArgInExternFn { node } => { + PatternArgInExternFn { node: *node }.into() + } + ExpressionStoreDiagnostics::FruInDestructuringAssignment { node } => { + FruInDestructuringAssignment { node: *node }.into() + } + }); + } + + source_map.macro_calls().for_each(|(_ast_id, call_id)| self.collect_macro_call(call_id)); + } +} + impl<'db> AnyDiagnostic<'db> { - pub(crate) fn body_validation_diagnostic( + fn body_validation_diagnostic( db: &'db dyn HirDatabase, diagnostic: BodyValidationDiagnostic<'db>, source_map: &hir_def::expr_store::BodySourceMap, @@ -814,12 +1551,13 @@ impl<'db> AnyDiagnostic<'db> { None } - pub(crate) fn inference_diagnostic( + fn inference_diagnostic( db: &'db dyn HirDatabase, - def: DefWithBodyId, + krate: base_db::Crate, + edition: Edition, d: &'db InferenceDiagnostic, - source_map: &hir_def::expr_store::BodySourceMap, - sig_map: &hir_def::expr_store::ExpressionStoreSourceMap, + source_map: &ExpressionStoreSourceMap, + sig_map: Option<&ExpressionStoreSourceMap>, type_owner: TypeOwnerId<'db>, ) -> Option> { let expr_syntax = |expr| Self::expr_syntax(expr, source_map); @@ -1012,7 +1750,7 @@ impl<'db> AnyDiagnostic<'db> { InferenceDiagnostic::TyDiagnostic { source, diag } => { let source_map = match source { InferenceTyDiagnosticSource::Body => source_map, - InferenceTyDiagnosticSource::Signature => sig_map, + InferenceTyDiagnosticSource::Signature => sig_map.expect("cannot have `InferenceTyDiagnosticSource::Signature` when there is no signature"), }; Self::ty_diagnostic(diag, source_map, db)? } @@ -1085,7 +1823,9 @@ impl<'db> AnyDiagnostic<'db> { rustc_type_ir::GenericArgKind::Type(ty) => Either::Left(new_ty(ty)), // FIXME: Printing the const to string is definitely not the correct thing to do here. rustc_type_ir::GenericArgKind::Const(konst) => Either::Right( - konst.display(db, DisplayTarget::from_crate(db, def.krate(db))).to_string(), + konst + .display(db, DisplayTarget::from_crate_and_edition(db, krate, edition)) + .to_string(), ), rustc_type_ir::GenericArgKind::Lifetime(_) => { unreachable!("we currently don't emit TypeMustBeKnown for lifetimes") @@ -1365,7 +2105,7 @@ impl<'db> AnyDiagnostic<'db> { }) } - pub(crate) fn ty_diagnostic( + fn ty_diagnostic( diag: &TyLoweringDiagnostic, source_map: &ExpressionStoreSourceMap, db: &'db dyn HirDatabase, diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 9238cdcb3ef85..34b9ede4985a6 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -41,7 +41,6 @@ pub use hir_def::ModuleId; use std::{ borrow::Borrow, fmt, iter, - mem::discriminant, ops::{ControlFlow, Not}, }; @@ -52,11 +51,11 @@ use hir_def::{ AdtId, AssocItemId, AssocItemLoc, BuiltinDeriveImplId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExpressionStoreOwnerId, ExternBlockId, ExternCrateId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalFieldId, - Lookup, MacroExpander, MacroId, StaticId, StructId, SyntheticSyntax, TupleId, TypeAliasId, - TypeOrConstParamId, TypeParamId, UnionId, + Lookup, MacroExpander, MacroId, StaticId, StructId, TupleId, TypeAliasId, TypeOrConstParamId, + TypeParamId, UnionId, attrs::AttrFlags, builtin_derive::BuiltinDeriveImplMethod, - expr_store::{ExpressionStore, ExpressionStoreDiagnostics, ExpressionStoreSourceMap}, + expr_store::ExpressionStore, hir::{ BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, LabelId, Pat, generics::{GenericParams, LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, @@ -64,10 +63,6 @@ use hir_def::{ item_tree::ImportAlias, lang_item::LangItemTarget, layout::{self, ReprOptions, TargetDataLayout}, - nameres::{ - assoc::TraitItems, - diagnostics::{DefDiagnostic, DefDiagnosticKind}, - }, per_ns::PerNs, resolver::{HasResolver, Resolver}, signatures::{ @@ -79,19 +74,15 @@ use hir_def::{ unstable_features::UnstableFeatures, visibility::visibility_from_ast, }; -use hir_expand::{ - AstId, MacroCallKind, RenderedExpandError, ValueResult, builtin::BuiltinDeriveExpander, - proc_macro::ProcMacroKind, -}; +use hir_expand::{builtin::BuiltinDeriveExpander, proc_macro::ProcMacroKind}; use hir_ty::{ - GenericPredicates, InferBodyId, InferenceResult, ParamEnvAndCrate, TyDefId, - TyLoweringDiagnostic, ValueTyDefId, all_super_traits, autoderef, check_orphan_rules, + GenericPredicates, InferBodyId, InferenceResult, ParamEnvAndCrate, TyDefId, ValueTyDefId, + all_super_traits, autoderef, check_orphan_rules, consteval::try_const_usize, db::{ AnonConstId, InternedClosure, InternedClosureId, InternedCoroutineClosureId, InternedCoroutineId, }, - diagnostics::BodyValidationDiagnostic, direct_super_traits, known_const_to_ast, layout::{Layout as TyLayout, RustcEnumVariantIdx, RustcFieldIdx, TagEncoding}, method_resolution::{self, InherentImpls, MethodResolutionContext}, @@ -101,7 +92,7 @@ use hir_ty::{ GenericArg, GenericArgs, ParamEnv, PolyFnSig, Region, SolverDefId, Ty, TyKind, TypingMode, infer::{DbInternerInferExt, InferCtxt}, }, - traits::{self, is_inherent_impl_coherent, structurally_normalize_ty}, + traits::{self, structurally_normalize_ty}, }; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; @@ -466,50 +457,6 @@ impl ModuleDef { Some(name) } - pub fn diagnostics<'db>( - self, - db: &'db dyn HirDatabase, - style_lints: bool, - ) -> Vec> { - let id = match self { - ModuleDef::Adt(it) => match it { - Adt::Struct(it) => it.id.into(), - Adt::Enum(it) => it.id.into(), - Adt::Union(it) => it.id.into(), - }, - ModuleDef::Trait(it) => it.id.into(), - ModuleDef::Function(it) => match it.id { - AnyFunctionId::FunctionId(it) => it.into(), - AnyFunctionId::BuiltinDeriveImplMethod { .. } => return Vec::new(), - }, - ModuleDef::TypeAlias(it) => it.id.into(), - ModuleDef::Module(it) => it.id.into(), - ModuleDef::Const(it) => it.id.into(), - ModuleDef::Static(it) => it.id.into(), - ModuleDef::EnumVariant(it) => it.id.into(), - ModuleDef::BuiltinType(_) | ModuleDef::Macro(_) => return Vec::new(), - }; - - let mut acc = Vec::new(); - - match self.as_def_with_body() { - Some(def) => { - def.diagnostics(db, &mut acc, style_lints); - } - None => { - for diag in hir_ty::diagnostics::incorrect_case(db, id) { - acc.push(diag.into()) - } - } - } - - if let Some(def) = self.as_self_generic_def() { - def.diagnostics(db, &mut acc); - } - - acc - } - pub fn as_def_with_body(self) -> Option { match self { ModuleDef::Function(it) => Some(it.into()), @@ -749,341 +696,7 @@ impl Module { acc: &mut Vec>, style_lints: bool, ) { - let _p = tracing::info_span!("diagnostics", name = ?self.name(db)).entered(); - let edition = self.id.krate(db).data(db).edition; - let def_map = self.id.def_map(db); - for diag in def_map.diagnostics() { - if diag.in_module != self.id { - // FIXME: This is accidentally quadratic. - continue; - } - emit_def_diagnostic(db, acc, diag, edition, def_map.krate()); - } - - if !self.id.is_block_module(db) { - // These are reported by the body of block modules - let scope = &def_map[self.id].scope; - scope.all_macro_calls().for_each(|it| macro_call_diagnostics(db, it, acc)); - } - - for def in self.declarations(db) { - match def { - ModuleDef::Module(m) => { - // Only add diagnostics from inline modules - if def_map[m.id].origin.is_inline() { - m.diagnostics(db, acc, style_lints) - } - acc.extend(def.diagnostics(db, style_lints)) - } - ModuleDef::Trait(t) => { - let krate = t.krate(db); - for diag in TraitItems::query_with_diagnostics(db, t.id).1.iter() { - emit_def_diagnostic(db, acc, diag, edition, krate.id); - } - - for item in t.items(db) { - item.diagnostics(db, acc, style_lints); - } - - t.all_macro_calls(db) - .iter() - .for_each(|&(_ast, call_id)| macro_call_diagnostics(db, call_id, acc)); - - acc.extend(def.diagnostics(db, style_lints)) - } - ModuleDef::Adt(adt) => { - match adt { - Adt::Struct(s) => { - let source_map = &StructSignature::with_source_map(db, s.id).1; - expr_store_diagnostics(db, acc, source_map); - let source_map = &s.id.fields_with_source_map(db).1; - expr_store_diagnostics(db, acc, source_map); - push_ty_diagnostics( - db, - acc, - db.field_types_with_diagnostics(s.id.into()).diagnostics(), - source_map, - ); - } - Adt::Union(u) => { - let source_map = &UnionSignature::with_source_map(db, u.id).1; - expr_store_diagnostics(db, acc, source_map); - let source_map = &u.id.fields_with_source_map(db).1; - expr_store_diagnostics(db, acc, source_map); - push_ty_diagnostics( - db, - acc, - db.field_types_with_diagnostics(u.id.into()).diagnostics(), - source_map, - ); - } - Adt::Enum(e) => { - let source_map = &EnumSignature::with_source_map(db, e.id).1; - expr_store_diagnostics(db, acc, source_map); - let (variants, diagnostics) = e.id.enum_variants_with_diagnostics(db); - let file = e.id.lookup(db).id.file_id; - let ast_id_map = file.ast_id_map(db); - for diag in diagnostics { - acc.push( - InactiveCode { - node: InFile::new( - file, - ast_id_map.get(diag.ast_id).syntax_node_ptr(), - ), - cfg: diag.cfg.clone(), - opts: diag.opts.clone(), - } - .into(), - ); - } - for &(v, _) in variants.variants.values() { - let source_map = &v.fields_with_source_map(db).1; - push_ty_diagnostics( - db, - acc, - db.field_types_with_diagnostics(v.into()).diagnostics(), - source_map, - ); - expr_store_diagnostics(db, acc, source_map); - } - } - } - acc.extend(def.diagnostics(db, style_lints)) - } - ModuleDef::Macro(m) => emit_macro_def_diagnostics(db, acc, m), - ModuleDef::TypeAlias(type_alias) => { - let source_map = &TypeAliasSignature::with_source_map(db, type_alias.id).1; - expr_store_diagnostics(db, acc, source_map); - push_ty_diagnostics( - db, - acc, - db.type_for_type_alias_with_diagnostics(type_alias.id).diagnostics(), - source_map, - ); - acc.extend(def.diagnostics(db, style_lints)); - } - _ => acc.extend(def.diagnostics(db, style_lints)), - } - } - self.legacy_macros(db).into_iter().for_each(|m| emit_macro_def_diagnostics(db, acc, m)); - - let interner = DbInterner::new_with(db, self.id.krate(db)); - let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); - - let mut impl_assoc_items_scratch = vec![]; - for impl_def in self.impl_defs(db) { - GenericDef::Impl(impl_def).diagnostics(db, acc); - - let AnyImplId::ImplId(impl_id) = impl_def.id else { - continue; - }; - let loc = impl_id.lookup(db); - let (impl_signature, source_map) = ImplSignature::with_source_map(db, impl_id); - expr_store_diagnostics(db, acc, source_map); - - let file_id = loc.id.file_id; - if file_id.macro_file().is_some_and(|it| it.kind(db) == MacroKind::DeriveBuiltIn) { - // these expansion come from us, diagnosing them is a waste of resources - // FIXME: Once we diagnose the inputs to builtin derives, we should at least extract those diagnostics somehow - continue; - } - impl_def - .all_macro_calls(db) - .iter() - .for_each(|&(_ast, call_id)| macro_call_diagnostics(db, call_id, acc)); - - let ast_id_map = file_id.ast_id_map(db); - - for diag in impl_id.impl_items_with_diagnostics(db).1.iter() { - emit_def_diagnostic(db, acc, diag, edition, loc.container.krate(db)); - } - - let trait_impl = impl_signature.target_trait.is_some(); - if !trait_impl && !is_inherent_impl_coherent(db, def_map, impl_id) { - acc.push(IncoherentImpl { impl_: ast_id_map.get(loc.id.value), file_id }.into()) - } - - if trait_impl && !impl_def.check_orphan_rules(db) { - acc.push(TraitImplOrphan { impl_: ast_id_map.get(loc.id.value), file_id }.into()) - } - - let trait_ = trait_impl.then(|| impl_def.trait_(db)).flatten(); - let mut trait_is_unsafe = trait_.is_some_and(|t| t.is_unsafe(db)); - let impl_is_negative = impl_def.is_negative(db); - let impl_is_unsafe = impl_def.is_unsafe(db); - - let trait_is_unresolved = trait_.is_none() && trait_impl; - if trait_is_unresolved { - // Ignore trait safety errors when the trait is unresolved, as otherwise we'll treat it as safe, - // which may not be correct. - trait_is_unsafe = impl_is_unsafe; - } - - let drop_maybe_dangle = (|| { - let trait_ = trait_?; - let drop_trait = interner.lang_items().Drop?; - if drop_trait != trait_.into() { - return None; - } - let parent = impl_id.into(); - let (lifetimes_attrs, type_and_consts_attrs) = - AttrFlags::query_generic_params(db, parent); - let res = lifetimes_attrs.values().any(|it| it.contains(AttrFlags::MAY_DANGLE)) - || type_and_consts_attrs.values().any(|it| it.contains(AttrFlags::MAY_DANGLE)); - Some(res) - })() - .unwrap_or(false); - - match (impl_is_unsafe, trait_is_unsafe, impl_is_negative, drop_maybe_dangle) { - // unsafe negative impl - (true, _, true, _) | - // unsafe impl for safe trait - (true, false, _, false) => acc.push(TraitImplIncorrectSafety { impl_: ast_id_map.get(loc.id.value), file_id, should_be_safe: true }.into()), - // safe impl for unsafe trait - (false, true, false, _) | - // safe impl of dangling drop - (false, false, _, true) => acc.push(TraitImplIncorrectSafety { impl_: ast_id_map.get(loc.id.value), file_id, should_be_safe: false }.into()), - _ => (), - }; - - // Negative impls can't have items, don't emit missing items diagnostic for them - if let (false, Some(trait_)) = (impl_is_negative, trait_) { - let items = &trait_.id.trait_items(db).items; - let required_items = items.iter().filter(|&(_, assoc)| match *assoc { - AssocItemId::FunctionId(it) => !FunctionSignature::of(db, it).has_body(), - AssocItemId::ConstId(id) => !ConstSignature::of(db, id).has_body(), - AssocItemId::TypeAliasId(it) => TypeAliasSignature::of(db, it).ty.is_none(), - }); - impl_assoc_items_scratch.extend(impl_id.impl_items(db).items.iter().cloned()); - - let redundant = impl_assoc_items_scratch - .iter() - .filter(|(name, id)| { - !items.iter().any(|(impl_name, impl_item)| { - discriminant(impl_item) == discriminant(id) && impl_name == name - }) - }) - .map(|(name, item)| (name.clone(), AssocItem::from(*item))); - for (name, assoc_item) in redundant { - acc.push( - TraitImplRedundantAssocItems { - trait_, - file_id, - impl_: ast_id_map.get(loc.id.value), - assoc_item: (name, assoc_item), - } - .into(), - ) - } - - let mut missing: Vec<_> = required_items - .filter(|(name, id)| { - !impl_assoc_items_scratch.iter().any(|(impl_name, impl_item)| { - discriminant(impl_item) == discriminant(id) && impl_name == name - }) - }) - .map(|(name, item)| (name.clone(), AssocItem::from(*item))) - .collect(); - - if !missing.is_empty() { - let env = ParamEnvAndCrate { - param_env: db.trait_environment(GenericDefId::from(impl_id)), - krate: self.id.krate(db), - }; - let self_ty = db.impl_self_ty(impl_id).instantiate_identity().skip_norm_wip(); - let self_ty = structurally_normalize_ty(&infcx, self_ty, env.param_env); - let tail_ty = struct_tail_raw(db, interner, self_ty, |ty| { - structurally_normalize_ty(&infcx, ty, env.param_env) - }); - let self_ty_is_guaranteed_unsized = matches!( - tail_ty.kind(), - TyKind::Dynamic(..) | TyKind::Slice(..) | TyKind::Str - ); - if self_ty_is_guaranteed_unsized { - missing.retain(|(_, assoc_item)| { - let assoc_item = match *assoc_item { - AssocItem::Function(it) => match it.id { - AnyFunctionId::FunctionId(id) => id.into(), - AnyFunctionId::BuiltinDeriveImplMethod { .. } => { - never!("should not have an `AnyFunctionId::BuiltinDeriveImplMethod` here"); - return false; - }, - }, - AssocItem::Const(it) => it.id.into(), - AssocItem::TypeAlias(it) => it.id.into(), - }; - !hir_ty::dyn_compatibility::generics_require_sized_self(db, assoc_item) - }); - } - } - - // HACK: When specialization is enabled in the current crate, and there exists - // *any* blanket impl that provides a default implementation for the missing item, - // suppress the missing associated item diagnostic. - // This can lead to false negatives when the impl in question does not actually - // specialize that blanket impl, but determining the exact specialization - // relationship here would be significantly more expensive. - if !missing.is_empty() { - let krate = self.krate(db).id; - let features = UnstableFeatures::query(db, krate); - if features.specialization || features.min_specialization { - missing.retain(|(assoc_name, assoc_item)| { - let AssocItem::Function(_) = assoc_item else { - return true; - }; - - for &impl_ in TraitImpls::for_crate(db, krate).blanket_impls(trait_.id) - { - if impl_ == impl_id { - continue; - } - - for (name, item) in &impl_.impl_items(db).items { - let AssocItemId::FunctionId(fn_) = item else { - continue; - }; - if name != assoc_name { - continue; - } - - if FunctionSignature::of(db, *fn_).is_default() { - return false; - } - } - } - - true - }); - } - } - - if !missing.is_empty() { - acc.push( - TraitImplMissingAssocItems { - impl_: ast_id_map.get(loc.id.value), - file_id, - missing, - } - .into(), - ) - } - impl_assoc_items_scratch.clear(); - } - - push_ty_diagnostics( - db, - acc, - db.impl_self_ty_with_diagnostics(impl_id).diagnostics(), - source_map, - ); - if let Some(it) = db.impl_trait_with_diagnostics(impl_id) { - push_ty_diagnostics(db, acc, it.diagnostics(), source_map); - } - - for &(_, item) in impl_id.impl_items(db).items.iter() { - AssocItem::from(item).diagnostics(db, acc, style_lints); - } - } + crate::diagnostics::DiagnosticsCollector::collect(db, self.id, acc, style_lints); } pub fn declarations(self, db: &dyn HirDatabase) -> Vec { @@ -1157,201 +770,6 @@ impl Module { } } -fn macro_call_diagnostics<'db>( - db: &'db dyn HirDatabase, - macro_call_id: MacroCallId, - acc: &mut Vec>, -) { - let Some(e) = macro_call_id.parse_macro_expansion_error(db) else { - return; - }; - let ValueResult { value: parse_errors, err } = e; - if let Some(err) = err { - let loc = macro_call_id.loc(db); - let file_id = loc.kind.file_id(); - let mut range = precise_macro_call_location(&loc.kind, db, loc.krate); - let RenderedExpandError { message, error, kind } = err.render_to_string(db); - if Some(err.span().anchor.file_id) == file_id.file_id().map(|it| it.span_file_id(db)) { - range.value = err.span().range - + file_id.ast_id_map(db).get_erased(err.span().anchor.ast_id).text_range().start(); - } - acc.push(MacroError { range, message, error, kind }.into()); - } - - if !parse_errors.is_empty() { - let loc = macro_call_id.loc(db); - let range = precise_macro_call_location(&loc.kind, db, loc.krate); - acc.push(MacroExpansionParseError { range, errors: parse_errors.clone() }.into()) - } -} - -fn emit_macro_def_diagnostics<'db>( - db: &'db dyn HirDatabase, - acc: &mut Vec>, - m: Macro, -) { - let id = m.id.definition(db); - let krate = id.krate; - if let hir_expand::MacroDefKind::Declarative(ast, _) = id.kind - && let expander = ast.decl_macro_expander(db, krate) - && let Some(e) = expander.mac.err() - { - let edition = krate.data(db).edition; - emit_def_diagnostic_( - db, - acc, - &DefDiagnosticKind::MacroDefError { ast, message: e.to_string() }, - edition, - krate, - ); - } -} - -fn emit_def_diagnostic<'db>( - db: &'db dyn HirDatabase, - acc: &mut Vec>, - diag: &DefDiagnostic, - edition: Edition, - krate: base_db::Crate, -) { - emit_def_diagnostic_(db, acc, &diag.kind, edition, krate) -} - -fn emit_def_diagnostic_<'db>( - db: &'db dyn HirDatabase, - acc: &mut Vec>, - diag: &DefDiagnosticKind, - edition: Edition, - krate: base_db::Crate, -) { - match diag { - DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates } => { - let decl = declaration.to_ptr(db); - acc.push( - UnresolvedModule { - decl: InFile::new(declaration.file_id, decl), - candidates: candidates.clone(), - } - .into(), - ) - } - DefDiagnosticKind::UnresolvedExternCrate { ast } => { - let item = ast.to_ptr(db); - acc.push(UnresolvedExternCrate { decl: InFile::new(ast.file_id, item) }.into()); - } - - DefDiagnosticKind::MacroError { ast, path, err } => { - let item = ast.to_ptr(db); - let RenderedExpandError { message, error, kind } = err.render_to_string(db); - acc.push( - MacroError { - range: InFile::new(ast.file_id, item.text_range()), - message: format!("{}: {message}", path.display(db, edition)), - error, - kind, - } - .into(), - ) - } - DefDiagnosticKind::UnresolvedImport { id, index } => { - let file_id = id.file_id; - - let use_tree = hir_def::src::use_tree_to_ast(db, *id, *index); - acc.push( - UnresolvedImport { decl: InFile::new(file_id, AstPtr::new(&use_tree)) }.into(), - ); - } - - DefDiagnosticKind::UnconfiguredCode { ast_id, cfg, opts } => { - let ast_id_map = ast_id.file_id.ast_id_map(db); - let ptr = ast_id_map.get_erased(ast_id.value); - acc.push( - InactiveCode { - node: InFile::new(ast_id.file_id, ptr), - cfg: cfg.clone(), - opts: opts.clone(), - } - .into(), - ); - } - DefDiagnosticKind::UnresolvedMacroCall { ast, path } => { - let location = precise_macro_call_location(ast, db, krate); - acc.push( - UnresolvedMacroCall { - range: location, - path: path.clone(), - is_bang: matches!(ast, MacroCallKind::FnLike { .. }), - } - .into(), - ); - } - DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => { - let node = ast.to_node(db); - // Must have a name, otherwise we wouldn't emit it. - let name = node.name().expect("unimplemented builtin macro with no name"); - acc.push( - UnimplementedBuiltinMacro { - node: ast.with_value(SyntaxNodePtr::from(AstPtr::new(&name))), - } - .into(), - ); - } - DefDiagnosticKind::InvalidDeriveTarget { ast, id } => { - let (_, attr) = id.find_attr_range(db, krate, *ast); - let derive = attr - .path() - .map(|path| path.syntax().text_range()) - .unwrap_or_else(|| attr.syntax().text_range()); - acc.push(InvalidDeriveTarget { range: ast.with_value(derive) }.into()); - } - DefDiagnosticKind::MalformedDerive { ast, id } => { - let derive = id.find_attr_range(db, krate, *ast).1.syntax().text_range(); - acc.push(MalformedDerive { range: ast.with_value(derive) }.into()); - } - DefDiagnosticKind::MacroDefError { ast, message } => { - let node = ast.to_node(db); - acc.push( - MacroDefError { - node: InFile::new(ast.file_id, AstPtr::new(&node)), - name: node.name().map(|it| it.syntax().text_range()), - message: message.clone(), - } - .into(), - ); - } - } -} - -fn precise_macro_call_location( - ast: &MacroCallKind, - db: &dyn HirDatabase, - krate: base_db::Crate, -) -> InFile { - // FIXME: maybe we actually want slightly different ranges for the different macro diagnostics - // - e.g. the full attribute for macro errors, but only the name for name resolution - match ast { - MacroCallKind::FnLike { ast_id, .. } => { - let node = ast_id.to_node(db); - let range = node - .path() - .and_then(|it| it.segment()) - .and_then(|it| it.name_ref()) - .map(|it| it.syntax().text_range()); - let range = range.unwrap_or_else(|| node.syntax().text_range()); - ast_id.with_value(range) - } - MacroCallKind::Derive { ast_id, derive_attr_index, derive_index, .. } => { - let range = derive_attr_index.find_derive_range(db, krate, *ast_id, *derive_index); - ast_id.with_value(range) - } - MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { - let attr_range = - attr_ids.invoc_attr().find_attr_range(db, krate, *ast_id).1.syntax().text_range(); - ast_id.with_value(attr_range) - } - } -} - impl HasVisibility for Module { fn visibility(&self, db: &dyn HirDatabase) -> Visibility { let def_map = self.id.def_map(db); @@ -2020,93 +1438,6 @@ impl DefWithBody { } } - pub fn diagnostics<'db>( - self, - db: &'db dyn HirDatabase, - acc: &mut Vec>, - style_lints: bool, - ) { - let Ok(id) = self.try_into() else { - return; - }; - - let (body, source_map) = Body::with_source_map(db, id); - let sig_source_map = match self { - DefWithBody::Function(id) => match id.id { - AnyFunctionId::FunctionId(id) => &FunctionSignature::with_source_map(db, id).1, - AnyFunctionId::BuiltinDeriveImplMethod { .. } => return, - }, - DefWithBody::Static(id) => &StaticSignature::with_source_map(db, id.into()).1, - DefWithBody::Const(id) => &ConstSignature::with_source_map(db, id.into()).1, - DefWithBody::EnumVariant(variant) => { - let enum_id = variant.parent_enum(db).id; - &EnumSignature::with_source_map(db, enum_id).1 - } - }; - - for (_, def_map) in body.blocks(db) { - Module { id: def_map.root_module_id() }.diagnostics(db, acc, style_lints); - } - - expr_store_diagnostics(db, acc, source_map); - - let infer = InferenceResult::of(db, id); - let type_owner = id.generic_def(db).into(); - for d in infer.diagnostics() { - acc.extend(AnyDiagnostic::inference_diagnostic( - db, - id, - d, - source_map, - sig_source_map, - type_owner, - )); - } - - let missing_unsafe = hir_ty::diagnostics::missing_unsafe(db, id); - for (node, reason) in missing_unsafe.unsafe_exprs { - match source_map.expr_or_pat_syntax(node) { - Ok(node) => acc.push( - MissingUnsafe { - node, - lint: if missing_unsafe.fn_is_unsafe { - UnsafeLint::UnsafeOpInUnsafeFn - } else { - UnsafeLint::HardError - }, - reason, - } - .into(), - ), - Err(SyntheticSyntax) => { - // FIXME: Here and elsewhere in this file, the `expr` was - // desugared, report or assert that this doesn't happen. - } - } - } - for node in missing_unsafe.deprecated_safe_calls { - match source_map.expr_syntax(node) { - Ok(node) => acc.push( - MissingUnsafe { - node, - lint: UnsafeLint::DeprecatedSafe2024, - reason: UnsafetyReason::UnsafeFnCall, - } - .into(), - ), - Err(SyntheticSyntax) => never!("synthetic DeprecatedSafe2024"), - } - } - - for diagnostic in BodyValidationDiagnostic::collect(db, id, style_lints) { - acc.extend(AnyDiagnostic::body_validation_diagnostic(db, diagnostic, source_map)); - } - - for diag in hir_ty::diagnostics::incorrect_case(db, id.into()) { - acc.push(diag.into()) - } - } - /// Returns an iterator over the inferred types of all expressions in this body. pub fn expression_types<'db>( self, @@ -2141,45 +1472,6 @@ impl DefWithBody { } } -fn expr_store_diagnostics<'db>( - db: &'db dyn HirDatabase, - acc: &mut Vec>, - source_map: &ExpressionStoreSourceMap, -) { - for diag in source_map.diagnostics() { - acc.push(match diag { - ExpressionStoreDiagnostics::InactiveCode { node, cfg, opts } => { - InactiveCode { node: *node, cfg: cfg.clone(), opts: opts.clone() }.into() - } - ExpressionStoreDiagnostics::UnresolvedMacroCall { node, path } => UnresolvedMacroCall { - range: node.map(|ptr| ptr.text_range()), - path: path.clone(), - is_bang: true, - } - .into(), - ExpressionStoreDiagnostics::AwaitOutsideOfAsync { node, location } => { - AwaitOutsideOfAsync { node: *node, location: location.clone() }.into() - } - ExpressionStoreDiagnostics::UnreachableLabel { node, name } => { - UnreachableLabel { node: *node, name: name.clone() }.into() - } - ExpressionStoreDiagnostics::UndeclaredLabel { node, name } => { - UndeclaredLabel { node: *node, name: name.clone() }.into() - } - ExpressionStoreDiagnostics::PatternArgInExternFn { node } => { - PatternArgInExternFn { node: *node }.into() - } - ExpressionStoreDiagnostics::FruInDestructuringAssignment { node } => { - FruInDestructuringAssignment { node: *node }.into() - } - }); - } - - source_map - .macro_calls() - .for_each(|(_ast_id, call_id)| macro_call_diagnostics(db, call_id, acc)); -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum AnyFunctionId { FunctionId(FunctionId), @@ -3004,10 +2296,6 @@ impl Trait { violations.is_empty().not().then_some(violations) } - fn all_macro_calls(&self, db: &dyn HirDatabase) -> Box<[(AstId, MacroCallId)]> { - self.id.trait_items(db).macro_calls.to_vec().into_boxed_slice() - } - /// `#[rust_analyzer::completions(...)]` mode. pub fn complete(self, db: &dyn HirDatabase) -> Complete { Complete::extract(true, self.attrs(db).attrs) @@ -3691,36 +2979,6 @@ impl AssocItem { _ => None, } } - - pub fn diagnostics<'db>( - self, - db: &'db dyn HirDatabase, - acc: &mut Vec>, - style_lints: bool, - ) { - match self { - AssocItem::Function(func) => { - GenericDef::Function(func).diagnostics(db, acc); - DefWithBody::from(func).diagnostics(db, acc, style_lints); - } - AssocItem::Const(const_) => { - GenericDef::Const(const_).diagnostics(db, acc); - DefWithBody::from(const_).diagnostics(db, acc, style_lints); - } - AssocItem::TypeAlias(type_alias) => { - GenericDef::TypeAlias(type_alias).diagnostics(db, acc); - push_ty_diagnostics( - db, - acc, - db.type_for_type_alias_with_diagnostics(type_alias.id).diagnostics(), - &TypeAliasSignature::with_source_map(db, type_alias.id).1, - ); - for diag in hir_ty::diagnostics::incorrect_case(db, type_alias.id.into()) { - acc.push(diag.into()); - } - } - } - } } impl HasVisibility for AssocItem { @@ -3846,48 +3104,6 @@ impl GenericDef { }) } - pub fn diagnostics<'db>(self, db: &'db dyn HirDatabase, acc: &mut Vec>) { - let Some(def) = self.id() else { return }; - - let generics = GenericParams::of(db, def); - - if generics.is_empty() && generics.has_no_predicates() { - return; - } - - let source_map = match def { - GenericDefId::AdtId(AdtId::EnumId(it)) => &EnumSignature::with_source_map(db, it).1, - GenericDefId::AdtId(AdtId::StructId(it)) => &StructSignature::with_source_map(db, it).1, - GenericDefId::AdtId(AdtId::UnionId(it)) => &UnionSignature::with_source_map(db, it).1, - GenericDefId::ConstId(_) => return, - GenericDefId::FunctionId(it) => &FunctionSignature::with_source_map(db, it).1, - GenericDefId::ImplId(it) => &ImplSignature::with_source_map(db, it).1, - GenericDefId::StaticId(_) => return, - GenericDefId::TraitId(it) => &TraitSignature::with_source_map(db, it).1, - GenericDefId::TypeAliasId(it) => &TypeAliasSignature::with_source_map(db, it).1, - }; - - expr_store_diagnostics(db, acc, source_map); - push_ty_diagnostics( - db, - acc, - db.generic_defaults_with_diagnostics(def).diagnostics(), - source_map, - ); - push_ty_diagnostics( - db, - acc, - GenericPredicates::query_with_diagnostics(db, def).diagnostics(), - source_map, - ); - push_ty_diagnostics( - db, - acc, - db.const_param_types_with_diagnostics(def).diagnostics(), - source_map, - ); - } - /// Returns a string describing the kind of this type. #[inline] pub fn description(self) -> &'static str { @@ -4745,13 +3961,6 @@ impl Impl { AnyImplId::BuiltinDeriveImplId(_) => true, } } - - fn all_macro_calls(&self, db: &dyn HirDatabase) -> Box<[(AstId, MacroCallId)]> { - match self.id { - AnyImplId::ImplId(id) => id.impl_items(db).macro_calls.to_vec().into_boxed_slice(), - AnyImplId::BuiltinDeriveImplId(_) => Box::default(), - } - } } #[derive(Clone, PartialEq, Eq, Debug, Hash)] @@ -7248,19 +6457,6 @@ pub enum DocLinkDef { SelfType(Trait), } -fn push_ty_diagnostics<'db>( - db: &'db dyn HirDatabase, - acc: &mut Vec>, - diagnostics: &[TyLoweringDiagnostic], - source_map: &ExpressionStoreSourceMap, -) { - acc.extend( - diagnostics - .iter() - .filter_map(|diagnostic| AnyDiagnostic::ty_diagnostic(diagnostic, source_map, db)), - ); -} - pub trait MethodCandidateCallback { fn on_inherent_method(&mut self, f: Function) -> ControlFlow<()>; @@ -7409,7 +6605,7 @@ impl MacroCallIdExt for span::MacroCallId { } // Like https://github.com/rust-lang/rust/blob/7c3c88f42ad444f4688b865591d84660be4ece2f/compiler/rustc_middle/src/ty/util.rs#L254-L310 -pub fn struct_tail_raw<'db>( +fn struct_tail_raw<'db>( db: &'db dyn HirDatabase, interner: DbInterner<'db>, mut ty: Ty<'db>, diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 4c4167a41de65..49055172a1552 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -2187,13 +2187,9 @@ impl<'db> SemanticsImpl<'db> { def: DefWithoutBodyWithAnonConsts, ) -> &'a ExprToAnonConst<'db> { cache.entry(def).or_insert_with(|| match def { - Either::Left(def) => { - let all_anon_consts = - AnonConstId::all_from_signature(self.db, def).into_iter().flatten().copied(); - all_anon_consts - .map(|anon_const| (anon_const.loc(self.db).expr, anon_const)) - .collect() - } + Either::Left(def) => AnonConstId::all_from_signature(self.db, def) + .map(|anon_const| (anon_const.loc(self.db).expr, anon_const)) + .collect(), Either::Right(def) => { let all_anon_consts = self.db.field_types_with_diagnostics(def).defined_anon_consts().iter().copied(); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs index 3716e8d66e9ca..a72da8e7722a5 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -61,7 +61,7 @@ fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &hir::IncorrectCase) -> Option u8 { "#, ); - check_fix( + check_fix_with_disabled( r#" fn some_fn() { let whatAWeird_Formatting$0 = 10; @@ -121,6 +121,7 @@ fn some_fn() { another_func(what_aweird_formatting); } "#, + &["E0425"], ); check_fix( @@ -853,8 +854,6 @@ static FOO: () = { } #[test] - // FIXME - #[should_panic] fn enum_variant_body_inner_item() { check_diagnostics( r#" From 3a3cb145966c44e381f98d76709cd0b82c508931 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 19 Aug 2026 03:08:41 +0300 Subject: [PATCH 20/38] Do not relower the signature again in inference, instead fetch it from other queries Not only this helps perf, this also make it possible to define `AnonConst` as a tracked struct (but this PR doesn't do that yet), because it won't be created twice. There's a slight regression in tests because I followed rustc and liberated late bounds regions in the signature, and the printing of bound regions is suboptimal. I didn't fix this to not interfere with @dfireBird's work. --- .../rust-analyzer/crates/hir-ty/src/infer.rs | 197 +++++------------- .../crates/hir-ty/src/infer/closure.rs | 8 +- .../crates/hir-ty/src/infer/diagnostics.rs | 29 ++- .../crates/hir-ty/src/infer/expr.rs | 6 +- .../crates/hir-ty/src/infer/path.rs | 7 +- .../rust-analyzer/crates/hir-ty/src/lib.rs | 5 +- .../rust-analyzer/crates/hir-ty/src/lower.rs | 5 - .../hir-ty/src/method_resolution/confirm.rs | 8 +- .../hir-ty/src/tests/display_source_code.rs | 2 +- .../crates/hir-ty/src/tests/regression.rs | 1 + .../crates/hir-ty/src/tests/simple.rs | 2 +- .../crates/hir-ty/src/tests/traits.rs | 2 +- .../crates/hir/src/diagnostics.rs | 46 +--- 13 files changed, 92 insertions(+), 226 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index 3fbb02aee94bd..0ab147096a749 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -42,16 +42,16 @@ use std::{ use base_db::{Crate, FxIndexMap}; use either::Either; use hir_def::{ - AdtId, AssocItemId, AttrDefId, ConstId, DefWithBodyId, ExpressionStoreOwnerId, FieldId, - FunctionId, GenericDefId, GenericParamId, HasModule, LocalFieldId, Lookup, StaticId, TraitId, - TupleFieldId, TupleId, VariantId, + AdtId, AssocItemId, AttrDefId, DefWithBodyId, ExpressionStoreOwnerId, FieldId, FunctionId, + GenericDefId, GenericParamId, HasModule, LocalFieldId, Lookup, TraitId, TupleFieldId, TupleId, + VariantId, attrs::AttrFlags, expr_store::{Body, ExpressionStore, HygieneId, body::Param, path::Path}, hir::{BindingId, ExprId, ExprOrPatId, ExprOrPatIdPacked, LabelId, PatId, UnaryOp}, lang_item::LangItems, layout::Integer, resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, - signatures::{ConstSignature, EnumSignature, FunctionSignature, StaticSignature}, + signatures::EnumSignature, type_ref::{LifetimeRefId, TypeRefId}, unstable_features::UnstableFeatures, }; @@ -74,7 +74,7 @@ use thin_vec::ThinVec; use crate::{ ImplTraitId, IncorrectGenericsLenKind, InferBodyId, PathLoweringDiagnostic, Span, - TargetFeatures, + TargetFeatures, ValueTyDefId, closure_analysis::PlaceBase, consteval::{create_anon_const, path_to_const}, db::{AnonConstId, GeneralConstId, HirDatabase, InternedOpaqueTyId}, @@ -95,8 +95,8 @@ use crate::{ unify::resolve_completely::WriteBackCtxt, }, lower::{ - ImplTraitIdx, ImplTraitLoweringMode, LifetimeElisionKind, LifetimeLoweringMode, - LoweringMode, diagnostics::TyLoweringDiagnostic, + ImplTraitIdx, LifetimeElisionKind, LifetimeLoweringMode, LoweringMode, + diagnostics::TyLoweringDiagnostic, }, method_resolution::CandidateId, next_solver::{ @@ -152,8 +152,8 @@ pub fn infer_query_with_inspect<'db>( DefWithBodyId::FunctionId(f) => { ctx.collect_fn(f, body.self_param.map(|param| param.formal), &body.params) } - DefWithBodyId::ConstId(c) => ctx.collect_const(c, ConstSignature::of(db, c)), - DefWithBodyId::StaticId(s) => ctx.collect_static(s, StaticSignature::of(db, s)), + DefWithBodyId::ConstId(c) => ctx.collect_const_or_static(c.into()), + DefWithBodyId::StaticId(s) => ctx.collect_const_or_static(s.into()), DefWithBodyId::VariantId(v) => { ctx.return_ty = match EnumSignature::variant_body_type(db, v.lookup(db).parent) { hir_def::layout::IntegerType::Pointer(signed) => match signed { @@ -286,14 +286,6 @@ pub enum ByRef { #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct BindingMode(pub ByRef, pub Mutability); -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum InferenceTyDiagnosticSource { - /// Diagnostics that come from types in the body. - Body, - /// Diagnostics that come from types in fn parameters/return type, or static & const types. - Signature, -} - #[derive(Debug, PartialEq, Eq, Clone, TypeVisitable, TypeFoldable)] pub enum InferenceDiagnostic { NoSuchField { @@ -475,8 +467,6 @@ pub enum InferenceDiagnostic { cast_ty: StoredTy, }, TyDiagnostic { - #[type_visitable(ignore)] - source: InferenceTyDiagnosticSource, #[type_visitable(ignore)] diag: TyLoweringDiagnostic, }, @@ -1530,6 +1520,7 @@ impl<'db> InferenceContext<'db> { ); } self.defined_anon_consts.borrow_mut().extend(other.defined_anon_consts.iter().copied()); + self.diagnostics.extend(&other.diagnostics); fn merge_hash_set(dest: &mut FxHashSet, source: &FxHashSet) { dest.extend(source.iter().cloned()); @@ -1764,28 +1755,9 @@ impl<'db> InferenceContext<'db> { result } - fn collect_const(&mut self, id: ConstId, data: &'db ConstSignature) { - let return_ty = self.make_ty( - data.type_ref, - &data.store, - InferenceTyDiagnosticSource::Signature, - ExpressionStoreOwnerId::Signature(id.into()), - LifetimeElisionKind::for_const(self.interner(), id.loc(self.db).container), - ); - - self.return_ty = return_ty; - } - - fn collect_static(&mut self, id: StaticId, data: &'db StaticSignature) { - let return_ty = self.make_ty( - data.type_ref, - &data.store, - InferenceTyDiagnosticSource::Signature, - ExpressionStoreOwnerId::Signature(id.into()), - LifetimeElisionKind::Elided(self.types.regions.statik), - ); - - self.return_ty = return_ty; + fn collect_const_or_static(&mut self, id: ValueTyDefId) { + let return_ty = self.db.value_ty(id).unwrap().instantiate_identity().skip_norm_wip(); + self.return_ty = self.process_remote_user_written_ty(return_ty); } fn collect_fn( @@ -1794,63 +1766,40 @@ impl<'db> InferenceContext<'db> { self_param: Option, params: &[Param], ) { - let data = FunctionSignature::of(self.db, func); - let mut param_tys = self.with_ty_lowering( - &data.store, - InferenceTyDiagnosticSource::Signature, - ExpressionStoreOwnerId::Signature(func.into()), - LifetimeElisionKind::for_fn_params(data), - |ctx| data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::>(), + let sig = self.interner().liberate_late_bound_regions( + func.into(), + self.db.callable_item_signature(func.into()).instantiate_identity().skip_norm_wip(), ); - // Check if function contains a va_list, if it does then we append it to the parameter types - // that are collected from the function data - if data.is_varargs() { - let va_list_ty = match self.resolve_va_list() { - Some(va_list) => Ty::new_adt( - self.interner(), - va_list, - GenericArgs::for_item_with_defaults( - self.interner(), - va_list.into(), - |_, id, _| self.table.var_for_def(id, Span::Dummy), - ), - ), - None => self.err_ty(), - }; + // C-variadic fns also have a `VaList` input that's not listed in `fn_sig` + // (as it's created inside the body itself, not passed in from outside). + let maybe_va_list = + if sig.fn_sig_kind.c_variadic() { self.resolve_va_list() } else { None }; + let maybe_va_list = maybe_va_list.map(|va_list| { + let region = self.table.next_region_var( + params.last().expect("variadic function must have parameters").user_written.into(), + ); + Ty::new_adt(self.interner(), va_list, GenericArgs::new_from_slice(&[region.into()])) + }); + + let mut param_tys = sig.inputs().iter().copied(); - param_tys.push(va_list_ty); - } - let mut param_tys = param_tys.into_iter(); if let Some(self_param) = self_param && let Some(ty) = param_tys.next() { - let ty = self.process_user_written_ty(ty); + let ty = self.process_remote_user_written_ty(ty); self.write_binding_ty(self_param, ty); } + + let mut param_tys = param_tys.chain(maybe_va_list); for pat in params { let ty = param_tys.next().unwrap_or_else(|| self.table.next_ty_var(Span::Dummy)); - let ty = self.process_user_written_ty(ty); + let ty = self.process_remote_user_written_ty(ty); self.infer_top_pat(pat.formal, ty, PatOrigin::Param); } - self.return_ty = match data.ret_type { - Some(return_ty) => { - let return_ty = self.with_ty_lowering( - &data.store, - InferenceTyDiagnosticSource::Signature, - ExpressionStoreOwnerId::Signature(func.into()), - LifetimeElisionKind::for_fn_ret(self.interner()), - |ctx| { - ctx.impl_trait_mode(ImplTraitLoweringMode::Opaque); - ctx.lower_ty(return_ty) - }, - ); - self.process_user_written_ty(return_ty) - } - None => self.types.types.unit, - }; + self.return_ty = self.process_remote_user_written_ty(sig.output()); self.return_coercion = Some(CoerceMany::new(self.return_ty)); } @@ -1983,76 +1932,33 @@ impl<'db> InferenceContext<'db> { self.deferred_call_resolutions.remove(&closure_def_id).unwrap_or_default() } - fn with_ty_lowering( - &mut self, - store: &'db ExpressionStore, - types_source: InferenceTyDiagnosticSource, - store_owner: ExpressionStoreOwnerId, - lifetime_elision: LifetimeElisionKind<'db>, - f: impl FnOnce(&mut TyLoweringContext<'db, '_>) -> R, - ) -> R { - let infer_vars = match types_source { - InferenceTyDiagnosticSource::Body => Some(&mut InferenceTyLoweringVarsCtx { - table: &mut self.table, - type_of_type_placeholder: &mut self.result.type_of_type_placeholder, - } as _), - InferenceTyDiagnosticSource::Signature => None, + fn with_ty_lowering(&mut self, f: impl FnOnce(&mut TyLoweringContext<'db, '_>) -> R) -> R { + let mut infer_vars = InferenceTyLoweringVarsCtx { + table: &mut self.table, + type_of_type_placeholder: &mut self.result.type_of_type_placeholder, }; let mut ctx = TyLoweringContext::new( self.db, &self.resolver, - store, + self.store, &self.diagnostics, - types_source, - store_owner, + self.store_owner, self.generic_def, &self.generics, - lifetime_elision, + LifetimeElisionKind::Infer, self.allow_using_generic_params, - infer_vars, + &mut infer_vars, &self.defined_anon_consts, LifetimeLoweringMode::LateParam, ); f(&mut ctx) } - fn with_body_ty_lowering( - &mut self, - f: impl FnOnce(&mut TyLoweringContext<'db, '_>) -> R, - ) -> R { - self.with_ty_lowering( - self.store, - InferenceTyDiagnosticSource::Body, - self.store_owner, - LifetimeElisionKind::Infer, - f, - ) - } - - fn make_ty( - &mut self, - type_ref: TypeRefId, - store: &'db ExpressionStore, - type_source: InferenceTyDiagnosticSource, - store_owner: ExpressionStoreOwnerId, - lifetime_elision: LifetimeElisionKind<'db>, - ) -> Ty<'db> { - let ty = self.with_ty_lowering(store, type_source, store_owner, lifetime_elision, |ctx| { - ctx.lower_ty(type_ref) - }); + pub(crate) fn make_ty(&mut self, type_ref: TypeRefId) -> Ty<'db> { + let ty = self.with_ty_lowering(|ctx| ctx.lower_ty(type_ref)); self.process_user_written_ty(ty) } - pub(crate) fn make_body_ty(&mut self, type_ref: TypeRefId) -> Ty<'db> { - self.make_ty( - type_ref, - self.store, - InferenceTyDiagnosticSource::Body, - self.store_owner, - LifetimeElisionKind::Infer, - ) - } - fn generics(&self) -> &Generics<'db> { self.generics.get_or_init(|| crate::generics::generics(self.db, self.generic_def)) } @@ -2063,7 +1969,7 @@ impl<'db> InferenceContext<'db> { }) } - pub(crate) fn create_body_anon_const( + pub(crate) fn create_anon_const( &mut self, expr: ExprId, expected_ty: Ty<'db>, @@ -2096,7 +2002,7 @@ impl<'db> InferenceContext<'db> { konst.unwrap_or_else(|_| self.table.next_const_var(Span::Dummy)) } - pub(crate) fn make_path_as_body_const(&mut self, path: &Path) -> Const<'db> { + pub(crate) fn make_path_as_const(&mut self, path: &Path) -> Const<'db> { let forbid_params_after = if self.allow_using_generic_params { None } else { Some(0) }; // FIXME: Report errors. path_to_const(self.db, &self.resolver, &|| self.generics(), forbid_params_after, path) @@ -2107,14 +2013,8 @@ impl<'db> InferenceContext<'db> { self.types.types.error } - pub(crate) fn make_body_lifetime(&mut self, lifetime_ref: LifetimeRefId) -> Region<'db> { - let lt = self.with_ty_lowering( - self.store, - InferenceTyDiagnosticSource::Body, - self.store_owner, - LifetimeElisionKind::Infer, - |ctx| ctx.lower_lifetime(lifetime_ref), - ); + pub(crate) fn make_lifetime(&mut self, lifetime_ref: LifetimeRefId) -> Region<'db> { + let lt = self.with_ty_lowering(|ctx| ctx.lower_lifetime(lifetime_ref)); self.insert_type_vars(lt) } @@ -2343,13 +2243,12 @@ impl<'db> InferenceContext<'db> { &self.resolver, self.store, &self.diagnostics, - InferenceTyDiagnosticSource::Body, self.store_owner, self.generic_def, &self.generics, LifetimeElisionKind::Infer, self.allow_using_generic_params, - Some(&mut vars_ctx), + &mut vars_ctx, &self.defined_anon_consts, LifetimeLoweringMode::LateParam, ); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs index 9af182fc49288..4ce7e3a72b956 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs @@ -967,7 +967,7 @@ impl<'db> InferenceContext<'db> { let interner = self.interner(); let supplied_return = match decl_output { - Some(output) => self.make_body_ty(output), + Some(output) => self.make_ty(output), None => match closure_kind { // In the case of the async block that we create for a function body, // we expect the return type of the block to match that of the enclosing @@ -1005,7 +1005,7 @@ impl<'db> InferenceContext<'db> { }; // First, convert the types that the user supplied (if any). let supplied_arguments = decl_inputs.iter().map(|&input| match input { - Some(input) => self.make_body_ty(input), + Some(input) => self.make_ty(input), None => self.table.next_ty_var(closure_expr.into()), }); @@ -1134,11 +1134,11 @@ impl<'db> InferenceContext<'db> { let err_ty = Ty::new_error(interner, ErrorGuaranteed); if let Some(output) = decl_output { - self.make_body_ty(output); + self.make_ty(output); } let supplied_arguments = decl_inputs.iter().map(|&input| match input { Some(input) => { - self.make_body_ty(input); + self.make_ty(input); err_ty } None => err_ty, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/diagnostics.rs index 69753687afddd..918965c4f5be3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/diagnostics.rs @@ -19,7 +19,7 @@ use thin_vec::ThinVec; use crate::lower::LifetimeLoweringMode; use crate::{ - InferenceDiagnostic, InferenceTyDiagnosticSource, Span, TyLoweringDiagnostic, + InferenceDiagnostic, Span, TyLoweringDiagnostic, db::{AnonConstId, HirDatabase}, generics::Generics, infer::unify::InferenceTable, @@ -42,14 +42,14 @@ impl Diagnostics { self.0.borrow_mut().push(diagnostic); } - fn push_ty_diagnostics( - &self, - source: InferenceTyDiagnosticSource, - diagnostics: ThinVec, - ) { - self.0.borrow_mut().extend( - diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }), - ); + pub(super) fn extend(&self, diagnostic: &[InferenceDiagnostic]) { + self.0.borrow_mut().extend(diagnostic.iter().cloned()); + } + + fn push_ty_diagnostics(&self, diagnostics: ThinVec) { + self.0 + .borrow_mut() + .extend(diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { diag })); } pub(super) fn finish(self) -> ThinVec { @@ -92,7 +92,6 @@ impl<'db> TyLoweringInferVarsCtx<'db> for InferenceTyLoweringVarsCtx<'_, 'db> { pub(super) struct InferenceTyLoweringContext<'db, 'a> { ctx: TyLoweringContext<'db, 'a>, diagnostics: &'a Diagnostics, - source: InferenceTyDiagnosticSource, defined_anon_consts: &'a RefCell>>, } @@ -103,13 +102,12 @@ impl<'db, 'a> InferenceTyLoweringContext<'db, 'a> { resolver: &'a Resolver<'db>, store: &'db ExpressionStore, diagnostics: &'a Diagnostics, - source: InferenceTyDiagnosticSource, def: ExpressionStoreOwnerId, generic_def: GenericDefId, generics: &'a OnceCell>, lifetime_elision: LifetimeElisionKind<'db>, allow_using_generic_params: bool, - infer_vars: Option<&'a mut dyn TyLoweringInferVarsCtx<'db>>, + infer_vars: &'a mut InferenceTyLoweringVarsCtx<'a, 'db>, defined_anon_consts: &'a RefCell>>, lifetime_lowering_mode: LifetimeLoweringMode, ) -> Self { @@ -123,11 +121,11 @@ impl<'db, 'a> InferenceTyLoweringContext<'db, 'a> { lifetime_elision, lifetime_lowering_mode, ) - .with_infer_vars_behavior(infer_vars); + .with_infer_vars_behavior(Some(infer_vars)); if !allow_using_generic_params { ctx.forbid_params_after(0, ForbidParamsAfterReason::AnonConst); } - Self { ctx, diagnostics, source, defined_anon_consts } + Self { ctx, diagnostics, defined_anon_consts } } #[inline] @@ -187,8 +185,7 @@ impl DerefMut for InferenceTyLoweringContext<'_, '_> { impl Drop for InferenceTyLoweringContext<'_, '_> { #[inline] fn drop(&mut self) { - self.diagnostics - .push_ty_diagnostics(self.source, std::mem::take(&mut self.ctx.diagnostics)); + self.diagnostics.push_ty_diagnostics(std::mem::take(&mut self.ctx.diagnostics)); self.defined_anon_consts.borrow_mut().extend(self.ctx.defined_anon_consts.iter().copied()); } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index 2ad12b8a374b4..af301731c7156 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -613,7 +613,7 @@ impl<'db> InferenceContext<'db> { Expr::Field { expr, name } => self.infer_field_access(tgt_expr, *expr, name, expected), Expr::Await { expr } => self.infer_await_expr(tgt_expr, *expr), Expr::Cast { expr, type_ref } => { - let cast_ty = self.make_body_ty(*type_ref); + let cast_ty = self.make_ty(*type_ref); let expr_ty = self.infer_expr(*expr, &Expectation::Castable(cast_ty), ExprIsRead::Yes); self.deferred_cast_checks.push(CastCheck::new(tgt_expr, *expr, expr_ty, cast_ty)); @@ -1310,7 +1310,7 @@ impl<'db> InferenceContext<'db> { expr: ExprId, ) -> Ty<'db> { let interner = self.interner(); - let count_ct = self.create_body_anon_const(count, self.types.types.usize, true); + let count_ct = self.create_anon_const(count, self.types.types.usize, true); let count = self.table.try_structurally_resolve_const(count.into(), count_ct); let uty = match expected { @@ -1476,7 +1476,7 @@ impl<'db> InferenceContext<'db> { Statement::Let { pat, type_ref, initializer, else_branch } => { let decl_ty = type_ref .as_ref() - .map(|&tr| this.make_body_ty(tr)) + .map(|&tr| this.make_ty(tr)) .unwrap_or_else(|| this.table.next_ty_var((*pat).into())); this.infer_let( diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs index 47a905fc142cb..ec0788b43b573 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs @@ -24,7 +24,7 @@ use crate::{ }, }; -use super::{InferenceContext, InferenceTyDiagnosticSource}; +use super::InferenceContext; impl<'db> InferenceContext<'db> { pub(super) fn infer_path( @@ -122,7 +122,7 @@ impl<'db> InferenceContext<'db> { // to the type alias and they may have different generics. self.types.empty.generic_args } else { - self.with_body_ty_lowering(|ctx| { + self.with_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 { @@ -159,13 +159,12 @@ impl<'db> InferenceContext<'db> { &self.resolver, self.store, &self.diagnostics, - InferenceTyDiagnosticSource::Body, self.store_owner, self.generic_def, &self.generics, LifetimeElisionKind::Infer, self.allow_using_generic_params, - Some(&mut vars_ctx), + &mut vars_ctx, &self.defined_anon_consts, LifetimeLoweringMode::LateParam, ); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index 21e68a6312d39..540d3db74d707 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -106,9 +106,8 @@ use crate::{ pub use autoderef::autoderef; pub use infer::{ Adjust, Adjustment, AutoBorrow, BindingMode, ByRef, ExplicitDropMethodUseKind, - InferenceDiagnostic, InferenceResult, InferenceTyDiagnosticSource, OverloadedDeref, - PointerCast, ReturnKind, cast::CastError, could_coerce, could_unify, could_unify_deeply, - infer_query_with_inspect, + InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast, ReturnKind, + cast::CastError, could_coerce, could_unify, could_unify_deeply, infer_query_with_inspect, }; pub use lower::{ FieldType, GenericDefaults, GenericDefaultsRef, GenericPredicates, LifetimeElisionKind, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 7733b49d32f78..b86585231cca1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -315,11 +315,6 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { Self { impl_trait_mode: ImplTraitLoweringState::new(impl_trait_mode), ..self } } - pub(crate) fn impl_trait_mode(&mut self, impl_trait_mode: ImplTraitLoweringMode) -> &mut Self { - self.impl_trait_mode = ImplTraitLoweringState::new(impl_trait_mode); - self - } - pub(crate) fn forbid_params_after(&mut self, index: u32, reason: ForbidParamsAfterReason) { self.forbid_params_after = Some(index); self.forbid_params_after_reason = reason; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/confirm.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/confirm.rs index 6d948464b1c03..5d10a0b21e46b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/confirm.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/confirm.rs @@ -395,16 +395,16 @@ impl<'a, 'db> ConfirmContext<'a, 'db> { ( GenericParamDataRef::LifetimeParamData(_), HirGenericArg::Lifetime(lifetime), - ) => self.ctx.make_body_lifetime(*lifetime).into(), + ) => self.ctx.make_lifetime(*lifetime).into(), (GenericParamDataRef::TypeParamData(_), HirGenericArg::Type(type_ref)) => { - self.ctx.make_body_ty(*type_ref).into() + self.ctx.make_ty(*type_ref).into() } (GenericParamDataRef::ConstParamData(_), HirGenericArg::Const(konst)) => { let GenericParamId::ConstParamId(const_id) = param_id else { unreachable!("non-const param ID for const param"); }; let const_ty = self.ctx.db.const_param_ty(const_id); - self.ctx.create_body_anon_const(konst.expr, const_ty, false).into() + self.ctx.create_anon_const(konst.expr, const_ty, false).into() } _ => unreachable!("unmatching param kinds were passed to `provided_kind()`"), } @@ -417,7 +417,7 @@ impl<'a, 'db> ConfirmContext<'a, 'db> { arg: TypeLikeConst<'_>, ) -> Const<'db> { match arg { - TypeLikeConst::Path(path) => self.ctx.make_path_as_body_const(path), + TypeLikeConst::Path(path) => self.ctx.make_path_as_const(path), TypeLikeConst::Infer => self.ctx.table.next_const_var(Span::Dummy), } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs index efbb49b0eeb81..db21c4cf949aa 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs @@ -85,7 +85,7 @@ fn render_dyn_for_ty() { trait Foo<'a> {} fn foo(foo: &dyn for<'a> Foo<'a>) {} - // ^^^ &(dyn Foo<'_> + 'static) + // ^^^ &(dyn Foo<'?0.0> + 'static) "#, ); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index ff0e075ff6d69..7934ffec28745 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs @@ -3039,6 +3039,7 @@ fn array_repeat_closure() { r#" fn f() {[_; || ()]} // ^^^^^^^^^^ expected (), got [{unknown}; _] + // ^^^^^ expected usize, got impl Fn() "#, ); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index 97921e8ab92d5..0a6250c450f8b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -4395,7 +4395,7 @@ fn hrtb_fn_ptr() { fn foo<'b>(f: for <'a> fn(&'a u32, &'b u32)) {} "#, expect![[r#" - 12..13 'f': fn(&'_ u32, &'_ u32) + 12..13 'f': fn(&'?0.0 u32, &'_ u32) 46..48 '{}': () "#]], ); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index fad944589dab6..2c107da42ee9a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -5372,7 +5372,7 @@ impl<'a, 'b> Trait<'a, 'b> for Foo {} fn run_dyn<'b>(val: &dyn for<'a> Trait<'a, 'b>) {} "#, expect![[r#" - 91..94 'val': &'? (dyn Trait<'_, '_> + 'static) + 91..94 'val': &'? (dyn Trait<'?0.0, '_> + 'static) 124..126 '{}': () "#]], ); diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index c342effa06aff..c20c6e471ac76 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -9,7 +9,7 @@ use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_def::{ AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, GenericDefId, GenericParamId, ImplId, - Lookup, MacroId, ModuleDefId, ModuleId, StaticId, SyntheticSyntax, TraitId, + Lookup, MacroId, ModuleDefId, ModuleId, SyntheticSyntax, TraitId, attrs::AttrFlags, expr_store::{ Body, ExprOrPatPtr, ExpressionStore, ExpressionStoreDiagnostics, ExpressionStoreSourceMap, @@ -22,8 +22,8 @@ use hir_def::{ diagnostics::{DefDiagnosticKind, DefDiagnostics}, }, signatures::{ - ConstSignature, FunctionSignature, ImplFlags, ImplSignature, StaticSignature, TraitFlags, - TraitSignature, TypeAliasSignature, + ConstSignature, FunctionSignature, ImplFlags, ImplSignature, TraitFlags, TraitSignature, + TypeAliasSignature, }, type_ref::TypeRefId, unstable_features::UnstableFeatures, @@ -34,8 +34,8 @@ use hir_expand::{ }; use hir_ty::{ CastError, ExplicitDropMethodUseKind, InferBodyId, InferenceDiagnostic, InferenceResult, - InferenceTyDiagnosticSource, ParamEnvAndCrate, PathGenericsSource, PathLoweringDiagnostic, - TyLoweringDiagnostic, check_orphan_rules, + ParamEnvAndCrate, PathGenericsSource, PathLoweringDiagnostic, TyLoweringDiagnostic, + check_orphan_rules, db::{AnonConstId, HirDatabase, signature_anon_consts_and_diagnostics}, diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, display::{DisplayTarget, HirDisplay}, @@ -1168,18 +1168,7 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { } fn collect_anon_const(&mut self, source_map: &ExpressionStoreSourceMap, def: AnonConstId<'db>) { - self.emit_inference_errors(def.into(), source_map, None, def.into()); - } - - fn collect_static(&mut self, def: StaticId) { - let (signature, signature_source_map) = StaticSignature::with_source_map(self.db, def); - self.collect_expr_store(&signature.store, signature_source_map); - - self.collect_def_with_body( - Some(signature_source_map), - def.into(), - TypeOwnerId::NoParams(self.krate), - ); + self.emit_inference_errors(def.into(), source_map, def.into()); } fn collect_enum(&mut self, def: EnumId) { @@ -1204,7 +1193,6 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { fn collect_enum_variant(&mut self, def: EnumVariantId) { self.collect_def_with_body( - None, def.into(), TypeOwnerId::GenericDefId(def.loc(self.db).parent.into()), ); @@ -1237,16 +1225,11 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { } } - fn collect_def_with_body( - &mut self, - sig_map: Option<&ExpressionStoreSourceMap>, - def: DefWithBodyId, - type_owner: TypeOwnerId<'db>, - ) { + fn collect_def_with_body(&mut self, def: DefWithBodyId, type_owner: TypeOwnerId<'db>) { let (body, source_map) = Body::with_source_map(self.db, def); self.collect_expr_store(body, source_map); - self.emit_inference_errors(def.into(), source_map, sig_map, type_owner); + self.emit_inference_errors(def.into(), source_map, type_owner); // FIXME: Missing unsafe and body validation should be defined for any `InferBodyId`. let missing_unsafe = hir_ty::diagnostics::missing_unsafe(self.db, def); @@ -1294,7 +1277,6 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { &mut self, def: InferBodyId<'db>, source_map: &ExpressionStoreSourceMap, - sig_map: Option<&ExpressionStoreSourceMap>, type_owner: TypeOwnerId<'db>, ) { let infer = InferenceResult::of(self.db, def); @@ -1306,7 +1288,6 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { self.edition, diag, source_map, - sig_map, type_owner, ) })); @@ -1334,7 +1315,7 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { self.collect_generic_def(signature_store, signature_source_map, generic_def); let def_with_body: DefWithBodyId = def.into(); - self.collect_def_with_body(Some(signature_source_map), def_with_body, generic_def.into()); + self.collect_def_with_body(def_with_body, generic_def.into()); } fn collect_generic_variant(&mut self, def: impl Into + Into + Copy) { @@ -1364,7 +1345,7 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { ModuleDefId::MacroId(def) => self.collect_macro_def(def), ModuleDefId::FunctionId(def) => self.collect_generic_def_with_body(def), ModuleDefId::ConstId(def) => self.collect_generic_def_with_body(def), - ModuleDefId::StaticId(def) => self.collect_static(def), + ModuleDefId::StaticId(def) => self.collect_generic_def_with_body(def), ModuleDefId::EnumVariantId(def) => self.collect_enum_variant(def), ModuleDefId::AdtId(AdtId::StructId(def)) => self.collect_generic_variant(def), ModuleDefId::AdtId(AdtId::UnionId(def)) => self.collect_generic_variant(def), @@ -1557,7 +1538,6 @@ impl<'db> AnyDiagnostic<'db> { edition: Edition, d: &'db InferenceDiagnostic, source_map: &ExpressionStoreSourceMap, - sig_map: Option<&ExpressionStoreSourceMap>, type_owner: TypeOwnerId<'db>, ) -> Option> { let expr_syntax = |expr| Self::expr_syntax(expr, source_map); @@ -1747,11 +1727,7 @@ impl<'db> AnyDiagnostic<'db> { let expr = expr_syntax(*expr)?; CannotIndexInto { expr, found: new_ty(found.as_ref()) }.into() } - InferenceDiagnostic::TyDiagnostic { source, diag } => { - let source_map = match source { - InferenceTyDiagnosticSource::Body => source_map, - InferenceTyDiagnosticSource::Signature => sig_map.expect("cannot have `InferenceTyDiagnosticSource::Signature` when there is no signature"), - }; + InferenceDiagnostic::TyDiagnostic { diag } => { Self::ty_diagnostic(diag, source_map, db)? } InferenceDiagnostic::PathDiagnostic { node, diag } => { From f375a4511a053d1c740732289be842d0af599c43 Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Thu, 3 Sep 2026 11:56:19 +0200 Subject: [PATCH 21/38] Update contributor guide about `ChangeWithProcMacros` --- .../rust-analyzer/docs/book/src/contributing/guide.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/docs/book/src/contributing/guide.md b/src/tools/rust-analyzer/docs/book/src/contributing/guide.md index 9e944bfe0fc4d..f985986f9635c 100644 --- a/src/tools/rust-analyzer/docs/book/src/contributing/guide.md +++ b/src/tools/rust-analyzer/docs/book/src/contributing/guide.md @@ -67,12 +67,12 @@ Next, let's talk about what the inputs to the `Analysis` are, precisely. rust-analyzer never does any I/O itself, all inputs get passed explicitly via the `AnalysisHost::apply_change` method, which accepts a single argument, a -`Change`. [`Change`] is a wrapper for `FileChange` that adds proc-macro knowledge. -[`FileChange`] is a builder for a single change "transaction", so it suffices -to study its methods to understand all the input data. +[`ChangeWithProcMacros`]. [`ChangeWithProcMacros`] is a wrapper for `FileChange` +that adds proc-macro knowledge. [`FileChange`] is a builder for a single change +"transaction", so it suffices to study its methods to understand all the input data. -[`Change`]: https://github.com/rust-lang/rust-analyzer/blob/2024-01-01/crates/hir-expand/src/change.rs#L10-L42 -[`FileChange`]: https://github.com/rust-lang/rust-analyzer/blob/2024-01-01/crates/base-db/src/change.rs#L14-L78 +[`ChangeWithProcMacros`]: https://github.com/rust-lang/rust-analyzer/blob/2026-08-03/crates/hir-expand/src/change.rs#L9-L42 +[`FileChange`]: https://github.com/rust-lang/rust-analyzer/blob/2026-08-03/crates/base-db/src/change.rs#L18-L99 The `change_file` method controls the set of the input files, where each file has an integer id (`FileId`, picked by the client) and text (`Option>`). From 4503613ac1c7b6577bf68fce4f3f4c6985071ccd Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Tue, 1 Sep 2026 15:21:20 +0200 Subject: [PATCH 22/38] Add missing body diagnostics --- .../crates/hir-def/src/expr_store.rs | 10 +++ .../crates/hir-def/src/expr_store/body.rs | 18 +++-- .../crates/hir-def/src/expr_store/lower.rs | 61 ++++++++++++++-- .../crates/hir-def/src/signatures.rs | 10 +-- .../crates/hir/src/diagnostics.rs | 12 +++- .../src/handlers/missing_body.rs | 72 +++++++++++++++++++ .../crates/ide-diagnostics/src/lib.rs | 2 + 7 files changed, 163 insertions(+), 22 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_body.rs diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs index 7d6b191c17ad0..3a63ca80ffc68 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs @@ -321,6 +321,15 @@ struct FormatTemplate { implicit_capture_to_source: FxHashMap>, } +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub enum MissingBodyItemKind { + AssocConst, + AssocType, + Const, + Static, + TypeAlias, +} + #[derive(Debug, Eq, PartialEq)] pub enum ExpressionStoreDiagnostics { InactiveCode { node: InFile, cfg: CfgExpr, opts: CfgOptions }, @@ -330,6 +339,7 @@ pub enum ExpressionStoreDiagnostics { UndeclaredLabel { node: InFile>, name: Name }, PatternArgInExternFn { node: InFile> }, FruInDestructuringAssignment { node: InFile> }, + MissingBody { node: InFile, kind: MissingBodyItemKind }, } impl ExpressionStoreBuilder { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/body.rs index 74c86ca4e89d4..cd04e2c335737 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/body.rs @@ -5,7 +5,7 @@ use std::ops; use base_db::SourceDatabase; use hir_expand::{InFile, Lookup}; use span::Edition; -use syntax::ast; +use syntax::{SyntaxNodePtr, ast}; use triomphe::Arc; use crate::{ @@ -96,7 +96,7 @@ impl Body { let mut is_async_fn = false; let mut is_gen_fn = false; - let InFile { file_id, value: body } = { + let (InFile { file_id, value: body }, syntax_node) = { match def { DefWithBodyId::FunctionId(f) => { let f = f.lookup(db); @@ -104,28 +104,32 @@ impl Body { params = src.value.param_list(); is_async_fn = src.value.async_token().is_some(); is_gen_fn = src.value.gen_token().is_some(); - src.map(|it| it.body().map(ast::Expr::from)) + let syntax_node = SyntaxNodePtr::new(src.syntax().value); + (src.map(|it| it.body().map(ast::Expr::from)), syntax_node) } DefWithBodyId::ConstId(c) => { let c = c.lookup(db); let src = c.source(db); - src.map(|it| it.body()) + let syntax_node = SyntaxNodePtr::new(src.syntax().value); + (src.map(|it| it.body()), syntax_node) } DefWithBodyId::StaticId(s) => { let s = s.lookup(db); let src = s.source(db); - src.map(|it| it.body()) + let syntax_node = SyntaxNodePtr::new(src.syntax().value); + (src.map(|it| it.body()), syntax_node) } DefWithBodyId::VariantId(v) => { let s = v.lookup(db); let src = s.source(db); - src.map(|it| it.const_arg()?.expr()) + let syntax_node = SyntaxNodePtr::new(src.syntax().value); + (src.map(|it| it.const_arg()?.expr()), syntax_node) } } }; let module = def.module(db); let (body, source_map) = - lower_body(db, def, file_id, module, params, body, is_async_fn, is_gen_fn); + lower_body(db, def, syntax_node, file_id, module, params, body, is_async_fn, is_gen_fn); (Arc::new(body), source_map) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index c6f07f3037616..6463bb1e1d613 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -35,14 +35,14 @@ use thin_vec::ThinVec; use tt::TextRange; use crate::{ - AdtId, BlockId, BlockIdLt, ConstId, DefWithBodyId, FunctionId, GenericDefId, ImplId, + AdtId, BlockId, BlockIdLt, ConstId, DefWithBodyId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LoweringMode, MacroId, ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro, attrs::AttrFlags, expr_store::{ Body, BodySourceMap, ExprPtr, ExprRoot, ExpressionStore, ExpressionStoreBuilder, ExpressionStoreDiagnostics, ExpressionStoreSourceMap, HygieneId, LabelPtr, LifetimePtr, - PatPtr, StoreVisitor, TypePtr, + MissingBodyItemKind, PatPtr, StoreVisitor, TypePtr, body::Param, expander::Expander, lower::generics::ImplTraitLowerFn, @@ -70,6 +70,7 @@ pub use self::path::hir_segment_to_ast_segment; pub(super) fn lower_body( db: &dyn SourceDatabase, owner: DefWithBodyId, + syntax_node: SyntaxNodePtr, current_file_id: HirFileId, module: ModuleId, parameters: Option, @@ -133,7 +134,7 @@ pub(super) fn lower_body( BodySourceMap { self_param: source_map_self_param, store: source_map }, ); } - + validate_required_body(db, owner, current_file_id, syntax_node, body.as_ref(), &mut collector); collector.with_expr_root(|collector| { if let DefWithBodyId::FunctionId(func) = owner && let Some(param_list) = parameters @@ -206,6 +207,40 @@ pub(super) fn lower_body( ) } +fn validate_required_body( + db: &(dyn SourceDatabase + 'static), + owner: DefWithBodyId, + current_file_id: HirFileId, + syntax_node: SyntaxNodePtr, + body: Option<&ast::Expr>, + collector: &mut ExprCollector<'_>, +) { + if body.is_some() { + return; + } + let diagnostic_kind = match owner { + // FIXME: add diagnostic for missing body + // rustc says: if body.is_none() && !is_intrinsic && !self.is_sdylib_interface + DefWithBodyId::FunctionId(_function_id) => None, + DefWithBodyId::StaticId(id) => match id.loc(db).container { + ItemContainerId::ModuleId(_) => Some(MissingBodyItemKind::Static), + ItemContainerId::ExternBlockId(_) + | ItemContainerId::ImplId(_) + | ItemContainerId::TraitId(_) => None, + }, + DefWithBodyId::ConstId(id) => match id.loc(db).container { + ItemContainerId::ModuleId(_) => Some(MissingBodyItemKind::Const), + ItemContainerId::ImplId(_) => Some(MissingBodyItemKind::AssocConst), + ItemContainerId::ExternBlockId(_) | ItemContainerId::TraitId(_) => None, + }, + DefWithBodyId::VariantId(_) => None, + }; + if let Some(kind) = diagnostic_kind { + let node = InFile::new(current_file_id, syntax_node); + collector.store.diagnostics.push(ExpressionStoreDiagnostics::MissingBody { node, kind }); + } +} + pub(crate) fn lower_type_ref( db: &dyn SourceDatabase, module: ModuleId, @@ -290,12 +325,13 @@ pub(crate) fn lower_trait( pub(crate) fn lower_type_alias( db: &dyn SourceDatabase, - module: ModuleId, + container: ItemContainerId, alias: InFile, type_alias_id: TypeAliasId, ) -> (ExpressionStore, ExpressionStoreSourceMap, GenericParams, Box<[TypeBound]>, Option) { - let mut expr_collector = ExprCollector::new(db, module, alias.file_id, LoweringMode::Analysis); + let mut expr_collector = + ExprCollector::new(db, container.module(db), alias.file_id, LoweringMode::Analysis); let bounds = alias .value .type_bound_list() @@ -319,6 +355,21 @@ pub(crate) fn lower_type_alias( .value .ty() .map(|ty| expr_collector.lower_type_ref(ty, &mut ExprCollector::impl_trait_allocator)); + if alias.value.ty().is_none() { + let diagnostic_kind = match container { + ItemContainerId::ModuleId(_) => Some(MissingBodyItemKind::TypeAlias), + ItemContainerId::ImplId(_) => Some(MissingBodyItemKind::AssocType), + ItemContainerId::ExternBlockId(_) => None, + ItemContainerId::TraitId(_) => None, + }; + if let Some(kind) = diagnostic_kind { + let node = InFile::new(alias.file_id, SyntaxNodePtr::new(alias.value.syntax())); + expr_collector + .store + .diagnostics + .push(ExpressionStoreDiagnostics::MissingBody { node, kind }); + } + }; let (store, source_map) = expr_collector.store.finish(); (store, source_map, params, bounds, type_ref) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs index b46d258686add..c61cffd56467e 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs @@ -834,7 +834,7 @@ impl TypeAliasSignature { let source = loc.source(db); let name = as_name_opt(source.value.name()); let (store, source_map, generic_params, bounds, ty) = - lower_type_alias(db, loc.container.module(db), source, id); + lower_type_alias(db, loc.container, source, id); ( Arc::new(TypeAliasSignature { store, generic_params, flags, bounds, name, ty }), @@ -849,14 +849,6 @@ pub struct FunctionBody { pub parameters: Box<[PatId]>, } -#[derive(Debug, PartialEq, Eq)] -pub struct SimpleBody { - pub store: ExpressionStore, -} -pub type StaticBody = SimpleBody; -pub type ConstBody = SimpleBody; -pub type EnumVariantBody = SimpleBody; - #[derive(Debug, PartialEq, Eq)] pub struct VariantFieldsBody { pub store: ExpressionStore, diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index c20c6e471ac76..5c3b628f386d5 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -62,7 +62,7 @@ use crate::{ struct_tail_raw, }; -pub use hir_def::VariantId; +pub use hir_def::{VariantId, expr_store::MissingBodyItemKind}; pub use hir_ty::{ GenericArgsProhibitedReason, IncorrectGenericsLenKind, ReturnKind, diagnostics::{CaseType, IncorrectCase}, @@ -141,6 +141,7 @@ diagnostics![AnyDiagnostic<'db> -> ExpectedFunction<'db>, ExplicitDropMethodUse, FruInDestructuringAssignment, + MissingBody, FunctionalRecordUpdateOnNonStruct, GenericDefaultRefersToSelf, InactiveCode, @@ -401,6 +402,12 @@ pub struct FruInDestructuringAssignment { pub node: InFile>, } +#[derive(Debug)] +pub struct MissingBody { + pub node: InFile, + pub kind: MissingBodyItemKind, +} + #[derive(Debug)] pub struct FunctionalRecordUpdateOnNonStruct { pub base_expr: InFile, @@ -1392,6 +1399,9 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { ExpressionStoreDiagnostics::FruInDestructuringAssignment { node } => { FruInDestructuringAssignment { node: *node }.into() } + ExpressionStoreDiagnostics::MissingBody { node, kind } => { + MissingBody { node: *node, kind: *kind }.into() + } }); } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_body.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_body.rs new file mode 100644 index 0000000000000..7905336272643 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_body.rs @@ -0,0 +1,72 @@ +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: missing-body +// +// This diagnostic is triggered when a body is missing. +pub(crate) fn missing_body(ctx: &DiagnosticsContext<'_, '_>, d: &hir::MissingBody) -> Diagnostic { + let message = match d.kind { + hir::MissingBodyItemKind::AssocConst => "associated constant in `impl` without body", + hir::MissingBodyItemKind::AssocType => "associated type in `impl` without body", + hir::MissingBodyItemKind::Const => "free constant item without body", + hir::MissingBodyItemKind::Static => "free static item without body", + hir::MissingBodyItemKind::TypeAlias => "free type alias without body", + }; + Diagnostic::new_with_syntax_node_ptr(ctx, DiagnosticCode::SyntaxError, message, d.node).stable() +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn associated_const() { + check_diagnostics( + r#" +trait Foo { const BAR: u32; } +impl Foo for () { const BAR: u32; } + //^^^^^^^^^^^^^^^ error: associated constant in `impl` without body + "#, + ); + } + + #[test] + fn associated_type_impl() { + check_diagnostics( + r#" +trait Foo { type Bar; } +impl Foo for () { type Bar; } + //^^^^^^^^^ error: associated type in `impl` without body + "#, + ); + } + + #[test] + fn free_const() { + check_diagnostics( + r#" + const FOO: u32; +//^^^^^^^^^^^^^^^ error: free constant item without body + "#, + ); + } + + #[test] + fn free_static() { + check_diagnostics( + r#" + static FOO: u32; +//^^^^^^^^^^^^^^^^ error: free static item without body + "#, + ); + } + + #[test] + fn type_alias_module() { + check_diagnostics( + r#" + type Foo; +//^^^^^^^^^ error: free type alias without body + "#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs index 5d816a8d41c3c..4e941f8da0709 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -60,6 +60,7 @@ mod handlers { pub(crate) mod method_call_illegal_sized_bound; pub(crate) mod mismatched_arg_count; pub(crate) mod mismatched_array_pat_len; + pub(crate) mod missing_body; pub(crate) mod missing_fields; pub(crate) mod missing_lifetime; pub(crate) mod missing_match_arms; @@ -545,6 +546,7 @@ pub fn semantic_diagnostics( } AnyDiagnostic::UnimplementedTrait(d) => handlers::unimplemented_trait::unimplemented_trait(&ctx, &d), AnyDiagnostic::FruInDestructuringAssignment(d) => handlers::fru_in_destructuring_assignment::fru_in_destructuring_assignment(&ctx, &d), + AnyDiagnostic::MissingBody(d) => handlers::missing_body::missing_body(&ctx, &d), AnyDiagnostic::ExplicitDropMethodUse(d) => handlers::explicit_drop_method_use::explicit_drop_method_use(&ctx, &d), AnyDiagnostic::YieldOutsideCoroutine(d) => handlers::yield_outside_coroutine::yield_outside_coroutine(&ctx, &d), AnyDiagnostic::ReturnOutsideFunction(d) => handlers::return_outside_function::return_outside_function(&ctx, &d), From 49fce003dc6ea4ec932f07131f604f6a0c827b6f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 1 Sep 2026 16:35:28 +0200 Subject: [PATCH 23/38] misc: make a FIXME more specific I spent more time than I'd like to admit wrapping the condition in an _actual_ block... let's not allow this to happen again --- .../rust-analyzer/crates/hir-def/src/expr_store/lower.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index c6f07f3037616..0b7d84df30c13 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -2325,8 +2325,11 @@ impl<'db> ExprCollector<'db> { /// } /// } /// ``` - /// FIXME: Rustc wraps the condition in a construct equivalent to `{ let _t = ; _t }` - /// to preserve drop semantics. We should probably do the same in future. + /// FIXME: Rustc wraps the condition in [`DropTemps`] -- a construct equivalent to + /// `{ let _t = ; _t }` -- to preserve drop semantics. + /// We should probably do the same in future. + /// + /// [`DropTemps`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/hir/enum.ExprKind.html#variant.DropTemps fn collect_while_loop(&mut self, syntax_ptr: AstPtr, e: ast::WhileExpr) -> ExprId { let label = e.label().map(|label| { (self.hygiene_id_for(label.syntax().text_range()), self.collect_label(label)) From 6f265f5e0ae93755ca8e5eb0781686dff83dc1be Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 2 Sep 2026 22:00:10 +0200 Subject: [PATCH 24/38] document `ExprCollector::expand_macros_to_string` --- src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index 0b7d84df30c13..0a7da7903d00d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -3413,6 +3413,9 @@ impl<'db> ExprCollector<'db> { } // endregion: labels + /// While `expr` is a macro call, repeatedly expand it. If the end result is a string literal, + /// return that, along with a boolean for whether it was a direct string literal, i.e. no macro + /// calls were involved. In all other cases, return `None`. fn expand_macros_to_string(&mut self, expr: ast::Expr) -> Option<(ast::String, bool)> { let m = match expr { ast::Expr::MacroExpr(m) => m, From 91339352a1f4decfb0736095e1930a548a3658f7 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 3 Sep 2026 11:39:15 +0200 Subject: [PATCH 25/38] use `match_ast!` in `syntax::ast::expr_ext` --- .../crates/syntax/src/ast/expr_ext.rs | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs index b44150f86842c..ced9163f661af 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs @@ -12,6 +12,7 @@ use crate::{ operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}, support, }, + match_ast, }; use super::RangeItem; @@ -56,11 +57,11 @@ impl AstNode for ElseBranch { } fn cast(syntax: SyntaxNode) -> Option { - if let Some(block_expr) = ast::BlockExpr::cast(syntax.clone()) { - Some(Self::Block(block_expr)) - } else { - ast::IfExpr::cast(syntax).map(Self::IfExpr) - } + match_ast!(match syntax { + ast::BlockExpr(it) => Some(Self::Block(it)), + ast::IfExpr(it) => Some(Self::IfExpr(it)), + _ => None, + }) } fn syntax(&self) -> &SyntaxNode { @@ -342,33 +343,20 @@ impl ast::Literal { pub fn kind(&self) -> LiteralKind { let token = self.token(); - if let Some(t) = ast::IntNumber::cast(token.clone()) { - return LiteralKind::IntNumber(t); - } - if let Some(t) = ast::FloatNumber::cast(token.clone()) { - return LiteralKind::FloatNumber(t); - } - if let Some(t) = ast::String::cast(token.clone()) { - return LiteralKind::String(t); - } - if let Some(t) = ast::ByteString::cast(token.clone()) { - return LiteralKind::ByteString(t); - } - if let Some(t) = ast::CString::cast(token.clone()) { - return LiteralKind::CString(t); - } - if let Some(t) = ast::Char::cast(token.clone()) { - return LiteralKind::Char(t); - } - if let Some(t) = ast::Byte::cast(token.clone()) { - return LiteralKind::Byte(t); - } - - match token.kind() { - T![true] => LiteralKind::Bool(true), - T![false] => LiteralKind::Bool(false), - _ => unreachable!(), - } + match_ast!(match token { + ast::IntNumber(t) => LiteralKind::IntNumber(t), + ast::FloatNumber(t) => LiteralKind::FloatNumber(t), + ast::String(t) => LiteralKind::String(t), + ast::ByteString(t) => LiteralKind::ByteString(t), + ast::CString(t) => LiteralKind::CString(t), + ast::Char(t) => LiteralKind::Char(t), + ast::Byte(t) => LiteralKind::Byte(t), + _ => match token.kind() { + T![true] => LiteralKind::Bool(true), + T![false] => LiteralKind::Bool(false), + _ => unreachable!(), + }, + }) } } From cee1e08dd94c8f51dec9bf7ecb78755b2997ec14 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 11 Aug 2026 19:12:06 +0200 Subject: [PATCH 26/38] clean-up `render_variant_after_name` Instead of matching two variants and basically immediately branching on which one we've matched, match the two variants separately. --- .../crates/hir-ty/src/display.rs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index 83bcf77003eee..a87a55b3c8980 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -1237,41 +1237,41 @@ fn render_variant_after_name<'db>( memory_map: &MemoryMap<'db>, ) -> Result { let param_env = ParamEnvAndCrate { param_env, krate: f.krate() }; + let render_field = |f: &mut HirFormatter<'_, 'db>, id: LocalFieldId| { + let offset = layout.fields.offset(u32::from(id.into_raw()) as usize).bytes_usize(); + let ty = field_types[id].ty().instantiate(f.interner, args).skip_norm_wip(); + let Ok(layout) = f.db.layout_of_ty(ty.store(), param_env.store()) else { + return f.write_str(""); + }; + let size = layout.size.bytes_usize(); + render_const_scalar(f, &b[offset..offset + size], memory_map, ty) + }; match data.shape { - FieldsShape::Record | FieldsShape::Tuple => { - let render_field = |f: &mut HirFormatter<'_, 'db>, id: LocalFieldId| { - let offset = layout.fields.offset(u32::from(id.into_raw()) as usize).bytes_usize(); - let ty = field_types[id].ty().instantiate(f.interner, args).skip_norm_wip(); - let Ok(layout) = f.db.layout_of_ty(ty.store(), param_env.store()) else { - return f.write_str(""); - }; - let size = layout.size.bytes_usize(); - render_const_scalar(f, &b[offset..offset + size], memory_map, ty) - }; + FieldsShape::Record => { let mut it = data.fields().iter(); - if matches!(data.shape, FieldsShape::Record) { - write!(f, " {{")?; - if let Some((id, data)) = it.next() { - write!(f, " {}: ", data.name.display(f.db, f.edition()))?; - render_field(f, id)?; - } - for (id, data) in it { - write!(f, ", {}: ", data.name.display(f.db, f.edition()))?; - render_field(f, id)?; - } - write!(f, " }}")?; - } else { - let mut it = it.map(|it| it.0); - write!(f, "(")?; - if let Some(id) = it.next() { - render_field(f, id)?; - } - for id in it { - write!(f, ", ")?; - render_field(f, id)?; - } - write!(f, ")")?; + write!(f, " {{")?; + if let Some((id, data)) = it.next() { + write!(f, " {}: ", data.name.display(f.db, f.edition()))?; + render_field(f, id)?; + } + for (id, data) in it { + write!(f, ", {}: ", data.name.display(f.db, f.edition()))?; + render_field(f, id)?; + } + write!(f, " }}")?; + Ok(()) + } + FieldsShape::Tuple => { + let mut it = data.fields().iter().map(|it| it.0); + write!(f, "(")?; + if let Some(id) = it.next() { + render_field(f, id)?; + } + for id in it { + write!(f, ", ")?; + render_field(f, id)?; } + write!(f, ")")?; Ok(()) } FieldsShape::Unit => Ok(()), From 2eb7af10dcaaea63938ddd73a1bf59813c9fe307 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 3 Sep 2026 12:54:22 +0200 Subject: [PATCH 27/38] use correct `what` string in `size_of_sized` calls --- src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index df3debf807e1a..d6db51dec52b9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -814,8 +814,7 @@ impl<'a, 'db> Evaluator<'a, 'db> { self.read_memory(locals.ptr[*op].addr, self.ptr_size())? ); metadata = None; // Result of index is always sized - let ty_size = - self.size_of_sized(ty.ty, locals, "array inner type should be sized")?; + let ty_size = self.size_of_sized(ty.ty, locals, "array inner type")?; addr = addr.offset(ty_size * offset); } &ProjectionElem::ConstantIndex { from_end, offset } => { @@ -838,8 +837,7 @@ impl<'a, 'db> Evaluator<'a, 'db> { offset as usize }; metadata = None; // Result of index is always sized - let ty_size = - self.size_of_sized(ty.ty, locals, "array inner type should be sized")?; + let ty_size = self.size_of_sized(ty.ty, locals, "array inner type")?; addr = addr.offset(ty_size * offset); } &ProjectionElem::Subslice { from, to } => { @@ -856,8 +854,7 @@ impl<'a, 'db> Evaluator<'a, 'db> { } None => None, }; - let ty_size = - self.size_of_sized(inner_ty, locals, "array inner type should be sized")?; + let ty_size = self.size_of_sized(inner_ty, locals, "array inner type")?; addr = addr.offset(ty_size * (from as usize)); } ProjectionElem::Field(f) => { From c5862f385fd671350980d244f0a51fa8aea8ba16 Mon Sep 17 00:00:00 2001 From: Aditya-PS-05 Date: Fri, 28 Aug 2026 13:40:03 +0530 Subject: [PATCH 28/38] fix: allow inner attributes on blocks in tuple expressions --- .../crates/syntax/src/validation/block.rs | 3 +- .../validation/0031_block_inner_attrs.rast | 131 ++++++++++++------ .../validation/0031_block_inner_attrs.rs | 6 + 3 files changed, 94 insertions(+), 46 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/validation/block.rs b/src/tools/rust-analyzer/crates/syntax/src/validation/block.rs index a140153884281..f1bce931faee2 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/validation/block.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/validation/block.rs @@ -9,7 +9,8 @@ use crate::{ pub(crate) fn validate_block_expr(block: ast::BlockExpr, errors: &mut Vec) { if let Some(parent) = block.syntax().parent() { match parent.kind() { - FN | EXPR_STMT | STMT_LIST | MACRO_STMTS | LOOP_EXPR | WHILE_EXPR | FOR_EXPR => { + FN | EXPR_STMT | STMT_LIST | MACRO_STMTS | LOOP_EXPR | WHILE_EXPR | FOR_EXPR + | TUPLE_EXPR => { return; } _ => {} diff --git a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast index 8952e88e70023..1fe832d82baee 100644 --- a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast +++ b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast @@ -1,5 +1,5 @@ -SOURCE_FILE@0..611 - FN@0..610 +SOURCE_FILE@0..771 + FN@0..770 FN_KW@0..2 "fn" WHITESPACE@2..3 " " NAME@3..8 @@ -8,8 +8,8 @@ SOURCE_FILE@0..611 L_PAREN@8..9 "(" R_PAREN@9..10 ")" WHITESPACE@10..11 " " - BLOCK_EXPR@11..610 - STMT_LIST@11..610 + BLOCK_EXPR@11..770 + STMT_LIST@11..770 L_CURLY@11..12 "{" WHITESPACE@12..17 "\n " LET_STMT@17..129 @@ -151,47 +151,88 @@ SOURCE_FILE@0..611 WHITESPACE@468..473 "\n " R_CURLY@473..474 "}" WHITESPACE@474..479 "\n " - FOR_EXPR@479..608 - FOR_KW@479..482 "for" - WHITESPACE@482..483 " " - WILDCARD_PAT@483..484 - UNDERSCORE@483..484 "_" - WHITESPACE@484..485 " " - IN_KW@485..487 "in" - WHITESPACE@487..488 " " - RANGE_EXPR@488..492 - LITERAL@488..489 - INT_NUMBER@488..489 "0" - DOT2@489..491 ".." - LITERAL@491..492 - INT_NUMBER@491..492 "1" - WHITESPACE@492..493 " " - BLOCK_EXPR@493..608 - STMT_LIST@493..608 - L_CURLY@493..494 "{" - WHITESPACE@494..503 "\n " - ATTR@503..564 - POUND@503..504 "#" - BANG@504..505 "!" - L_BRACK@505..506 "[" - TOKEN_TREE_META@506..563 - PATH@506..509 - PATH_SEGMENT@506..509 - NAME_REF@506..509 - IDENT@506..509 "doc" - TOKEN_TREE@509..563 - L_PAREN@509..510 "(" - STRING@510..562 "\"This is fine, `for` ..." - R_PAREN@562..563 ")" - R_BRACK@563..564 "]" - WHITESPACE@564..573 "\n " - DOC_COMMENT@573..602 - INNER_DOC_COMMENT@573..602 "//! So are ModuleDoc ..." - WHITESPACE@602..607 "\n " - R_CURLY@607..608 "}" - WHITESPACE@608..609 "\n" - R_CURLY@609..610 "}" - WHITESPACE@610..611 "\n" + EXPR_STMT@479..608 + FOR_EXPR@479..608 + FOR_KW@479..482 "for" + WHITESPACE@482..483 " " + WILDCARD_PAT@483..484 + UNDERSCORE@483..484 "_" + WHITESPACE@484..485 " " + IN_KW@485..487 "in" + WHITESPACE@487..488 " " + RANGE_EXPR@488..492 + LITERAL@488..489 + INT_NUMBER@488..489 "0" + DOT2@489..491 ".." + LITERAL@491..492 + INT_NUMBER@491..492 "1" + WHITESPACE@492..493 " " + BLOCK_EXPR@493..608 + STMT_LIST@493..608 + L_CURLY@493..494 "{" + WHITESPACE@494..503 "\n " + ATTR@503..564 + POUND@503..504 "#" + BANG@504..505 "!" + L_BRACK@505..506 "[" + TOKEN_TREE_META@506..563 + PATH@506..509 + PATH_SEGMENT@506..509 + NAME_REF@506..509 + IDENT@506..509 "doc" + TOKEN_TREE@509..563 + L_PAREN@509..510 "(" + STRING@510..562 "\"This is fine, `for` ..." + R_PAREN@562..563 ")" + R_BRACK@563..564 "]" + WHITESPACE@564..573 "\n " + DOC_COMMENT@573..602 + INNER_DOC_COMMENT@573..602 "//! So are ModuleDoc ..." + WHITESPACE@602..607 "\n " + R_CURLY@607..608 "}" + WHITESPACE@608..613 "\n " + LET_STMT@613..768 + LET_KW@613..616 "let" + WHITESPACE@616..617 " " + IDENT_PAT@617..618 + NAME@617..618 + IDENT@617..618 "t" + WHITESPACE@618..619 " " + EQ@619..620 "=" + WHITESPACE@620..621 " " + TUPLE_EXPR@621..767 + L_PAREN@621..622 "(" + WHITESPACE@622..631 "\n " + BLOCK_EXPR@631..760 + STMT_LIST@631..760 + L_CURLY@631..632 "{" + WHITESPACE@632..645 "\n " + ATTR@645..708 + POUND@645..646 "#" + BANG@646..647 "!" + L_BRACK@647..648 "[" + TOKEN_TREE_META@648..707 + PATH@648..651 + PATH_SEGMENT@648..651 + NAME_REF@648..651 + IDENT@648..651 "doc" + TOKEN_TREE@651..707 + L_PAREN@651..652 "(" + STRING@652..706 "\"This is fine, tuple ..." + R_PAREN@706..707 ")" + R_BRACK@707..708 "]" + WHITESPACE@708..721 "\n " + DOC_COMMENT@721..750 + INNER_DOC_COMMENT@721..750 "//! So are ModuleDoc ..." + WHITESPACE@750..759 "\n " + R_CURLY@759..760 "}" + COMMA@760..761 "," + WHITESPACE@761..766 "\n " + R_PAREN@766..767 ")" + SEMICOLON@767..768 ";" + WHITESPACE@768..769 "\n" + R_CURLY@769..770 "}" + WHITESPACE@770..771 "\n" error 39..83: A block in this position cannot accept inner attributes error 152..171: A block in this position cannot accept inner attributes error 180..212: A block in this position cannot accept inner attributes diff --git a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rs b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rs index 65bb7e1914248..d3973155ef973 100644 --- a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rs +++ b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rs @@ -20,4 +20,10 @@ fn block() { #![doc("This is fine, `for` bodies accept inner attributes")] //! So are ModuleDoc comments } + let t = ( + { + #![doc("This is fine, tuple elements accept inner attributes")] + //! So are ModuleDoc comments + }, + ); } From fac655cffc8cf25a1c0931ec9c33e72984e71797 Mon Sep 17 00:00:00 2001 From: Aditya Pratap Singh Date: Fri, 4 Sep 2026 23:14:03 +0530 Subject: [PATCH 29/38] accept inner block attributes in array exprs and arg lists --- .../crates/syntax/src/validation/block.rs | 2 +- .../validation/0031_block_inner_attrs.rast | 127 +++++++++++++++++- .../validation/0031_block_inner_attrs.rs | 14 ++ 3 files changed, 135 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/validation/block.rs b/src/tools/rust-analyzer/crates/syntax/src/validation/block.rs index f1bce931faee2..00d87ba0e6876 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/validation/block.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/validation/block.rs @@ -10,7 +10,7 @@ pub(crate) fn validate_block_expr(block: ast::BlockExpr, errors: &mut Vec { + | TUPLE_EXPR | ARRAY_EXPR | ARG_LIST => { return; } _ => {} diff --git a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast index 1fe832d82baee..c75c1362116bd 100644 --- a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast +++ b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast @@ -1,5 +1,5 @@ -SOURCE_FILE@0..771 - FN@0..770 +SOURCE_FILE@0..1186 + FN@0..1185 FN_KW@0..2 "fn" WHITESPACE@2..3 " " NAME@3..8 @@ -8,8 +8,8 @@ SOURCE_FILE@0..771 L_PAREN@8..9 "(" R_PAREN@9..10 ")" WHITESPACE@10..11 " " - BLOCK_EXPR@11..770 - STMT_LIST@11..770 + BLOCK_EXPR@11..1185 + STMT_LIST@11..1185 L_CURLY@11..12 "{" WHITESPACE@12..17 "\n " LET_STMT@17..129 @@ -230,9 +230,122 @@ SOURCE_FILE@0..771 WHITESPACE@761..766 "\n " R_PAREN@766..767 ")" SEMICOLON@767..768 ";" - WHITESPACE@768..769 "\n" - R_CURLY@769..770 "}" - WHITESPACE@770..771 "\n" + WHITESPACE@768..773 "\n " + LET_STMT@773..928 + LET_KW@773..776 "let" + WHITESPACE@776..777 " " + IDENT_PAT@777..778 + NAME@777..778 + IDENT@777..778 "a" + WHITESPACE@778..779 " " + EQ@779..780 "=" + WHITESPACE@780..781 " " + ARRAY_EXPR@781..927 + L_BRACK@781..782 "[" + WHITESPACE@782..791 "\n " + BLOCK_EXPR@791..920 + STMT_LIST@791..920 + L_CURLY@791..792 "{" + WHITESPACE@792..805 "\n " + ATTR@805..868 + POUND@805..806 "#" + BANG@806..807 "!" + L_BRACK@807..808 "[" + TOKEN_TREE_META@808..867 + PATH@808..811 + PATH_SEGMENT@808..811 + NAME_REF@808..811 + IDENT@808..811 "doc" + TOKEN_TREE@811..867 + L_PAREN@811..812 "(" + STRING@812..866 "\"This is fine, array ..." + R_PAREN@866..867 ")" + R_BRACK@867..868 "]" + WHITESPACE@868..881 "\n " + DOC_COMMENT@881..910 + INNER_DOC_COMMENT@881..910 "//! So are ModuleDoc ..." + WHITESPACE@910..919 "\n " + R_CURLY@919..920 "}" + COMMA@920..921 "," + WHITESPACE@921..926 "\n " + R_BRACK@926..927 "]" + SEMICOLON@927..928 ";" + WHITESPACE@928..933 "\n " + EXPR_STMT@933..1054 + CALL_EXPR@933..1053 + PATH_EXPR@933..934 + PATH@933..934 + PATH_SEGMENT@933..934 + NAME_REF@933..934 + IDENT@933..934 "g" + ARG_LIST@934..1053 + L_PAREN@934..935 "(" + BLOCK_EXPR@935..1052 + STMT_LIST@935..1052 + L_CURLY@935..936 "{" + WHITESPACE@936..945 "\n " + ATTR@945..1008 + POUND@945..946 "#" + BANG@946..947 "!" + L_BRACK@947..948 "[" + TOKEN_TREE_META@948..1007 + PATH@948..951 + PATH_SEGMENT@948..951 + NAME_REF@948..951 + IDENT@948..951 "doc" + TOKEN_TREE@951..1007 + L_PAREN@951..952 "(" + STRING@952..1006 "\"This is fine, call a ..." + R_PAREN@1006..1007 ")" + R_BRACK@1007..1008 "]" + WHITESPACE@1008..1017 "\n " + DOC_COMMENT@1017..1046 + INNER_DOC_COMMENT@1017..1046 "//! So are ModuleDoc ..." + WHITESPACE@1046..1051 "\n " + R_CURLY@1051..1052 "}" + R_PAREN@1052..1053 ")" + SEMICOLON@1053..1054 ";" + WHITESPACE@1054..1059 "\n " + EXPR_STMT@1059..1183 + METHOD_CALL_EXPR@1059..1182 + PATH_EXPR@1059..1060 + PATH@1059..1060 + PATH_SEGMENT@1059..1060 + NAME_REF@1059..1060 + IDENT@1059..1060 "s" + DOT@1060..1061 "." + NAME_REF@1061..1062 + IDENT@1061..1062 "m" + ARG_LIST@1062..1182 + L_PAREN@1062..1063 "(" + BLOCK_EXPR@1063..1181 + STMT_LIST@1063..1181 + L_CURLY@1063..1064 "{" + WHITESPACE@1064..1073 "\n " + ATTR@1073..1137 + POUND@1073..1074 "#" + BANG@1074..1075 "!" + L_BRACK@1075..1076 "[" + TOKEN_TREE_META@1076..1136 + PATH@1076..1079 + PATH_SEGMENT@1076..1079 + NAME_REF@1076..1079 + IDENT@1076..1079 "doc" + TOKEN_TREE@1079..1136 + L_PAREN@1079..1080 "(" + STRING@1080..1135 "\"This is fine, method ..." + R_PAREN@1135..1136 ")" + R_BRACK@1136..1137 "]" + WHITESPACE@1137..1146 "\n " + DOC_COMMENT@1146..1175 + INNER_DOC_COMMENT@1146..1175 "//! So are ModuleDoc ..." + WHITESPACE@1175..1180 "\n " + R_CURLY@1180..1181 "}" + R_PAREN@1181..1182 ")" + SEMICOLON@1182..1183 ";" + WHITESPACE@1183..1184 "\n" + R_CURLY@1184..1185 "}" + WHITESPACE@1185..1186 "\n" error 39..83: A block in this position cannot accept inner attributes error 152..171: A block in this position cannot accept inner attributes error 180..212: A block in this position cannot accept inner attributes diff --git a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rs b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rs index d3973155ef973..870be17c07c1c 100644 --- a/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rs +++ b/src/tools/rust-analyzer/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rs @@ -26,4 +26,18 @@ fn block() { //! So are ModuleDoc comments }, ); + let a = [ + { + #![doc("This is fine, array elements accept inner attributes")] + //! So are ModuleDoc comments + }, + ]; + g({ + #![doc("This is fine, call arguments accept inner attributes")] + //! So are ModuleDoc comments + }); + s.m({ + #![doc("This is fine, method call arguments accept attributes")] + //! So are ModuleDoc comments + }); } From 0b30592405f31dd3cadb55730ccd9631afa4d239 Mon Sep 17 00:00:00 2001 From: Angad Tendulkar Date: Fri, 28 Aug 2026 20:51:08 -0400 Subject: [PATCH 30/38] Follow symlinks when discovering prebuilt rustc proc-macro dylibs The scan of the target libdir for rustc_macros & co. used DirEntry::file_type(), which does not follow symlinks. Toolchains assembled out of symlinks (e.g. by nix / oxalica's rust-overlay) link every dylib into the sysroot, so all proc-macro dylibs were skipped. As a result `rustc_queries!` never expanded for rustc_private projects and the macro-generated TyCtxt query getters (`tcx.mir_keys(())` etc.) did not resolve at all. Use fs::metadata, which traverses symlinks, instead. Co-Authored-By: Claude Fable 5 --- .../crates/project-model/src/build_dependencies.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs index 8da0a574e7645..40cf975e4522b 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs @@ -211,7 +211,10 @@ impl WorkspaceBuildScripts { let proc_macro_dylibs: Vec<(String, AbsPathBuf)> = std::fs::read_dir(target_libdir)? .filter_map(|entry| { let dir_entry = entry.ok()?; - if dir_entry.file_type().ok()?.is_file() { + // Use `fs::metadata` rather than `DirEntry::file_type` so that symlinks + // are followed; sysroots assembled out of symlinks (e.g. by nix) link + // the proc-macro dylibs into the target libdir. + if std::fs::metadata(dir_entry.path()).ok()?.is_file() { let path = dir_entry.path(); let extension = path.extension()?; if extension == std::env::consts::DLL_EXTENSION { From 876f11bbaf8720b779c8d1e5e03b1f5fc68cb0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 5 Sep 2026 13:01:53 +0200 Subject: [PATCH 31/38] Do not execute `llvm-config` in the `FileCheck` step when cross-compiling --- src/bootstrap/src/core/build_steps/llvm.rs | 49 ++++++++++++---------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index f1864c736ed03..abd3465f67235 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -2231,30 +2231,37 @@ impl Step for FileCheck { }; // There is a LLVM config set, take filecheck from it - // Note: because `download-ci-llvm` currently overrides `llvm-config`, when the LLVM is - // downloaded, we go through this branch. Ideally, this should be changed so that - // `download-ci-llvm` doesn't override the config. - if let Some(s) = target_config.and_then(|c| c.llvm_config.as_ref()) { - let llvm_bindir = command(s).arg("--bindir").run_capture_stdout(builder).stdout(); - let filecheck = Path::new(llvm_bindir.trim()).join(exe("FileCheck", self.target)); - let filecheck = if filecheck.exists() { + if let Some(llvm_config) = target_config.and_then(|c| c.llvm_config.as_ref()) { + // We can only execute llvm-config if we're on the same host target + return if builder.is_host_target(self.target) { + let llvm_bindir = + command(llvm_config).arg("--bindir").run_capture_stdout(builder).stdout(); + let filecheck = Path::new(llvm_bindir.trim()).join(exe("FileCheck", self.target)); + let filecheck = if filecheck.exists() { + filecheck + } else { + // On Fedora the system LLVM installs FileCheck in the + // llvm subdirectory of the libdir. + let llvm_libdir = + command(llvm_config).arg("--libdir").run_capture_stdout(builder).stdout(); + let lib_filecheck = Path::new(llvm_libdir.trim()) + .join("llvm") + .join(exe("FileCheck", self.target)); + if lib_filecheck.exists() { + lib_filecheck + } else { + // Return the most normal file name, even though + // it doesn't exist, so that any error message + // refers to that. + filecheck + } + }; filecheck } else { - // On Fedora the system LLVM installs FileCheck in the - // llvm subdirectory of the libdir. - let llvm_libdir = command(s).arg("--libdir").run_capture_stdout(builder).stdout(); - let lib_filecheck = - Path::new(llvm_libdir.trim()).join("llvm").join(exe("FileCheck", self.target)); - if lib_filecheck.exists() { - lib_filecheck - } else { - // Return the most normal file name, even though - // it doesn't exist, so that any error message - // refers to that. - filecheck - } + // In other cases, just guess that Filecheck is available in the same directory + // as the llvm-config + llvm_config.parent().unwrap().join(exe("FileCheck", self.target)) }; - return filecheck; } // Here we take the filecheck from LLVM directly let llvm_output = builder.ensure(Llvm { target: self.target }); From f85fa6326bf42ef9891b496a15133af495e1a3bd Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Sat, 5 Sep 2026 21:14:41 +0200 Subject: [PATCH 32/38] tempfile cannot be constructed --- .../rust-analyzer/crates/stdx/src/tempfile.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/stdx/src/tempfile.rs b/src/tools/rust-analyzer/crates/stdx/src/tempfile.rs index fe9ae83ef539e..8907f4ca18b80 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/tempfile.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/tempfile.rs @@ -91,7 +91,7 @@ mod general_imp { INTERNAL_COUNTER.fetch_add(1, Ordering::AcqRel), )); let mut open_options = OpenOptions::new(); - open_options.create_new(true); + open_options.write(true).create_new(true); match create(open_options, &path) { Err(e) if e.kind() == ErrorKind::AlreadyExists => {} Err(e) => { @@ -187,3 +187,19 @@ mod imp { Ok(NamedTempFile { _file: Some(file), path, delete_on_drop: true }) } } + +#[cfg(test)] +mod tests { + use std::fs; + + use super::*; + + #[test] + fn named_temp_file_new_creates_file() { + let file = NamedTempFile::new("test-").unwrap(); + assert!(file.path().exists()); + let path = file.path().as_os_str().to_owned(); + drop(file); + assert!(!fs::exists(path).unwrap()); + } +} From d4e6b8f2460d08f8028c71e5383a6300eaec1a9f Mon Sep 17 00:00:00 2001 From: malezjaa Date: Sun, 6 Sep 2026 20:17:40 +0200 Subject: [PATCH 33/38] fix bare urls split text --- src/librustdoc/passes/lint/bare_urls.rs | 27 +++++++++++++++---------- tests/rustdoc-ui/lints/bare-urls.fixed | 4 ++++ tests/rustdoc-ui/lints/bare-urls.rs | 4 ++++ tests/rustdoc-ui/lints/bare-urls.stderr | 14 ++++++++++++- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs index 0928980e390a8..287a1f50b5aa1 100644 --- a/src/librustdoc/passes/lint/bare_urls.rs +++ b/src/librustdoc/passes/lint/bare_urls.rs @@ -2,13 +2,14 @@ //! Suggests wrapping the link with angle brackets: `Go to .` to linkify it. use core::ops::Range; -use std::mem; use std::sync::LazyLock; use regex::Regex; use rustc_errors::{Applicability, DiagDecorator}; use rustc_hir::HirId; -use rustc_resolve::rustdoc::pulldown_cmark::{Event, Parser, Tag}; +use rustc_resolve::rustdoc::pulldown_cmark::{ + DefaultBrokenLinkCallback, Event, Tag, TextMergeWithOffset, +}; use rustc_resolve::rustdoc::source_span_for_markdown_range; use tracing::trace; @@ -55,21 +56,20 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & ); }; - let mut p = Parser::new_ext(dox, main_body_opts()).into_offset_iter(); + // pulldown-cmark can split a URL into multiple `Text` events while processing + // characters such as `_` according to CommonMark's emphasis rules. + // `TextMergeWithOffset` merges these events so we can check the complete URL. + let mut p = TextMergeWithOffset::::new_ext(dox, main_body_opts()); while let Some((event, range)) = p.next() { match event { Event::Text(s) => find_raw_urls(cx, dox, &s, range, &report_diag), // We don't want to check the text inside code blocks or links. Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link { .. })) => { + let end = tag.to_end(); for (event, _) in p.by_ref() { - match event { - Event::End(end) - if mem::discriminant(&end) == mem::discriminant(&tag.to_end()) => - { - break; - } - _ => {} + if matches!(event, Event::End(tag) if tag == end) { + break; } } } @@ -83,7 +83,12 @@ static URL_REGEX: LazyLock = LazyLock::new(|| { r"https?://", // url scheme r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", // one or more subdomains r"[a-zA-Z]{2,63}", // root domain - r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)", // optional query or url fragments + // Match URL characters and balanced parenthesized segments, without + // consuming a trailing `)` that belongs to the surrounding prose. + r"\b(?:", + r"[-a-zA-Z0-9@:%_\+.~#?&/=]", + r"|\([-a-zA-Z0-9@:%_\+.~#?&/=]*\)", + r")*", )) .expect("failed to build regex") }); diff --git a/tests/rustdoc-ui/lints/bare-urls.fixed b/tests/rustdoc-ui/lints/bare-urls.fixed index 996214b5ff14f..b18aae11c77cf 100644 --- a/tests/rustdoc-ui/lints/bare-urls.fixed +++ b/tests/rustdoc-ui/lints/bare-urls.fixed @@ -92,3 +92,7 @@ pub fn trailing_period() {} /// ] //~^ ERROR this URL is not a hyperlink pub fn lint_with_brackets() {} + +/// See +//~^ ERROR this URL is not a hyperlink +pub fn hippo() {} diff --git a/tests/rustdoc-ui/lints/bare-urls.rs b/tests/rustdoc-ui/lints/bare-urls.rs index 9b4fe68e00322..fb39ec6b6ccbd 100644 --- a/tests/rustdoc-ui/lints/bare-urls.rs +++ b/tests/rustdoc-ui/lints/bare-urls.rs @@ -92,3 +92,7 @@ pub fn trailing_period() {} /// https://bloob.blob] //~^ ERROR this URL is not a hyperlink pub fn lint_with_brackets() {} + +/// See https://en.wikipedia.org/wiki/Rust_(programming_language) +//~^ ERROR this URL is not a hyperlink +pub fn hippo() {} diff --git a/tests/rustdoc-ui/lints/bare-urls.stderr b/tests/rustdoc-ui/lints/bare-urls.stderr index 05ddd2ed42ab1..a3a291e8e4bca 100644 --- a/tests/rustdoc-ui/lints/bare-urls.stderr +++ b/tests/rustdoc-ui/lints/bare-urls.stderr @@ -364,5 +364,17 @@ help: use an automatic link instead LL | /// ] | + + -error: aborting due to 30 previous errors +error: this URL is not a hyperlink + --> $DIR/bare-urls.rs:96:9 + | +LL | /// See https://en.wikipedia.org/wiki/Rust_(programming_language) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +help: use an automatic link instead + | +LL | /// See + | + + + +error: aborting due to 31 previous errors From cf7c0ffef791c6d9786a08449f697347ec3ae180 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 6 Sep 2026 22:06:44 +0100 Subject: [PATCH 34/38] std: fix set_permissions_nofollow on espidf and horizon read(true) was chained onto custom_flags(O_NOFOLLOW) inside a cfg block that excludes those two targets, so their OpenOptions had no access mode set and open() returned EINVAL before any chmod happened. Neither target has an fchmodat arm either, so set_permissions_nofollow could never succeed there. --- library/std/src/sys/fs/unix.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 5d5eae5b26a19..613397e6903c1 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1911,6 +1911,7 @@ pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { use crate::fs::{OpenOptions, Permissions}; let mut options = OpenOptions::new(); + options.read(true); // ESP-IDF and Horizon do not support O_NOFOLLOW, so we skip setting it. // Their filesystems do not have symbolic links, so no special handling is required. @@ -1920,7 +1921,7 @@ pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { use crate::os::unix::fs::OpenOptionsExt; #[cfg(target_os = "wasi")] use crate::os::wasi::fs::OpenOptionsExt; - options.read(true).custom_flags(libc::O_NOFOLLOW); + options.custom_flags(libc::O_NOFOLLOW); } // SAFETY: Since this function is called with `with_native_path` From ff721141e7cccf9aeb3bb7ccc49c58e981245b20 Mon Sep 17 00:00:00 2001 From: Daedalus <16168171+RedDaedalus@users.noreply.github.com> Date: Sun, 6 Sep 2026 15:43:33 -0600 Subject: [PATCH 35/38] remove outdated UnsafeCell raw_get comment --- library/core/src/cell.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index e8cd3a500084a..d332908954b8f 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -2494,8 +2494,7 @@ impl UnsafeCell { #[rustc_diagnostic_item = "unsafe_cell_raw_get"] pub const fn raw_get(this: *const Self) -> *mut T { // We can just cast the pointer from `UnsafeCell` to `T` because of - // #[repr(transparent)]. This exploits std's special status, there is - // no guarantee for user code that this will work in future versions of the compiler! + // #[repr(transparent)]. this as *const T as *mut T } From 14989dc2c2d75acf0c09a38ffea2d099cb095876 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 5 Sep 2026 20:11:57 +0200 Subject: [PATCH 36/38] add regression test for packus_epi16 issue --- tests/assembly-llvm/x86-vendor-intrinsics.rs | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/assembly-llvm/x86-vendor-intrinsics.rs diff --git a/tests/assembly-llvm/x86-vendor-intrinsics.rs b/tests/assembly-llvm/x86-vendor-intrinsics.rs new file mode 100644 index 0000000000000..b600b74c66ecd --- /dev/null +++ b/tests/assembly-llvm/x86-vendor-intrinsics.rs @@ -0,0 +1,21 @@ +// Output differs depending on ABI so we need to match the full target. +//@ only-x86_64-unknown-linux-gnu +//@ assembly-output: emit-asm +//@ compile-flags: -Ctarget-feature=-sse3 -C opt-level=3 + +// Regression test for various cases where we used to compile x86 vendor intrinsics in a suboptimal +// way. + +#![crate_type = "lib"] + +use std::arch::x86_64::*; + +// CHECK-LABEL: test_packus_epi16: +#[unsafe(no_mangle)] +#[target_feature(enable = "sse2")] +extern "C" fn test_packus_epi16(a: __m128i, b: __m128i) -> __m128i { + // CHECK: .cfi_startproc + // CHECK-NEXT: packuswb + // CHECK-NEXT: ret + _mm_packus_epi16(a, b) +} From 2ece704fb83a98d24927d15372bf202bec74374f Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 7 Sep 2026 04:21:13 +0000 Subject: [PATCH 37/38] Prepare for merging from rust-lang/rust This updates the rust-version file to 32d94cc9be3f6e6c3fa1deaea9e0ab93c4980dba. --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index 46cba50173768..18fea436747c7 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -59dabe56f7b78d9dd427645cd7f8e7fd426724cc +32d94cc9be3f6e6c3fa1deaea9e0ab93c4980dba From a490ea63fa80ae94509ceff014ad0a8a82b65268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 5 Sep 2026 13:03:23 +0200 Subject: [PATCH 38/38] Allow overriding `llvm-filecheck` even if LLVM is otherwise downloaded or built --- src/bootstrap/src/core/build_steps/llvm.rs | 6 +++--- src/bootstrap/src/core/config/config.rs | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index abd3465f67235..91a6ecf4d6bb9 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -2237,7 +2237,8 @@ impl Step for FileCheck { let llvm_bindir = command(llvm_config).arg("--bindir").run_capture_stdout(builder).stdout(); let filecheck = Path::new(llvm_bindir.trim()).join(exe("FileCheck", self.target)); - let filecheck = if filecheck.exists() { + + if filecheck.exists() { filecheck } else { // On Fedora the system LLVM installs FileCheck in the @@ -2255,8 +2256,7 @@ impl Step for FileCheck { // refers to that. filecheck } - }; - filecheck + } } else { // In other cases, just guess that Filecheck is available in the same directory // as the llvm-config diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index f87e0780ce49c..a513c45bce9b1 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -1008,11 +1008,6 @@ impl Config { target.llvm_has_rust_patches = Some(patches); } if let Some(ref s) = target_llvm_filecheck { - if target_llvm_config.is_none() { - panic!( - "You must also configure `llvm-config` when setting `llvm-filecheck` for target {triple}", - ); - } target.llvm_filecheck = Some(src.join(s)); } target.llvm_libunwind = target_llvm_libunwind.as_ref().map(|v| {