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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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 cccde2f30b3d0ff3739fa78c9d634907f38eace6 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 26 Jul 2026 20:54:37 +0200 Subject: [PATCH 29/53] add placeholder for amdgpu --- tests/codegen-llvm/complex-abi.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index 81d93b704ee1c..d7a881a0b3bfa 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -83,6 +83,10 @@ // [NVPTX] compile-flags: --target nvptx64-nvidia-cuda // [NVPTX] needs-llvm-components: nvptx +// revisions: AMDGPU +// [AMDGPU] compile-flags: --target amdgcn-amd-amdhsa -Ctarget-cpu=gfx900 +// [AMDGPU] needs-llvm-components: amdgpu + // revisions: BPF // [BPF] compile-flags: --target bpfel-unknown-none // [BPF] needs-llvm-components: bpf From 838270f2b68a0983fb09828a5dab061cb8dd8392 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 13 Aug 2026 14:22:00 +0200 Subject: [PATCH 30/53] add `TyAndLayout::complex_number` --- compiler/rustc_abi/src/layout/ty.rs | 54 +++++++++++++++-------------- compiler/rustc_abi/src/lib.rs | 25 +++++++++++++ 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index 7c878cc619472..27ec34f519870 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -7,7 +7,7 @@ use rustc_macros::StableHash; use crate::layout::{FieldIdx, VariantIdx}; use crate::{ - AbiAlign, Align, BackendRepr, FieldsShape, Float, HasDataLayout, LayoutData, Niche, + AbiAlign, Align, BackendRepr, FieldsShape, Float, HasDataLayout, LayoutData, Niche, Numeric, PointeeInfo, Primitive, Size, Variants, }; @@ -229,12 +229,12 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { } /// Returns `true` if this type needs to match the ABI of the C `_Complex` type. See - /// [`TyAndLayout::complex_number_primitive`] for details. + /// [`TyAndLayout::complex_number`] for details. pub fn is_complex_number(self, cx: &C) -> bool where Ty: TyAbiInterface<'a, C> + Copy, { - self.complex_number_primitive(cx).is_some() + self.complex_number(cx).is_some() } pub fn is_scalable_vector(self) -> bool @@ -302,9 +302,11 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { } /// If this type should match the ABI of the C `_Complex` type, returns the primitive that is - /// used for its parts. This only returns `Some(T)` for `core::num::Complex` where `T` is + /// used for its components. + /// + /// This function only returns `Some(T)` for `core::num::Complex` where `T` is /// either a float or an integer. `repr(transparent)` wrapper types are automatically handled. - pub fn complex_number_primitive(&self, cx: &C) -> Option + pub fn complex_number(&self, cx: &C) -> Option where Ty: TyAbiInterface<'a, C> + Copy, { @@ -313,35 +315,35 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { return None; } - let part = complex.field(cx, 0).peel_transparent_wrappers(cx); - - if let BackendRepr::Scalar(scalar) = part.backend_repr { - // Only Complex<{ float }> and Complex<{ integer }> have special layout. - let primitive = scalar.primitive(); - match primitive { - // Explicitly spell out all the float types so that any new ones have to be added to - // one of the match branches. - Primitive::Int(..) - | Primitive::Float(Float::F16 | Float::F32 | Float::F64 | Float::F128) => { - Some(primitive) - } - Primitive::Pointer(..) => None, + let component = complex.field(cx, 0).peel_transparent_wrappers(cx); + + let BackendRepr::Scalar(scalar) = component.backend_repr else { + return None; + }; + + // Only Complex<{ float }> and Complex<{ integer }> have special layout. + // + // Explicitly spell out all the float types so that any new ones have to be added to + // one of the match branches. + let primitive = scalar.primitive(); + match primitive { + Primitive::Int(integer, is_signed) => Some(Numeric::Int(integer, is_signed)), + Primitive::Float(float @ (Float::F16 | Float::F32 | Float::F64 | Float::F128)) => { + Some(Numeric::Float(float)) } - } else { - None + Primitive::Pointer(..) => None, } } - /// Returns `Some` if this type has the ABI of the C `_Complex` type with float parts. See - /// [`TyAndLayout::complex_number_primitive`] for details. + /// Returns `Some` if this type has the ABI of the C `_Complex` type with float components. + /// See [`TyAndLayout::complex_number`] for details. pub fn complex_float(&self, cx: &C) -> Option where Ty: TyAbiInterface<'a, C> + Copy, { - if let Some(Primitive::Float(float)) = self.complex_number_primitive(cx) { - Some(float) - } else { - None + match self.complex_number(cx) { + Some(Numeric::Float(float)) => Some(float), + _ => None, } } diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index 7cfb93ca1b86d..a306b494ceced 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -1451,6 +1451,31 @@ impl Float { } } +/// Numeric primitives. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "nightly", derive(StableHash))] +pub enum Numeric { + /// The `bool` is the signedness of the `Integer` type. + Int(Integer, bool), + Float(Float), +} + +impl Numeric { + pub fn size(self) -> Size { + match self { + Numeric::Int(integer, _) => integer.size(), + Numeric::Float(float) => float.size(), + } + } + + pub fn reg_kind(self) -> RegKind { + match self { + Numeric::Int(_, _) => RegKind::Integer, + Numeric::Float(_) => RegKind::Float, + } + } +} + /// Fundamental unit of memory access and layout. #[derive(Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "nightly", derive(StableHash))] From 821527b61128e78ba66b7f635245d909f2535f39 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 14 Jul 2026 23:43:42 +0200 Subject: [PATCH 31/53] make SPARC64 `Complex` ABI GCC-compatible --- compiler/rustc_target/src/callconv/sparc64.rs | 10 ++++++++-- tests/codegen-llvm/complex-abi.rs | 20 +++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_target/src/callconv/sparc64.rs b/compiler/rustc_target/src/callconv/sparc64.rs index 7e441d7100c39..bfaa3f3cb19c8 100644 --- a/compiler/rustc_target/src/callconv/sparc64.rs +++ b/compiler/rustc_target/src/callconv/sparc64.rs @@ -1,7 +1,7 @@ use arrayvec::ArrayVec; use rustc_abi::{ - Align, BackendRepr, FieldsShape, Float, HasDataLayout, Primitive, Reg, Size, TyAbiInterface, - TyAndLayout, Variants, + Align, BackendRepr, FieldsShape, Float, HasDataLayout, Integer, Numeric, Primitive, Reg, + RegKind, Size, TyAbiInterface, TyAndLayout, Variants, }; use crate::callconv::{ArgAbi, ArgAttribute, CastTarget, FnAbi, Uniform}; @@ -143,6 +143,12 @@ fn classify_arg<'a, Ty, C>( *total_double_word_count = start_double_word_count + double_word_count; + // Compatible with GCC and Clang 24. + if let Some(Numeric::Int(Integer::I8 | Integer::I16, _)) = arg.layout.complex_number(cx) { + arg.cast_to(Reg { kind: RegKind::Integer, size: total }); + return; + } + const ARGUMENT_REGISTERS: usize = 8; let mut double_words = [DoubleWord::Words([Word::Integer; 2]); ARGUMENT_REGISTERS / 2]; diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index d7a881a0b3bfa..d4008e63a0015 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -61,8 +61,16 @@ //@ [CSKY] compile-flags: --target csky-unknown-linux-gnuabiv2 //@ [CSKY] needs-llvm-components: csky +//@ revisions: SPARC64 +//@ [SPARC64] compile-flags: --target sparc64-unknown-linux-gnu +//@ [SPARC64] needs-llvm-components: sparc + // FIXME: the below revisions are deliberately disabled for now. +// revisions: SPARC +// [SPARC] compile-flags: --target sparc-unknown-linux-gnu +// [SPARC] needs-llvm-components: sparc + // revisions: POWERPC POWERPC64LE POWERPC64 AIX // [POWERPC] compile-flags: --target powerpc-unknown-linux-gnu // [POWERPC] needs-llvm-components: powerpc @@ -143,7 +151,7 @@ pub extern "C" fn cplx_f32(x: Complex) -> Complex { // RISCV32: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // RISCV64: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // S390X: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} {{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) + // SPARC64: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // SPARC: define{{.*}} { float, float } @cplx_f32(ptr {{.*}} byval({ float, float }) {{.*}}) // WASM32: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) @@ -177,7 +185,7 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { // RISCV32: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // RISCV64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // S390X: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) + // SPARC64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // SPARC: define{{.*}} { double, double } @cplx_f64(ptr {{.*}} byval({ double, double }) {{.*}}) // WASM32: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) @@ -225,7 +233,7 @@ pub extern "C" fn cplx_i8(x: Complex) -> Complex { // RISCV32: define{{.*}} i32 @cplx_i8(i32{{.*}}) // RISCV64: define{{.*}} i64 @cplx_i8(i64{{.*}}) // S390X: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} {{.*}} i64 @cplx_i8(i64{{.*}}) + // SPARC64: define{{.*}} i16 @cplx_i8(i16{{.*}}) // SPARC: define{{.*}} { i8, i8 } @cplx_i8(ptr {{.*}} byval({ i8, i8 }) {{.*}}) // WASM32: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) @@ -259,7 +267,7 @@ pub extern "C" fn cplx_i16(x: Complex) -> Complex { // RISCV32: define{{.*}} i32 @cplx_i16(i32 {{.*}}) // RISCV64: define{{.*}} i64 @cplx_i16(i64{{.*}}) // S390X: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} {{.*}} i64 @cplx_i16(i64{{.*}}) + // SPARC64: define{{.*}} i32 @cplx_i16(i32{{.*}}) // SPARC: define{{.*}} { i16, i16 } @cplx_i16(ptr {{.*}} byval({ i16, i16 }) {{.*}}) // WASM32: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) @@ -327,7 +335,7 @@ pub extern "C" fn cplx_i64(x: Complex) -> Complex { // RISCV32: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // RISCV64: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) // S390X: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) + // SPARC64: define{{.*}} { i64, i64 } @cplx_i64({ i64, i64 } {{.*}}) // SPARC: define{{.*}} { i64, i64 } @cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) // WASM32: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) @@ -366,7 +374,7 @@ pub extern "C" fn wrapper_cplx_i64( // RISCV32: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // RISCV64: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) // S390X: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) + // SPARC64: define { i64, i64 } @wrapper_cplx_i64({ i64, i64 } {{.*}}) // SPARC: define{{.*}} { i64, i64 } @wrapper_cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) // WASM32: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) From 9e53b2693213954cc8f047fb3952bb69bb044f4c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 24 Aug 2026 21:41:30 +0200 Subject: [PATCH 32/53] make PowerPC64 `Complex` ABI GCC-compatible --- .../rustc_target/src/callconv/powerpc64.rs | 15 +++++- tests/codegen-llvm/complex-abi.rs | 52 ++++++++++--------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_target/src/callconv/powerpc64.rs b/compiler/rustc_target/src/callconv/powerpc64.rs index 3eb40abe90f33..ad0f4787ed7ba 100644 --- a/compiler/rustc_target/src/callconv/powerpc64.rs +++ b/compiler/rustc_target/src/callconv/powerpc64.rs @@ -2,9 +2,9 @@ // Alignment of 128 bit types is not currently handled, this will // need to be fixed when PowerPC vector support is added. -use rustc_abi::{HasDataLayout, TyAbiInterface}; +use rustc_abi::{HasDataLayout, Integer, Numeric, TyAbiInterface}; -use crate::callconv::{Align, ArgAbi, FnAbi, Reg, RegKind, Uniform}; +use crate::callconv::{Align, ArgAbi, CastTarget, FnAbi, Reg, RegKind, Uniform}; use crate::spec::{HasTargetSpec, LlvmAbi, Os}; #[derive(Debug, Clone, Copy, PartialEq)] @@ -60,6 +60,17 @@ where arg.extend_integer_width_to(64); return; } + if let Some(component) = arg.layout.complex_number(cx) { + if let Numeric::Int(Integer::I16, _) = component { + // FIXME: use `PassMode::Cast` here. In LLVM 23 doing so would hit + // https://github.com/llvm/llvm-project/issues/218676. + return; + } + + let reg = Reg { kind: component.reg_kind(), size: component.size() }; + arg.cast_to(CastTarget::pair(reg, reg)); + return; + } // The AIX ABI expect byval for aggregates // See https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/Targets/PPC.cpp. diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index d4008e63a0015..4ba7ecad764f2 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -65,21 +65,23 @@ //@ [SPARC64] compile-flags: --target sparc64-unknown-linux-gnu //@ [SPARC64] needs-llvm-components: sparc +//@ revisions: POWERPC64LE POWERPC64 AIX +//@ [POWERPC64LE] compile-flags: --target powerpc64le-unknown-linux-gnu +//@ [POWERPC64LE] needs-llvm-components: powerpc +//@ [POWERPC64] compile-flags: --target powerpc64-unknown-linux-gnu +//@ [POWERPC64] needs-llvm-components: powerpc +//@ [AIX] compile-flags: --target powerpc64-ibm-aix +//@ [AIX] needs-llvm-components: powerpc + // FIXME: the below revisions are deliberately disabled for now. // revisions: SPARC // [SPARC] compile-flags: --target sparc-unknown-linux-gnu // [SPARC] needs-llvm-components: sparc -// revisions: POWERPC POWERPC64LE POWERPC64 AIX +// revisions: POWERPC // [POWERPC] compile-flags: --target powerpc-unknown-linux-gnu // [POWERPC] needs-llvm-components: powerpc -// [POWERPC64LE] compile-flags: --target powerpc64le-unknown-linux-gnu -// [POWERPC64LE] needs-llvm-components: powerpc -// [POWERPC64] compile-flags: --target powerpc64-unknown-linux-gnu -// [POWERPC64] needs-llvm-components: powerpc -// [AIX] compile-flags: --target powerpc64-ibm-aix -// [AIX] needs-llvm-components: powerpc // revisions: MIPS64EL MIPS // [MIPS64EL] compile-flags: --target mips64el-unknown-linux-gnuabi64 @@ -134,7 +136,7 @@ pub extern "C" fn cplx_f32(x: Complex) -> Complex { // AARCH64: define{{.*}} [2 x float] @cplx_f32([2 x float] {{.*}}) // AARCH64_DARWIN: define{{.*}} [2 x float] @cplx_f32([2 x float] {{.*}}) // AARCH64_MSVC: define{{.*}} [2 x float] @cplx_f32([2 x float] {{.*}}) - // AIX: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) + // AIX: define{{.*}} { float, float } @cplx_f32({ float, float } %0) // ARM64EC: define{{.*}} [2 x float] @cplx_f32([2 x float] {{.*}}) // ARM: define{{.*}} [2 x float] @cplx_f32([2 x float] {{.*}}) // BPF: define{{.*}} void @cplx_f32(ptr {{.*}} sret({ float, float }) {{.*}}, i64 {{.*}}) @@ -145,8 +147,8 @@ pub extern "C" fn cplx_f32(x: Complex) -> Complex { // MIPS64EL: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) // MIPS: define{{.*}} { float, float } @cplx_f32([2 x i32] {{.*}}) // NVPTX: define{{.*}} { float, float } @cplx_f32(ptr {{.*}} byval({ float, float }) {{.*}}) - // POWERPC64: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) - // POWERPC64LE: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) + // POWERPC64: define{{.*}} { float, float } @cplx_f32({ float, float } %0) + // POWERPC64LE: define{{.*}} { float, float } @cplx_f32({ float, float } %0) // POWERPC: define{{.*}} void @cplx_f32(ptr {{.*}} sret({ float, float }) {{.*}}, ptr {{.*}} byval({ float, float }) {{.*}}) // RISCV32: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // RISCV64: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) @@ -168,7 +170,7 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { // AARCH64: define{{.*}} [2 x double] @cplx_f64([2 x double] {{.*}}) // AARCH64_DARWIN: define{{.*}} [2 x double] @cplx_f64([2 x double] {{.*}}) // AARCH64_MSVC: define{{.*}} [2 x double] @cplx_f64([2 x double] {{.*}}) - // AIX: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) + // AIX: define{{.*}} { double, double } @cplx_f64({ double, double } %0) // ARM64EC: define{{.*}} [2 x double] @cplx_f64([2 x double] {{.*}}) // ARM: define{{.*}} [2 x double] @cplx_f64([2 x double] {{.*}}) // BPF: define{{.*}} void @cplx_f64(ptr {{.*}} sret({ double, double }) {{.*}}, [2 x i64] {{.*}}) @@ -179,8 +181,8 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { // MIPS64EL: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) // MIPS: define{{.*}} { double, double } @cplx_f64(i32 {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 {{.*}}) // NVPTX: define{{.*}} { double, double } @cplx_f64(ptr {{.*}} byval({ double, double }) {{.*}}) - // POWERPC64: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) - // POWERPC64LE: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) + // POWERPC64: define{{.*}} { double, double } @cplx_f64({ double, double } %0) + // POWERPC64LE: define{{.*}} { double, double } @cplx_f64({ double, double } %0) // POWERPC: define{{.*}} void @cplx_f64(ptr {{.*}} sret({ double, double }) {{.*}}, ptr {{.*}} byval({ double, double }) {{.*}}) // RISCV32: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // RISCV64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) @@ -216,7 +218,7 @@ pub extern "C" fn cplx_i8(x: Complex) -> Complex { // AARCH64: define{{.*}} i64 @cplx_i8(i64{{.*}}) // AARCH64_DARWIN: define{{.*}} i64 @cplx_i8(i64{{.*}}) // AARCH64_MSVC: define{{.*}} i64 @cplx_i8(i64{{.*}}) - // AIX: define{{.*}} { i8, i8 } @cplx_i8(i8 {{.*}}, i8 {{.*}}) + // AIX: define{{.*}} { i8, i8 } @cplx_i8({ i8, i8 } %0) // ARM64EC: define{{.*}} i64 @cplx_i8(i64{{.*}}) // ARM: define{{.*}} i32 @cplx_i8(i32{{.*}}) // BPF: define{{.*}} void @cplx_i8(ptr {{.*}} sret({ i8, i8 }) {{.*}}, i16 {{.*}}) @@ -227,8 +229,8 @@ pub extern "C" fn cplx_i8(x: Complex) -> Complex { // MIPS64EL: define{{.*}} { i8, i8 } @cplx_i8(i16 {{.*}}) // MIPS: define{{.*}} { i8, i8 } @cplx_i8(i16 {{.*}}) // NVPTX: define{{.*}} { i8, i8 } @cplx_i8(ptr {{.*}} byval({ i8, i8 }) {{.*}}) - // POWERPC64: define{{.*}} { i8, i8 } @cplx_i8(i8 {{.*}}, i8 {{.*}}) - // POWERPC64LE: define{{.*}} { i8, i8 } @cplx_i8(i8 {{.*}}, i8 {{.*}}) + // POWERPC64: define{{.*}} { i8, i8 } @cplx_i8({ i8, i8 } %0) + // POWERPC64LE: define{{.*}} { i8, i8 } @cplx_i8({ i8, i8 } %0) // POWERPC: define{{.*}} void @cplx_i8(ptr {{.*}} sret({ i8, i8 }) {{.*}}, ptr {{.*}} byval({ i8, i8 }) {{.*}}) // RISCV32: define{{.*}} i32 @cplx_i8(i32{{.*}}) // RISCV64: define{{.*}} i64 @cplx_i8(i64{{.*}}) @@ -284,7 +286,7 @@ pub extern "C" fn cplx_i32(x: Complex) -> Complex { // AARCH64: define{{.*}} i64 @cplx_i32(i64 {{.*}}) // AARCH64_DARWIN: define{{.*}} i64 @cplx_i32(i64 {{.*}}) // AARCH64_MSVC: define{{.*}} i64 @cplx_i32(i64 {{.*}}) - // AIX: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) + // AIX: define{{.*}} { i32, i32 } @cplx_i32({ i32, i32 } %0) // ARM64EC: define{{.*}} i64 @cplx_i32(i64 {{.*}}) // ARM: define{{.*}} void @cplx_i32(ptr {{.*}} sret([8 x i8]) {{.*}}, [2 x i32] {{.*}}) // BPF: define{{.*}} void @cplx_i32(ptr {{.*}} sret({ i32, i32 }) {{.*}}, i64 {{.*}}) @@ -295,8 +297,8 @@ pub extern "C" fn cplx_i32(x: Complex) -> Complex { // MIPS64EL: define{{.*}} { i32, i32 } @cplx_i32(i64 {{.*}}) // MIPS: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) // NVPTX: define{{.*}} { i32, i32 } @cplx_i32(ptr {{.*}} byval({ i32, i32 }) {{.*}}) - // POWERPC64: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) - // POWERPC64LE: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) + // POWERPC64: define{{.*}} { i32, i32 } @cplx_i32({ i32, i32 } %0) + // POWERPC64LE: define{{.*}} { i32, i32 } @cplx_i32({ i32, i32 } %0) // POWERPC: define{{.*}} void @cplx_i32(ptr {{.*}} sret({ i32, i32 }) {{.*}}, ptr {{.*}} byval({ i32, i32 }) {{.*}}) // RISCV32: define{{.*}} [2 x i32] @cplx_i32([2 x i32] {{.*}}) // RISCV64: define{{.*}} i64 @cplx_i32(i64 {{.*}}) @@ -318,7 +320,7 @@ pub extern "C" fn cplx_i64(x: Complex) -> Complex { // AARCH64: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) // AARCH64_DARWIN: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) // AARCH64_MSVC: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) - // AIX: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) + // AIX: define{{.*}} { i64, i64 } @cplx_i64({ i64, i64 } %0) // ARM64EC: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) // ARM: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, [2 x i64] {{.*}}) // BPF: define{{.*}} void @cplx_i64(ptr {{.*}} sret({ i64, i64 }) {{.*}}, [2 x i64] {{.*}}) @@ -329,8 +331,8 @@ pub extern "C" fn cplx_i64(x: Complex) -> Complex { // MIPS64EL: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) // MIPS: define{{.*}} { i64, i64 } @cplx_i64(i32 {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 {{.*}}) // NVPTX: define{{.*}} { i64, i64 } @cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) - // POWERPC64: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) - // POWERPC64LE: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) + // POWERPC64: define{{.*}} { i64, i64 } @cplx_i64({ i64, i64 } %0) + // POWERPC64LE: define{{.*}} { i64, i64 } @cplx_i64({ i64, i64 } %0) // POWERPC: define{{.*}} void @cplx_i64(ptr {{.*}} sret({ i64, i64 }) {{.*}}, ptr {{.*}} byval({ i64, i64 }) {{.*}}) // RISCV32: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // RISCV64: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) @@ -357,7 +359,7 @@ pub extern "C" fn wrapper_cplx_i64( // AARCH64: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) // AARCH64_DARWIN: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) // AARCH64_MSVC: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) - // AIX: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) + // AIX: define{{.*}} { i64, i64 } @wrapper_cplx_i64({ i64, i64 } %0) // ARM64EC: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) // ARM: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, [2 x i64] {{.*}}) // BPF: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret({ i64, i64 }) {{.*}}, [2 x i64] {{.*}}) @@ -368,8 +370,8 @@ pub extern "C" fn wrapper_cplx_i64( // MIPS64EL: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) // MIPS: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i32 {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 {{.*}}) // NVPTX: define{{.*}} { i64, i64 } @wrapper_cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) - // POWERPC64: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) - // POWERPC64LE: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) + // POWERPC64: define{{.*}} { i64, i64 } @wrapper_cplx_i64({ i64, i64 } %0) + // POWERPC64LE: define{{.*}} { i64, i64 } @wrapper_cplx_i64({ i64, i64 } %0) // POWERPC: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret({ i64, i64 }) {{.*}}, ptr {{.*}} byval({ i64, i64 }) {{.*}}) // RISCV32: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // RISCV64: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) From fac655cffc8cf25a1c0931ec9c33e72984e71797 Mon Sep 17 00:00:00 2001 From: Aditya Pratap Singh Date: Fri, 4 Sep 2026 23:14:03 +0530 Subject: [PATCH 33/53] 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 34/53] 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 a9fd5e0ccbc0f9503118a7d5f265a8c381ad5800 Mon Sep 17 00:00:00 2001 From: Bardi Harborow Date: Sat, 5 Sep 2026 15:35:18 +1000 Subject: [PATCH 35/53] docs(core): correct ARMv8-M Baseline atomic CAS support ARMv8-M Baseline targets support CAS operations, but the atomic module docs incorrectly list them as load and store-only. Limit that restriction to ARMv6-M and include thumbv8m.base-* among the targets with full CAS support. --- library/core/src/sync/atomic.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index 8b770528a1736..d7354b8deccd8 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs @@ -135,11 +135,10 @@ //! entirely, but the Linux targets [use the kernel] to assist (which comes //! with a performance penalty). It's not until ARMv6K onwards that ARM CPUs //! have support for load/store and Compare and Swap (CAS) atomics in hardware. -//! * ARMv6-M and ARMv8-M baseline targets (`thumbv6m-*` and -//! `thumbv8m.base-*`) only provide `load` and `store` operations, and do +//! * ARMv6-M targets (`thumbv6m-*`) only provide `load` and `store` operations, and do //! not support Compare and Swap (CAS) operations, such as `swap`, //! `fetch_add`, etc. Full CAS support is available on ARMv7-M and ARMv8-M -//! Mainline (`thumbv7m-*`, `thumbv7em*` and `thumbv8m.main-*`). +//! (`thumbv7m-*`, `thumbv7em*`, `thumbv8m.base-*` and `thumbv8m.main-*`). //! //! [use the kernel]: https://www.kernel.org/doc/Documentation/arm/kernel_user_helpers.txt //! 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 36/53] 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 37/53] 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 1069fc2caebab1b1ea274ac3ba06e5337457dd15 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 6 Sep 2026 12:38:09 +0200 Subject: [PATCH 38/53] Remove `reason_span` from `EvalConfigResult::False` Since `reason` already has a span itself --- .../rustc_attr_parsing/src/attributes/cfg.rs | 27 +++++++++---------- compiler/rustc_expand/src/expand.rs | 4 +-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index e9ace7088d8f0..8102208ec519d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -237,42 +237,39 @@ pub fn eval_config_entry(sess: &Session, cfg_entry: &CfgEntry) -> EvalConfigResu } EvalConfigResult::True } - CfgEntry::Any(subs, span) => { + CfgEntry::Any(subs, _) => { for sub in subs { let res = eval_config_entry(sess, sub); if res.as_bool() { return res; } } - EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span } + EvalConfigResult::False { reason: cfg_entry.clone() } } - CfgEntry::Not(sub, span) => { + CfgEntry::Not(sub, _) => { if eval_config_entry(sess, sub).as_bool() { - EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span } + EvalConfigResult::False { reason: cfg_entry.clone() } } else { EvalConfigResult::True } } - CfgEntry::Bool(b, span) => { + CfgEntry::Bool(b, _) => { if *b { EvalConfigResult::True } else { - EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span } + EvalConfigResult::False { reason: cfg_entry.clone() } } } - CfgEntry::NameValue { name, value, span } => { + CfgEntry::NameValue { name, value, span: _ } => { if sess.config.contains(&(*name, *value)) { EvalConfigResult::True } else { - EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span } + EvalConfigResult::False { reason: cfg_entry.clone() } } } - CfgEntry::Version(min_version, version_span) => { + CfgEntry::Version(min_version, _) => { let Some(min_version) = min_version else { - return EvalConfigResult::False { - reason: cfg_entry.clone(), - reason_span: *version_span, - }; + return EvalConfigResult::False { reason: cfg_entry.clone() }; }; // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details let min_version_ok = if sess.opts.unstable_opts.assume_incomplete_release { @@ -283,7 +280,7 @@ pub fn eval_config_entry(sess: &Session, cfg_entry: &CfgEntry) -> EvalConfigResu if min_version_ok { EvalConfigResult::True } else { - EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *version_span } + EvalConfigResult::False { reason: cfg_entry.clone() } } } } @@ -291,7 +288,7 @@ pub fn eval_config_entry(sess: &Session, cfg_entry: &CfgEntry) -> EvalConfigResu pub enum EvalConfigResult { True, - False { reason: CfgEntry, reason_span: Span }, + False { reason: CfgEntry }, } impl EvalConfigResult { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 024ee2871b125..268011a50eb14 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -2366,13 +2366,13 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { let res = self.expand_cfg_true(&mut node, attr, pos); match res { EvalConfigResult::True => continue, - EvalConfigResult::False { reason, reason_span } => { + EvalConfigResult::False { reason } => { for ident in node.declared_idents() { self.cx.resolver.append_stripped_cfg_item( self.cx.current_expansion.lint_node_id, ident, reason.clone(), - reason_span, + reason.span(), ) } } From 8aa88d18ef72a4edda8771f430826ca86f5e3c4d Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 6 Sep 2026 12:48:48 +0200 Subject: [PATCH 39/53] Add a regression test before improving the diagnostics --- tests/ui/macros/cfg_select.rs | 29 ++++++++++ tests/ui/macros/cfg_select.stderr | 93 +++++++++++++++++++++---------- 2 files changed, 93 insertions(+), 29 deletions(-) diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index 0f03a8a99c4fb..0d5fcec971550 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -1,3 +1,4 @@ +//@ compile-flags: --check-cfg 'cfg(feature, values("meow"))' #![crate_type = "lib"] #![warn(unreachable_cfg_select_predicates)] // Unused warnings are disabled by default in UI tests. @@ -251,3 +252,31 @@ cfg_select! { debug_assertions => {} _ => {} } + +cfg_select! { + all(true, false) => { + struct Thing1; + } + _ => {} +} + +cfg_select! { + feature = "meow" => { + struct Thing2; + } + _ => {} +} + +cfg_select! { + all(true, feature = "meow") => { + struct Thing2; + } + _ => {} +} + +fn usages() { + let t1: Thing1; + //~^ ERROR cannot find type `Thing1` in this scope [E0425] + let t2: Thing2; + //~^ ERROR cannot find type `Thing2` in this scope [E0425] +} diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index 8e09aa4da93f3..80dd5738c128f 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -1,5 +1,5 @@ error: none of the predicates in this `cfg_select` evaluated to true - --> $DIR/cfg_select.rs:161:1 + --> $DIR/cfg_select.rs:162:1 | LL | / cfg_select! { LL | | @@ -8,73 +8,73 @@ LL | | } | |_^ error: none of the predicates in this `cfg_select` evaluated to true - --> $DIR/cfg_select.rs:166:1 + --> $DIR/cfg_select.rs:167:1 | LL | cfg_select! {} | ^^^^^^^^^^^^^^ error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `=>` - --> $DIR/cfg_select.rs:170:5 + --> $DIR/cfg_select.rs:171:5 | LL | => {} | ^^ error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression - --> $DIR/cfg_select.rs:175:5 + --> $DIR/cfg_select.rs:176:5 | LL | () => {} | ^^ expressions are not allowed here error[E0565]: malformed `cfg_select` macro input - --> $DIR/cfg_select.rs:180:5 + --> $DIR/cfg_select.rs:181:5 | LL | "str" => {} | ^^^^^ expected a valid identifier here error[E0565]: malformed `cfg_select` macro input - --> $DIR/cfg_select.rs:185:5 + --> $DIR/cfg_select.rs:186:5 | LL | a::b => {} | ^^^^ expected a valid identifier here error[E0539]: malformed `cfg_select` macro input - --> $DIR/cfg_select.rs:190:5 + --> $DIR/cfg_select.rs:191:5 | LL | a() => {} | ^^^ valid arguments are `any`, `all`, `not` or `target` error: expected one of `(`, `::`, `=>`, or `=`, found `+` - --> $DIR/cfg_select.rs:195:7 + --> $DIR/cfg_select.rs:196:7 | LL | a + 1 => {} | ^ expected one of `(`, `::`, `=>`, or `=` error: expected one of `(`, `::`, `=>`, or `=`, found `!` - --> $DIR/cfg_select.rs:201:8 + --> $DIR/cfg_select.rs:202:8 | LL | cfg!() => {} | ^ expected one of `(`, `::`, `=>`, or `=` error: doc comments are not allowed on `cfg_select` branches - --> $DIR/cfg_select.rs:208:5 + --> $DIR/cfg_select.rs:209:5 | LL | /// doc comment | ^^^^^^^^^^^^^^^ error: doc comments are not allowed on `cfg_select` branches - --> $DIR/cfg_select.rs:211:5 + --> $DIR/cfg_select.rs:212:5 | LL | /// doc comment | ^^^^^^^^^^^^^^^ error: attributes are not allowed on `cfg_select` branches - --> $DIR/cfg_select.rs:217:5 + --> $DIR/cfg_select.rs:218:5 | LL | #[cfg(false)] | ^^^^^^^^^^^^^ error: an inner attribute is not permitted in this context - --> $DIR/cfg_select.rs:224:5 + --> $DIR/cfg_select.rs:225:5 | LL | #![cfg(false)] | ^^^^^^^^^^^^^^ @@ -83,7 +83,7 @@ LL | #![cfg(false)] = note: outer attributes, like `#[test]`, annotate the item following them error[E0753]: expected outer doc comment - --> $DIR/cfg_select.rs:231:5 + --> $DIR/cfg_select.rs:232:5 | LL | //! inner doc comment | ^^^^^^^^^^^^^^^^^^^^^ @@ -96,7 +96,7 @@ LL + // inner doc comment | error: doc comments are not allowed on `cfg_select` branches - --> $DIR/cfg_select.rs:239:5 + --> $DIR/cfg_select.rs:240:5 | LL | /// line1 | ^^^^^^^^^ @@ -105,7 +105,7 @@ LL | /// line3 | ^^^^^^^^^ error[E0753]: expected outer doc comment - --> $DIR/cfg_select.rs:249:5 + --> $DIR/cfg_select.rs:250:5 | LL | //! inner doc comment | ^^^^^^^^^^^^^^^^^^^^^ @@ -118,13 +118,48 @@ LL + // inner doc comment | error: doc comments are not allowed on `cfg_select` branches - --> $DIR/cfg_select.rs:247:5 + --> $DIR/cfg_select.rs:248:5 | LL | /// outer doc comment | ^^^^^^^^^^^^^^^^^^^^^ +error[E0425]: cannot find type `Thing1` in this scope + --> $DIR/cfg_select.rs:278:13 + | +LL | let t1: Thing1; + | ^^^^^^ not found in this scope + | +note: found an item that was configured out + --> $DIR/cfg_select.rs:258:16 + | +LL | all(true, false) => { + | ------------- the item is gated here +LL | struct Thing1; + | ^^^^^^ + +error[E0425]: cannot find type `Thing2` in this scope + --> $DIR/cfg_select.rs:280:13 + | +LL | let t2: Thing2; + | ^^^^^^ not found in this scope + | +note: found an item that was configured out + --> $DIR/cfg_select.rs:265:16 + | +LL | feature = "meow" => { + | ---------------- the item is gated behind the `meow` feature +LL | struct Thing2; + | ^^^^^^ +note: found an item that was configured out + --> $DIR/cfg_select.rs:272:16 + | +LL | all(true, feature = "meow") => { + | ------------------------ the item is gated here +LL | struct Thing2; + | ^^^^^^ + warning: unreachable configuration predicate - --> $DIR/cfg_select.rs:136:5 + --> $DIR/cfg_select.rs:137:5 | LL | _ => {} | - always matches @@ -132,13 +167,13 @@ LL | true => {} | ^^^^ this configuration predicate is never reached | note: the lint level is defined here - --> $DIR/cfg_select.rs:2:9 + --> $DIR/cfg_select.rs:3:9 | LL | #![warn(unreachable_cfg_select_predicates)] // Unused warnings are disabled by default in UI tests. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: unreachable configuration predicate - --> $DIR/cfg_select.rs:142:5 + --> $DIR/cfg_select.rs:143:5 | LL | true => {} | ---- always matches @@ -146,36 +181,36 @@ LL | _ => {} | ^ this configuration predicate is never reached warning: unreachable configuration predicate - --> $DIR/cfg_select.rs:149:5 + --> $DIR/cfg_select.rs:150:5 | LL | _ => {} | ^ this configuration predicate is never reached warning: unreachable configuration predicate - --> $DIR/cfg_select.rs:155:5 + --> $DIR/cfg_select.rs:156:5 | LL | test => {} | ^^^^ this configuration predicate is never reached warning: unreachable configuration predicate - --> $DIR/cfg_select.rs:157:5 + --> $DIR/cfg_select.rs:158:5 | LL | _ => {} | ^ this configuration predicate is never reached warning: unexpected `cfg` condition name: `a` - --> $DIR/cfg_select.rs:195:5 + --> $DIR/cfg_select.rs:196:5 | LL | a + 1 => {} | ^ help: found config with similar value: `target_feature = "a"` | - = help: expected names are: `FALSE` and `test` and 34 more + = help: expected names are: `FALSE`, `feature`, and `test` and 34 more = help: to expect this configuration use `--check-cfg=cfg(a)` = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default warning: unexpected `cfg` condition name: `cfg` - --> $DIR/cfg_select.rs:201:5 + --> $DIR/cfg_select.rs:202:5 | LL | cfg!() => {} | ^^^ @@ -183,7 +218,7 @@ LL | cfg!() => {} = help: to expect this configuration use `--check-cfg=cfg(cfg)` = note: see for more information about checking conditional configuration -error: aborting due to 17 previous errors; 7 warnings emitted +error: aborting due to 19 previous errors; 7 warnings emitted -Some errors have detailed explanations: E0539, E0565, E0753. -For more information about an error, try `rustc --explain E0539`. +Some errors have detailed explanations: E0425, E0539, E0565, E0753. +For more information about an error, try `rustc --explain E0425`. From 2b57478affcb4ac44556cc55cd6e195263dc6a12 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 6 Sep 2026 12:57:43 +0200 Subject: [PATCH 40/53] Use `reason` for tracked item diagnostics from `cfg_select!` --- .../src/attributes/cfg_select.rs | 16 +++++++++++----- compiler/rustc_builtin_macros/src/cfg_select.rs | 6 ++---- tests/ui/macros/cfg_select.stderr | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index 68de0a53e918b..4333559afb1c0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -15,7 +15,8 @@ use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; use crate::attributes::AttributeSafety; use crate::parser::{AllowExprMetavar, MetaItemOrLitParser}; use crate::{ - AttributeParser, AttributeTemplate, ParsedDescription, ShouldEmit, diagnostics, parse_cfg_entry, + AttributeParser, AttributeTemplate, EvalConfigResult, ParsedDescription, ShouldEmit, + diagnostics, parse_cfg_entry, }; #[derive(Clone)] @@ -49,11 +50,16 @@ impl CfgSelectBranches { /// or the wildcard if none of the reachable branches satisfied the predicate. pub fn pop_first_match(&mut self, predicate: F) -> Option<(CfgEntry, TokenStream, Span)> where - F: Fn(&CfgEntry) -> bool, + F: Fn(&CfgEntry) -> EvalConfigResult, { - for (index, (cfg, _, _)) in self.reachable.iter().enumerate() { - if predicate(cfg) { - return Some(self.reachable.remove(index)); + for (index, (cfg, _, _)) in self.reachable.iter_mut().enumerate() { + match predicate(cfg) { + EvalConfigResult::True => { + return Some(self.reachable.remove(index)); + } + EvalConfigResult::False { reason } => { + *cfg = reason; + } } } diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index 7202c56efed4e..69c3802ceafae 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -3,7 +3,7 @@ use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AttrKind, Expr, SyntheticAttr, ast}; use rustc_attr_ir::CfgEntry; use rustc_attr_parsing as attr; -use rustc_attr_parsing::{CfgSelectBranches, EvalConfigResult, parse_cfg_select}; +use rustc_attr_parsing::{CfgSelectBranches, parse_cfg_select}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult}; use rustc_expand::expand::DeclaredIdents; use rustc_span::{Ident, Span, sym}; @@ -129,9 +129,7 @@ pub(super) fn expand_cfg_select<'cx>( ) { Ok(mut branches) => { if let Some((cfg_entry, selected_tts, selected_span)) = - branches.pop_first_match(|cfg| { - matches!(attr::eval_config_entry(ecx.sess, cfg), EvalConfigResult::True) - }) + branches.pop_first_match(|cfg| attr::eval_config_entry(ecx.sess, cfg)) { let mac = CfgSelectResult { ecx, diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index 80dd5738c128f..9af74456367f2 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -133,7 +133,7 @@ note: found an item that was configured out --> $DIR/cfg_select.rs:258:16 | LL | all(true, false) => { - | ------------- the item is gated here + | ----- the item is gated here LL | struct Thing1; | ^^^^^^ @@ -154,7 +154,7 @@ note: found an item that was configured out --> $DIR/cfg_select.rs:272:16 | LL | all(true, feature = "meow") => { - | ------------------------ the item is gated here + | ---------------- the item is gated behind the `meow` feature LL | struct Thing2; | ^^^^^^ From 44965fd1043f1f5d07b3275f46ea5bb3ad4e6ec0 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sun, 23 Aug 2026 18:38:39 -0300 Subject: [PATCH 41/53] Point closure trait errors at captured values --- .../src/error_reporting/traits/suggestions.rs | 143 ++++++++++++++++-- .../async-closures/not-clone-closure.stderr | 8 +- .../issue-70935-complex-spans.stderr | 8 +- ...osure-clone-requires-captured-clone.stderr | 8 +- tests/ui/closures/closure-move-sync.stderr | 8 +- ...ntract-captures-via-closure-noncopy.stderr | 6 +- .../auto-trait-leak2.stderr | 12 +- .../ui/threads-sendsync/rc-is-not-send.stderr | 8 +- .../catch-unwind-cell-interior-mut.stderr | 6 +- tests/ui/traits/closure-capture-not-send.rs | 12 ++ .../ui/traits/closure-capture-not-send.stderr | 30 ++++ tests/ui/traits/closure-rc-not-send.stderr | 6 +- 12 files changed, 206 insertions(+), 49 deletions(-) create mode 100644 tests/ui/traits/closure-capture-not-send.rs create mode 100644 tests/ui/traits/closure-capture-not-send.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index b8e4521451a25..79e726e21cc9c 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -3751,6 +3751,54 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ); } + fn note_closure_capture( + &self, + err: &mut Diag<'_, G>, + closure_def_id: DefId, + upvar_args: ty::UpvarArgs<'tcx>, + capture_ty: Option>, + ) -> bool { + let Some(closure_def_id) = closure_def_id.as_local() else { + return false; + }; + let Some(capture_ty) = capture_ty else { + return false; + }; + + let typeck_root = self.tcx.typeck_root_def_id(closure_def_id.to_def_id()); + // Querying `typeck` for the body currently being checked would create a cycle, so use the + // in-progress results when they belong to this closure's typeck root. + let captures: Vec<_> = match &self.typeck_results { + Some(typeck_results) if typeck_results.hir_owner.to_def_id() == typeck_root => { + typeck_results.closure_min_captures_flattened(closure_def_id).collect() + } + _ => self.tcx.closure_captures(closure_def_id).to_vec(), + }; + let upvar_tys = upvar_args.upvar_tys(); + if captures.len() != upvar_tys.len() { + return false; + } + + let capture_ty = + self.tcx.erase_and_anonymize_regions(self.resolve_vars_if_possible(capture_ty)); + let Some(capture) = captures.iter().zip(upvar_tys).find_map(|(&capture, upvar_ty)| { + let upvar_ty = + self.tcx.erase_and_anonymize_regions(self.resolve_vars_if_possible(upvar_ty)); + (upvar_ty == capture_ty).then_some(capture) + }) else { + return false; + }; + + err.span_note( + capture.get_path_span(self.tcx), + format!( + "required because `{}` is used within this closure", + capture.to_string(self.tcx) + ), + ); + true + } + pub(super) fn note_obligation_cause_code( &self, body_def_id: LocalDefId, @@ -3762,6 +3810,36 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { seen_requirements: &mut FxHashSet, ) where T: Upcast, ty::Predicate<'tcx>>, + { + let predicate = predicate.upcast(self.tcx); + // Built-in closure obligations pass through a synthetic tuple of upvar types. Remember + // the last constituent before that tuple so it can be matched to a captured place. + let mut closure_capture_ty = + predicate.as_trait_clause().map(|trait_pred| trait_pred.skip_binder().self_ty()); + self.note_obligation_cause_code_inner( + body_def_id, + err, + predicate, + param_env, + cause_code, + obligated_types, + seen_requirements, + &mut closure_capture_ty, + ); + } + + fn note_obligation_cause_code_inner( + &self, + body_def_id: LocalDefId, + err: &mut Diag<'_, G>, + predicate: T, + param_env: ty::ParamEnv<'tcx>, + cause_code: &ObligationCauseCode<'tcx>, + obligated_types: &mut Vec>, + seen_requirements: &mut FxHashSet, + closure_capture_ty: &mut Option>, + ) where + T: Upcast, ty::Predicate<'tcx>>, { let tcx = self.tcx; let predicate = predicate.upcast(tcx); @@ -4333,6 +4411,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let nested_ty = parent_trait_ref.skip_binder().self_ty(); matches!(nested_ty.kind(), ty::Coroutine(..)) || matches!(nested_ty.kind(), ty::Closure(..)) + || matches!(nested_ty.kind(), ty::CoroutineClosure(..)) } else { false }; @@ -4396,11 +4475,31 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // Skip printing coroutine-witnesses, since we'll drill into // the bad field in another derived obligation cause. } - ty::Closure(def_id, _) | ty::CoroutineClosure(def_id, _) => { - err.span_note( - tcx.def_span(def_id), - "required because it's used within this closure", - ); + ty::Closure(def_id, args) => { + if !self.note_closure_capture( + err, + def_id, + ty::UpvarArgs::Closure(args), + *closure_capture_ty, + ) { + err.span_note( + tcx.def_span(def_id), + "required because it's used within this closure", + ); + } + } + ty::CoroutineClosure(def_id, args) => { + if !self.note_closure_capture( + err, + def_id, + ty::UpvarArgs::CoroutineClosure(args), + *closure_capture_ty, + ) { + err.span_note( + tcx.def_span(def_id), + "required because it's used within this closure", + ); + } } ty::Str => { err.note("`str` is considered to contain a `[u8]` slice for auto trait purposes"); @@ -4412,11 +4511,15 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }; } + if !is_upvar_tys_infer_tuple { + *closure_capture_ty = Some(ty); + } + obligated_types.push(ty); let parent_predicate = parent_trait_ref; if !self.is_recursive_obligation(obligated_types, &data.parent_code) { - self.note_obligation_cause_code( + self.note_obligation_cause_code_inner( body_def_id, err, parent_predicate, @@ -4424,9 +4527,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { &data.parent_code, obligated_types, seen_requirements, + closure_capture_ty, ); } else { - self.note_obligation_cause_code( + self.note_obligation_cause_code_inner( body_def_id, err, parent_predicate, @@ -4434,6 +4538,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { cause_code.peel_derives(), obligated_types, seen_requirements, + closure_capture_ty, ); } } @@ -4455,7 +4560,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // Skip PinDerefMutHelper in suggestions, but still show downstream suggestions. - self.note_obligation_cause_code( + *closure_capture_ty = Some(parent_trait_pred.skip_binder().self_ty()); + self.note_obligation_cause_code_inner( body_def_id, err, parent_predicate, @@ -4463,6 +4569,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { &data.derived.parent_code, obligated_types, seen_requirements, + closure_capture_ty, ); return; } @@ -4614,7 +4721,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ); err.note(format!("required for `{self_ty}` to implement `{trait_path}`")); } - self.note_obligation_cause_code( + *closure_capture_ty = Some(parent_trait_pred.skip_binder().self_ty()); + self.note_obligation_cause_code_inner( body_def_id, err, parent_predicate, @@ -4622,6 +4730,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { &data.parent_code, obligated_types, seen_requirements, + closure_capture_ty, ) } ObligationCauseCode::ImplDerivedHost(ref data) => { @@ -4656,7 +4765,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } } - self.note_obligation_cause_code( + self.note_obligation_cause_code_inner( body_def_id, err, data.derived.parent_host_clause, @@ -4664,10 +4773,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { &data.derived.parent_code, obligated_types, seen_requirements, + closure_capture_ty, ); } ObligationCauseCode::BuiltinDerivedHost(ref data) => { - self.note_obligation_cause_code( + self.note_obligation_cause_code_inner( body_def_id, err, data.parent_host_clause, @@ -4675,13 +4785,15 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { &data.parent_code, obligated_types, seen_requirements, + closure_capture_ty, ); } ObligationCauseCode::WellFormedDerived(ref data) => { let parent_trait_ref = self.resolve_vars_if_possible(data.parent_trait_pred); let parent_predicate = parent_trait_ref; - self.note_obligation_cause_code( + *closure_capture_ty = Some(parent_trait_ref.skip_binder().self_ty()); + self.note_obligation_cause_code_inner( body_def_id, err, parent_predicate, @@ -4689,10 +4801,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { &data.parent_code, obligated_types, seen_requirements, + closure_capture_ty, ); } ObligationCauseCode::TypeAlias(ref nested, span, def_id) => { - self.note_obligation_cause_code( + self.note_obligation_cause_code_inner( body_def_id, err, predicate, @@ -4700,6 +4813,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { nested, obligated_types, seen_requirements, + closure_capture_ty, ); let mut multispan = MultiSpan::from(span); multispan.push_span_label(span, "required by this bound"); @@ -4721,7 +4835,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { call_hir_id, ); - self.note_obligation_cause_code( + self.note_obligation_cause_code_inner( body_def_id, err, predicate, @@ -4729,6 +4843,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { parent_code, obligated_types, seen_requirements, + closure_capture_ty, ); } // Suppress `compare_type_clause_entailment` errors for RPITITs, since they diff --git a/tests/ui/async-await/async-closures/not-clone-closure.stderr b/tests/ui/async-await/async-closures/not-clone-closure.stderr index a08ee45f97049..2fae02ba614f4 100644 --- a/tests/ui/async-await/async-closures/not-clone-closure.stderr +++ b/tests/ui/async-await/async-closures/not-clone-closure.stderr @@ -5,11 +5,11 @@ LL | not_clone.clone(); | ^^^^^ unsatisfied trait bound | = help: within `{async closure@$DIR/not-clone-closure.rs:27:21: 27:34}`, the trait `Clone` is not implemented for `NotClonableUpvar` -note: required because it's used within this closure - --> $DIR/not-clone-closure.rs:27:21 +note: required because `z` is used within this closure + --> $DIR/not-clone-closure.rs:28:20 | -LL | let not_clone = async move || { - | ^^^^^^^^^^^^^ +LL | println!("{z:?}"); + | ^ help: consider annotating `NotClonableUpvar` with `#[derive(Clone)]` | LL + #[derive(Clone)] diff --git a/tests/ui/async-await/issue-70935-complex-spans.stderr b/tests/ui/async-await/issue-70935-complex-spans.stderr index b9180c620b0dc..909c73aa927d4 100644 --- a/tests/ui/async-await/issue-70935-complex-spans.stderr +++ b/tests/ui/async-await/issue-70935-complex-spans.stderr @@ -13,11 +13,11 @@ note: required because it appears within the type `NotSync` LL | struct NotSync(PhantomData<*mut ()>); | ^^^^^^^ = note: required for `&NotSync` to implement `Send` -note: required because it's used within this closure - --> $DIR/issue-70935-complex-spans.rs:18:13 +note: required because `x` is used within this closure + --> $DIR/issue-70935-complex-spans.rs:19:17 | -LL | baz(|| async { - | ^^ +LL | foo(x.clone()); + | ^ note: required because it's used within this `async` fn body --> $DIR/issue-70935-complex-spans.rs:12:67 | diff --git a/tests/ui/closures/closure-clone-requires-captured-clone.stderr b/tests/ui/closures/closure-clone-requires-captured-clone.stderr index 9bc5f8ef24126..8aa58cf0f2e68 100644 --- a/tests/ui/closures/closure-clone-requires-captured-clone.stderr +++ b/tests/ui/closures/closure-clone-requires-captured-clone.stderr @@ -7,11 +7,11 @@ LL | let closure = move || { LL | closure.clone(); | ^^^^^ within `{closure@$DIR/closure-clone-requires-captured-clone.rs:14:19: 14:26}`, the trait `Clone` is not implemented for `NonClone` | -note: required because it's used within this closure - --> $DIR/closure-clone-requires-captured-clone.rs:14:19 +note: required because `captured_value` is used within this closure + --> $DIR/closure-clone-requires-captured-clone.rs:15:17 | -LL | let closure = move || { - | ^^^^^^^ +LL | let _ = captured_value.0; + | ^^^^^^^^^^^^^^ help: consider annotating `NonClone` with `#[derive(Clone)]` | LL + #[derive(Clone)] diff --git a/tests/ui/closures/closure-move-sync.stderr b/tests/ui/closures/closure-move-sync.stderr index 7241f78c3539e..3c3945e7df5cd 100644 --- a/tests/ui/closures/closure-move-sync.stderr +++ b/tests/ui/closures/closure-move-sync.stderr @@ -12,11 +12,11 @@ LL | | }); | = help: the trait `Sync` is not implemented for `std::sync::mpsc::Receiver<()>` = note: required for `&std::sync::mpsc::Receiver<()>` to implement `Send` -note: required because it's used within this closure - --> $DIR/closure-move-sync.rs:6:27 +note: required because `recv` is used within this closure + --> $DIR/closure-move-sync.rs:7:9 | -LL | let t = thread::spawn(|| { - | ^^ +LL | recv.recv().unwrap(); + | ^^^^ note: required by a bound in `spawn` --> $SRC_DIR/std/src/thread/functions.rs:LL:COL diff --git a/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr index 20c220e98bcc8..76070e3792b6f 100644 --- a/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr +++ b/tests/ui/contracts/contract-captures-via-closure-noncopy.stderr @@ -10,11 +10,11 @@ LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz | required by a bound introduced by this call | = help: within `{closure@$DIR/contract-captures-via-closure-noncopy.rs:13:42: 13:57}`, the trait `Copy` is not implemented for `Baz` -note: required because it's used within this closure - --> $DIR/contract-captures-via-closure-noncopy.rs:13:42 +note: required because `old` is used within this closure + --> $DIR/contract-captures-via-closure-noncopy.rs:13:69 | LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })] - | ^^^^^^^^^^^^^^^ + | ^^^^^^^ note: required by a bound in `build_check_ensures` --> $SRC_DIR/core/src/contracts.rs:LL:COL help: consider annotating `Baz` with `#[derive(Copy)]` diff --git a/tests/ui/impl-trait/auto-trait-leakage/auto-trait-leak2.stderr b/tests/ui/impl-trait/auto-trait-leakage/auto-trait-leak2.stderr index ba76d9ba2b8fc..476eb087eb48e 100644 --- a/tests/ui/impl-trait/auto-trait-leakage/auto-trait-leak2.stderr +++ b/tests/ui/impl-trait/auto-trait-leakage/auto-trait-leak2.stderr @@ -10,11 +10,11 @@ LL | send(before()); | required by a bound introduced by this call | = help: within `impl Fn(i32)`, the trait `Send` is not implemented for `Rc>` -note: required because it's used within this closure - --> $DIR/auto-trait-leak2.rs:11:5 +note: required because `p` is used within this closure + --> $DIR/auto-trait-leak2.rs:11:14 | LL | move |x| p.set(x) - | ^^^^^^^^ + | ^ note: required because it appears within the type `impl Fn(i32)` --> $DIR/auto-trait-leak2.rs:6:16 | @@ -38,11 +38,11 @@ LL | fn after() -> impl Fn(i32) { | ------------ within this `impl Fn(i32)` | = help: within `impl Fn(i32)`, the trait `Send` is not implemented for `Rc>` -note: required because it's used within this closure - --> $DIR/auto-trait-leak2.rs:39:5 +note: required because `p` is used within this closure + --> $DIR/auto-trait-leak2.rs:39:14 | LL | move |x| p.set(x) - | ^^^^^^^^ + | ^ note: required because it appears within the type `impl Fn(i32)` --> $DIR/auto-trait-leak2.rs:34:15 | diff --git a/tests/ui/threads-sendsync/rc-is-not-send.stderr b/tests/ui/threads-sendsync/rc-is-not-send.stderr index 784a0999c6011..28a74d89def6c 100644 --- a/tests/ui/threads-sendsync/rc-is-not-send.stderr +++ b/tests/ui/threads-sendsync/rc-is-not-send.stderr @@ -24,11 +24,11 @@ note: required because it appears within the type `Foo` | LL | struct Foo { | ^^^ -note: required because it's used within this closure - --> $DIR/rc-is-not-send.rs:25:19 +note: required because `x` is used within this closure + --> $DIR/rc-is-not-send.rs:27:17 | -LL | thread::spawn(move || { - | ^^^^^^^ +LL | let y = x; + | ^ note: required by a bound in `spawn` --> $SRC_DIR/std/src/thread/functions.rs:LL:COL diff --git a/tests/ui/traits/catch-unwind-cell-interior-mut.stderr b/tests/ui/traits/catch-unwind-cell-interior-mut.stderr index 6f58c880554a6..a2a251396c6ce 100644 --- a/tests/ui/traits/catch-unwind-cell-interior-mut.stderr +++ b/tests/ui/traits/catch-unwind-cell-interior-mut.stderr @@ -10,11 +10,11 @@ LL | catch_unwind(|| { x.set(23); }); note: required because it appears within the type `Cell` --> $SRC_DIR/core/src/cell.rs:LL:COL = note: required for `&Cell` to implement `UnwindSafe` -note: required because it's used within this closure - --> $DIR/catch-unwind-cell-interior-mut.rs:7:18 +note: required because `x` is used within this closure + --> $DIR/catch-unwind-cell-interior-mut.rs:7:23 | LL | catch_unwind(|| { x.set(23); }); - | ^^ + | ^ note: required by a bound in `std::panic::catch_unwind` --> $SRC_DIR/std/src/panic.rs:LL:COL diff --git a/tests/ui/traits/closure-capture-not-send.rs b/tests/ui/traits/closure-capture-not-send.rs new file mode 100644 index 0000000000000..ee784b848e122 --- /dev/null +++ b/tests/ui/traits/closure-capture-not-send.rs @@ -0,0 +1,12 @@ +fn require_send(_: T) {} + +fn main() { + let foo = std::ptr::null_mut::<()>(); + let other = String::new(); + + require_send(move || { + //~^ ERROR `*mut ()` cannot be sent between threads safely + drop(other); + unsafe { foo.read() }; + }); +} diff --git a/tests/ui/traits/closure-capture-not-send.stderr b/tests/ui/traits/closure-capture-not-send.stderr new file mode 100644 index 0000000000000..34603f0a45c79 --- /dev/null +++ b/tests/ui/traits/closure-capture-not-send.stderr @@ -0,0 +1,30 @@ +error[E0277]: `*mut ()` cannot be sent between threads safely + --> $DIR/closure-capture-not-send.rs:7:18 + | +LL | require_send(move || { + | ------------ ^------ + | | | + | _____|____________within this `{closure@$DIR/closure-capture-not-send.rs:7:18: 7:25}` + | | | + | | required by a bound introduced by this call +LL | | +LL | | drop(other); +LL | | unsafe { foo.read() }; +LL | | }); + | |_____^ `*mut ()` cannot be sent between threads safely + | + = help: within `{closure@$DIR/closure-capture-not-send.rs:7:18: 7:25}`, the trait `Send` is not implemented for `*mut ()` +note: required because `foo` is used within this closure + --> $DIR/closure-capture-not-send.rs:10:18 + | +LL | unsafe { foo.read() }; + | ^^^ +note: required by a bound in `require_send` + --> $DIR/closure-capture-not-send.rs:1:20 + | +LL | fn require_send(_: T) {} + | ^^^^ required by this bound in `require_send` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/traits/closure-rc-not-send.stderr b/tests/ui/traits/closure-rc-not-send.stderr index 5e73f13648d5c..f169377811366 100644 --- a/tests/ui/traits/closure-rc-not-send.stderr +++ b/tests/ui/traits/closure-rc-not-send.stderr @@ -9,11 +9,11 @@ LL | bar(move || foo(x)); | required by a bound introduced by this call | = help: within `{closure@$DIR/closure-rc-not-send.rs:9:9: 9:16}`, the trait `Send` is not implemented for `Rc` -note: required because it's used within this closure - --> $DIR/closure-rc-not-send.rs:9:9 +note: required because `x` is used within this closure + --> $DIR/closure-rc-not-send.rs:9:21 | LL | bar(move || foo(x)); - | ^^^^^^^ + | ^ note: required by a bound in `bar` --> $DIR/closure-rc-not-send.rs:5:22 | From 98d37eb97674073352e80615af7469f5ff85b0ed Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sun, 6 Sep 2026 12:08:35 -0300 Subject: [PATCH 42/53] Only read closure captures from in-progress typeck results Reaching for `tcx.closure_captures` asks for `typeck` again. When the error is reported while typeck of the closure's root is still running, that turns the reported error into a query cycle, so restrict the note to the results that are already at hand. --- .../src/error_reporting/traits/suggestions.rs | 20 +++++++++++-------- .../issue-70935-complex-spans.stderr | 8 ++++---- .../auto-trait-leak2.stderr | 12 +++++------ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 79e726e21cc9c..65e589d336500 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -3765,15 +3765,19 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { return false; }; - let typeck_root = self.tcx.typeck_root_def_id(closure_def_id.to_def_id()); - // Querying `typeck` for the body currently being checked would create a cycle, so use the - // in-progress results when they belong to this closure's typeck root. - let captures: Vec<_> = match &self.typeck_results { - Some(typeck_results) if typeck_results.hir_owner.to_def_id() == typeck_root => { - typeck_results.closure_min_captures_flattened(closure_def_id).collect() - } - _ => self.tcx.closure_captures(closure_def_id).to_vec(), + // This can run while `typeck` for the closure's root is still on the query stack, for + // example when reporting an overflow from inside trait selection, so only the in-progress + // results are safe to look at. Asking for the captures through `tcx.closure_captures` + // would call `typeck` again and replace the error being reported with a cycle error. + let Some(typeck_results) = &self.typeck_results else { + return false; }; + let typeck_root = self.tcx.typeck_root_def_id(closure_def_id.to_def_id()); + if typeck_results.hir_owner.to_def_id() != typeck_root { + return false; + } + let captures: Vec<_> = + typeck_results.closure_min_captures_flattened(closure_def_id).collect(); let upvar_tys = upvar_args.upvar_tys(); if captures.len() != upvar_tys.len() { return false; diff --git a/tests/ui/async-await/issue-70935-complex-spans.stderr b/tests/ui/async-await/issue-70935-complex-spans.stderr index 909c73aa927d4..b9180c620b0dc 100644 --- a/tests/ui/async-await/issue-70935-complex-spans.stderr +++ b/tests/ui/async-await/issue-70935-complex-spans.stderr @@ -13,11 +13,11 @@ note: required because it appears within the type `NotSync` LL | struct NotSync(PhantomData<*mut ()>); | ^^^^^^^ = note: required for `&NotSync` to implement `Send` -note: required because `x` is used within this closure - --> $DIR/issue-70935-complex-spans.rs:19:17 +note: required because it's used within this closure + --> $DIR/issue-70935-complex-spans.rs:18:13 | -LL | foo(x.clone()); - | ^ +LL | baz(|| async { + | ^^ note: required because it's used within this `async` fn body --> $DIR/issue-70935-complex-spans.rs:12:67 | diff --git a/tests/ui/impl-trait/auto-trait-leakage/auto-trait-leak2.stderr b/tests/ui/impl-trait/auto-trait-leakage/auto-trait-leak2.stderr index 476eb087eb48e..ba76d9ba2b8fc 100644 --- a/tests/ui/impl-trait/auto-trait-leakage/auto-trait-leak2.stderr +++ b/tests/ui/impl-trait/auto-trait-leakage/auto-trait-leak2.stderr @@ -10,11 +10,11 @@ LL | send(before()); | required by a bound introduced by this call | = help: within `impl Fn(i32)`, the trait `Send` is not implemented for `Rc>` -note: required because `p` is used within this closure - --> $DIR/auto-trait-leak2.rs:11:14 +note: required because it's used within this closure + --> $DIR/auto-trait-leak2.rs:11:5 | LL | move |x| p.set(x) - | ^ + | ^^^^^^^^ note: required because it appears within the type `impl Fn(i32)` --> $DIR/auto-trait-leak2.rs:6:16 | @@ -38,11 +38,11 @@ LL | fn after() -> impl Fn(i32) { | ------------ within this `impl Fn(i32)` | = help: within `impl Fn(i32)`, the trait `Send` is not implemented for `Rc>` -note: required because `p` is used within this closure - --> $DIR/auto-trait-leak2.rs:39:14 +note: required because it's used within this closure + --> $DIR/auto-trait-leak2.rs:39:5 | LL | move |x| p.set(x) - | ^ + | ^^^^^^^^ note: required because it appears within the type `impl Fn(i32)` --> $DIR/auto-trait-leak2.rs:34:15 | From 5332fa8e6acf4a66d4782089a5327796ab929eaf Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sun, 6 Sep 2026 13:07:23 -0300 Subject: [PATCH 43/53] Add regression test for the closure capture note cycling on overflow --- ...closure-capture-overflow-no-query-cycle.rs | 19 +++++++++++++++ ...ure-capture-overflow-no-query-cycle.stderr | 24 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/ui/traits/closure-capture-overflow-no-query-cycle.rs create mode 100644 tests/ui/traits/closure-capture-overflow-no-query-cycle.stderr diff --git a/tests/ui/traits/closure-capture-overflow-no-query-cycle.rs b/tests/ui/traits/closure-capture-overflow-no-query-cycle.rs new file mode 100644 index 0000000000000..b81e03337c82c --- /dev/null +++ b/tests/ui/traits/closure-capture-overflow-no-query-cycle.rs @@ -0,0 +1,19 @@ +// Naming the captured value in the closure note needs the captures of the closure, which are only +// available through `typeck`. Overflow errors are reported while `typeck` of the closure's parent +// is still running, so asking for them there turned this overflow into a cycle error. +// +// The legacy solver is what still walks the obligation chain for overflow errors, so the note is +// only reachable from here. +//@ compile-flags: -Znext-solver=no + +#![recursion_limit = "2"] + +fn require_send(_: T) {} + +fn main() { + let x = (1u8,); + require_send(move || { + //~^ ERROR overflow evaluating the requirement `(u8,): Send` + drop(x); + }); +} diff --git a/tests/ui/traits/closure-capture-overflow-no-query-cycle.stderr b/tests/ui/traits/closure-capture-overflow-no-query-cycle.stderr new file mode 100644 index 0000000000000..71fc549e80ebc --- /dev/null +++ b/tests/ui/traits/closure-capture-overflow-no-query-cycle.stderr @@ -0,0 +1,24 @@ +error[E0275]: overflow evaluating the requirement `(u8,): Send` + --> $DIR/closure-capture-overflow-no-query-cycle.rs:15:5 + | +LL | / require_send(move || { +LL | | +LL | | drop(x); +LL | | }); + | |______^ + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "4"]` attribute to your crate (`closure_capture_overflow_no_query_cycle`) +note: required because it's used within this closure + --> $DIR/closure-capture-overflow-no-query-cycle.rs:15:18 + | +LL | require_send(move || { + | ^^^^^^^ +note: required by a bound in `require_send` + --> $DIR/closure-capture-overflow-no-query-cycle.rs:11:20 + | +LL | fn require_send(_: T) {} + | ^^^^ required by this bound in `require_send` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0275`. From d4e6b8f2460d08f8028c71e5383a6300eaec1a9f Mon Sep 17 00:00:00 2001 From: malezjaa Date: Sun, 6 Sep 2026 20:17:40 +0200 Subject: [PATCH 44/53] 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 ed9f5ed9ea3dd2572fbf94bd0985125626693074 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 6 Sep 2026 20:48:29 +0200 Subject: [PATCH 45/53] Add a hint to the `nolimit` message --- src/ci/citool/src/jobs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ci/citool/src/jobs.rs b/src/ci/citool/src/jobs.rs index 9800f14d8fa39..353b42606dfa5 100644 --- a/src/ci/citool/src/jobs.rs +++ b/src/ci/citool/src/jobs.rs @@ -317,7 +317,7 @@ fn calculate_jobs( } if jobs.len() > MAX_TRY_JOBS_COUNT && !nolimit { return Err(anyhow::anyhow!( - "It is only possible to schedule up to {MAX_TRY_JOBS_COUNT} custom jobs, received {} custom jobs expanded from {} pattern(s)", + "It is only possible to schedule up to {MAX_TRY_JOBS_COUNT} custom jobs, received {} custom jobs expanded from {} pattern(s). Use `@bors try jobs=... nolimit` to allow running an arbitrary number of try jobs.", jobs.len(), patterns.len() )); From eb7a027e05fa0b305b7108e4de99b1d9a6512bef Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 6 Sep 2026 20:50:32 +0200 Subject: [PATCH 46/53] remove EnumSizeOpt --- .../rustc_mir_transform/src/large_enums.rs | 226 ------------------ compiler/rustc_mir_transform/src/lib.rs | 2 - .../enum_opt.cand.EnumSizeOpt.32bit.diff | 72 ------ .../enum_opt.cand.EnumSizeOpt.64bit.diff | 72 ------ .../enum_opt.invalid.EnumSizeOpt.32bit.diff | 28 --- .../enum_opt.invalid.EnumSizeOpt.64bit.diff | 28 --- tests/mir-opt/enum_opt.rs | 86 ------- .../enum_opt.trunc.EnumSizeOpt.32bit.diff | 37 --- .../enum_opt.trunc.EnumSizeOpt.64bit.diff | 37 --- .../enum_opt.unin.EnumSizeOpt.32bit.diff | 72 ------ .../enum_opt.unin.EnumSizeOpt.64bit.diff | 72 ------ 11 files changed, 732 deletions(-) delete mode 100644 compiler/rustc_mir_transform/src/large_enums.rs delete mode 100644 tests/mir-opt/enum_opt.cand.EnumSizeOpt.32bit.diff delete mode 100644 tests/mir-opt/enum_opt.cand.EnumSizeOpt.64bit.diff delete mode 100644 tests/mir-opt/enum_opt.invalid.EnumSizeOpt.32bit.diff delete mode 100644 tests/mir-opt/enum_opt.invalid.EnumSizeOpt.64bit.diff delete mode 100644 tests/mir-opt/enum_opt.rs delete mode 100644 tests/mir-opt/enum_opt.trunc.EnumSizeOpt.32bit.diff delete mode 100644 tests/mir-opt/enum_opt.trunc.EnumSizeOpt.64bit.diff delete mode 100644 tests/mir-opt/enum_opt.unin.EnumSizeOpt.32bit.diff delete mode 100644 tests/mir-opt/enum_opt.unin.EnumSizeOpt.64bit.diff diff --git a/compiler/rustc_mir_transform/src/large_enums.rs b/compiler/rustc_mir_transform/src/large_enums.rs deleted file mode 100644 index 1fdf827c14044..0000000000000 --- a/compiler/rustc_mir_transform/src/large_enums.rs +++ /dev/null @@ -1,226 +0,0 @@ -use rustc_abi::{HasDataLayout, Size, TagEncoding, Variants}; -use rustc_const_eval::interpret::{Scalar, alloc_range}; -use rustc_data_structures::fx::FxHashMap; -use rustc_middle::mir::interpret::AllocId; -use rustc_middle::mir::*; -use rustc_middle::ty::util::IntTypeExt; -use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; - -use crate::PassPolicy; -use crate::patch::MirPatch; - -/// A pass that seeks to optimize unnecessary moves of large enum types, if there is a large -/// enough discrepancy between them. -/// -/// i.e. If there are two variants: -/// ``` -/// enum Example { -/// Small, -/// Large([u32; 1024]), -/// } -/// ``` -/// Instead of emitting moves of the large variant, perform a memcpy instead. -/// Based off of [this HackMD](https://hackmd.io/@ft4bxUsFT5CEUBmRKYHr7w/rJM8BBPzD). -/// -/// In summary, what this does is at runtime determine which enum variant is active, -/// and instead of copying all the bytes of the largest possible variant, -/// copy only the bytes for the currently active variant. The number of bytes to copy is determined -/// by a lookup table: a discriminant-indexed array indicating the size of each variant. -pub(super) struct EnumSizeOpt { - pub(crate) discrepancy: u64, -} - -impl<'tcx> crate::MirPass<'tcx> for EnumSizeOpt { - fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { - // There are some differences in behavior on wasm and ARM that are not properly - // understood, so we conservatively treat this optimization as unsound: - // https://github.com/rust-lang/rust/issues/154413 - PassPolicy::optional(ctx.mir_opt_level() >= 3 && ctx.opts.unstable_opts.unsound_mir_opts) - } - - fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { - // NOTE: This pass may produce different MIR based on the alignment of the target - // platform, but it will still be valid. - - let mut alloc_cache = FxHashMap::default(); - let typing_env = body.typing_env(tcx); - - let mut patch = MirPatch::new(body); - - for (block, data) in body.basic_blocks.as_mut().iter_enumerated_mut() { - for (statement_index, st) in data.statements.iter_mut().enumerate() { - let StatementKind::Assign(( - lhs, - Rvalue::Use(Operand::Copy(rhs) | Operand::Move(rhs), _), - )) = &st.kind - else { - continue; - }; - - let location = Location { block, statement_index }; - - let ty = lhs.ty(&body.local_decls, tcx).ty; - - let Some((adt_def, num_variants, alloc_id)) = - self.candidate(tcx, typing_env, ty, &mut alloc_cache) - else { - continue; - }; - - let span = st.source_info.span; - - let tmp_ty = Ty::new_array(tcx, tcx.types.usize, num_variants as u64); - let size_array_local = patch.new_temp(tmp_ty, span); - - let store_live = StatementKind::StorageLive(size_array_local); - - let place = Place::from(size_array_local); - let constant_vals = ConstOperand { - span, - user_ty: None, - const_: Const::Val( - ConstValue::Indirect { alloc_id, offset: Size::ZERO }, - tmp_ty, - ), - }; - let rval = Rvalue::Use(Operand::Constant(Box::new(constant_vals)), WithRetag::No); - let const_assign = StatementKind::Assign(Box::new((place, rval))); - - let discr_place = - Place::from(patch.new_temp(adt_def.repr().discr_type().to_ty(tcx), span)); - let store_discr = - StatementKind::Assign(Box::new((discr_place, Rvalue::Discriminant(*rhs)))); - - let discr_cast_place = Place::from(patch.new_temp(tcx.types.usize, span)); - let cast_discr = StatementKind::Assign(Box::new(( - discr_cast_place, - Rvalue::Cast(CastKind::IntToInt, Operand::Copy(discr_place), tcx.types.usize), - ))); - - let size_place = Place::from(patch.new_temp(tcx.types.usize, span)); - let store_size = StatementKind::Assign(Box::new(( - size_place, - Rvalue::Use( - Operand::Copy(Place { - local: size_array_local, - projection: tcx - .mk_place_elems(&[PlaceElem::Index(discr_cast_place.local)]), - }), - WithRetag::No, - ), - ))); - - let dst = Place::from(patch.new_temp(Ty::new_mut_ptr(tcx, ty), span)); - let dst_ptr = - StatementKind::Assign(Box::new((dst, Rvalue::RawPtr(RawPtrKind::Mut, *lhs)))); - - let dst_cast_ty = Ty::new_mut_ptr(tcx, tcx.types.u8); - let dst_cast_place = Place::from(patch.new_temp(dst_cast_ty, span)); - let dst_cast = StatementKind::Assign(Box::new(( - dst_cast_place, - Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(dst), dst_cast_ty), - ))); - - let src = Place::from(patch.new_temp(Ty::new_imm_ptr(tcx, ty), span)); - let src_ptr = - StatementKind::Assign(Box::new((src, Rvalue::RawPtr(RawPtrKind::Const, *rhs)))); - - let src_cast_ty = Ty::new_imm_ptr(tcx, tcx.types.u8); - let src_cast_place = Place::from(patch.new_temp(src_cast_ty, span)); - let src_cast = StatementKind::Assign(Box::new(( - src_cast_place, - Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(src), src_cast_ty), - ))); - - let copy_bytes = StatementKind::Intrinsic(Box::new( - NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping { - src: Operand::Copy(src_cast_place), - dst: Operand::Copy(dst_cast_place), - count: Operand::Copy(size_place), - }), - )); - - let store_dead = StatementKind::StorageDead(size_array_local); - - let stmts = [ - store_live, - const_assign, - store_discr, - cast_discr, - store_size, - dst_ptr, - dst_cast, - src_ptr, - src_cast, - copy_bytes, - store_dead, - ]; - for stmt in stmts { - patch.add_statement(location, stmt); - } - - st.make_nop(true); - } - } - - patch.apply(body); - } -} - -impl EnumSizeOpt { - fn candidate<'tcx>( - &self, - tcx: TyCtxt<'tcx>, - typing_env: ty::TypingEnv<'tcx>, - ty: Ty<'tcx>, - alloc_cache: &mut FxHashMap, AllocId>, - ) -> Option<(AdtDef<'tcx>, usize, AllocId)> { - let adt_def = match ty.kind() { - ty::Adt(adt_def, _args) if adt_def.is_enum() => adt_def, - _ => return None, - }; - let layout = tcx.layout_of(typing_env.as_query_input(ty)).ok()?; - let variants = match &layout.variants { - Variants::Single { .. } | Variants::Empty => return None, - Variants::Multiple { tag_encoding: TagEncoding::Niche { .. }, .. } => return None, - - Variants::Multiple { variants, .. } if variants.len() <= 1 => return None, - Variants::Multiple { variants, .. } => variants, - }; - let min = variants.iter().map(|v| v.size).min().unwrap(); - let max = variants.iter().map(|v| v.size).max().unwrap(); - if max.bytes() - min.bytes() < self.discrepancy { - return None; - } - - let num_discrs = adt_def.discriminants(tcx).count(); - if variants.iter_enumerated().any(|(var_idx, _)| { - let discr_for_var = adt_def.discriminant_for_variant(tcx, var_idx).val; - (discr_for_var > usize::MAX as u128) || (discr_for_var as usize >= num_discrs) - }) { - return None; - } - if let Some(alloc_id) = alloc_cache.get(&ty) { - return Some((*adt_def, num_discrs, *alloc_id)); - } - - // Construct an in-memory array mapping discriminant idx to variant size. - let data_layout = tcx.data_layout(); - let ptr_size = data_layout.pointer_size(); - let mut alloc = interpret::Allocation::from_bytes( - vec![0; ptr_size.bytes_usize() * num_discrs], - tcx.data_layout.ptr_sized_integer().align(&tcx.data_layout).abi, - Mutability::Mut, - (), - ); - for (var_idx, layout) in variants.iter_enumerated() { - let curr_idx = ptr_size * adt_def.discriminant_for_variant(tcx, var_idx).val as u64; - let val = Scalar::from_target_usize(layout.size.bytes(), &tcx); - alloc.write_scalar(&tcx, alloc_range(curr_idx, val.size()), val).unwrap(); - } - alloc.mutability = Mutability::Not; - let alloc = tcx.reserve_and_set_memory_alloc(tcx.mk_const_alloc(alloc)); - - Some((*adt_def, num_discrs, *alloc_cache.entry(ty).or_insert(alloc))) - } -} diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 6fdccad1505a5..283b963e732f9 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -159,7 +159,6 @@ declare_passes! { mod instsimplify : InstSimplify { BeforeInline, AfterSimplifyCfg }; mod jump_threading : JumpThreading; mod known_panics_lint : KnownPanicsLint; - mod large_enums : EnumSizeOpt; mod lint_and_remove_uninhabited : LintAndRemoveUninhabited; mod lower_intrinsics : LowerIntrinsics; mod lower_slice_len : LowerSliceLenCalls; @@ -763,7 +762,6 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<' &dest_prop::DestinationPropagation, &simplify::SimplifyLocals::Final, &multiple_return_terminators::MultipleReturnTerminators, - &large_enums::EnumSizeOpt { discrepancy: 128 }, // Some cleanup necessary at least for LLVM and potentially other codegen backends. &add_call_guards::CriticalCallEdges, // Cleanup for human readability, off by default. diff --git a/tests/mir-opt/enum_opt.cand.EnumSizeOpt.32bit.diff b/tests/mir-opt/enum_opt.cand.EnumSizeOpt.32bit.diff deleted file mode 100644 index ea189cf2fb82d..0000000000000 --- a/tests/mir-opt/enum_opt.cand.EnumSizeOpt.32bit.diff +++ /dev/null @@ -1,72 +0,0 @@ -- // MIR for `cand` before EnumSizeOpt -+ // MIR for `cand` after EnumSizeOpt - - fn cand() -> Candidate { - let mut _0: Candidate; - let mut _1: Candidate; - let mut _2: Candidate; - let mut _3: [u8; 8196]; -+ let mut _4: [usize; 2]; -+ let mut _5: isize; -+ let mut _6: usize; -+ let mut _7: usize; -+ let mut _8: *mut Candidate; -+ let mut _9: *mut u8; -+ let mut _10: *const Candidate; -+ let mut _11: *const u8; -+ let mut _12: [usize; 2]; -+ let mut _13: isize; -+ let mut _14: usize; -+ let mut _15: usize; -+ let mut _16: *mut Candidate; -+ let mut _17: *mut u8; -+ let mut _18: *const Candidate; -+ let mut _19: *const u8; - scope 1 { - debug a => _1; - } - - bb0: { - StorageLive(_1); - _1 = Candidate::Small(const 1_u8); - StorageLive(_2); - StorageLive(_3); - _3 = [const 1_u8; 8196]; - _2 = Candidate::Large(move _3); - StorageDead(_3); -- _1 = move _2; -+ StorageLive(_4); -+ _4 = no_retag const [2_usize, 8197_usize]; -+ _5 = discriminant(_2); -+ _6 = copy _5 as usize (IntToInt); -+ _7 = no_retag copy _4[_6]; -+ _8 = &raw mut _1; -+ _9 = copy _8 as *mut u8 (PtrToPtr); -+ _10 = &raw const _2; -+ _11 = copy _10 as *const u8 (PtrToPtr); -+ copy_nonoverlapping(dst = copy _9, src = copy _11, count = copy _7); -+ StorageDead(_4); -+ nop; - StorageDead(_2); -- _0 = move _1; -+ StorageLive(_12); -+ _12 = no_retag const [2_usize, 8197_usize]; -+ _13 = discriminant(_1); -+ _14 = copy _13 as usize (IntToInt); -+ _15 = no_retag copy _12[_14]; -+ _16 = &raw mut _0; -+ _17 = copy _16 as *mut u8 (PtrToPtr); -+ _18 = &raw const _1; -+ _19 = copy _18 as *const u8 (PtrToPtr); -+ copy_nonoverlapping(dst = copy _17, src = copy _19, count = copy _15); -+ StorageDead(_12); -+ nop; - StorageDead(_1); - return; - } -+ } -+ -+ ALLOC0 (size: 8, align: 4) { -+ 02 00 00 00 05 20 00 00 │ ..... .. - } - diff --git a/tests/mir-opt/enum_opt.cand.EnumSizeOpt.64bit.diff b/tests/mir-opt/enum_opt.cand.EnumSizeOpt.64bit.diff deleted file mode 100644 index 6e46bdc8ed442..0000000000000 --- a/tests/mir-opt/enum_opt.cand.EnumSizeOpt.64bit.diff +++ /dev/null @@ -1,72 +0,0 @@ -- // MIR for `cand` before EnumSizeOpt -+ // MIR for `cand` after EnumSizeOpt - - fn cand() -> Candidate { - let mut _0: Candidate; - let mut _1: Candidate; - let mut _2: Candidate; - let mut _3: [u8; 8196]; -+ let mut _4: [usize; 2]; -+ let mut _5: isize; -+ let mut _6: usize; -+ let mut _7: usize; -+ let mut _8: *mut Candidate; -+ let mut _9: *mut u8; -+ let mut _10: *const Candidate; -+ let mut _11: *const u8; -+ let mut _12: [usize; 2]; -+ let mut _13: isize; -+ let mut _14: usize; -+ let mut _15: usize; -+ let mut _16: *mut Candidate; -+ let mut _17: *mut u8; -+ let mut _18: *const Candidate; -+ let mut _19: *const u8; - scope 1 { - debug a => _1; - } - - bb0: { - StorageLive(_1); - _1 = Candidate::Small(const 1_u8); - StorageLive(_2); - StorageLive(_3); - _3 = [const 1_u8; 8196]; - _2 = Candidate::Large(move _3); - StorageDead(_3); -- _1 = move _2; -+ StorageLive(_4); -+ _4 = no_retag const [2_usize, 8197_usize]; -+ _5 = discriminant(_2); -+ _6 = copy _5 as usize (IntToInt); -+ _7 = no_retag copy _4[_6]; -+ _8 = &raw mut _1; -+ _9 = copy _8 as *mut u8 (PtrToPtr); -+ _10 = &raw const _2; -+ _11 = copy _10 as *const u8 (PtrToPtr); -+ copy_nonoverlapping(dst = copy _9, src = copy _11, count = copy _7); -+ StorageDead(_4); -+ nop; - StorageDead(_2); -- _0 = move _1; -+ StorageLive(_12); -+ _12 = no_retag const [2_usize, 8197_usize]; -+ _13 = discriminant(_1); -+ _14 = copy _13 as usize (IntToInt); -+ _15 = no_retag copy _12[_14]; -+ _16 = &raw mut _0; -+ _17 = copy _16 as *mut u8 (PtrToPtr); -+ _18 = &raw const _1; -+ _19 = copy _18 as *const u8 (PtrToPtr); -+ copy_nonoverlapping(dst = copy _17, src = copy _19, count = copy _15); -+ StorageDead(_12); -+ nop; - StorageDead(_1); - return; - } -+ } -+ -+ ALLOC0 (size: 16, align: 8) { -+ 02 00 00 00 00 00 00 00 05 20 00 00 00 00 00 00 │ ......... ...... - } - diff --git a/tests/mir-opt/enum_opt.invalid.EnumSizeOpt.32bit.diff b/tests/mir-opt/enum_opt.invalid.EnumSizeOpt.32bit.diff deleted file mode 100644 index b627fd279071f..0000000000000 --- a/tests/mir-opt/enum_opt.invalid.EnumSizeOpt.32bit.diff +++ /dev/null @@ -1,28 +0,0 @@ -- // MIR for `invalid` before EnumSizeOpt -+ // MIR for `invalid` after EnumSizeOpt - - fn invalid() -> InvalidIdxs { - let mut _0: InvalidIdxs; - let mut _1: InvalidIdxs; - let mut _2: InvalidIdxs; - let mut _3: [u64; 1024]; - scope 1 { - debug a => _1; - } - - bb0: { - StorageLive(_1); - _1 = InvalidIdxs::A; - StorageLive(_2); - StorageLive(_3); - _3 = [const 0_u64; 1024]; - _2 = InvalidIdxs::Large(move _3); - StorageDead(_3); - _1 = move _2; - StorageDead(_2); - _0 = move _1; - StorageDead(_1); - return; - } - } - diff --git a/tests/mir-opt/enum_opt.invalid.EnumSizeOpt.64bit.diff b/tests/mir-opt/enum_opt.invalid.EnumSizeOpt.64bit.diff deleted file mode 100644 index b627fd279071f..0000000000000 --- a/tests/mir-opt/enum_opt.invalid.EnumSizeOpt.64bit.diff +++ /dev/null @@ -1,28 +0,0 @@ -- // MIR for `invalid` before EnumSizeOpt -+ // MIR for `invalid` after EnumSizeOpt - - fn invalid() -> InvalidIdxs { - let mut _0: InvalidIdxs; - let mut _1: InvalidIdxs; - let mut _2: InvalidIdxs; - let mut _3: [u64; 1024]; - scope 1 { - debug a => _1; - } - - bb0: { - StorageLive(_1); - _1 = InvalidIdxs::A; - StorageLive(_2); - StorageLive(_3); - _3 = [const 0_u64; 1024]; - _2 = InvalidIdxs::Large(move _3); - StorageDead(_3); - _1 = move _2; - StorageDead(_2); - _0 = move _1; - StorageDead(_1); - return; - } - } - diff --git a/tests/mir-opt/enum_opt.rs b/tests/mir-opt/enum_opt.rs deleted file mode 100644 index 90697a71cdfc1..0000000000000 --- a/tests/mir-opt/enum_opt.rs +++ /dev/null @@ -1,86 +0,0 @@ -//@ skip-filecheck -//@ test-mir-pass: EnumSizeOpt -// EMIT_MIR_FOR_EACH_BIT_WIDTH -//@ compile-flags: -Zunsound-mir-opts -//@ ignore-endian-big - -// Tests that an enum with a variant with no data gets correctly transformed. -pub enum NoData { - Large([u8; 8196]), - None, -} - -// Tests that an enum with a variant with data that is a valid candidate gets transformed. -pub enum Candidate { - Small(u8), - Large([u8; 8196]), -} - -// Tests that an enum which has a discriminant much higher than the variant does not get -// tformed. -#[repr(u32)] -pub enum InvalidIdxs { - A = 302, - Large([u64; 1024]), -} - -// Tests that an enum with too high of a discriminant index (not in bounds of usize) does not -// get tformed. -#[repr(u128)] -pub enum NotTrunctable { - A = 0, - B([u8; 1024]) = 1, - C([u8; 4096]) = 0x10000000000000001, -} - -// Tests that an enum with discriminants in random order still gets tformed correctly. -#[repr(u32)] -pub enum RandOrderDiscr { - A = 13, - B([u8; 1024]) = 5, - C = 7, -} - -// EMIT_MIR enum_opt.unin.EnumSizeOpt.diff -pub fn unin() -> NoData { - let mut a = NoData::None; - a = NoData::Large([1; 8196]); - a -} - -// EMIT_MIR enum_opt.cand.EnumSizeOpt.diff -pub fn cand() -> Candidate { - let mut a = Candidate::Small(1); - a = Candidate::Large([1; 8196]); - a -} - -// EMIT_MIR enum_opt.invalid.EnumSizeOpt.diff -pub fn invalid() -> InvalidIdxs { - let mut a = InvalidIdxs::A; - a = InvalidIdxs::Large([0; 1024]); - a -} - -// EMIT_MIR enum_opt.trunc.EnumSizeOpt.diff -pub fn trunc() -> NotTrunctable { - let mut a = NotTrunctable::A; - a = NotTrunctable::B([0; 1024]); - a = NotTrunctable::C([0; 4096]); - a -} - -pub fn rand_order() -> RandOrderDiscr { - let mut a = RandOrderDiscr::A; - a = RandOrderDiscr::B([0; 1024]); - a = RandOrderDiscr::C; - a -} - -pub fn main() { - unin(); - cand(); - invalid(); - trunc(); - rand_order(); -} diff --git a/tests/mir-opt/enum_opt.trunc.EnumSizeOpt.32bit.diff b/tests/mir-opt/enum_opt.trunc.EnumSizeOpt.32bit.diff deleted file mode 100644 index 100a73e56f22a..0000000000000 --- a/tests/mir-opt/enum_opt.trunc.EnumSizeOpt.32bit.diff +++ /dev/null @@ -1,37 +0,0 @@ -- // MIR for `trunc` before EnumSizeOpt -+ // MIR for `trunc` after EnumSizeOpt - - fn trunc() -> NotTrunctable { - let mut _0: NotTrunctable; - let mut _1: NotTrunctable; - let mut _2: NotTrunctable; - let mut _3: [u8; 1024]; - let mut _4: NotTrunctable; - let mut _5: [u8; 4096]; - scope 1 { - debug a => _1; - } - - bb0: { - StorageLive(_1); - _1 = NotTrunctable::A; - StorageLive(_2); - StorageLive(_3); - _3 = [const 0_u8; 1024]; - _2 = NotTrunctable::B(move _3); - StorageDead(_3); - _1 = move _2; - StorageDead(_2); - StorageLive(_4); - StorageLive(_5); - _5 = [const 0_u8; 4096]; - _4 = NotTrunctable::C(move _5); - StorageDead(_5); - _1 = move _4; - StorageDead(_4); - _0 = move _1; - StorageDead(_1); - return; - } - } - diff --git a/tests/mir-opt/enum_opt.trunc.EnumSizeOpt.64bit.diff b/tests/mir-opt/enum_opt.trunc.EnumSizeOpt.64bit.diff deleted file mode 100644 index 100a73e56f22a..0000000000000 --- a/tests/mir-opt/enum_opt.trunc.EnumSizeOpt.64bit.diff +++ /dev/null @@ -1,37 +0,0 @@ -- // MIR for `trunc` before EnumSizeOpt -+ // MIR for `trunc` after EnumSizeOpt - - fn trunc() -> NotTrunctable { - let mut _0: NotTrunctable; - let mut _1: NotTrunctable; - let mut _2: NotTrunctable; - let mut _3: [u8; 1024]; - let mut _4: NotTrunctable; - let mut _5: [u8; 4096]; - scope 1 { - debug a => _1; - } - - bb0: { - StorageLive(_1); - _1 = NotTrunctable::A; - StorageLive(_2); - StorageLive(_3); - _3 = [const 0_u8; 1024]; - _2 = NotTrunctable::B(move _3); - StorageDead(_3); - _1 = move _2; - StorageDead(_2); - StorageLive(_4); - StorageLive(_5); - _5 = [const 0_u8; 4096]; - _4 = NotTrunctable::C(move _5); - StorageDead(_5); - _1 = move _4; - StorageDead(_4); - _0 = move _1; - StorageDead(_1); - return; - } - } - diff --git a/tests/mir-opt/enum_opt.unin.EnumSizeOpt.32bit.diff b/tests/mir-opt/enum_opt.unin.EnumSizeOpt.32bit.diff deleted file mode 100644 index c8d615383c0c3..0000000000000 --- a/tests/mir-opt/enum_opt.unin.EnumSizeOpt.32bit.diff +++ /dev/null @@ -1,72 +0,0 @@ -- // MIR for `unin` before EnumSizeOpt -+ // MIR for `unin` after EnumSizeOpt - - fn unin() -> NoData { - let mut _0: NoData; - let mut _1: NoData; - let mut _2: NoData; - let mut _3: [u8; 8196]; -+ let mut _4: [usize; 2]; -+ let mut _5: isize; -+ let mut _6: usize; -+ let mut _7: usize; -+ let mut _8: *mut NoData; -+ let mut _9: *mut u8; -+ let mut _10: *const NoData; -+ let mut _11: *const u8; -+ let mut _12: [usize; 2]; -+ let mut _13: isize; -+ let mut _14: usize; -+ let mut _15: usize; -+ let mut _16: *mut NoData; -+ let mut _17: *mut u8; -+ let mut _18: *const NoData; -+ let mut _19: *const u8; - scope 1 { - debug a => _1; - } - - bb0: { - StorageLive(_1); - _1 = NoData::None; - StorageLive(_2); - StorageLive(_3); - _3 = [const 1_u8; 8196]; - _2 = NoData::Large(move _3); - StorageDead(_3); -- _1 = move _2; -+ StorageLive(_4); -+ _4 = no_retag const [8197_usize, 1_usize]; -+ _5 = discriminant(_2); -+ _6 = copy _5 as usize (IntToInt); -+ _7 = no_retag copy _4[_6]; -+ _8 = &raw mut _1; -+ _9 = copy _8 as *mut u8 (PtrToPtr); -+ _10 = &raw const _2; -+ _11 = copy _10 as *const u8 (PtrToPtr); -+ copy_nonoverlapping(dst = copy _9, src = copy _11, count = copy _7); -+ StorageDead(_4); -+ nop; - StorageDead(_2); -- _0 = move _1; -+ StorageLive(_12); -+ _12 = no_retag const [8197_usize, 1_usize]; -+ _13 = discriminant(_1); -+ _14 = copy _13 as usize (IntToInt); -+ _15 = no_retag copy _12[_14]; -+ _16 = &raw mut _0; -+ _17 = copy _16 as *mut u8 (PtrToPtr); -+ _18 = &raw const _1; -+ _19 = copy _18 as *const u8 (PtrToPtr); -+ copy_nonoverlapping(dst = copy _17, src = copy _19, count = copy _15); -+ StorageDead(_12); -+ nop; - StorageDead(_1); - return; - } -+ } -+ -+ ALLOC0 (size: 8, align: 4) { -+ 05 20 00 00 01 00 00 00 │ . ...... - } - diff --git a/tests/mir-opt/enum_opt.unin.EnumSizeOpt.64bit.diff b/tests/mir-opt/enum_opt.unin.EnumSizeOpt.64bit.diff deleted file mode 100644 index e25644d7a4383..0000000000000 --- a/tests/mir-opt/enum_opt.unin.EnumSizeOpt.64bit.diff +++ /dev/null @@ -1,72 +0,0 @@ -- // MIR for `unin` before EnumSizeOpt -+ // MIR for `unin` after EnumSizeOpt - - fn unin() -> NoData { - let mut _0: NoData; - let mut _1: NoData; - let mut _2: NoData; - let mut _3: [u8; 8196]; -+ let mut _4: [usize; 2]; -+ let mut _5: isize; -+ let mut _6: usize; -+ let mut _7: usize; -+ let mut _8: *mut NoData; -+ let mut _9: *mut u8; -+ let mut _10: *const NoData; -+ let mut _11: *const u8; -+ let mut _12: [usize; 2]; -+ let mut _13: isize; -+ let mut _14: usize; -+ let mut _15: usize; -+ let mut _16: *mut NoData; -+ let mut _17: *mut u8; -+ let mut _18: *const NoData; -+ let mut _19: *const u8; - scope 1 { - debug a => _1; - } - - bb0: { - StorageLive(_1); - _1 = NoData::None; - StorageLive(_2); - StorageLive(_3); - _3 = [const 1_u8; 8196]; - _2 = NoData::Large(move _3); - StorageDead(_3); -- _1 = move _2; -+ StorageLive(_4); -+ _4 = no_retag const [8197_usize, 1_usize]; -+ _5 = discriminant(_2); -+ _6 = copy _5 as usize (IntToInt); -+ _7 = no_retag copy _4[_6]; -+ _8 = &raw mut _1; -+ _9 = copy _8 as *mut u8 (PtrToPtr); -+ _10 = &raw const _2; -+ _11 = copy _10 as *const u8 (PtrToPtr); -+ copy_nonoverlapping(dst = copy _9, src = copy _11, count = copy _7); -+ StorageDead(_4); -+ nop; - StorageDead(_2); -- _0 = move _1; -+ StorageLive(_12); -+ _12 = no_retag const [8197_usize, 1_usize]; -+ _13 = discriminant(_1); -+ _14 = copy _13 as usize (IntToInt); -+ _15 = no_retag copy _12[_14]; -+ _16 = &raw mut _0; -+ _17 = copy _16 as *mut u8 (PtrToPtr); -+ _18 = &raw const _1; -+ _19 = copy _18 as *const u8 (PtrToPtr); -+ copy_nonoverlapping(dst = copy _17, src = copy _19, count = copy _15); -+ StorageDead(_12); -+ nop; - StorageDead(_1); - return; - } -+ } -+ -+ ALLOC0 (size: 16, align: 8) { -+ 05 20 00 00 00 00 00 00 01 00 00 00 00 00 00 00 │ . .............. - } - From cf7c0ffef791c6d9786a08449f697347ec3ae180 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 6 Sep 2026 22:06:44 +0100 Subject: [PATCH 47/53] 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 48/53] 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 49/53] 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 805aaa1f6888df2474cfe1dc325d068cfc2d4e2e Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 6 Sep 2026 23:35:01 -0400 Subject: [PATCH 50/53] docs: Ask for ABI documentation in the platform support template We occasionally run into questions about how things should get passed and need to hunt down a definitive answer. Ask for a link in the platform docs to make this easier. --- src/doc/rustc/src/platform-support/TEMPLATE.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc/src/platform-support/TEMPLATE.md b/src/doc/rustc/src/platform-support/TEMPLATE.md index f523237ab06ff..40ff9faccce58 100644 --- a/src/doc/rustc/src/platform-support/TEMPLATE.md +++ b/src/doc/rustc/src/platform-support/TEMPLATE.md @@ -22,9 +22,12 @@ version of the OS or environment do they expect? Are there notable `#[target_feature(...)]` or `-C target-feature=` values that programs may wish to use? -What calling convention does `extern "C"` use on the target? +What calling convention does `extern "C"` use on the target? Where can the most +up-to-date calling convention and ABI documentation be found? Please link to +this information if publicly available. -What format do binaries use by default? ELF, PE, something else? +What format do binaries use by default? ELF, PE, something else? Provide links +if the format is not standard. ## Building the target 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 51/53] 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 52/53] 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| { From d742397a97c500f08b7df1ccca06225a2c3d12c0 Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Sun, 6 Sep 2026 11:58:06 +0330 Subject: [PATCH 53/53] delay unexpected successful goal during ambiguity reporting Signed-off-by: Amirhossein Akhlaghpour refactor next solver ambiguity error reporting Signed-off-by: Amirhossein Akhlaghpour --- .../src/solve/fulfill.rs | 25 ++++++--- .../src/solve/fulfill/derive_errors.rs | 51 ++++++++++++------- ...cessful-goal-during-ambiguity-reporting.rs | 24 +++++++++ ...ful-goal-during-ambiguity-reporting.stderr | 14 +++++ 4 files changed, 89 insertions(+), 25 deletions(-) create mode 100644 tests/ui/traits/next-solver/successful-goal-during-ambiguity-reporting.rs create mode 100644 tests/ui/traits/next-solver/successful-goal-during-ambiguity-reporting.stderr diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index f7eb76bbe72b5..318897c36b010 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -17,7 +17,7 @@ use tracing::instrument; use self::derive_errors::*; use super::Certainty; use super::delegate::SolverDelegate; -use crate::traits::{FulfillmentError, ScrubbedTraitError}; +use crate::traits::{FulfillmentError, FulfillmentErrorCode, ScrubbedTraitError}; mod derive_errors; @@ -191,7 +191,8 @@ where // the other case. TraitErrors::NoErrors } else { - TraitErrors::HasErrors(collect_remaining_errors_impl(self, infcx)) + let errors = collect_remaining_errors_impl(self, infcx); + TraitErrors::from_iter(errors.into_iter()) } } @@ -405,7 +406,9 @@ where cx.obligations .pending .drain(..) - .map(|(obligation, _)| NextSolverError::Ambiguity(obligation)) + .filter_map(|(obligation, _)| { + try_ambiguity_error_for_stalled(infcx, obligation).map(NextSolverError::Ambiguity) + }) .chain( cx.obligations .overflowed @@ -416,9 +419,19 @@ where .collect() } +// We evaluate stalled obligations while collecting remaining errors because a +// previously ambiguous goal may have become successful. In that case we emit a +// delayed bug instead of producing a fulfillment error. Store the diagnostic +// information here so error conversion does not reevaluate the goal. +pub struct NextSolverAmbiguityError<'tcx> { + root_obligation: PredicateObligation<'tcx>, + code: FulfillmentErrorCode<'tcx>, + refine_obligation: bool, +} + pub enum NextSolverError<'tcx> { TrueError(PredicateObligation<'tcx>), - Ambiguity(PredicateObligation<'tcx>), + Ambiguity(NextSolverAmbiguityError<'tcx>), Overflow(PredicateObligation<'tcx>), } @@ -428,8 +441,8 @@ impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for FulfillmentError<'tc NextSolverError::TrueError(obligation) => { fulfillment_error_for_no_solution(infcx, obligation) } - NextSolverError::Ambiguity(obligation) => { - fulfillment_error_for_stalled(infcx, obligation) + NextSolverError::Ambiguity(ambiguity) => { + fulfillment_error_for_stalled(infcx, ambiguity) } NextSolverError::Overflow(obligation) => { fulfillment_error_for_overflow(infcx, obligation) diff --git a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs index 59336396ab11c..1c7d5b742e1ac 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs @@ -14,6 +14,7 @@ use rustc_middle::{bug, span_bug}; use rustc_next_trait_solver::solve::{GoalEvaluation, MaybeInfo, SolverDelegateEvalExt as _}; use tracing::{instrument, trace}; +use super::NextSolverAmbiguityError; use crate::solve::delegate::SolverDelegate; use crate::solve::inspect::{self, InferCtxtProofTreeExt, ProofTreeVisitor}; use crate::solve::{Certainty, deeply_normalize_for_diagnostics}; @@ -84,9 +85,24 @@ pub(super) fn fulfillment_error_for_no_solution<'tcx>( pub(super) fn fulfillment_error_for_stalled<'tcx>( infcx: &InferCtxt<'tcx>, - root_obligation: PredicateObligation<'tcx>, + ambiguity: NextSolverAmbiguityError<'tcx>, ) -> FulfillmentError<'tcx> { - let (code, refine_obligation) = infcx.probe(|_| { + let NextSolverAmbiguityError { root_obligation, code, refine_obligation } = ambiguity; + + let obligation = if refine_obligation { + find_best_leaf_obligation(infcx, &root_obligation, true) + } else { + root_obligation.clone() + }; + + FulfillmentError { obligation, code, root_obligation } +} + +pub(super) fn try_ambiguity_error_for_stalled<'tcx>( + infcx: &InferCtxt<'tcx>, + root_obligation: PredicateObligation<'tcx>, +) -> Option> { + let evaluation = infcx.probe(|_| { match <&SolverDelegate<'tcx>>::from(infcx).evaluate_root_goal( root_obligation.as_goal(), root_obligation.cause.span, @@ -100,7 +116,7 @@ pub(super) fn fulfillment_error_for_stalled<'tcx>( stalled_on_coroutines: _, }), .. - }) => (FulfillmentErrorCode::Ambiguity { overflow: None }, true), + }) => Some((FulfillmentErrorCode::Ambiguity { overflow: None }, true)), Ok(GoalEvaluation { certainty: Certainty::Maybe(MaybeInfo { @@ -110,7 +126,7 @@ pub(super) fn fulfillment_error_for_stalled<'tcx>( stalled_on_coroutines: _, }), .. - }) => ( + }) => Some(( FulfillmentErrorCode::Ambiguity { overflow: Some(suggest_increasing_limit) }, // Don't look into overflows because we treat overflows weirdly anyways. // We discard the inference constraints from overflowing goals, so @@ -119,14 +135,17 @@ pub(super) fn fulfillment_error_for_stalled<'tcx>( // // FIXME: We should probably just look into overflows here. false, - ), + )), Ok(GoalEvaluation { certainty: Certainty::Yes, .. }) => { - span_bug!( + infcx.dcx().span_delayed_bug( root_obligation.cause.span, - "did not expect successful goal when collecting ambiguity errors for `{:?}`", - infcx.resolve_vars_if_possible(root_obligation.predicate), - ) - } + format!( + "did not expect successful goal when collecting ambiguity errors for `{:?}`", + infcx.resolve_vars_if_possible(root_obligation.predicate), + ), + ); + None + }, Err(_) => { span_bug!( root_obligation.cause.span, @@ -137,15 +156,9 @@ pub(super) fn fulfillment_error_for_stalled<'tcx>( } }); - FulfillmentError { - obligation: if refine_obligation { - find_best_leaf_obligation(infcx, &root_obligation, true) - } else { - root_obligation.clone() - }, - code, - root_obligation, - } + let (code, refine_obligation) = evaluation?; + + Some(NextSolverAmbiguityError { root_obligation, code, refine_obligation }) } pub(super) fn fulfillment_error_for_overflow<'tcx>( diff --git a/tests/ui/traits/next-solver/successful-goal-during-ambiguity-reporting.rs b/tests/ui/traits/next-solver/successful-goal-during-ambiguity-reporting.rs new file mode 100644 index 0000000000000..1032554301487 --- /dev/null +++ b/tests/ui/traits/next-solver/successful-goal-during-ambiguity-reporting.rs @@ -0,0 +1,24 @@ +// Regression test for https://github.com/rust-lang/rust/issues/161669. +//@ edition:2024 +//@ compile-flags: -Znext-solver + +#![allow(dead_code)] +trait MyTrait {} +impl MyTrait for () {} + +impl<'de> DeserTrait<'de> for &'de DeserStruct {} + +trait DeserTrait<'de> {} + +struct DeserStruct; + +impl DeserTrait<'_> for &'static MyTrait {} +//~^ ERROR expected a type, found a trait + +fn test() -> impl Send { + testfn(&DeserStruct) +} + +fn testfn<'de, D: DeserTrait<'de>>(_deserializer: D) -> impl MyTrait + 'static {} + +fn main() {} diff --git a/tests/ui/traits/next-solver/successful-goal-during-ambiguity-reporting.stderr b/tests/ui/traits/next-solver/successful-goal-during-ambiguity-reporting.stderr new file mode 100644 index 0000000000000..ca2a80c8d0a12 --- /dev/null +++ b/tests/ui/traits/next-solver/successful-goal-during-ambiguity-reporting.stderr @@ -0,0 +1,14 @@ +error[E0782]: expected a type, found a trait + --> $DIR/successful-goal-during-ambiguity-reporting.rs:15:34 + | +LL | impl DeserTrait<'_> for &'static MyTrait {} + | ^^^^^^^ + | +help: you can add the `dyn` keyword if you want a trait object + | +LL | impl DeserTrait<'_> for &'static dyn MyTrait {} + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0782`.