From 673e443793a4e0c47dc194209888fbc0c50cf815 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 23:46:59 +0000 Subject: [PATCH 01/60] Bump lodash from 4.17.21 to 4.17.23 in /editors/code Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../rust-analyzer/editors/code/package-lock.json | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index 57f6bf69beb08..887162292d9e4 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -1486,7 +1486,6 @@ "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.25.0", "@typescript-eslint/types": "8.25.0", @@ -1870,7 +1869,6 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2840,7 +2838,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3322,7 +3319,6 @@ "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -4410,7 +4406,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -4655,9 +4650,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -6678,7 +6673,6 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From 802ee0156a35e4e334eb32bec9a8f9ea16995494 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 27 Jan 2026 21:18:09 +0800 Subject: [PATCH 02/60] fix: offer `toggle_macro_delimiter` in nested macro Example --- ```rust prt!{abc!$0(3 + 5)}; ``` **Before this PR** Assist not applicable **After this PR** ```rust prt!{abc!{3 + 5}}; ``` --- .../src/handlers/toggle_macro_delimiter.rs | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs index 60b0797f028a9..e9865aec4a146 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs @@ -1,6 +1,7 @@ use ide_db::assists::AssistId; use syntax::{ - AstNode, SyntaxToken, T, + AstNode, SyntaxKind, SyntaxToken, T, + algo::{previous_non_trivia_token, skip_trivia_token}, ast::{self, syntax_factory::SyntaxFactory}, }; @@ -36,15 +37,18 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>) RCur, } - let makro = ctx.find_node_at_offset::()?; + let token_tree = ctx.find_node_at_offset::()?; let cursor_offset = ctx.offset(); - let semicolon = macro_semicolon(&makro); - let token_tree = makro.token_tree()?; + let semicolon = macro_semicolon(&token_tree); let ltoken = token_tree.left_delimiter_token()?; let rtoken = token_tree.right_delimiter_token()?; + if !is_macro_call(&token_tree)? { + return None; + } + if !ltoken.text_range().contains(cursor_offset) && !rtoken.text_range().contains(cursor_offset) { return None; @@ -70,7 +74,7 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>) token_tree.syntax().text_range(), |builder| { let make = SyntaxFactory::with_mappings(); - let mut editor = builder.make_editor(makro.syntax()); + let mut editor = builder.make_editor(token_tree.syntax()); match token { MacroDelims::LPar | MacroDelims::RPar => { @@ -102,12 +106,21 @@ pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>) ) } -fn macro_semicolon(makro: &ast::MacroCall) -> Option { - makro.semicolon_token().or_else(|| { - let macro_expr = ast::MacroExpr::cast(makro.syntax().parent()?)?; - let expr_stmt = ast::ExprStmt::cast(macro_expr.syntax().parent()?)?; - expr_stmt.semicolon_token() - }) +fn is_macro_call(token_tree: &ast::TokenTree) -> Option { + let parent = token_tree.syntax().parent()?; + if ast::MacroCall::can_cast(parent.kind()) { + return Some(true); + } + + let token_tree = ast::TokenTree::cast(parent)?; + let prev = previous_non_trivia_token(token_tree.syntax().clone())?; + let prev_prev = previous_non_trivia_token(prev.clone())?; + Some(prev.kind() == T![!] && prev_prev.kind() == SyntaxKind::IDENT) +} + +fn macro_semicolon(token_tree: &ast::TokenTree) -> Option { + let next_token = token_tree.syntax().last_token()?.next_token()?; + skip_trivia_token(next_token, syntax::Direction::Next).filter(|it| it.kind() == T![;]) } fn needs_semicolon(tt: ast::TokenTree) -> bool { @@ -405,7 +418,7 @@ prt!{(3 + 5)} // FIXME @alibektas : Inner macro_call is not seen as such. So this doesn't work. #[test] fn test_nested_macros() { - check_assist_not_applicable( + check_assist( toggle_macro_delimiter, r#" macro_rules! prt { @@ -420,7 +433,22 @@ macro_rules! abc { }}; } -prt!{abc!($03 + 5)}; +prt!{abc!$0(3 + 5)}; +"#, + r#" +macro_rules! prt { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +macro_rules! abc { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +prt!{abc!{3 + 5}}; "#, ) } From 2755c3759775a34068d624ce3196f92a7a82b4a4 Mon Sep 17 00:00:00 2001 From: Jesung Yang Date: Wed, 28 Jan 2026 13:48:24 +0900 Subject: [PATCH 03/60] Remove unncecessary `#[serde(default)]` Remove `#[serde(default)]` from optional fields. Fields of type `Option` are automatically deserialized to `None` when the corresponding keys are missing in `rust-project.json`, making the explicit attribute redundant. --- .../rust-analyzer/crates/project-model/src/project_json.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index 6938010cbd708..9b9111012b541 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -391,7 +391,6 @@ struct CrateData { display_name: Option, root_module: Utf8PathBuf, edition: EditionData, - #[serde(default)] version: Option, deps: Vec, #[serde(default)] @@ -408,11 +407,8 @@ struct CrateData { source: Option, #[serde(default)] is_proc_macro: bool, - #[serde(default)] repository: Option, - #[serde(default)] build: Option, - #[serde(default)] proc_macro_cwd: Option, } From c85189795bfd3a5b6de77b516f51d80e5e321eef Mon Sep 17 00:00:00 2001 From: Jesung Yang Date: Wed, 28 Jan 2026 14:14:45 +0900 Subject: [PATCH 04/60] Align with internal deserialized data structure Mark `cfg`, `env`, and `is_proc_macro` fields optional to match the corresponding data structure, `project_json::CrateData`. --- .../docs/book/src/non_cargo_based_projects.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md b/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md index f1f10ae336534..9cc3292444980 100644 --- a/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md +++ b/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md @@ -135,7 +135,7 @@ interface Crate { cfg_groups?: string[]; /// The set of cfgs activated for a given crate, like /// `["unix", "feature=\"foo\"", "feature=\"bar\""]`. - cfg: string[]; + cfg?: string[]; /// Target tuple for this Crate. /// /// Used when running `rustc --print cfg` @@ -143,7 +143,7 @@ interface Crate { target?: string; /// Environment variables, used for /// the `env!` macro - env: { [key: string]: string; }; + env?: { [key: string]: string; }; /// Extra crate-level attributes applied to this crate. /// /// rust-analyzer will behave as if these attributes @@ -155,7 +155,8 @@ interface Crate { crate_attrs?: string[]; /// Whether the crate is a proc-macro crate. - is_proc_macro: boolean; + /// Defaults to `false` if unspecified. + is_proc_macro?: boolean; /// For proc-macro crates, path to compiled /// proc-macro (.so file). proc_macro_dylib_path?: string; From fe9b791c5554327d7da234c48f7f27e43a901b68 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 6 Feb 2026 16:15:22 +0800 Subject: [PATCH 05/60] feat: offer block let fallback postfix complete Example --- ```rust fn main() { match 2 { bar => bar.$0 } } ``` -> ```rust fn main() { match 2 { bar => { let $1 = bar; $0 } } } ``` --- .../ide-completion/src/completions/postfix.rs | 65 ++++++++++++++++++- .../crates/ide-completion/src/render.rs | 2 + .../ide-completion/src/tests/expression.rs | 14 ++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index cffc44f8afb33..3a92903d05128 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -161,7 +161,20 @@ pub(crate) fn complete_postfix( postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};")) .add_to(acc, ctx.db); } - _ => (), + _ => { + postfix_snippet( + "let", + "let", + &format!("{{\n let $1 = {receiver_text};\n $0\n}}"), + ) + .add_to(acc, ctx.db); + postfix_snippet( + "letm", + "let mut", + &format!("{{\n let mut $1 = {receiver_text};\n $0\n}}"), + ) + .add_to(acc, ctx.db); + } } } @@ -581,6 +594,8 @@ fn main() { sn dbgr dbg!(&expr) sn deref *expr sn if if expr {} + sn let let + sn letm let mut sn match match expr {} sn not !expr sn ref &expr @@ -795,6 +810,54 @@ fn main() { ); } + #[test] + fn let_fallback_block() { + check( + r#" +fn main() { + match 2 { + bar => bar.$0 + } +} +"#, + expect![[r#" + sn box Box::new(expr) + sn call function(expr) + sn const const {} + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + "#]], + ); + check_edit( + "let", + r#" +fn main() { + match 2 { + bar => bar.$0 + } +} +"#, + r#" +fn main() { + match 2 { + bar => { + let $1 = bar; + $0 +} + } +} +"#, + ); + } + #[test] fn option_letelse() { check_edit( 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 765304d8187de..7a7b054b3974b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -3033,6 +3033,8 @@ fn main() { sn dbgr dbg!(&expr) [] sn deref *expr [] sn if if expr {} [] + sn let let [] + sn letm let mut [] sn match match expr {} [] sn ref &expr [] sn refm &mut expr [] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs index df39591a33460..b9b0f76c884c3 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs @@ -2341,6 +2341,8 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr + sn let let + sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2368,6 +2370,8 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr + sn let let + sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2399,6 +2403,8 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr + sn let let + sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2426,6 +2432,8 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr + sn let let + sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2453,6 +2461,8 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr + sn let let + sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2480,6 +2490,8 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr + sn let let + sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -3268,6 +3280,8 @@ fn foo() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr + sn let let + sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr From 2eec6178094351111d908627511628005c220826 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 7 Feb 2026 16:02:23 +0800 Subject: [PATCH 06/60] internal: remove redundant double call in postfix --- .../crates/ide-completion/src/completions/postfix.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index cffc44f8afb33..a916d1d30566c 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -91,8 +91,7 @@ pub(crate) fn complete_postfix( // so it's better to consider references now to avoid breaking the compilation let (dot_receiver_including_refs, prefix) = include_references(dot_receiver); - let mut receiver_text = - get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal); + let mut receiver_text = receiver_text; receiver_text.insert_str(0, &prefix); let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver_including_refs) { From 99c45c503eef0218c96406623808e7e37d4e430c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 7 Feb 2026 16:07:17 +0800 Subject: [PATCH 07/60] fix: complete `.let` on block tail prefix expression Example --- ```rust fn main() { &baz.l$0 } ``` **Before this PR** ```text sn if if expr {} sn match match expr {} ``` **After this PR** ```text sn if if expr {} sn let let sn letm let mut sn match match expr {} ``` --- .../ide-completion/src/completions/postfix.rs | 85 ++++++++++++++++++- .../ide-completion/src/tests/expression.rs | 2 + 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index cffc44f8afb33..90ac1de3f1f65 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -16,7 +16,7 @@ use itertools::Itertools; use stdx::never; use syntax::{ SmolStr, - SyntaxKind::{EXPR_STMT, STMT_LIST}, + SyntaxKind::{BLOCK_EXPR, EXPR_STMT, STMT_LIST}, T, TextRange, TextSize, ToSmolStr, ast::{self, AstNode, AstToken}, format_smolstr, match_ast, @@ -155,7 +155,7 @@ pub(crate) fn complete_postfix( postfix_snippet("let", "let", &format!("let $1 = {receiver_text}")) .add_to(acc, ctx.db); } - _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT) => { + _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT | BLOCK_EXPR) => { postfix_snippet("let", "let", &format!("let $0 = {receiver_text};")) .add_to(acc, ctx.db); postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};")) @@ -652,6 +652,87 @@ fn main() { baz.l$0 res } +"#, + expect![[r#" + sn box Box::new(expr) + sn call function(expr) + sn const const {} + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn if if expr {} + sn let let + sn letm let mut + sn match match expr {} + sn not !expr + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + sn while while expr {} + "#]], + ); + check( + r#" +fn main() { + &baz.l$0 + res +} +"#, + expect![[r#" + sn box Box::new(expr) + sn call function(expr) + sn const const {} + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn if if expr {} + sn let let + sn letm let mut + sn match match expr {} + sn not !expr + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + sn while while expr {} + "#]], + ); + } + + #[test] + fn let_tail_block() { + check( + r#" +fn main() { + baz.l$0 +} +"#, + expect![[r#" + sn box Box::new(expr) + sn call function(expr) + sn const const {} + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn if if expr {} + sn let let + sn letm let mut + sn match match expr {} + sn not !expr + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + sn while while expr {} + "#]], + ); + + check( + r#" +fn main() { + &baz.l$0 +} "#, expect![[r#" sn box Box::new(expr) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs index df39591a33460..5fef8c44deb12 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs @@ -3268,6 +3268,8 @@ fn foo() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr + sn let let + sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr From 0c724e57c309e5804f98097d53c188bb36e42acc Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 12 Feb 2026 18:50:20 +0200 Subject: [PATCH 08/60] Refactor handling of associated type shorthand for type parameters, i.e. `T::AssocType` without specifying the trait I believe the new code is both cleaner and more robust, and should fix some edge cases. rustc does all of this very differently with plenty of queries for various forms of predicate lowering; but we have tight memory constraints so we prefer a different approach. --- .../crates/hir-def/src/hir/generics.rs | 2 +- .../rust-analyzer/crates/hir-def/src/lib.rs | 16 +- .../crates/hir-ty/src/generics.rs | 16 - .../rust-analyzer/crates/hir-ty/src/lib.rs | 63 +- .../rust-analyzer/crates/hir-ty/src/lower.rs | 796 ++++++------------ .../crates/hir-ty/src/lower/path.rs | 98 ++- .../rust-analyzer/crates/hir-ty/src/utils.rs | 65 +- src/tools/rust-analyzer/crates/hir/src/lib.rs | 2 + .../crates/ide/src/signature_help.rs | 4 +- 9 files changed, 413 insertions(+), 649 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir/generics.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir/generics.rs index 482cf36f95b0b..022f8adfdb06b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/hir/generics.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir/generics.rs @@ -184,7 +184,7 @@ static EMPTY: LazyLock> = LazyLock::new(|| { impl GenericParams { /// The index of the self param in the generic of the non-parent definition. - pub(crate) const SELF_PARAM_ID_IN_SELF: la_arena::Idx = + pub const SELF_PARAM_ID_IN_SELF: la_arena::Idx = LocalTypeOrConstParamId::from_raw(RawIdx::from_u32(0)); pub fn new(db: &dyn DefDatabase, def: GenericDefId) -> Arc { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs index 8d6c418d75dc5..de674be05f643 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -86,7 +86,7 @@ use crate::{ builtin_type::BuiltinType, db::DefDatabase, expr_store::ExpressionStoreSourceMap, - hir::generics::{LocalLifetimeParamId, LocalTypeOrConstParamId}, + hir::generics::{GenericParams, LocalLifetimeParamId, LocalTypeOrConstParamId}, nameres::{ LocalDefMap, assoc::{ImplItems, TraitItems}, @@ -553,15 +553,25 @@ pub struct TypeOrConstParamId { pub struct TypeParamId(TypeOrConstParamId); impl TypeParamId { + #[inline] pub fn parent(&self) -> GenericDefId { self.0.parent } + + #[inline] pub fn local_id(&self) -> LocalTypeOrConstParamId { self.0.local_id } -} -impl TypeParamId { + #[inline] + pub fn trait_self(trait_: TraitId) -> TypeParamId { + TypeParamId::from_unchecked(TypeOrConstParamId { + parent: trait_.into(), + local_id: GenericParams::SELF_PARAM_ID_IN_SELF, + }) + } + + #[inline] /// Caller should check if this toc id really belongs to a type pub fn from_unchecked(it: TypeOrConstParamId) -> Self { Self(it) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/generics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/generics.rs index 5f0261437b77a..b1500bcdb7568 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/generics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/generics.rs @@ -224,22 +224,6 @@ impl Generics { } } -pub(crate) fn trait_self_param_idx(db: &dyn DefDatabase, def: GenericDefId) -> Option { - match def { - GenericDefId::TraitId(_) => { - let params = db.generic_params(def); - params.trait_self_param().map(|idx| idx.into_raw().into_u32() as usize) - } - GenericDefId::ImplId(_) => None, - _ => { - let parent_def = parent_generic_def(db, def)?; - let parent_params = db.generic_params(parent_def); - let parent_self_idx = parent_params.trait_self_param()?.into_raw().into_u32() as usize; - Some(parent_self_idx) - } - } -} - pub(crate) fn parent_generic_def(db: &dyn DefDatabase, def: GenericDefId) -> Option { let container = match def { GenericDefId::FunctionId(it) => it.lookup(db).container, 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 f8920904f06f2..ca817ae7134b1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -57,9 +57,12 @@ mod test_db; #[cfg(test)] mod tests; -use std::hash::Hash; +use std::{hash::Hash, ops::ControlFlow}; -use hir_def::{CallableDefId, TypeOrConstParamId, type_ref::Rawness}; +use hir_def::{ + CallableDefId, GenericDefId, TypeAliasId, TypeOrConstParamId, TypeParamId, + hir::generics::GenericParams, resolver::TypeNs, type_ref::Rawness, +}; use hir_expand::name::Name; use indexmap::{IndexMap, map::Entry}; use intern::{Symbol, sym}; @@ -77,10 +80,11 @@ use crate::{ db::HirDatabase, display::{DisplayTarget, HirDisplay}, infer::unify::InferenceTable, + lower::SupertraitsInfo, next_solver::{ AliasTy, Binder, BoundConst, BoundRegion, BoundRegionKind, BoundTy, BoundTyKind, Canonical, - CanonicalVarKind, CanonicalVars, Const, ConstKind, DbInterner, FnSig, GenericArgs, - PolyFnSig, Predicate, Region, RegionKind, TraitRef, Ty, TyKind, Tys, abi, + CanonicalVarKind, CanonicalVars, ClauseKind, Const, ConstKind, DbInterner, FnSig, + GenericArgs, PolyFnSig, Predicate, Region, RegionKind, TraitRef, Ty, TyKind, Tys, abi, }, }; @@ -94,7 +98,7 @@ pub use infer::{ }; pub use lower::{ GenericPredicates, ImplTraits, LifetimeElisionKind, TyDefId, TyLoweringContext, ValueTyDefId, - associated_type_shorthand_candidates, diagnostics::*, + diagnostics::*, }; pub use next_solver::interner::{attach_db, attach_db_allow_change, with_attached_db}; pub use target_feature::TargetFeatures; @@ -478,6 +482,55 @@ where } } +/// To be used from `hir` only. +pub fn associated_type_shorthand_candidates( + db: &dyn HirDatabase, + def: GenericDefId, + res: TypeNs, + mut cb: impl FnMut(&Name, TypeAliasId) -> bool, +) -> Option { + let interner = DbInterner::new_no_crate(db); + let (def, param) = match res { + TypeNs::GenericParam(param) => (def, param), + TypeNs::SelfType(impl_) => { + let impl_trait = db.impl_trait(impl_)?.skip_binder().def_id.0; + let param = TypeParamId::from_unchecked(TypeOrConstParamId { + parent: impl_trait.into(), + local_id: GenericParams::SELF_PARAM_ID_IN_SELF, + }); + (impl_trait.into(), param) + } + _ => return None, + }; + + let mut dedup_map = FxHashSet::default(); + let param_ty = Ty::new_param(interner, param, param_idx(db, param.into()).unwrap() as u32); + // We use the ParamEnv and not the predicates because the ParamEnv elaborates bounds. + let param_env = db.trait_environment(def); + for clause in param_env.clauses { + let ClauseKind::Trait(trait_clause) = clause.kind().skip_binder() else { continue }; + if trait_clause.self_ty() != param_ty { + continue; + } + let trait_id = trait_clause.def_id().0; + dedup_map.extend( + SupertraitsInfo::query(db, trait_id) + .defined_assoc_types + .iter() + .map(|(name, id)| (name, *id)), + ); + } + + dedup_map + .into_iter() + .try_for_each( + |(name, id)| { + if cb(name, id) { ControlFlow::Break(id) } else { ControlFlow::Continue(()) } + }, + ) + .break_value() +} + /// To be used from `hir` only. pub fn callable_sig_from_fn_trait<'db>( self_ty: Ty<'db>, 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 386556b156843..1290874177ac4 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -20,7 +20,8 @@ use hir_def::{ builtin_type::BuiltinType, expr_store::{ExpressionStore, HygieneId, path::Path}, hir::generics::{ - GenericParamDataRef, TypeOrConstParamData, TypeParamProvenance, WherePredicate, + GenericParamDataRef, GenericParams, TypeOrConstParamData, TypeParamProvenance, + WherePredicate, }, item_tree::FieldsShape, lang_item::LangItems, @@ -36,27 +37,23 @@ use la_arena::{Arena, ArenaMap, Idx}; use path::{PathDiagnosticCallback, PathLoweringContext}; use rustc_ast_ir::Mutability; use rustc_hash::FxHashSet; -use rustc_pattern_analysis::Captures; use rustc_type_ir::{ AliasTyKind, BoundVarIndexKind, ConstKind, DebruijnIndex, ExistentialPredicate, ExistentialProjection, ExistentialTraitRef, FnSig, Interner, OutlivesPredicate, TermKind, TyKind::{self}, TypeFoldable, TypeVisitableExt, Upcast, UpcastFrom, elaborate, - inherent::{ - Clause as _, GenericArg as _, GenericArgs as _, IntoKind as _, Region as _, SliceLike, - Ty as _, - }, + inherent::{Clause as _, GenericArgs as _, IntoKind as _, Region as _, Ty as _}, }; -use smallvec::{SmallVec, smallvec}; +use smallvec::SmallVec; use stdx::{impl_from, never}; use tracing::debug; use triomphe::{Arc, ThinArc}; use crate::{ - FnAbi, ImplTraitId, TyLoweringDiagnostic, TyLoweringDiagnosticKind, all_super_traits, + FnAbi, ImplTraitId, TyLoweringDiagnostic, TyLoweringDiagnosticKind, consteval::intern_const_ref, db::{HirDatabase, InternedOpaqueTyId}, - generics::{Generics, generics, trait_self_param_idx}, + generics::{Generics, generics}, next_solver::{ AliasTy, Binder, BoundExistentialPredicates, Clause, ClauseKind, Clauses, Const, DbInterner, EarlyBinder, EarlyParamRegion, ErrorGuaranteed, FxIndexMap, GenericArg, @@ -618,33 +615,12 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { &'b mut self, where_predicate: &'b WherePredicate, ignore_bindings: bool, - generics: &Generics, - predicate_filter: PredicateFilter, ) -> impl Iterator, GenericPredicateSource)> + use<'a, 'b, 'db> { match where_predicate { WherePredicate::ForLifetime { target, bound, .. } | WherePredicate::TypeBound { target, bound } => { - if let PredicateFilter::SelfTrait = predicate_filter { - let target_type = &self.store[*target]; - let self_type = 'is_self: { - if let TypeRef::Path(path) = target_type - && path.is_self_type() - { - break 'is_self true; - } - if let TypeRef::TypeParam(param) = target_type - && generics[param.local_id()].is_trait_self() - { - break 'is_self true; - } - false - }; - if !self_type { - return Either::Left(Either::Left(iter::empty())); - } - } let self_ty = self.lower_ty(*target); - Either::Left(Either::Right(self.lower_type_bound(bound, self_ty, ignore_bindings))) + Either::Left(self.lower_type_bound(bound, self_ty, ignore_bindings)) } &WherePredicate::Lifetime { bound, target } => Either::Right(iter::once(( Clause(Predicate::new( @@ -1626,6 +1602,92 @@ pub(crate) fn field_types_with_diagnostics_query<'db>( (res, create_diagnostics(ctx.diagnostics)) } +#[derive(Debug, PartialEq, Eq, Default)] +pub(crate) struct SupertraitsInfo { + /// This includes the trait itself. + pub(crate) all_supertraits: Box<[TraitId]>, + pub(crate) direct_supertraits: Box<[TraitId]>, + pub(crate) defined_assoc_types: Box<[(Name, TypeAliasId)]>, +} + +impl SupertraitsInfo { + #[inline] + pub(crate) fn query(db: &dyn HirDatabase, trait_: TraitId) -> &Self { + return supertraits_info(db, trait_); + + #[salsa::tracked(returns(ref), cycle_result = supertraits_info_cycle)] + fn supertraits_info(db: &dyn HirDatabase, trait_: TraitId) -> SupertraitsInfo { + let mut all_supertraits = FxHashSet::default(); + let mut direct_supertraits = FxHashSet::default(); + let mut defined_assoc_types = FxHashSet::default(); + + all_supertraits.insert(trait_); + defined_assoc_types.extend(trait_.trait_items(db).items.iter().filter_map( + |(name, id)| match *id { + AssocItemId::TypeAliasId(id) => Some((name.clone(), id)), + _ => None, + }, + )); + + let resolver = trait_.resolver(db); + let signature = db.trait_signature(trait_); + for pred in signature.generic_params.where_predicates() { + let (WherePredicate::TypeBound { target, bound } + | WherePredicate::ForLifetime { lifetimes: _, target, bound }) = pred + else { + continue; + }; + let (TypeBound::Path(bounded_trait, TraitBoundModifier::None) + | TypeBound::ForLifetime(_, bounded_trait)) = *bound + else { + continue; + }; + let target = &signature.store[*target]; + match target { + TypeRef::TypeParam(param) + if param.local_id() == GenericParams::SELF_PARAM_ID_IN_SELF => {} + TypeRef::Path(path) if path.is_self_type() => {} + _ => continue, + } + let Some(TypeNs::TraitId(bounded_trait)) = + resolver.resolve_path_in_type_ns_fully(db, &signature.store[bounded_trait]) + else { + continue; + }; + let SupertraitsInfo { + all_supertraits: bounded_trait_all_supertraits, + direct_supertraits: _, + defined_assoc_types: bounded_traits_defined_assoc_types, + } = SupertraitsInfo::query(db, bounded_trait); + all_supertraits.extend(bounded_trait_all_supertraits); + direct_supertraits.insert(bounded_trait); + defined_assoc_types.extend(bounded_traits_defined_assoc_types.iter().cloned()); + } + + SupertraitsInfo { + all_supertraits: Box::from_iter(all_supertraits), + direct_supertraits: Box::from_iter(direct_supertraits), + defined_assoc_types: Box::from_iter(defined_assoc_types), + } + } + + fn supertraits_info_cycle( + _db: &dyn HirDatabase, + _: salsa::Id, + _trait_: TraitId, + ) -> SupertraitsInfo { + SupertraitsInfo::default() + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum TypeParamAssocTypeShorthandError { + AssocTypeNotFound, + AmbiguousAssocType, + Cycle, +} + /// Predicates for `param_id` of the form `P: SomeTrait`. If /// `assoc_name` is provided, only return predicates referencing traits /// that have an associated type of that name. @@ -1640,15 +1702,14 @@ pub(crate) fn field_types_with_diagnostics_query<'db>( /// following bounds are disallowed: `T: Foo, U: Foo`, but /// these are fine: `T: Foo, U: Foo<()>`. #[tracing::instrument(skip(db), ret)] -#[salsa::tracked(returns(ref), cycle_result = generic_predicates_for_param_cycle_result)] -pub(crate) fn generic_predicates_for_param<'db>( - db: &'db dyn HirDatabase, +#[salsa::tracked(returns(ref), cycle_result = resolve_type_param_assoc_type_shorthand_cycle_result)] +fn resolve_type_param_assoc_type_shorthand( + db: &dyn HirDatabase, def: GenericDefId, - param_id: TypeOrConstParamId, - assoc_name: Option, -) -> StoredEarlyBinder { + param: TypeParamId, + assoc_name: Name, +) -> Result, TypeParamAssocTypeShorthandError> { let generics = generics(db, def); - let interner = DbInterner::new_no_crate(db); let resolver = def.resolver(db); let mut ctx = TyLoweringContext::new( db, @@ -1657,128 +1718,109 @@ pub(crate) fn generic_predicates_for_param<'db>( def, LifetimeElisionKind::AnonymousReportError, ); + let interner = ctx.interner; + let mut result = None; + let param_ty = Ty::new_param( + interner, + param, + generics.type_or_const_param_idx(param.into()).unwrap() as u32, + ); - // we have to filter out all other predicates *first*, before attempting to lower them - let has_relevant_bound = |pred: &_, ctx: &mut TyLoweringContext<'_, '_>| match pred { - WherePredicate::ForLifetime { target, bound, .. } - | WherePredicate::TypeBound { target, bound, .. } => { - let invalid_target = { ctx.lower_ty_only_param(*target) != Some(param_id) }; - if invalid_target { - // FIXME(sized-hierarchy): Revisit and adjust this properly once we have implemented - // sized-hierarchy correctly. - // If this is filtered out without lowering, `?Sized` or `PointeeSized` is not gathered into - // `ctx.unsized_types` - let lower = || -> bool { - match bound { - TypeBound::Path(_, TraitBoundModifier::Maybe) => true, - TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => { - let TypeRef::Path(path) = &ctx.store[path.type_ref()] else { - return false; - }; - let Some(pointee_sized) = ctx.lang_items.PointeeSized else { - return false; - }; - // Lower the path directly with `Resolver` instead of PathLoweringContext` - // to prevent diagnostics duplications. - ctx.resolver.resolve_path_in_type_ns_fully(ctx.db, path).is_some_and( - |it| matches!(it, TypeNs::TraitId(tr) if tr == pointee_sized), - ) - } - _ => false, - } - }(); - if lower { - ctx.lower_where_predicate(pred, true, &generics, PredicateFilter::All) - .for_each(drop); - } - return false; - } - - match bound { - &TypeBound::ForLifetime(_, path) | &TypeBound::Path(path, _) => { - // Only lower the bound if the trait could possibly define the associated - // type we're looking for. - let path = &ctx.store[path]; - - let Some(assoc_name) = &assoc_name else { return true }; - let Some(TypeNs::TraitId(tr)) = - resolver.resolve_path_in_type_ns_fully(db, path) - else { - return false; - }; - - trait_or_supertrait_has_assoc_type(db, tr, assoc_name) - } - TypeBound::Use(_) | TypeBound::Lifetime(_) | TypeBound::Error => false, - } + if let GenericDefId::TraitId(containing_trait) = param.parent() + && param.local_id() == GenericParams::SELF_PARAM_ID_IN_SELF + { + // Add the trait's own associated types. + if let Some(assoc_type) = + containing_trait.trait_items(db).associated_type_by_name(&assoc_name) + { + let args = GenericArgs::identity_for_item(interner, containing_trait.into()); + result = Some(StoredEarlyBinder::bind((assoc_type, args.store()))); } - WherePredicate::Lifetime { .. } => false, - }; - let mut predicates = Vec::new(); + } + for maybe_parent_generics in std::iter::successors(Some(&generics), |generics| generics.parent_generics()) { ctx.store = maybe_parent_generics.store(); for pred in maybe_parent_generics.where_predicates() { - if has_relevant_bound(pred, &mut ctx) { - predicates.extend( - ctx.lower_where_predicate( - pred, - true, - maybe_parent_generics, - PredicateFilter::All, - ) - .map(|(pred, _)| pred), - ); + let (WherePredicate::TypeBound { target, bound } + | WherePredicate::ForLifetime { lifetimes: _, target, bound }) = pred + else { + continue; + }; + let (TypeBound::Path(bounded_trait_path, TraitBoundModifier::None) + | TypeBound::ForLifetime(_, bounded_trait_path)) = *bound + else { + continue; + }; + let Some(target) = ctx.lower_ty_only_param(*target) else { continue }; + if target != param.into() { + continue; + } + let Some(TypeNs::TraitId(bounded_trait)) = + resolver.resolve_path_in_type_ns_fully(db, &ctx.store[bounded_trait_path]) + else { + continue; + }; + if !SupertraitsInfo::query(db, bounded_trait) + .defined_assoc_types + .iter() + .any(|(name, _)| *name == assoc_name) + { + continue; } + + let Some((bounded_trait_ref, _)) = + ctx.lower_trait_ref_from_path(bounded_trait_path, param_ty) + else { + continue; + }; + // Now, search from the start on the *bounded* trait like if we wrote `Self::Assoc`. Eventually, we'll get + // the correct trait ref (or a cycle). + let lookup_on_bounded_trait = resolve_type_param_assoc_type_shorthand( + db, + bounded_trait.into(), + TypeParamId::trait_self(bounded_trait), + assoc_name.clone(), + ); + let lookup_on_bounded_trait = match lookup_on_bounded_trait { + Ok(it) => it, + Err( + err @ (TypeParamAssocTypeShorthandError::AmbiguousAssocType + | TypeParamAssocTypeShorthandError::Cycle), + ) => return Err(*err), + Err(TypeParamAssocTypeShorthandError::AssocTypeNotFound) => { + never!("we checked that the trait defines this assoc type"); + continue; + } + }; + let (assoc_type, args) = lookup_on_bounded_trait + .get_with(|(assoc_type, args)| (*assoc_type, args.as_ref())) + .skip_binder(); + let args = EarlyBinder::bind(args).instantiate(interner, bounded_trait_ref.args); + let current_result = StoredEarlyBinder::bind((assoc_type, args.store())); + // If we already have a result, this is an ambiguity - unless this is the same result, then we are fine + // (e.g. rustc allows to write the same bound twice without ambiguity). + if let Some(existing_result) = result + && existing_result != current_result + { + return Err(TypeParamAssocTypeShorthandError::AmbiguousAssocType); + } + result = Some(current_result); } } - let args = GenericArgs::identity_for_item(interner, def.into()); - if !args.is_empty() { - let explicitly_unsized_tys = ctx.unsized_types; - if let Some(implicitly_sized_predicates) = implicitly_sized_clauses( - db, - ctx.lang_items, - param_id.parent, - &explicitly_unsized_tys, - &args, - ) { - predicates.extend(implicitly_sized_predicates); - }; - } - StoredEarlyBinder::bind(Clauses::new_from_slice(&predicates).store()) + result.ok_or(TypeParamAssocTypeShorthandError::AssocTypeNotFound) } -pub(crate) fn generic_predicates_for_param_cycle_result( - db: &dyn HirDatabase, +fn resolve_type_param_assoc_type_shorthand_cycle_result( + _db: &dyn HirDatabase, _: salsa::Id, _def: GenericDefId, - _param_id: TypeOrConstParamId, - _assoc_name: Option, -) -> StoredEarlyBinder { - StoredEarlyBinder::bind(Clauses::empty(DbInterner::new_no_crate(db)).store()) -} - -/// Check if this trait or any of its supertraits define an associated -/// type with the given name. -fn trait_or_supertrait_has_assoc_type( - db: &dyn HirDatabase, - tr: TraitId, - assoc_name: &Name, -) -> bool { - for trait_id in all_super_traits(db, tr) { - if trait_id - .trait_items(db) - .items - .iter() - .any(|(name, item)| matches!(item, AssocItemId::TypeAliasId(_)) && name == assoc_name) - { - return true; - } - } - - false + _param: TypeParamId, + _assoc_name: Name, +) -> Result, TypeParamAssocTypeShorthandError> { + Err(TypeParamAssocTypeShorthandError::Cycle) } #[inline] @@ -1904,7 +1946,7 @@ impl<'db> GenericPredicates { db: &'db dyn HirDatabase, def: GenericDefId, ) -> (GenericPredicates, Diagnostics) { - generic_predicates_filtered_by(db, def, PredicateFilter::All, |_| true) + generic_predicates(db, def) } } @@ -2042,24 +2084,10 @@ pub(crate) fn trait_environment<'db>(db: &'db dyn HirDatabase, def: GenericDefId } } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(crate) enum PredicateFilter { - SelfTrait, - All, -} - /// Resolve the where clause(s) of an item with generics, /// with a given filter -#[tracing::instrument(skip(db, filter), ret)] -pub(crate) fn generic_predicates_filtered_by( - db: &dyn HirDatabase, - def: GenericDefId, - predicate_filter: PredicateFilter, - filter: F, -) -> (GenericPredicates, Diagnostics) -where - F: Fn(GenericDefId) -> bool, -{ +#[tracing::instrument(skip(db), ret)] +fn generic_predicates(db: &dyn HirDatabase, def: GenericDefId) -> (GenericPredicates, Diagnostics) { let generics = generics(db, def); let resolver = def.resolver(db); let interner = DbInterner::new_no_crate(db); @@ -2081,9 +2109,9 @@ where let all_generics = std::iter::successors(Some(&generics), |generics| generics.parent_generics()) .collect::>(); - let own_implicit_trait_predicate = implicit_trait_predicate(interner, def, predicate_filter); + let own_implicit_trait_predicate = implicit_trait_predicate(interner, def); let parent_implicit_trait_predicate = if all_generics.len() > 1 { - implicit_trait_predicate(interner, all_generics.last().unwrap().def(), predicate_filter) + implicit_trait_predicate(interner, all_generics.last().unwrap().def()) } else { None }; @@ -2091,97 +2119,85 @@ where // Collect only diagnostics from the child, not including parents. ctx.diagnostics.clear(); - if filter(maybe_parent_generics.def()) { - ctx.store = maybe_parent_generics.store(); - for pred in maybe_parent_generics.where_predicates() { - tracing::debug!(?pred); - for (pred, source) in - ctx.lower_where_predicate(pred, false, maybe_parent_generics, predicate_filter) - { - match source { - GenericPredicateSource::SelfOnly => { - if maybe_parent_generics.def() == def { - own_predicates.push(pred); - } else { - parent_predicates.push(pred); - } + ctx.store = maybe_parent_generics.store(); + for pred in maybe_parent_generics.where_predicates() { + tracing::debug!(?pred); + for (pred, source) in ctx.lower_where_predicate(pred, false) { + match source { + GenericPredicateSource::SelfOnly => { + if maybe_parent_generics.def() == def { + own_predicates.push(pred); + } else { + parent_predicates.push(pred); } - GenericPredicateSource::AssocTyBound => { - if maybe_parent_generics.def() == def { - own_assoc_ty_bounds.push(pred); - } else { - parent_assoc_ty_bounds.push(pred); - } + } + GenericPredicateSource::AssocTyBound => { + if maybe_parent_generics.def() == def { + own_assoc_ty_bounds.push(pred); + } else { + parent_assoc_ty_bounds.push(pred); } } } } + } - if maybe_parent_generics.def() == def { - push_const_arg_has_type_predicates(db, &mut own_predicates, maybe_parent_generics); - } else { - push_const_arg_has_type_predicates( - db, - &mut parent_predicates, - maybe_parent_generics, - ); - } + if maybe_parent_generics.def() == def { + push_const_arg_has_type_predicates(db, &mut own_predicates, maybe_parent_generics); + } else { + push_const_arg_has_type_predicates(db, &mut parent_predicates, maybe_parent_generics); + } - if let Some(sized_trait) = sized_trait { - let mut add_sized_clause = |param_idx, param_id, param_data| { - let ( - GenericParamId::TypeParamId(param_id), - GenericParamDataRef::TypeParamData(param_data), - ) = (param_id, param_data) - else { - return; - }; + if let Some(sized_trait) = sized_trait { + let mut add_sized_clause = |param_idx, param_id, param_data| { + let ( + GenericParamId::TypeParamId(param_id), + GenericParamDataRef::TypeParamData(param_data), + ) = (param_id, param_data) + else { + return; + }; - if param_data.provenance == TypeParamProvenance::TraitSelf { - return; - } + if param_data.provenance == TypeParamProvenance::TraitSelf { + return; + } - let param_ty = Ty::new_param(interner, param_id, param_idx); - if ctx.unsized_types.contains(¶m_ty) { - return; - } - let trait_ref = TraitRef::new_from_args( - interner, - sized_trait.into(), - GenericArgs::new_from_slice(&[param_ty.into()]), - ); - let clause = Clause(Predicate::new( - interner, - Binder::dummy(rustc_type_ir::PredicateKind::Clause( - rustc_type_ir::ClauseKind::Trait(TraitPredicate { - trait_ref, - polarity: rustc_type_ir::PredicatePolarity::Positive, - }), - )), - )); - if maybe_parent_generics.def() == def { - own_predicates.push(clause); - } else { - parent_predicates.push(clause); - } - }; - let parent_params_len = maybe_parent_generics.len_parent(); - maybe_parent_generics.iter_self().enumerate().for_each( - |(param_idx, (param_id, param_data))| { - add_sized_clause( - (param_idx + parent_params_len) as u32, - param_id, - param_data, - ); - }, + let param_ty = Ty::new_param(interner, param_id, param_idx); + if ctx.unsized_types.contains(¶m_ty) { + return; + } + let trait_ref = TraitRef::new_from_args( + interner, + sized_trait.into(), + GenericArgs::new_from_slice(&[param_ty.into()]), ); - } - - // We do not clear `ctx.unsized_types`, as the `?Sized` clause of a child (e.g. an associated type) can - // be declared on the parent (e.g. the trait). It is nevertheless fine to register the implicit `Sized` - // predicates before lowering the child, as a child cannot define a `?Sized` predicate for its parent. - // But we do have to lower the parent first. + let clause = Clause(Predicate::new( + interner, + Binder::dummy(rustc_type_ir::PredicateKind::Clause( + rustc_type_ir::ClauseKind::Trait(TraitPredicate { + trait_ref, + polarity: rustc_type_ir::PredicatePolarity::Positive, + }), + )), + )); + if maybe_parent_generics.def() == def { + own_predicates.push(clause); + } else { + parent_predicates.push(clause); + } + }; + let parent_params_len = maybe_parent_generics.len_parent(); + maybe_parent_generics.iter_self().enumerate().for_each( + |(param_idx, (param_id, param_data))| { + add_sized_clause((param_idx + parent_params_len) as u32, param_id, param_data); + }, + ); } + + // We do not clear `ctx.unsized_types`, as the `?Sized` clause of a child (e.g. an associated type) can + // be declared on the parent (e.g. the trait). It is nevertheless fine to register the implicit `Sized` + // predicates before lowering the child, as a child cannot define a `?Sized` predicate for its parent. + // But we do have to lower the parent first. } let diagnostics = create_diagnostics(ctx.diagnostics); @@ -2229,7 +2245,6 @@ where fn implicit_trait_predicate<'db>( interner: DbInterner<'db>, def: GenericDefId, - predicate_filter: PredicateFilter, ) -> Option> { // For traits, add `Self: Trait` predicate. This is // not part of the predicates that a user writes, but it @@ -2243,9 +2258,7 @@ where // prove that the trait applies to the types that were // used, and adding the predicate into this list ensures // that this is done. - if let GenericDefId::TraitId(def_id) = def - && predicate_filter == PredicateFilter::All - { + if let GenericDefId::TraitId(def_id) = def { Some(TraitRef::identity(interner, def_id.into()).upcast(interner)) } else { None @@ -2282,49 +2295,6 @@ fn push_const_arg_has_type_predicates<'db>( } } -/// Generate implicit `: Sized` predicates for all generics that has no `?Sized` bound. -/// Exception is Self of a trait def. -fn implicitly_sized_clauses<'a, 'subst, 'db>( - db: &'db dyn HirDatabase, - lang_items: &LangItems, - def: GenericDefId, - explicitly_unsized_tys: &'a FxHashSet>, - args: &'subst GenericArgs<'db>, -) -> Option> + Captures<'a> + Captures<'subst>> { - let interner = DbInterner::new_no_crate(db); - let sized_trait = lang_items.Sized?; - - let trait_self_idx = trait_self_param_idx(db, def); - - Some( - args.iter() - .enumerate() - .filter_map( - move |(idx, generic_arg)| { - if Some(idx) == trait_self_idx { None } else { Some(generic_arg) } - }, - ) - .filter_map(|generic_arg| generic_arg.as_type()) - .filter(move |self_ty| !explicitly_unsized_tys.contains(self_ty)) - .map(move |self_ty| { - let trait_ref = TraitRef::new_from_args( - interner, - sized_trait.into(), - GenericArgs::new_from_slice(&[self_ty.into()]), - ); - Clause(Predicate::new( - interner, - Binder::dummy(rustc_type_ir::PredicateKind::Clause( - rustc_type_ir::ClauseKind::Trait(TraitPredicate { - trait_ref, - polarity: rustc_type_ir::PredicatePolarity::Positive, - }), - )), - )) - }), - ) -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GenericDefaults(Option>]>>); @@ -2602,235 +2572,19 @@ pub(crate) fn associated_ty_item_bounds<'db>( pub(crate) fn associated_type_by_name_including_super_traits<'db>( db: &'db dyn HirDatabase, trait_ref: TraitRef<'db>, - name: &Name, -) -> Option<(TraitRef<'db>, TypeAliasId)> { - let module = trait_ref.def_id.0.module(db); - let interner = DbInterner::new_with(db, module.krate(db)); - all_supertraits_trait_refs(db, trait_ref.def_id.0) - .map(|t| t.instantiate(interner, trait_ref.args)) - .find_map(|t| { - let trait_id = t.def_id.0; - let assoc_type = trait_id.trait_items(db).associated_type_by_name(name)?; - Some((t, assoc_type)) - }) -} - -pub fn associated_type_shorthand_candidates( - db: &dyn HirDatabase, - def: GenericDefId, - res: TypeNs, - mut cb: impl FnMut(&Name, TypeAliasId) -> bool, -) -> Option { - let interner = DbInterner::new_no_crate(db); - named_associated_type_shorthand_candidates(interner, def, res, None, |name, _, id| { - cb(name, id).then_some(id) - }) -} - -#[tracing::instrument(skip(interner, check_alias))] -fn named_associated_type_shorthand_candidates<'db, R>( - interner: DbInterner<'db>, - // If the type parameter is defined in an impl and we're in a method, there - // might be additional where clauses to consider - def: GenericDefId, - res: TypeNs, - assoc_name: Option, - mut check_alias: impl FnMut(&Name, TraitRef<'db>, TypeAliasId) -> Option, -) -> Option { - let db = interner.db; - let mut search = |t: TraitRef<'db>| -> Option { - let mut checked_traits = FxHashSet::default(); - let mut check_trait = |trait_ref: TraitRef<'db>| { - let trait_id = trait_ref.def_id.0; - let name = &db.trait_signature(trait_id).name; - tracing::debug!(?trait_id, ?name); - if !checked_traits.insert(trait_id) { - return None; - } - let data = trait_id.trait_items(db); - - tracing::debug!(?data.items); - for (name, assoc_id) in &data.items { - if let &AssocItemId::TypeAliasId(alias) = assoc_id - && let Some(ty) = check_alias(name, trait_ref, alias) - { - return Some(ty); - } - } - None - }; - let mut stack: SmallVec<[_; 4]> = smallvec![t]; - while let Some(trait_ref) = stack.pop() { - if let Some(alias) = check_trait(trait_ref) { - return Some(alias); - } - let predicates = generic_predicates_filtered_by( - db, - GenericDefId::TraitId(trait_ref.def_id.0), - PredicateFilter::SelfTrait, - // We are likely in the midst of lowering generic predicates of `def`. - // So, if we allow `pred == def` we might fall into an infinite recursion. - // Actually, we have already checked for the case `pred == def` above as we started - // with a stack including `trait_id` - |pred| pred != def && pred == GenericDefId::TraitId(trait_ref.def_id.0), - ) - .0 - .predicates; - for pred in predicates.get().instantiate_identity() { - tracing::debug!(?pred); - let sup_trait_ref = match pred.kind().skip_binder() { - rustc_type_ir::ClauseKind::Trait(pred) => pred.trait_ref, - _ => continue, - }; - let sup_trait_ref = - EarlyBinder::bind(sup_trait_ref).instantiate(interner, trait_ref.args); - stack.push(sup_trait_ref); - } - tracing::debug!(?stack); - } - - None - }; - - match res { - TypeNs::SelfType(impl_id) => { - let trait_ref = db.impl_trait(impl_id)?; - - // FIXME(next-solver): same method in `lower` checks for impl or not - // Is that needed here? - - // we're _in_ the impl -- the binders get added back later. Correct, - // but it would be nice to make this more explicit - search(trait_ref.skip_binder()) - } - TypeNs::GenericParam(param_id) => { - // Handle `Self::Type` referring to own associated type in trait definitions - // This *must* be done first to avoid cycles with - // `generic_predicates_for_param`, but not sure that it's sufficient, - if let GenericDefId::TraitId(trait_id) = param_id.parent() { - let trait_name = &db.trait_signature(trait_id).name; - tracing::debug!(?trait_name); - let trait_generics = generics(db, trait_id.into()); - tracing::debug!(?trait_generics); - if trait_generics[param_id.local_id()].is_trait_self() { - let args = GenericArgs::identity_for_item(interner, trait_id.into()); - let trait_ref = TraitRef::new_from_args(interner, trait_id.into(), args); - tracing::debug!(?args, ?trait_ref); - return search(trait_ref); - } - } - - let predicates = - generic_predicates_for_param(db, def, param_id.into(), assoc_name.clone()); - predicates - .get() - .iter_identity() - .find_map(|pred| match pred.kind().skip_binder() { - rustc_type_ir::ClauseKind::Trait(trait_predicate) => Some(trait_predicate), - _ => None, - }) - .and_then(|trait_predicate| { - let trait_ref = trait_predicate.trait_ref; - assert!( - !trait_ref.has_escaping_bound_vars(), - "FIXME unexpected higher-ranked trait bound" - ); - search(trait_ref) - }) - } - _ => None, - } -} - -/// During lowering, elaborating supertraits can cause cycles. To avoid that, we have a separate query -/// to only collect supertraits. -/// -/// Technically, it is possible to avoid even more cycles by only collecting the `TraitId` of supertraits -/// without their args. However rustc doesn't do that, so we don't either. -pub(crate) fn all_supertraits_trait_refs( - db: &dyn HirDatabase, - trait_: TraitId, -) -> impl ExactSizeIterator>> { + name: Name, +) -> Option<(TypeAliasId, GenericArgs<'db>)> { + let assoc_type = resolve_type_param_assoc_type_shorthand( + db, + trait_ref.def_id.0.into(), + TypeParamId::trait_self(trait_ref.def_id.0), + name.clone(), + ) + .as_ref() + .ok()?; + let (assoc_type, trait_args) = assoc_type + .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref())) + .skip_binder(); let interner = DbInterner::new_no_crate(db); - return all_supertraits_trait_refs_query(db, trait_).iter().map(move |trait_ref| { - trait_ref.get_with(|(trait_, args)| { - TraitRef::new_from_args(interner, (*trait_).into(), args.as_ref()) - }) - }); - - #[salsa_macros::tracked(returns(deref), cycle_result = all_supertraits_trait_refs_cycle_result)] - pub(crate) fn all_supertraits_trait_refs_query( - db: &dyn HirDatabase, - trait_: TraitId, - ) -> Box<[StoredEarlyBinder<(TraitId, StoredGenericArgs)>]> { - let resolver = trait_.resolver(db); - let signature = db.trait_signature(trait_); - let mut ctx = TyLoweringContext::new( - db, - &resolver, - &signature.store, - trait_.into(), - LifetimeElisionKind::AnonymousReportError, - ); - let interner = ctx.interner; - - let self_param_ty = Ty::new_param( - interner, - TypeParamId::from_unchecked(TypeOrConstParamId { - parent: trait_.into(), - local_id: Idx::from_raw(la_arena::RawIdx::from_u32(0)), - }), - 0, - ); - - let mut supertraits = FxHashSet::default(); - supertraits.insert(StoredEarlyBinder::bind(( - trait_, - GenericArgs::identity_for_item(interner, trait_.into()).store(), - ))); - - for pred in signature.generic_params.where_predicates() { - let WherePredicate::TypeBound { target, bound } = pred else { - continue; - }; - let target = &signature.store[*target]; - if let TypeRef::TypeParam(param_id) = target - && param_id.local_id().into_raw().into_u32() == 0 - { - // This is `Self`. - } else if let TypeRef::Path(path) = target - && path.is_self_type() - { - // Also `Self`. - } else { - // Not `Self`! - continue; - } - - ctx.lower_type_bound(bound, self_param_ty, true).for_each(|(clause, _)| { - if let ClauseKind::Trait(trait_ref) = clause.kind().skip_binder() { - supertraits.extend( - all_supertraits_trait_refs(db, trait_ref.trait_ref.def_id.0).map(|t| { - let trait_ref = t.instantiate(interner, trait_ref.trait_ref.args); - StoredEarlyBinder::bind((trait_ref.def_id.0, trait_ref.args.store())) - }), - ); - } - }); - } - - Box::from_iter(supertraits) - } - - pub(crate) fn all_supertraits_trait_refs_cycle_result( - db: &dyn HirDatabase, - _: salsa::Id, - trait_: TraitId, - ) -> Box<[StoredEarlyBinder<(TraitId, StoredGenericArgs)>]> { - let interner = DbInterner::new_no_crate(db); - Box::new([StoredEarlyBinder::bind(( - trait_, - GenericArgs::identity_for_item(interner, trait_.into()).store(), - ))]) - } + Some((assoc_type, EarlyBinder::bind(trait_args).instantiate(interner, trait_ref.args))) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs index f3d0de12275ed..ab686a9aac133 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs @@ -2,7 +2,7 @@ use either::Either; use hir_def::{ - GenericDefId, GenericParamId, Lookup, TraitId, TypeAliasId, + GenericDefId, GenericParamId, Lookup, TraitId, TypeParamId, expr_store::{ ExpressionStore, HygieneId, path::{ @@ -17,7 +17,6 @@ use hir_def::{ signatures::TraitFlags, type_ref::{TypeRef, TypeRefId}, }; -use hir_expand::name::Name; use rustc_type_ir::{ AliasTerm, AliasTy, AliasTyKind, inherent::{GenericArgs as _, Region as _, Ty as _}, @@ -31,13 +30,10 @@ use crate::{ consteval::{unknown_const, unknown_const_as_generic}, db::HirDatabase, generics::{Generics, generics}, - lower::{ - GenericPredicateSource, LifetimeElisionKind, PathDiagnosticCallbackData, - named_associated_type_shorthand_candidates, - }, + lower::{GenericPredicateSource, LifetimeElisionKind, PathDiagnosticCallbackData}, next_solver::{ - Binder, Clause, Const, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, Predicate, - ProjectionPredicate, Region, TraitRef, Ty, + Binder, Clause, Const, DbInterner, EarlyBinder, ErrorGuaranteed, GenericArg, GenericArgs, + Predicate, ProjectionPredicate, Region, TraitRef, Ty, }, }; @@ -481,43 +477,59 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { #[tracing::instrument(skip(self), ret)] fn select_associated_type(&mut self, res: Option, infer_args: bool) -> Ty<'db> { let interner = self.ctx.interner; - let Some(res) = res else { - return Ty::new_error(self.ctx.interner, ErrorGuaranteed); - }; + let db = self.ctx.db; let def = self.ctx.def; let segment = self.current_or_prev_segment; let assoc_name = segment.name; - let check_alias = |name: &Name, t: TraitRef<'db>, associated_ty: TypeAliasId| { - if name != assoc_name { - return None; + let error_ty = || Ty::new_error(self.ctx.interner, ErrorGuaranteed); + let (assoc_type, trait_args) = match res { + Some(TypeNs::GenericParam(param)) => { + let Ok(assoc_type) = super::resolve_type_param_assoc_type_shorthand( + db, + def, + param, + assoc_name.clone(), + ) else { + return error_ty(); + }; + assoc_type + .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref())) + .skip_binder() } - - // FIXME: `substs_from_path_segment()` pushes `TyKind::Error` for every parent - // generic params. It's inefficient to splice the `Substitution`s, so we may want - // that method to optionally take parent `Substitution` as we already know them at - // this point (`t.substitution`). - let substs = - self.substs_from_path_segment(associated_ty.into(), infer_args, None, true); - - let substs = GenericArgs::new_from_iter( - interner, - t.args.iter().chain(substs.iter().skip(t.args.len())), - ); - - Some(Ty::new_alias( - interner, - AliasTyKind::Projection, - AliasTy::new_from_args(interner, associated_ty.into(), substs), - )) + Some(TypeNs::SelfType(impl_)) => { + let Some(impl_trait) = db.impl_trait(impl_) else { + return error_ty(); + }; + let impl_trait = impl_trait.instantiate_identity(); + // Searching for `Self::Assoc` in `impl Trait for Type` is like searching for `Self::Assoc` in `Trait`. + let Ok(assoc_type) = super::resolve_type_param_assoc_type_shorthand( + db, + impl_trait.def_id.0.into(), + TypeParamId::trait_self(impl_trait.def_id.0), + assoc_name.clone(), + ) else { + return error_ty(); + }; + let (assoc_type, trait_args) = assoc_type + .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref())) + .skip_binder(); + (assoc_type, EarlyBinder::bind(trait_args).instantiate(interner, impl_trait.args)) + } + _ => return error_ty(), }; - named_associated_type_shorthand_candidates( + + // FIXME: `substs_from_path_segment()` pushes `TyKind::Error` for every parent + // generic params. It's inefficient to splice the `Substitution`s, so we may want + // that method to optionally take parent `Substitution` as we already know them at + // this point (`t.substitution`). + let substs = self.substs_from_path_segment(assoc_type.into(), infer_args, None, true); + + let substs = GenericArgs::new_from_iter( interner, - def, - res, - Some(assoc_name.clone()), - check_alias, - ) - .unwrap_or_else(|| Ty::new_error(interner, ErrorGuaranteed)) + trait_args.iter().chain(substs.iter().skip(trait_args.len())), + ); + + Ty::new_projection_from_args(interner, assoc_type.into(), substs) } fn lower_path_inner(&mut self, typeable: TyDefId, infer_args: bool) -> Ty<'db> { @@ -862,9 +874,9 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { let found = associated_type_by_name_including_super_traits( self.ctx.db, trait_ref, - &binding.name, + binding.name.clone(), ); - let (super_trait_ref, associated_ty) = match found { + let (associated_ty, super_trait_args) = match found { None => return SmallVec::new(), Some(t) => t, }; @@ -878,7 +890,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { binding.args.as_ref(), associated_ty.into(), false, // this is not relevant - Some(super_trait_ref.self_ty()), + Some(super_trait_args.type_at(0)), PathGenericsSource::AssocType { segment: this.current_segment_u32(), assoc_type: binding_idx as u32, @@ -889,7 +901,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { }); let args = GenericArgs::new_from_iter( interner, - super_trait_ref.args.iter().chain(args.iter().skip(super_trait_ref.args.len())), + super_trait_args.iter().chain(args.iter().skip(super_trait_args.len())), ); let projection_term = AliasTerm::new_from_args(interner, associated_ty.into(), args); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs index 148300deb875f..be64f55ea550e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs @@ -1,28 +1,19 @@ //! Helper functions for working with def, which don't need to be a separate //! query, but can't be computed directly from `*Data` (ie, which need a `db`). -use std::cell::LazyCell; - use base_db::target::{self, TargetData}; use hir_def::{ - EnumId, EnumVariantId, FunctionId, Lookup, TraitId, - attrs::AttrFlags, - db::DefDatabase, - hir::generics::WherePredicate, - lang_item::LangItems, - resolver::{HasResolver, TypeNs}, - type_ref::{TraitBoundModifier, TypeRef}, + EnumId, EnumVariantId, FunctionId, Lookup, TraitId, attrs::AttrFlags, lang_item::LangItems, }; use intern::sym; use rustc_abi::TargetDataLayout; -use smallvec::{SmallVec, smallvec}; use span::Edition; use crate::{ TargetFeatures, db::HirDatabase, layout::{Layout, TagEncoding}, - lower::all_supertraits_trait_refs, + lower::SupertraitsInfo, mir::pad16, }; @@ -51,55 +42,13 @@ pub(crate) fn fn_traits(lang_items: &LangItems) -> impl Iterator } /// Returns an iterator over the direct super traits (including the trait itself). -pub fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> { - let mut result = smallvec![trait_]; - direct_super_traits_cb(db, trait_, |tt| { - if !result.contains(&tt) { - result.push(tt); - } - }); - result -} - -/// Returns an iterator over the whole super trait hierarchy (including the -/// trait itself). -pub fn all_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> { - let mut supertraits = all_supertraits_trait_refs(db, trait_) - .map(|trait_ref| trait_ref.skip_binder().def_id.0) - .collect::>(); - supertraits.sort_unstable(); - supertraits.dedup(); - supertraits +pub fn direct_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> &[TraitId] { + &SupertraitsInfo::query(db, trait_).direct_supertraits } -fn direct_super_traits_cb(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut(TraitId)) { - let resolver = LazyCell::new(|| trait_.resolver(db)); - let (generic_params, store) = db.generic_params_and_store(trait_.into()); - let trait_self = generic_params.trait_self_param(); - generic_params - .where_predicates() - .iter() - .filter_map(|pred| match pred { - WherePredicate::ForLifetime { target, bound, .. } - | WherePredicate::TypeBound { target, bound } => { - let is_trait = match &store[*target] { - TypeRef::Path(p) => p.is_self_type(), - TypeRef::TypeParam(p) => Some(p.local_id()) == trait_self, - _ => false, - }; - match is_trait { - true => bound.as_path(&store), - false => None, - } - } - WherePredicate::Lifetime { .. } => None, - }) - .filter(|(_, bound_modifier)| matches!(bound_modifier, TraitBoundModifier::None)) - .filter_map(|(path, _)| match resolver.resolve_path_in_type_ns_fully(db, path) { - Some(TypeNs::TraitId(t)) => Some(t), - _ => None, - }) - .for_each(cb); +/// Returns the whole super trait hierarchy (including the trait itself). +pub fn all_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> &[TraitId] { + &SupertraitsInfo::query(db, trait_).all_supertraits } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 5820a6714b023..9dbee16dae6b7 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -6167,6 +6167,7 @@ impl<'db> Type<'db> { self.autoderef_(db) .filter_map(|ty| ty.dyn_trait()) .flat_map(move |dyn_trait_id| hir_ty::all_super_traits(db, dyn_trait_id)) + .copied() .map(Trait::from) } @@ -6184,6 +6185,7 @@ impl<'db> Type<'db> { _ => None, }) .flat_map(|t| hir_ty::all_super_traits(db, t)) + .copied() }) .map(Trait::from) } diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index f86974b4ec76c..9ab07565e9efc 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -1975,8 +1975,8 @@ trait Sub: Super + Super { fn f() -> impl Sub<$0 "#, expect![[r#" - trait Sub - ^^^^^^^^^^^ --------- + trait Sub + ^^^^^^^^^ ----------- "#]], ); } From 5c13e0ced9213c883f2dea69dfccfa29da1c9dd7 Mon Sep 17 00:00:00 2001 From: Flora Hill Date: Fri, 13 Feb 2026 04:07:52 +0000 Subject: [PATCH 09/60] Squash commits for review --- .../crates/ide-db/src/imports/insert_use.rs | 41 ++++- .../ide-db/src/imports/insert_use/tests.rs | 143 ++++++++++++++++++ .../ide-db/src/imports/merge_imports.rs | 13 +- 3 files changed, 183 insertions(+), 14 deletions(-) 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 f26952fa1535d..da8525d1fb72b 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 @@ -94,7 +94,7 @@ impl ImportScope { .item_list() .map(ImportScopeKind::Module) .map(|kind| ImportScope { kind, required_cfgs }); - } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax) { + } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax.clone()) { if block.is_none() && let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone()) && let Some(b) = sema.original_ast_node(b) @@ -105,11 +105,34 @@ impl ImportScope { .attrs() .any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")) { - if let Some(b) = block { - return Some(ImportScope { - kind: ImportScopeKind::Block(b), - required_cfgs, + if let Some(b) = block.clone() { + let current_cfgs = has_attrs.attrs().filter(|attr| { + attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg") }); + + let total_cfgs: Vec<_> = + required_cfgs.iter().cloned().chain(current_cfgs).collect(); + + let parent = syntax.parent(); + let mut can_merge = false; + if let Some(parent) = parent { + can_merge = parent.children().filter_map(ast::Use::cast).any(|u| { + let u_attrs = u.attrs().filter(|attr| { + attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg") + }); + crate::imports::merge_imports::eq_attrs( + u_attrs, + total_cfgs.iter().cloned(), + ) + }); + } + + if !can_merge { + return Some(ImportScope { + kind: ImportScopeKind::Block(b), + required_cfgs, + }); + } } required_cfgs.extend(has_attrs.attrs().filter(|attr| { attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg") @@ -546,7 +569,9 @@ fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) { // skip the curly brace .skip(l_curly.is_some() as usize) .take_while(|child| match child { - NodeOrToken::Node(node) => is_inner_attribute(node.clone()), + NodeOrToken::Node(node) => { + is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none() + } NodeOrToken::Token(token) => { [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG] .contains(&token.kind()) @@ -667,7 +692,9 @@ fn insert_use_with_editor_( // skip the curly brace .skip(l_curly.is_some() as usize) .take_while(|child| match child { - NodeOrToken::Node(node) => is_inner_attribute(node.clone()), + NodeOrToken::Node(node) => { + is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none() + } NodeOrToken::Token(token) => { [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG] .contains(&token.kind()) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs index 3350e1c3d207f..7763d1e595d04 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs @@ -1438,3 +1438,146 @@ fn check_guess(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: Import let file = ImportScope { kind: ImportScopeKind::File(syntax), required_cfgs: vec![] }; assert_eq!(super::guess_granularity_from_scope(&file), expected); } + +#[test] +fn insert_with_existing_imports_and_cfg_module() { + check( + "std::fmt", + r#" +use foo::bar; + +#[cfg(target_arch = "x86_64")] +pub mod api; +"#, + r#" +use std::fmt; + +use foo::bar; + +#[cfg(target_arch = "x86_64")] +pub mod api; +"#, + ImportGranularity::Crate, + ); +} + +#[test] +fn insert_before_cfg_module() { + check( + "std::fmt", + r#" +#[cfg(target_arch = "x86_64")] +pub mod api; +"#, + r#" +use std::fmt; + +#[cfg(target_arch = "x86_64")] +pub mod api; +"#, + ImportGranularity::Crate, + ); +} + +fn check_merge(ra_fixture0: &str, ra_fixture1: &str, last: &str, mb: MergeBehavior) { + let use0 = ast::SourceFile::parse(ra_fixture0, span::Edition::CURRENT) + .tree() + .syntax() + .descendants() + .find_map(ast::Use::cast) + .unwrap(); + + let use1 = ast::SourceFile::parse(ra_fixture1, span::Edition::CURRENT) + .tree() + .syntax() + .descendants() + .find_map(ast::Use::cast) + .unwrap(); + + let result = try_merge_imports(&use0, &use1, mb); + assert_eq!(result.map(|u| u.to_string().trim().to_owned()), Some(last.trim().to_owned())); +} + +#[test] +fn merge_gated_imports() { + check_merge( + r#"#[cfg(test)] use foo::bar;"#, + r#"#[cfg(test)] use foo::baz;"#, + r#"#[cfg(test)] use foo::{bar, baz};"#, + MergeBehavior::Crate, + ); +} + +#[test] +fn merge_gated_imports_with_different_values() { + let use0 = ast::SourceFile::parse(r#"#[cfg(a)] use foo::bar;"#, span::Edition::CURRENT) + .tree() + .syntax() + .descendants() + .find_map(ast::Use::cast) + .unwrap(); + + let use1 = ast::SourceFile::parse(r#"#[cfg(b)] use foo::baz;"#, span::Edition::CURRENT) + .tree() + .syntax() + .descendants() + .find_map(ast::Use::cast) + .unwrap(); + + let result = try_merge_imports(&use0, &use1, MergeBehavior::Crate); + assert_eq!(result, None); +} + +#[test] +fn merge_into_existing_cfg_import() { + check( + r#"foo::Foo"#, + r#" +#[cfg(target_os = "windows")] +use bar::Baz; + +#[cfg(target_os = "windows")] +fn buzz() { + Foo$0; +} +"#, + r#" +#[cfg(target_os = "windows")] +use bar::Baz; +#[cfg(target_os = "windows")] +use foo::Foo; + +#[cfg(target_os = "windows")] +fn buzz() { + Foo; +} +"#, + ImportGranularity::Crate, + ); +} + +#[test] +fn reproduce_user_issue_missing_semicolon() { + check( + "std::fmt", + r#" +use { + foo +} + +#[cfg(target_arch = "x86_64")] +pub mod api; +"#, + r#" +use std::fmt; + +use { + foo +} + +#[cfg(target_arch = "x86_64")] +pub mod api; +"#, + ImportGranularity::Crate, + ); +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs index 635ed7368c4c0..bc2c2d0c0c368 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs @@ -692,13 +692,12 @@ pub fn eq_attrs( attrs1: impl Iterator, ) -> bool { // FIXME order of attributes should not matter - let attrs0 = attrs0 - .flat_map(|attr| attr.syntax().descendants_with_tokens()) - .flat_map(|it| it.into_token()); - let attrs1 = attrs1 - .flat_map(|attr| attr.syntax().descendants_with_tokens()) - .flat_map(|it| it.into_token()); - stdx::iter_eq_by(attrs0, attrs1, |tok, tok2| tok.text() == tok2.text()) + let mut attrs0: Vec<_> = attrs0.map(|attr| attr.syntax().text().to_string()).collect(); + let mut attrs1: Vec<_> = attrs1.map(|attr| attr.syntax().text().to_string()).collect(); + attrs0.sort(); + attrs1.sort(); + + attrs0 == attrs1 } fn path_is_self(path: &ast::Path) -> bool { From 3a8a3c74f27ff22b2bbb11594bfe2fcbfd8698a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?flora=20=7E=20=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F=20=E2=9D=A4=20=F0=9F=A6=80?= <35181375+tascord@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:47:49 +1100 Subject: [PATCH 10/60] Remove resolved FIXME --- .../rust-analyzer/crates/ide-db/src/imports/merge_imports.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs index bc2c2d0c0c368..d63b239ff43c7 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs @@ -691,7 +691,6 @@ pub fn eq_attrs( attrs0: impl Iterator, attrs1: impl Iterator, ) -> bool { - // FIXME order of attributes should not matter let mut attrs0: Vec<_> = attrs0.map(|attr| attr.syntax().text().to_string()).collect(); let mut attrs1: Vec<_> = attrs1.map(|attr| attr.syntax().text().to_string()).collect(); attrs0.sort(); From b451b42018616243f66ffd96f40b2cc1cee08561 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 14 Feb 2026 22:15:08 +0800 Subject: [PATCH 11/60] internal: to use SmolStr in fn_param --- .../src/completions/fn_param.rs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs index 34d25c9c67283..ad8a325359837 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs @@ -4,9 +4,9 @@ use hir::HirDisplay; use ide_db::FxHashMap; use itertools::Either; use syntax::{ - AstNode, Direction, SyntaxKind, TextRange, TextSize, algo, + AstNode, Direction, SmolStr, SyntaxKind, TextRange, TextSize, ToSmolStr, algo, ast::{self, HasModuleItem}, - match_ast, + format_smolstr, match_ast, }; use crate::{ @@ -51,7 +51,10 @@ pub(crate) fn complete_fn_param( ParamKind::Closure(closure) => { let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?; params_from_stmt_list_scope(ctx, stmt_list, |name, ty| { - add_new_item_to_acc(&format!("{}: {ty}", name.display(ctx.db, ctx.edition))); + add_new_item_to_acc(&format_smolstr!( + "{}: {ty}", + name.display(ctx.db, ctx.edition) + )); }); } } @@ -71,9 +74,8 @@ fn fill_fn_params( let mut extract_params = |f: ast::Fn| { f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| { if let Some(pat) = param.pat() { - // FIXME: We should be able to turn these into SmolStr without having to allocate a String - let whole_param = param.syntax().text().to_string(); - let binding = pat.syntax().text().to_string(); + let whole_param = param.to_smolstr(); + let binding = pat.to_smolstr(); file_params.entry(whole_param).or_insert(binding); } }); @@ -102,8 +104,8 @@ fn fill_fn_params( if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) { params_from_stmt_list_scope(ctx, stmt_list, |name, ty| { file_params - .entry(format!("{}: {ty}", name.display(ctx.db, ctx.edition))) - .or_insert(name.display(ctx.db, ctx.edition).to_string()); + .entry(format_smolstr!("{}: {ty}", name.display(ctx.db, ctx.edition))) + .or_insert(name.display(ctx.db, ctx.edition).to_smolstr()); }); } remove_duplicated(&mut file_params, param_list.params()); @@ -139,11 +141,11 @@ fn params_from_stmt_list_scope( } fn remove_duplicated( - file_params: &mut FxHashMap, + file_params: &mut FxHashMap, fn_params: ast::AstChildren, ) { fn_params.for_each(|param| { - let whole_param = param.syntax().text().to_string(); + let whole_param = param.to_smolstr(); file_params.remove(&whole_param); match param.pat() { @@ -151,7 +153,7 @@ fn remove_duplicated( // if the type is missing we are checking the current param to be completed // in which case this would find itself removing the suggestions due to itself Some(pattern) if param.ty().is_some() => { - let binding = pattern.syntax().text().to_string(); + let binding = pattern.to_smolstr(); file_params.retain(|_, v| v != &binding); } _ => (), @@ -173,7 +175,7 @@ fn should_add_self_completions( } } -fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> String, TextRange)> { +fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> SmolStr, TextRange)> { let param = ctx.original_token.parent_ancestors().find(|node| node.kind() == SyntaxKind::PARAM)?; @@ -196,5 +198,5 @@ fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> String matches!(prev_token_kind, SyntaxKind::COMMA | SyntaxKind::L_PAREN | SyntaxKind::PIPE); let leading = if has_leading_comma { "" } else { ", " }; - Some((move |label: &_| format!("{leading}{label}{trailing}"), param.text_range())) + Some((move |label: &_| format_smolstr!("{leading}{label}{trailing}"), param.text_range())) } From 71e4b03b02dcd190d48b508e14d70146fa7007ef Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 14 Feb 2026 22:01:28 +0800 Subject: [PATCH 12/60] fix: no complete suggest param in complex pattern Example --- ```rust fn foo(bar: u32) {} fn bar((a, bar$0)) {} ``` **Before this PR** ```rust fn foo(bar: u32) {} fn bar(bar: u32)) {} ``` **After this PR** Not complete `bar: u32` --- .../src/completions/fn_param.rs | 34 ++++++++---- .../ide-completion/src/tests/fn_param.rs | 54 +++++++++++++++++++ 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs index ad8a325359837..96dac66b8a199 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs @@ -25,7 +25,7 @@ pub(crate) fn complete_fn_param( ctx: &CompletionContext<'_>, pattern_ctx: &PatternContext, ) -> Option<()> { - let (ParamContext { param_list, kind, .. }, impl_or_trait) = match pattern_ctx { + let (ParamContext { param_list, kind, param, .. }, impl_or_trait) = match pattern_ctx { PatternContext { param_ctx: Some(kind), impl_or_trait, .. } => (kind, impl_or_trait), _ => return None, }; @@ -46,16 +46,18 @@ pub(crate) fn complete_fn_param( match kind { ParamKind::Function(function) => { - fill_fn_params(ctx, function, param_list, impl_or_trait, add_new_item_to_acc); + fill_fn_params(ctx, function, param_list, param, impl_or_trait, add_new_item_to_acc); } ParamKind::Closure(closure) => { - let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?; - params_from_stmt_list_scope(ctx, stmt_list, |name, ty| { - add_new_item_to_acc(&format_smolstr!( - "{}: {ty}", - name.display(ctx.db, ctx.edition) - )); - }); + if is_simple_param(param) { + let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?; + params_from_stmt_list_scope(ctx, stmt_list, |name, ty| { + add_new_item_to_acc(&format_smolstr!( + "{}: {ty}", + name.display(ctx.db, ctx.edition) + )); + }); + } } } @@ -66,12 +68,16 @@ fn fill_fn_params( ctx: &CompletionContext<'_>, function: &ast::Fn, param_list: &ast::ParamList, + current_param: &ast::Param, impl_or_trait: &Option>, mut add_new_item_to_acc: impl FnMut(&str), ) { let mut file_params = FxHashMap::default(); let mut extract_params = |f: ast::Fn| { + if !is_simple_param(current_param) { + return; + } f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| { if let Some(pat) = param.pat() { let whole_param = param.to_smolstr(); @@ -101,7 +107,9 @@ fn fill_fn_params( }; } - if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) { + if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) + && is_simple_param(current_param) + { params_from_stmt_list_scope(ctx, stmt_list, |name, ty| { file_params .entry(format_smolstr!("{}: {ty}", name.display(ctx.db, ctx.edition))) @@ -200,3 +208,9 @@ fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> SmolSt Some((move |label: &_| format_smolstr!("{leading}{label}{trailing}"), param.text_range())) } + +fn is_simple_param(param: &ast::Param) -> bool { + param + .pat() + .is_none_or(|pat| matches!(pat, ast::Pat::IdentPat(ident_pat) if ident_pat.pat().is_none())) +} diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs index 02cba6b6467e5..d6d73da3f140b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs @@ -292,6 +292,60 @@ fn bar(bar$0) {} ) } +#[test] +fn not_shows_fully_equal_inside_pattern_params() { + check( + r#" +fn foo(bar: u32) {} +fn bar((a, bar$0)) {} +"#, + expect![[r#" + kw mut + kw ref + "#]], + ) +} + +#[test] +fn not_shows_locals_inside_pattern_params() { + check( + r#" +fn outer() { + let foo = 3; + { + let bar = 3; + |($0)| {}; + let baz = 3; + let qux = 3; + } + let fez = 3; +} +"#, + expect![[r#" + kw mut + kw ref + "#]], + ); + check( + r#" +fn outer() { + let foo = 3; + { + let bar = 3; + fn inner(($0)) {} + let baz = 3; + let qux = 3; + } + let fez = 3; +} +"#, + expect![[r#" + kw mut + kw ref + "#]], + ); +} + #[test] fn completes_for_params_with_attributes() { check( From 25a71ea88818c2a364a915196e06e0bac39693dd Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 16 Feb 2026 00:11:51 +0200 Subject: [PATCH 13/60] Fix predicates of builtin derive traits with two parameters defaulting to `Self` I.e. `PartialEq` and `PartialOrd`. --- .../crates/hir-ty/src/builtin_derive.rs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs index 0c3c51366861b..b7a1585c808b2 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs @@ -260,7 +260,22 @@ fn simple_trait_predicates<'db>( let param_idx = param_idx.into_raw().into_u32() + (generic_params.len_lifetimes() as u32); let param_ty = Ty::new_param(interner, param_id, param_idx); - let trait_ref = TraitRef::new(interner, trait_id.into(), [param_ty]); + let trait_args = match loc.trait_ { + BuiltinDeriveImplTrait::Copy + | BuiltinDeriveImplTrait::Clone + | BuiltinDeriveImplTrait::Default + | BuiltinDeriveImplTrait::Debug + | BuiltinDeriveImplTrait::Hash + | BuiltinDeriveImplTrait::Ord + | BuiltinDeriveImplTrait::Eq => GenericArgs::new_from_slice(&[param_ty.into()]), + BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => { + GenericArgs::new_from_slice(&[param_ty.into(), param_ty.into()]) + } + BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { + unreachable!() + } + }; + let trait_ref = TraitRef::new_from_args(interner, trait_id.into(), trait_args); trait_ref.upcast(interner) }); let mut assoc_type_bounds = Vec::new(); @@ -528,7 +543,7 @@ struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]); Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) - Clause(Binder { value: TraitPredicate(#1: PartialEq, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: PartialEq<[#1]>, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) @@ -538,7 +553,7 @@ struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]); Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) - Clause(Binder { value: TraitPredicate(#1: PartialOrd, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: PartialOrd<[#1]>, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) From 1c462c2d502bf1252788149a5d791eaccea2a742 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 15 Feb 2026 18:35:22 +0800 Subject: [PATCH 14/60] fix: complete derive helpers on empty nameref Example --- ```rust //- /mac.rs crate:mac pub fn my_derive() {} //- /lib.rs crate:lib deps:mac pub struct Foo(#[$0] i32); ``` **Before this PR** ```text ... at must_use at no_mangle ... ``` **After this PR** ```text ... at must_use at my_cool_helper_attribute derive helper of `MyDerive` at no_mangle ... ``` --- .../ide-completion/src/context/analysis.rs | 2 +- .../ide-completion/src/tests/attribute.rs | 66 ++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index 1c8bc656ca251..4b0cc0c7cd98e 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -1501,7 +1501,7 @@ fn classify_name_ref<'db>( | SyntaxKind::RECORD_FIELD ) }) - .and_then(|_| nameref.as_ref()?.syntax().ancestors().find_map(ast::Adt::cast)) + .and_then(|_| find_node_at_offset::(original_file, original_offset)) .and_then(|adt| sema.derive_helpers_in_scope(&adt)) .unwrap_or_default(); Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind, derive_helpers } }) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs index 3701416dfc849..131911be91558 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs @@ -68,7 +68,71 @@ pub struct Foo(#[m$0] i32); kw crate:: kw self:: "#]], - ) + ); + check( + r#" +//- /mac.rs crate:mac +#![crate_type = "proc-macro"] + +#[proc_macro_derive(MyDerive, attributes(my_cool_helper_attribute))] +pub fn my_derive() {} + +//- /lib.rs crate:lib deps:mac +#[rustc_builtin_macro] +pub macro derive($item:item) {} + +#[derive(mac::MyDerive)] +pub struct Foo(#[$0] i32); +"#, + expect![[r#" + at allow(…) + at automatically_derived + at cfg(…) + at cfg_attr(…) + at cold + at deny(…) + at deprecated + at derive macro derive + at derive(…) + at diagnostic::do_not_recommend + at diagnostic::on_unimplemented + at doc = "…" + at doc = include_str!("…") + at doc(alias = "…") + at doc(hidden) + at expect(…) + at export_name = "…" + at forbid(…) + at global_allocator + at ignore = "…" + at inline + at link + at link_name = "…" + at link_section = "…" + at macro_export + at macro_use + at must_use + at my_cool_helper_attribute derive helper of `MyDerive` + at no_mangle + at non_exhaustive + at panic_handler + at path = "…" + at proc_macro + at proc_macro_attribute + at proc_macro_derive(…) + at repr(…) + at should_panic + at target_feature(enable = "…") + at test + at track_caller + at unsafe(…) + at used + at warn(…) + md mac + kw crate:: + kw self:: + "#]], + ); } #[test] From 6ac0ee92bd982b8255bc706a2078cb3cf0bf6c83 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Feb 2026 20:43:40 +0530 Subject: [PATCH 15/60] add span_source to ProcMacroClientInterface --- src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs index c548dc620ad13..6b770e440c7bb 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs @@ -120,6 +120,7 @@ pub trait ProcMacroClientInterface { fn line_column(&mut self, span: Span) -> Option<(u32, u32)>; fn byte_range(&mut self, span: Span) -> Range; + fn span_source(&mut self, span: Span) -> Span; } const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; From 2541de34b5e10ccb4fbeb95e08d3c4cf83d20fee Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Feb 2026 20:56:34 +0530 Subject: [PATCH 16/60] update utils to include span_source in the interface --- .../rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs index b7c5c4fdd21f0..28d826d01ea73 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs @@ -142,6 +142,10 @@ impl ProcMacroClientInterface for MockCallback<'_> { fn byte_range(&mut self, span: Span) -> Range { Range { start: span.range.start().into(), end: span.range.end().into() } } + + fn span_source(&mut self, span: Span) -> Span { + span + } } pub fn assert_expand_with_callback( From 9378836300eaf67d9b6c628a318f2d5f7c69fcbf Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Feb 2026 20:57:13 +0530 Subject: [PATCH 17/60] add span_source implementation in proc-macro-srv-cli --- .../proc-macro-srv-cli/src/main_loop.rs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs index 9be3199a3836a..2c54b18077bb0 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -273,6 +273,42 @@ impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle<'_> { other => handle_failure(other), } } + + fn span_source( + &mut self, + proc_macro_srv::span::Span { range, anchor, ctx }: proc_macro_srv::span::Span, + ) -> proc_macro_srv::span::Span { + match self.roundtrip(bidirectional::SubRequest::SpanSource { + file_id: anchor.file_id.as_u32(), + ast_id: anchor.ast_id.into_raw(), + start: range.start().into(), + end: range.end().into(), + ctx: ctx.into_u32(), + }) { + Ok(bidirectional::SubResponse::SpanSourceResult { + file_id, + ast_id, + start, + end, + ctx, + }) => { + proc_macro_srv::span::Span { + range: proc_macro_srv::span::TextRange::new( + proc_macro_srv::span::TextSize::new(start), + proc_macro_srv::span::TextSize::new(end), + ), + anchor: proc_macro_srv::span::SpanAnchor { + file_id: proc_macro_srv::span::EditionedFileId::from_raw(file_id), + ast_id: proc_macro_srv::span::ErasedFileAstId::from_raw(ast_id), + }, + // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen, + // but that will be their problem. + ctx: unsafe { proc_macro_srv::span::SyntaxContext::from_u32(ctx) }, + } + } + other => handle_failure(other), + } + } } fn handle_expand_ra( From a7d65c7d7067432ee63cbe8dd969fc2fb1083e2c Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Feb 2026 20:59:47 +0530 Subject: [PATCH 18/60] add span source implementation to proc-macro-srv --- .../proc-macro-srv/src/server_impl/rust_analyzer_span.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index c114d52ec33c6..0890e72c4fb1a 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -173,7 +173,9 @@ impl server::Server for RaSpanServer<'_> { None } fn span_source(&mut self, span: Self::Span) -> Self::Span { - // FIXME requires db, returns the top level call site + if let Some(ref mut callback) = self.callback { + return callback.span_source(span); + } span } fn span_byte_range(&mut self, span: Self::Span) -> Range { From 7f2956e19e6983005772979775d0ce1364390613 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Feb 2026 21:00:33 +0530 Subject: [PATCH 19/60] extend span source and span source result variant in message --- .../proc-macro-api/src/bidirectional_protocol/msg.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs index 3f0422dc5bc83..10a8d66677ac4 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs @@ -21,6 +21,7 @@ pub enum SubRequest { LocalFilePath { file_id: u32 }, LineColumn { file_id: u32, ast_id: u32, offset: u32 }, ByteRange { file_id: u32, ast_id: u32, start: u32, end: u32 }, + SpanSource { file_id: u32, ast_id: u32, start: u32, end: u32, ctx: u32 }, } #[derive(Debug, Serialize, Deserialize)] @@ -42,6 +43,13 @@ pub enum SubResponse { ByteRangeResult { range: Range, }, + SpanSourceResult { + file_id: u32, + ast_id: u32, + start: u32, + end: u32, + ctx: u32, + }, Cancel { reason: String, }, From a9d3e24a3b9026c604606f5b3d0819292fd76265 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Feb 2026 21:01:11 +0530 Subject: [PATCH 20/60] add span source client implementation --- .../crates/load-cargo/src/lib.rs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index 70a00cf825162..654ff4f75b0e6 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -612,6 +612,53 @@ impl ProcMacroExpander for Expander { Ok(SubResponse::ByteRangeResult { range: range.range.into() }) } + SubRequest::SpanSource { file_id, ast_id, start, end, ctx } => { + let span = Span { + range: TextRange::new(TextSize::from(start), TextSize::from(end)), + anchor: SpanAnchor { + file_id: span::EditionedFileId::from_raw(file_id), + ast_id: span::ErasedFileAstId::from_raw(ast_id), + }, + // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen, + // but that will be their problem. + ctx: unsafe { SyntaxContext::from_u32(ctx) }, + }; + + let mut current_span = span; + let mut current_ctx = span.ctx; + + while let Some(macro_call_id) = current_ctx.outer_expn(db) { + let macro_call_loc = db.lookup_intern_macro_call(macro_call_id.into()); + + let call_site_file = macro_call_loc.kind.file_id(); + + let resolved = db.resolve_span(current_span); + + current_ctx = macro_call_loc.ctxt; + current_span = Span { + range: resolved.range, + anchor: SpanAnchor { + file_id: resolved.file_id.editioned_file_id(db), + ast_id: span::ROOT_ERASED_FILE_AST_ID, + }, + ctx: current_ctx, + }; + + if call_site_file.file_id().is_some() { + break; + } + } + + let resolved = db.resolve_span(current_span); + + Ok(SubResponse::SpanSourceResult { + file_id: resolved.file_id.editioned_file_id(db).as_u32(), + ast_id: span::ROOT_ERASED_FILE_AST_ID.into_raw(), + start: u32::from(resolved.range.start()), + end: u32::from(resolved.range.end()), + ctx: current_span.ctx.into_u32(), + }) + } }; match self.0.expand( subtree.view(), From 12cacac0fb9efc0e5802b4584f380df24f6394c0 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 17 Feb 2026 15:43:36 +0000 Subject: [PATCH 21/60] fix: Ensure cpu_profiler feature compiles on Rust edition 2024 Without `unsafe` on this block, this feature didn't compile. --- .../rust-analyzer/crates/profile/src/google_cpu_profiler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/profile/src/google_cpu_profiler.rs b/src/tools/rust-analyzer/crates/profile/src/google_cpu_profiler.rs index cae6caeaa669c..d77c945f26319 100644 --- a/src/tools/rust-analyzer/crates/profile/src/google_cpu_profiler.rs +++ b/src/tools/rust-analyzer/crates/profile/src/google_cpu_profiler.rs @@ -9,7 +9,7 @@ use std::{ #[link(name = "profiler")] #[allow(non_snake_case)] -extern "C" { +unsafe extern "C" { fn ProfilerStart(fname: *const c_char) -> i32; fn ProfilerStop(); } From cf246bd45dcb8b702ae5409df999a8a2c9f19a1d Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 17 Feb 2026 13:19:19 +0000 Subject: [PATCH 22/60] Add some basic flycheck integration tests --- .../tests/slow-tests/flycheck.rs | 76 +++++++++++++++++++ .../rust-analyzer/tests/slow-tests/main.rs | 1 + .../rust-analyzer/tests/slow-tests/support.rs | 51 ++++++++++++- 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs new file mode 100644 index 0000000000000..0aa107e8233d9 --- /dev/null +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs @@ -0,0 +1,76 @@ +use test_utils::skip_slow_tests; + +use crate::support::Project; + +#[test] +fn test_flycheck_diagnostics_for_unused_variable() { + if skip_slow_tests() { + return; + } + + let server = Project::with_fixture( + r#" +//- /Cargo.toml +[package] +name = "foo" +version = "0.0.0" + +//- /src/main.rs +fn main() { + let x = 1; +} +"#, + ) + .with_config(serde_json::json!({ + "checkOnSave": true, + })) + .server() + .wait_until_workspace_is_loaded(); + + let diagnostics = server.wait_for_diagnostics(); + assert!( + diagnostics.diagnostics.iter().any(|d| d.message.contains("unused variable")), + "expected unused variable diagnostic, got: {:?}", + diagnostics.diagnostics, + ); +} + +#[test] +fn test_flycheck_diagnostic_cleared_after_fix() { + if skip_slow_tests() { + return; + } + + let server = Project::with_fixture( + r#" +//- /Cargo.toml +[package] +name = "foo" +version = "0.0.0" + +//- /src/main.rs +fn main() { + let x = 1; +} +"#, + ) + .with_config(serde_json::json!({ + "checkOnSave": true, + })) + .server() + .wait_until_workspace_is_loaded(); + + // Wait for the unused variable diagnostic to appear. + let diagnostics = server.wait_for_diagnostics(); + assert!( + diagnostics.diagnostics.iter().any(|d| d.message.contains("unused variable")), + "expected unused variable diagnostic, got: {:?}", + diagnostics.diagnostics, + ); + + // Fix the code by removing the unused variable. + server.write_file_and_save("src/main.rs", "fn main() {}\n".to_owned()); + + // Wait for diagnostics to be cleared. + server.wait_for_diagnostics_cleared(); +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs index b4a7b44d165ac..26ba549a29be1 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs @@ -15,6 +15,7 @@ extern crate rustc_driver as _; mod cli; +mod flycheck; mod ratoml; mod support; mod testdir; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs index 195ad226ae0c5..b8592c57de51f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs @@ -8,7 +8,9 @@ use std::{ use crossbeam_channel::{Receiver, after, select}; use itertools::Itertools; use lsp_server::{Connection, Message, Notification, Request}; -use lsp_types::{TextDocumentIdentifier, Url, notification::Exit, request::Shutdown}; +use lsp_types::{ + PublishDiagnosticsParams, TextDocumentIdentifier, Url, notification::Exit, request::Shutdown, +}; use parking_lot::{Mutex, MutexGuard}; use paths::{Utf8Path, Utf8PathBuf}; use rust_analyzer::{ @@ -407,6 +409,53 @@ impl Server { .unwrap_or_else(|Timeout| panic!("timeout while waiting for ws to load")); self } + pub(crate) fn wait_for_diagnostics(&self) -> PublishDiagnosticsParams { + for msg in self.messages.borrow().iter() { + if let Message::Notification(n) = msg { + if n.method == "textDocument/publishDiagnostics" { + let params: PublishDiagnosticsParams = + serde_json::from_value(n.params.clone()).unwrap(); + if !params.diagnostics.is_empty() { + return params; + } + } + } + } + loop { + let msg = self + .recv() + .unwrap_or_else(|Timeout| panic!("timeout while waiting for diagnostics")) + .expect("connection closed while waiting for diagnostics"); + if let Message::Notification(n) = &msg { + if n.method == "textDocument/publishDiagnostics" { + let params: PublishDiagnosticsParams = + serde_json::from_value(n.params.clone()).unwrap(); + if !params.diagnostics.is_empty() { + return params; + } + } + } + } + } + + pub(crate) fn wait_for_diagnostics_cleared(&self) { + loop { + let msg = self + .recv() + .unwrap_or_else(|Timeout| panic!("timeout while waiting for diagnostics to clear")) + .expect("connection closed while waiting for diagnostics to clear"); + if let Message::Notification(n) = &msg { + if n.method == "textDocument/publishDiagnostics" { + let params: PublishDiagnosticsParams = + serde_json::from_value(n.params.clone()).unwrap(); + if params.diagnostics.is_empty() { + return; + } + } + } + } + } + fn wait_for_message_cond( &self, n: usize, From 0a6961d2e002bd7730c427c18c8e65e84a5991c0 Mon Sep 17 00:00:00 2001 From: Raushan Kumar Date: Wed, 18 Feb 2026 06:46:34 +0000 Subject: [PATCH 23/60] fix: prevent path transformation of parameter names matching modules --- .../src/handlers/add_missing_impl_members.rs | 80 +++++++++++++++++++ .../crates/ide-db/src/path_transform.rs | 5 ++ 2 files changed, 85 insertions(+) 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 65ca1ceae11e9..afdced4215f9f 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 @@ -2534,6 +2534,86 @@ impl Test for () { ${0:todo!()} } } +"#, + ); + } + + #[test] + fn test_param_name_not_qualified() { + check_assist( + add_missing_impl_members, + r#" +mod ptr { + pub struct NonNull(T); +} +mod alloc { + use super::ptr::NonNull; + pub trait Allocator { + unsafe fn deallocate(&self, ptr: NonNull); + } +} + +struct System; + +unsafe impl alloc::Allocator for System { + $0 +} +"#, + r#" +mod ptr { + pub struct NonNull(T); +} +mod alloc { + use super::ptr::NonNull; + pub trait Allocator { + unsafe fn deallocate(&self, ptr: NonNull); + } +} + +struct System; + +unsafe impl alloc::Allocator for System { + unsafe fn deallocate(&self, ptr: ptr::NonNull) { + ${0:todo!()} + } +} +"#, + ); + } + + #[test] + fn test_param_name_shadows_module() { + check_assist( + add_missing_impl_members, + r#" +mod m { } +use m as p; + +pub trait Allocator { + fn deallocate(&self, p: u8); +} + +struct System; + +impl Allocator for System { + $0 +} +"#, + r#" +mod m { } +use m as p; + +pub trait Allocator { + fn deallocate(&self, p: u8); +} + +struct System; + +impl Allocator for System { + fn deallocate(&self, p: u8) { + ${0:todo!()} + } +} "#, ); } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs index 48305c20823c1..d0a28e7d2f86c 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs @@ -553,6 +553,11 @@ impl Ctx<'_> { return None; } + // Similarly, modules cannot be used in pattern position. + if matches!(def, hir::ModuleDef::Module(_)) { + return None; + } + let cfg = FindPathConfig { prefer_no_std: false, prefer_prelude: true, From cc3115d386eb4836f84b3fba0971c1780ed76bd4 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 17 Feb 2026 14:37:45 +0000 Subject: [PATCH 24/60] Enable debug printing on more types --- .../crates/rust-analyzer/src/flycheck.rs | 4 ++ .../rust-analyzer/tests/slow-tests/support.rs | 42 +++++++++---------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs index 47f7a57f72eca..f7f861a99e8e6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -382,6 +382,7 @@ enum FlycheckCommandOrigin { ProjectJsonRunnable, } +#[derive(Debug)] enum StateChange { Restart { generation: DiagnosticsGeneration, @@ -435,6 +436,7 @@ enum DiagnosticsReceived { } #[allow(clippy::large_enum_variant)] +#[derive(Debug)] enum Event { RequestStateChange(StateChange), CheckEvent(Option), @@ -445,6 +447,7 @@ const SAVED_FILE_PLACEHOLDER_DOLLAR: &str = "$saved_file"; const LABEL_INLINE: &str = "{label}"; const SAVED_FILE_INLINE: &str = "{saved_file}"; +#[derive(Debug)] struct Substitutions<'a> { label: Option<&'a str>, saved_file: Option<&'a str>, @@ -950,6 +953,7 @@ impl FlycheckActor { } #[allow(clippy::large_enum_variant)] +#[derive(Debug)] enum CheckMessage { /// A message from `cargo check`, including details like the path /// to the relevant `Cargo.toml`. diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs index b8592c57de51f..7ee31f3d53eab 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs @@ -411,13 +411,13 @@ impl Server { } pub(crate) fn wait_for_diagnostics(&self) -> PublishDiagnosticsParams { for msg in self.messages.borrow().iter() { - if let Message::Notification(n) = msg { - if n.method == "textDocument/publishDiagnostics" { - let params: PublishDiagnosticsParams = - serde_json::from_value(n.params.clone()).unwrap(); - if !params.diagnostics.is_empty() { - return params; - } + if let Message::Notification(n) = msg + && n.method == "textDocument/publishDiagnostics" + { + let params: PublishDiagnosticsParams = + serde_json::from_value(n.params.clone()).unwrap(); + if !params.diagnostics.is_empty() { + return params; } } } @@ -426,13 +426,13 @@ impl Server { .recv() .unwrap_or_else(|Timeout| panic!("timeout while waiting for diagnostics")) .expect("connection closed while waiting for diagnostics"); - if let Message::Notification(n) = &msg { - if n.method == "textDocument/publishDiagnostics" { - let params: PublishDiagnosticsParams = - serde_json::from_value(n.params.clone()).unwrap(); - if !params.diagnostics.is_empty() { - return params; - } + if let Message::Notification(n) = &msg + && n.method == "textDocument/publishDiagnostics" + { + let params: PublishDiagnosticsParams = + serde_json::from_value(n.params.clone()).unwrap(); + if !params.diagnostics.is_empty() { + return params; } } } @@ -444,13 +444,13 @@ impl Server { .recv() .unwrap_or_else(|Timeout| panic!("timeout while waiting for diagnostics to clear")) .expect("connection closed while waiting for diagnostics to clear"); - if let Message::Notification(n) = &msg { - if n.method == "textDocument/publishDiagnostics" { - let params: PublishDiagnosticsParams = - serde_json::from_value(n.params.clone()).unwrap(); - if params.diagnostics.is_empty() { - return; - } + if let Message::Notification(n) = &msg + && n.method == "textDocument/publishDiagnostics" + { + let params: PublishDiagnosticsParams = + serde_json::from_value(n.params.clone()).unwrap(); + if params.diagnostics.is_empty() { + return; } } } From b7d95aab1606edcec11255fb60fd573d3ce75b5c Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 17 Feb 2026 18:26:39 +0000 Subject: [PATCH 25/60] internal: Add flycheck test for custom check command and debounce This adds an integration test for flycheck with a custom check command. On its own, the test actually fails due to an issue with the debounce logic. We would trigger a flycheck when the workspace is loaded, but the check command references $saved_file and flycheck does nothing (we don't know which file was saved). Since we debounce state changes over 50 milliseconds, we would then discard the later save request that actually specifies the file. This bug is probably very rare in practice, 50 milliseconds isn't very long. I think it's a genuine fix though. AI disclosure: Partly written by Opus 4.6. --- .../crates/rust-analyzer/src/flycheck.rs | 34 ++++++++++++++---- .../tests/slow-tests/flycheck.rs | 36 +++++++++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs index f7f861a99e8e6..cdaf944bbad42 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -559,17 +559,37 @@ impl FlycheckActor { self.cancel_check_process(); } Event::RequestStateChange(StateChange::Restart { - generation, - scope, - saved_file, - target, + mut generation, + mut scope, + mut saved_file, + mut target, }) => { // Cancel the previously spawned process self.cancel_check_process(); + + // Debounce by briefly waiting for other state changes. while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) { - // restart chained with a stop, so just cancel - if let StateChange::Cancel = restart { - continue 'event; + match restart { + StateChange::Cancel => { + // We got a cancel straight after this restart request, so + // don't do anything. + continue 'event; + } + StateChange::Restart { + generation: g, + scope: s, + saved_file: sf, + target: t, + } => { + // We got another restart request. Take the parameters + // from the last restart request in this time window, + // because the most recent request is probably the most + // relevant to the user. + generation = g; + scope = s; + saved_file = sf; + target = t; + } } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs index 0aa107e8233d9..c1d53fb33ab60 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs @@ -74,3 +74,39 @@ fn main() { // Wait for diagnostics to be cleared. server.wait_for_diagnostics_cleared(); } + +#[test] +fn test_flycheck_diagnostic_with_override_command() { + if skip_slow_tests() { + return; + } + + let server = Project::with_fixture( + r#" +//- /Cargo.toml +[package] +name = "foo" +version = "0.0.0" + +//- /src/main.rs +fn main() {} +"#, + ) + .with_config(serde_json::json!({ + "checkOnSave": true, + "check": { + "overrideCommand": ["rustc", "--error-format=json", "$saved_file"] + } + })) + .server() + .wait_until_workspace_is_loaded(); + + server.write_file_and_save("src/main.rs", "fn main() {\n let x = 1;\n}\n".to_owned()); + + let diagnostics = server.wait_for_diagnostics(); + assert!( + diagnostics.diagnostics.iter().any(|d| d.message.contains("unused variable")), + "expected unused variable diagnostic, got: {:?}", + diagnostics.diagnostics, + ); +} From c99225675c5bdea059e57bdc9bea0aa4a8bf1409 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 6 Feb 2026 18:29:02 +0800 Subject: [PATCH 26/60] Only offer block let fallback in match-arm --- .../ide-completion/src/completions/postfix.rs | 7 +++---- .../crates/ide-completion/src/render.rs | 2 -- .../crates/ide-completion/src/tests/expression.rs | 14 -------------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index 3a92903d05128..067d906cd192c 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -161,7 +161,7 @@ pub(crate) fn complete_postfix( postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};")) .add_to(acc, ctx.db); } - _ => { + _ if ast::MatchArm::can_cast(second_ancestor.kind()) => { postfix_snippet( "let", "let", @@ -175,6 +175,7 @@ pub(crate) fn complete_postfix( ) .add_to(acc, ctx.db); } + _ => (), } } @@ -594,8 +595,6 @@ fn main() { sn dbgr dbg!(&expr) sn deref *expr sn if if expr {} - sn let let - sn letm let mut sn match match expr {} sn not !expr sn ref &expr @@ -811,7 +810,7 @@ fn main() { } #[test] - fn let_fallback_block() { + fn match_arm_let_block() { check( r#" fn main() { 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 7a7b054b3974b..765304d8187de 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -3033,8 +3033,6 @@ fn main() { sn dbgr dbg!(&expr) [] sn deref *expr [] sn if if expr {} [] - sn let let [] - sn letm let mut [] sn match match expr {} [] sn ref &expr [] sn refm &mut expr [] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs index b9b0f76c884c3..df39591a33460 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs @@ -2341,8 +2341,6 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr - sn let let - sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2370,8 +2368,6 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr - sn let let - sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2403,8 +2399,6 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr - sn let let - sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2432,8 +2426,6 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr - sn let let - sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2461,8 +2453,6 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr - sn let let - sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -2490,8 +2480,6 @@ fn main() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr - sn let let - sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr @@ -3280,8 +3268,6 @@ fn foo() { sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn deref *expr - sn let let - sn letm let mut sn match match expr {} sn ref &expr sn refm &mut expr From 140672a4658563ff2fe3c570ceec06e8910d212d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Wed, 18 Feb 2026 18:35:01 +0200 Subject: [PATCH 27/60] Bump rustup-toolchain-install-master --- .../rust-analyzer/.github/workflows/ci.yaml | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index ca7d3058d8f0b..04de6d11e3ebf 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.6.0 + run: cargo install rustup-toolchain-install-master@1.11.0 # Install a pinned rustc commit to avoid surprises - name: Install Rust toolchain @@ -226,7 +226,12 @@ jobs: strategy: matrix: - target: [powerpc-unknown-linux-gnu, x86_64-unknown-linux-musl, wasm32-unknown-unknown] + target: + [ + powerpc-unknown-linux-gnu, + x86_64-unknown-linux-musl, + wasm32-unknown-unknown, + ] include: # The rust-analyzer binary is not expected to compile on WASM, but the IDE # crate should @@ -330,7 +335,18 @@ jobs: run: typos conclusion: - needs: [rust, rust-cross, typescript, typo-check, proc-macro-srv, miri, rustfmt, clippy, analysis-stats] + needs: + [ + rust, + rust-cross, + typescript, + typo-check, + proc-macro-srv, + miri, + rustfmt, + clippy, + analysis-stats, + ] # We need to ensure this job does *not* get skipped if its dependencies fail, # because a skipped job is considered a success by GitHub. So we have to # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run From 053cde5303ef82c02a1a879aeba8d58ef4bf2444 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Fri, 13 Feb 2026 01:14:08 +0900 Subject: [PATCH 28/60] Switch to env var CARGO_RESOLVER_LOCKFILE_PATH for copied lockfiles for toolchains >= `1.95.0` --- .../project-model/src/build_dependencies.rs | 33 ++++++---- .../project-model/src/cargo_config_file.rs | 62 +++++++++++++++---- .../project-model/src/cargo_workspace.rs | 48 +++++++------- 3 files changed, 93 insertions(+), 50 deletions(-) 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 fedc6944f5f86..a4eec872b62cd 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 @@ -22,8 +22,9 @@ use triomphe::Arc; use crate::{ CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot, - TargetKind, cargo_config_file::make_lockfile_copy, - cargo_workspace::MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH, utf8_stdout, + TargetKind, + cargo_config_file::{LockfileCopy, LockfileUsage, make_lockfile_copy}, + utf8_stdout, }; /// Output of the build script and proc-macro building steps for a workspace. @@ -436,7 +437,7 @@ impl WorkspaceBuildScripts { current_dir: &AbsPath, sysroot: &Sysroot, toolchain: Option<&semver::Version>, - ) -> io::Result<(Option, Command)> { + ) -> io::Result<(Option, Command)> { match config.run_build_script_command.as_deref() { Some([program, args @ ..]) => { let mut cmd = toolchain::command(program, current_dir, &config.extra_env); @@ -461,17 +462,25 @@ impl WorkspaceBuildScripts { if let Some(target) = &config.target { cmd.args(["--target", target]); } - let mut temp_dir_guard = None; - if toolchain - .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH) - { + let mut lockfile_copy = None; + if let Some(toolchain) = toolchain { let lockfile_path = <_ as AsRef>::as_ref(manifest_path).with_extension("lock"); - if let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile_path) { + lockfile_copy = make_lockfile_copy(toolchain, &lockfile_path); + if let Some(lockfile_copy) = &lockfile_copy { requires_unstable_options = true; - temp_dir_guard = Some(temp_dir); - cmd.arg("--lockfile-path"); - cmd.arg(target_lockfile.as_str()); + match lockfile_copy.usage { + LockfileUsage::WithFlag => { + cmd.arg("--lockfile-path"); + cmd.arg(lockfile_copy.path.as_str()); + } + LockfileUsage::WithEnvVar => { + cmd.env( + "CARGO_RESOLVER_LOCKFILE_PATH", + lockfile_copy.path.as_os_str(), + ); + } + } } } match &config.features { @@ -542,7 +551,7 @@ impl WorkspaceBuildScripts { cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly"); cmd.arg("-Zunstable-options"); } - Ok((temp_dir_guard, cmd)) + Ok((lockfile_copy, cmd)) } } } diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs index 5d6e5fd648b38..ae36deb71f02a 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs @@ -132,25 +132,65 @@ impl<'a> CargoConfigFileReader<'a> { } } +pub(crate) struct LockfileCopy { + pub(crate) path: Utf8PathBuf, + pub(crate) usage: LockfileUsage, + _temp_dir: temp_dir::TempDir, +} + +pub(crate) enum LockfileUsage { + /// Rust [1.82.0, 1.95.0). `cargo --lockfile-path ` + WithFlag, + /// Rust >= 1.95.0. `CARGO_RESOLVER_LOCKFILE_PATH= cargo ` + WithEnvVar, +} + pub(crate) fn make_lockfile_copy( + toolchain_version: &semver::Version, lockfile_path: &Utf8Path, -) -> Option<(temp_dir::TempDir, Utf8PathBuf)> { +) -> Option { + const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_FLAG: semver::Version = + semver::Version { + major: 1, + minor: 82, + patch: 0, + pre: semver::Prerelease::EMPTY, + build: semver::BuildMetadata::EMPTY, + }; + + const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV: semver::Version = + semver::Version { + major: 1, + minor: 95, + patch: 0, + pre: semver::Prerelease::EMPTY, + build: semver::BuildMetadata::EMPTY, + }; + + let usage = if *toolchain_version >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV { + LockfileUsage::WithEnvVar + } else if *toolchain_version >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_FLAG { + LockfileUsage::WithFlag + } else { + return None; + }; + let temp_dir = temp_dir::TempDir::with_prefix("rust-analyzer").ok()?; - let target_lockfile = temp_dir.path().join("Cargo.lock").try_into().ok()?; - match std::fs::copy(lockfile_path, &target_lockfile) { + let path: Utf8PathBuf = temp_dir.path().join("Cargo.lock").try_into().ok()?; + let path = match std::fs::copy(lockfile_path, &path) { Ok(_) => { - tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, target_lockfile); - Some((temp_dir, target_lockfile)) + tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, path); + path } // lockfile does not yet exist, so we can just create a new one in the temp dir - Err(e) if e.kind() == std::io::ErrorKind::NotFound => Some((temp_dir, target_lockfile)), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => path, Err(e) => { - tracing::warn!( - "Failed to copy lock file from `{lockfile_path}` to `{target_lockfile}`: {e}", - ); - None + tracing::warn!("Failed to copy lock file from `{lockfile_path}` to `{path}`: {e}",); + return None; } - } + }; + + Some(LockfileCopy { path, usage, _temp_dir: temp_dir }) } #[test] diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index 483ab28450455..f7e57c5b075c7 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -16,18 +16,10 @@ use toolchain::{NO_RUSTUP_AUTO_INSTALL_ENV, Tool}; use triomphe::Arc; use crate::{ - CfgOverrides, InvocationStrategy, ManifestPath, Sysroot, cargo_config_file::make_lockfile_copy, + CfgOverrides, InvocationStrategy, ManifestPath, Sysroot, + cargo_config_file::{LockfileCopy, LockfileUsage, make_lockfile_copy}, }; -pub(crate) const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version = - semver::Version { - major: 1, - minor: 82, - patch: 0, - pre: semver::Prerelease::EMPTY, - build: semver::BuildMetadata::EMPTY, - }; - /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo /// workspace. It pretty closely mirrors `cargo metadata` output. /// @@ -628,7 +620,7 @@ pub(crate) struct FetchMetadata { command: cargo_metadata::MetadataCommand, #[expect(dead_code)] manifest_path: ManifestPath, - lockfile_path: Option, + lockfile_copy: Option, #[expect(dead_code)] kind: &'static str, no_deps: bool, @@ -688,15 +680,14 @@ impl FetchMetadata { } } - let mut lockfile_path = None; + let mut lockfile_copy = None; if cargo_toml.is_rust_manifest() { other_options.push("-Zscript".to_owned()); - } else if config - .toolchain_version - .as_ref() - .is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH) - { - lockfile_path = Some(<_ as AsRef>::as_ref(cargo_toml).with_extension("lock")); + } else if let Some(v) = config.toolchain_version.as_ref() { + lockfile_copy = make_lockfile_copy( + v, + &<_ as AsRef>::as_ref(cargo_toml).with_extension("lock"), + ); } if !config.targets.is_empty() { @@ -729,7 +720,7 @@ impl FetchMetadata { Self { manifest_path: cargo_toml.clone(), command, - lockfile_path, + lockfile_copy, kind: config.kind, no_deps, no_deps_result, @@ -749,7 +740,7 @@ impl FetchMetadata { let Self { mut command, manifest_path: _, - lockfile_path, + lockfile_copy, kind: _, no_deps, no_deps_result, @@ -761,13 +752,16 @@ impl FetchMetadata { } let mut using_lockfile_copy = false; - let mut _temp_dir_guard; - if let Some(lockfile) = lockfile_path - && let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile) - { - _temp_dir_guard = temp_dir; - other_options.push("--lockfile-path".to_owned()); - other_options.push(target_lockfile.to_string()); + if let Some(lockfile_copy) = &lockfile_copy { + match lockfile_copy.usage { + LockfileUsage::WithFlag => { + other_options.push("--lockfile-path".to_owned()); + other_options.push(lockfile_copy.path.to_string()); + } + LockfileUsage::WithEnvVar => { + command.env("CARGO_RESOLVER_LOCKFILE_PATH", lockfile_copy.path.as_os_str()); + } + } using_lockfile_copy = true; } if using_lockfile_copy || other_options.iter().any(|it| it.starts_with("-Z")) { From 5d19cb96c14e4f2fd721d05b3cfc39393c328072 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Thu, 19 Feb 2026 02:05:41 +0900 Subject: [PATCH 29/60] Append `-Zlockfile-path` --- .../rust-analyzer/crates/project-model/src/build_dependencies.rs | 1 + .../rust-analyzer/crates/project-model/src/cargo_workspace.rs | 1 + 2 files changed, 2 insertions(+) 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 a4eec872b62cd..aff5391697414 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 @@ -475,6 +475,7 @@ impl WorkspaceBuildScripts { cmd.arg(lockfile_copy.path.as_str()); } LockfileUsage::WithEnvVar => { + cmd.arg("-Zlockfile-path"); cmd.env( "CARGO_RESOLVER_LOCKFILE_PATH", lockfile_copy.path.as_os_str(), diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index f7e57c5b075c7..792206b74f849 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -759,6 +759,7 @@ impl FetchMetadata { other_options.push(lockfile_copy.path.to_string()); } LockfileUsage::WithEnvVar => { + other_options.push("-Zlockfile-path".to_owned()); command.env("CARGO_RESOLVER_LOCKFILE_PATH", lockfile_copy.path.as_os_str()); } } From 1278a87b81780e5fbfa0c20e38c3b26f11f0290c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 12 Feb 2026 01:14:25 +0800 Subject: [PATCH 30/60] feat: offer on is_some_and for replace_is_method_with_if_let_method Example --- ```rust fn main() { let x = Some(1); if x.is_som$0e_and(|it| it != 3) {} } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn main() { let x = Some(1); if let Some(it) = x && it != 3 {} } ``` --- .../replace_is_method_with_if_let_method.rs | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs index 5a2307739cff5..d22e951b5dab0 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs @@ -1,6 +1,6 @@ use either::Either; use ide_db::syntax_helpers::suggest_name; -use syntax::ast::{self, AstNode, syntax_factory::SyntaxFactory}; +use syntax::ast::{self, AstNode, HasArgList, syntax_factory::SyntaxFactory}; use crate::{AssistContext, AssistId, Assists, utils::cover_let_chain}; @@ -34,8 +34,9 @@ pub(crate) fn replace_is_method_with_if_let_method( _ => return None, }; - let name_ref = call_expr.name_ref()?; - match name_ref.text().as_str() { + let token = call_expr.name_ref()?.ident_token()?; + let method_kind = token.text().strip_suffix("_and").unwrap_or(token.text()); + match method_kind { "is_some" | "is_ok" => { let receiver = call_expr.receiver()?; @@ -47,8 +48,9 @@ pub(crate) fn replace_is_method_with_if_let_method( } else { name_generator.for_variable(&receiver, &ctx.sema) }; + let (pat, predicate) = method_predicate(&call_expr).unzip(); - let (assist_id, message, text) = if name_ref.text() == "is_some" { + let (assist_id, message, text) = if method_kind == "is_some" { ("replace_is_some_with_if_let_some", "Replace `is_some` with `let Some`", "Some") } else { ("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `let Ok`", "Ok") @@ -62,19 +64,29 @@ pub(crate) fn replace_is_method_with_if_let_method( let make = SyntaxFactory::with_mappings(); let mut editor = edit.make_editor(call_expr.syntax()); - let var_pat = make.ident_pat(false, false, make.name(&var_name)); - let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat.into()]); - let let_expr = make.expr_let(pat.into(), receiver); + let var_pat = pat.unwrap_or_else(|| { + make.ident_pat(false, false, make.name(&var_name)).into() + }); + let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat]).into(); + let let_expr = make.expr_let(pat, receiver); if let Some(cap) = ctx.config.snippet_cap && let Some(ast::Pat::TupleStructPat(pat)) = let_expr.pat() && let Some(first_var) = pat.fields().next() + && predicate.is_none() { let placeholder = edit.make_placeholder_snippet(cap); editor.add_annotation(first_var.syntax(), placeholder); } - editor.replace(call_expr.syntax(), let_expr.syntax()); + let new_expr = if let Some(predicate) = predicate { + let op = ast::BinaryOp::LogicOp(ast::LogicOp::And); + make.expr_bin(let_expr.into(), op, predicate).into() + } else { + ast::Expr::from(let_expr) + }; + editor.replace(call_expr.syntax(), new_expr.syntax()); + editor.add_mappings(make.finish_with_mappings()); edit.add_file_edits(ctx.vfs_file_id(), editor); }, @@ -84,6 +96,17 @@ pub(crate) fn replace_is_method_with_if_let_method( } } +fn method_predicate(call_expr: &ast::MethodCallExpr) -> Option<(ast::Pat, ast::Expr)> { + let argument = call_expr.arg_list()?.args().next()?; + match argument { + ast::Expr::ClosureExpr(it) => { + let pat = it.param_list()?.params().next()?.pat()?; + Some((pat, it.body()?)) + } + _ => None, + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -194,6 +217,25 @@ fn main() { ); } + #[test] + fn replace_is_some_and_with_if_let_chain_some_works() { + check_assist( + replace_is_method_with_if_let_method, + r#" +fn main() { + let x = Some(1); + if x.is_som$0e_and(|it| it != 3) {} +} +"#, + r#" +fn main() { + let x = Some(1); + if let Some(it) = x && it != 3 {} +} +"#, + ); + } + #[test] fn replace_is_some_with_if_let_some_in_let_chain() { check_assist( From 574c7b85a2f345e644bbba7fc33a526828b49161 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 18 Feb 2026 04:32:16 +0200 Subject: [PATCH 31/60] Add handling for cycles in `sizedness_constraint_for_ty()` Ported mostly from rustc. --- .../rust-analyzer/crates/hir-ty/src/lib.rs | 1 + .../crates/hir-ty/src/next_solver/util.rs | 31 +++-- .../crates/hir-ty/src/representability.rs | 131 ++++++++++++++++++ .../crates/hir-ty/src/tests/regression.rs | 25 ++++ 4 files changed, 178 insertions(+), 10 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-ty/src/representability.rs 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 ca817ae7134b1..8d87276a0b152 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -31,6 +31,7 @@ mod inhabitedness; mod lower; pub mod next_solver; mod opaques; +mod representability; mod specialization; mod target_feature; mod utils; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs index 9a1b476976e34..c175062bda37c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs @@ -13,13 +13,16 @@ use rustc_type_ir::{ solve::SizedTraitKind, }; -use crate::next_solver::{ - BoundConst, FxIndexMap, ParamEnv, Placeholder, PlaceholderConst, PlaceholderRegion, - PolyTraitRef, - infer::{ - InferCtxt, - traits::{Obligation, ObligationCause, PredicateObligation}, +use crate::{ + next_solver::{ + BoundConst, FxIndexMap, ParamEnv, Placeholder, PlaceholderConst, PlaceholderRegion, + PolyTraitRef, + infer::{ + InferCtxt, + traits::{Obligation, ObligationCause, PredicateObligation}, + }, }, + representability::Representability, }; use super::{ @@ -419,10 +422,18 @@ pub fn sizedness_constraint_for_ty<'db>( .next_back() .and_then(|ty| sizedness_constraint_for_ty(interner, sizedness, ty)), - Adt(adt, args) => adt.struct_tail_ty(interner).and_then(|tail_ty| { - let tail_ty = tail_ty.instantiate(interner, args); - sizedness_constraint_for_ty(interner, sizedness, tail_ty) - }), + Adt(adt, args) => { + if crate::representability::representability(interner.db, adt.def_id().0) + == Representability::Infinite + { + return None; + } + + adt.struct_tail_ty(interner).and_then(|tail_ty| { + let tail_ty = tail_ty.instantiate(interner, args); + sizedness_constraint_for_ty(interner, sizedness, tail_ty) + }) + } Placeholder(..) | Bound(..) | Infer(..) => { panic!("unexpected type `{ty:?}` in sizedness_constraint_for_ty") diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/representability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/representability.rs new file mode 100644 index 0000000000000..7e40f2d7ac75f --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/representability.rs @@ -0,0 +1,131 @@ +//! Detecting whether a type is infinitely-sized. + +use hir_def::{AdtId, VariantId}; +use rustc_type_ir::inherent::{AdtDef, IntoKind}; + +use crate::{ + db::HirDatabase, + next_solver::{GenericArgKind, GenericArgs, Ty, TyKind}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Representability { + Representable, + Infinite, +} + +macro_rules! rtry { + ($e:expr) => { + match $e { + e @ Representability::Infinite => return e, + Representability::Representable => {} + } + }; +} + +#[salsa::tracked(cycle_result = representability_cycle)] +pub(crate) fn representability(db: &dyn HirDatabase, id: AdtId) -> Representability { + match id { + AdtId::StructId(id) => variant_representability(db, id.into()), + AdtId::UnionId(id) => variant_representability(db, id.into()), + AdtId::EnumId(id) => { + for &(variant, ..) in &id.enum_variants(db).variants { + rtry!(variant_representability(db, variant.into())); + } + Representability::Representable + } + } +} + +pub(crate) fn representability_cycle( + _db: &dyn HirDatabase, + _: salsa::Id, + _id: AdtId, +) -> Representability { + Representability::Infinite +} + +fn variant_representability(db: &dyn HirDatabase, id: VariantId) -> Representability { + for ty in db.field_types(id).values() { + rtry!(representability_ty(db, ty.get().instantiate_identity())); + } + Representability::Representable +} + +fn representability_ty<'db>(db: &'db dyn HirDatabase, ty: Ty<'db>) -> Representability { + match ty.kind() { + TyKind::Adt(adt_id, args) => representability_adt_ty(db, adt_id.def_id().0, args), + // FIXME(#11924) allow zero-length arrays? + TyKind::Array(ty, _) => representability_ty(db, ty), + TyKind::Tuple(tys) => { + for ty in tys { + rtry!(representability_ty(db, ty)); + } + Representability::Representable + } + _ => Representability::Representable, + } +} + +fn representability_adt_ty<'db>( + db: &'db dyn HirDatabase, + def_id: AdtId, + args: GenericArgs<'db>, +) -> Representability { + rtry!(representability(db, def_id)); + + // At this point, we know that the item of the ADT type is representable; + // but the type parameters may cause a cycle with an upstream type + let params_in_repr = params_in_repr(db, def_id); + for (i, arg) in args.iter().enumerate() { + if let GenericArgKind::Type(ty) = arg.kind() + && params_in_repr[i] + { + rtry!(representability_ty(db, ty)); + } + } + Representability::Representable +} + +fn params_in_repr(db: &dyn HirDatabase, def_id: AdtId) -> Box<[bool]> { + let generics = db.generic_params(def_id.into()); + let mut params_in_repr = (0..generics.len_lifetimes() + generics.len_type_or_consts()) + .map(|_| false) + .collect::>(); + let mut handle_variant = |variant| { + for field in db.field_types(variant).values() { + params_in_repr_ty(db, field.get().instantiate_identity(), &mut params_in_repr); + } + }; + match def_id { + AdtId::StructId(def_id) => handle_variant(def_id.into()), + AdtId::UnionId(def_id) => handle_variant(def_id.into()), + AdtId::EnumId(def_id) => { + for &(variant, ..) in &def_id.enum_variants(db).variants { + handle_variant(variant.into()); + } + } + } + params_in_repr +} + +fn params_in_repr_ty<'db>(db: &'db dyn HirDatabase, ty: Ty<'db>, params_in_repr: &mut [bool]) { + match ty.kind() { + TyKind::Adt(adt, args) => { + let inner_params_in_repr = self::params_in_repr(db, adt.def_id().0); + for (i, arg) in args.iter().enumerate() { + if let GenericArgKind::Type(ty) = arg.kind() + && inner_params_in_repr[i] + { + params_in_repr_ty(db, ty, params_in_repr); + } + } + } + TyKind::Array(ty, _) => params_in_repr_ty(db, ty, params_in_repr), + TyKind::Tuple(tys) => tys.iter().for_each(|ty| params_in_repr_ty(db, ty, params_in_repr)), + TyKind::Param(param) => { + params_in_repr[param.index as usize] = true; + } + _ => {} + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index 5291bf80e8219..658c304aac012 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 @@ -2770,3 +2770,28 @@ unsafe extern "C" { "#, ); } + +#[test] +fn infinitely_sized_type() { + check_infer( + r#" +//- minicore: sized + +pub struct Recursive { + pub content: Recursive, +} + +fn is_sized() {} + +fn foo() { + is_sized::(); +} + "#, + expect![[r#" + 79..81 '{}': () + 92..124 '{ ...>(); }': () + 98..119 'is_siz...rsive>': fn is_sized() + 98..121 'is_siz...ive>()': () + "#]], + ); +} From 2a3d765883f559d9e626cce2edda17418fd212ac Mon Sep 17 00:00:00 2001 From: protonblu Date: Thu, 19 Feb 2026 01:51:08 +0530 Subject: [PATCH 32/60] fix: exclude macro refs in tests when excludeTests is enabled --- .../rust-analyzer/crates/ide-db/src/search.rs | 8 +++-- .../crates/ide/src/references.rs | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index 1d865892a22b5..5cb5d4aa2b1cc 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -1370,8 +1370,10 @@ fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool { } fn is_name_ref_in_test(sema: &Semantics<'_, RootDatabase>, name_ref: &ast::NameRef) -> bool { - name_ref.syntax().ancestors().any(|node| match ast::Fn::cast(node) { - Some(it) => sema.to_def(&it).is_some_and(|func| func.is_test(sema.db)), - None => false, + sema.ancestors_with_macros(name_ref.syntax().clone()).any(|node| { + match ast::Fn::cast(node) { + Some(it) => sema.to_def(&it).is_some_and(|func| func.is_test(sema.db)), + None => false, + } }) } diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 38ee09703398b..cf20224c52884 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -532,6 +532,41 @@ fn test() { "#]], ); } + #[test] + fn exclude_tests_macro_refs() { + check( + r#" +fn foo$0() -> i32 { 42 } + +fn bar() { + foo(); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn t1() { + let f = foo(); + } + + #[test] + fn t2() { + dbg!(foo()); + assert_eq!(foo(), 42); + let v = vec![foo()]; + } +} +"#, + expect![[r#" + foo Function FileId(0) 0..22 3..6 + + FileId(0) 39..42 + FileId(0) 135..138 + "#]], + ); + } #[test] fn test_struct_literal_after_space() { From 918214c19ae9f44eaca32d03f4a4cb10cc6089e9 Mon Sep 17 00:00:00 2001 From: protonblu Date: Thu, 19 Feb 2026 02:14:34 +0530 Subject: [PATCH 33/60] style: fix rustfmt formatting --- src/tools/rust-analyzer/crates/ide-db/src/search.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index 5cb5d4aa2b1cc..4196a13aa3fa7 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -1370,10 +1370,8 @@ fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool { } fn is_name_ref_in_test(sema: &Semantics<'_, RootDatabase>, name_ref: &ast::NameRef) -> bool { - sema.ancestors_with_macros(name_ref.syntax().clone()).any(|node| { - match ast::Fn::cast(node) { - Some(it) => sema.to_def(&it).is_some_and(|func| func.is_test(sema.db)), - None => false, - } + sema.ancestors_with_macros(name_ref.syntax().clone()).any(|node| match ast::Fn::cast(node) { + Some(it) => sema.to_def(&it).is_some_and(|func| func.is_test(sema.db)), + None => false, }) } From 9a57e950777a5d435076300c8148a3b0016d4b12 Mon Sep 17 00:00:00 2001 From: Paul Hanzlik <52867007+pjhanzlik@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:44:58 -0500 Subject: [PATCH 34/60] Update vs_code.md flatpak SDK extensions to 25.08 runtime --- src/tools/rust-analyzer/docs/book/src/vs_code.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/docs/book/src/vs_code.md b/src/tools/rust-analyzer/docs/book/src/vs_code.md index 75f940e69a885..69a96156b8261 100644 --- a/src/tools/rust-analyzer/docs/book/src/vs_code.md +++ b/src/tools/rust-analyzer/docs/book/src/vs_code.md @@ -98,12 +98,12 @@ some directories, `/usr/bin` will always be mounted under system-wide installation of Rust, or any other libraries you might want to link to. Some compilers and libraries can be acquired as Flatpak SDKs, such as `org.freedesktop.Sdk.Extension.rust-stable` or -`org.freedesktop.Sdk.Extension.llvm15`. +`org.freedesktop.Sdk.Extension.llvm21`. If you use a Flatpak SDK for Rust, it must be in your `PATH`: - * install the SDK extensions with `flatpak install org.freedesktop.Sdk.Extension.{llvm15,rust-stable}//23.08` - * enable SDK extensions in the editor with the environment variable `FLATPAK_ENABLE_SDK_EXT=llvm15,rust-stable` (this can be done using flatseal or `flatpak override`) + * install the SDK extensions with `flatpak install org.freedesktop.Sdk.Extension.{llvm21,rust-stable}//25.08` + * enable SDK extensions in the editor with the environment variable `FLATPAK_ENABLE_SDK_EXT=llvm21,rust-stable` (this can be done using flatseal or `flatpak override`) If you want to use Flatpak in combination with `rustup`, the following steps might help: From 08a7b9a734c9a1d7b4bc33a4199726308c8b551d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 19 Feb 2026 09:53:25 +0800 Subject: [PATCH 35/60] Remove fixme --- .../crates/ide-assists/src/handlers/toggle_macro_delimiter.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs index e9865aec4a146..15143575e7d84 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs @@ -415,7 +415,6 @@ prt!{(3 + 5)} ) } - // FIXME @alibektas : Inner macro_call is not seen as such. So this doesn't work. #[test] fn test_nested_macros() { check_assist( From 89832e9a778217ced3deca501c1918ae6a36ace0 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 1 Aug 2025 09:15:40 +0800 Subject: [PATCH 36/60] Add partial selection for generate_getter_or_setter Example --- ```rust struct Context { data$0: Data, count$0: usize, other: usize, } ``` **Before this PR** Assist not applicable **After this PR** ```rust struct Context { data: Data, count: usize, other: usize, } impl Context { fn data(&self) -> &Data { &self.data } fn $0count(&self) -> &usize { &self.count } } ``` --- .../src/handlers/generate_getter_or_setter.rs | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs index 51b967437b568..92a654743b580 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs @@ -11,7 +11,7 @@ use syntax::{ use crate::{ AssistContext, AssistId, Assists, GroupLabel, - utils::{convert_reference_type, find_struct_impl}, + utils::{convert_reference_type, find_struct_impl, is_selected}, }; // Assist: generate_setter @@ -377,7 +377,7 @@ fn extract_and_parse_record_fields( let info_of_record_fields_in_selection = ele .fields() .filter_map(|record_field| { - if selection_range.contains_range(record_field.syntax().text_range()) { + if is_selected(&record_field, selection_range, false) { let record_field_info = parse_record_field(record_field, assist_type)?; field_names.push(record_field_info.fn_name.clone()); return Some(record_field_info); @@ -934,6 +934,37 @@ struct Context { count: usize, } +impl Context { + fn data(&self) -> &Data { + &self.data + } + + fn $0count(&self) -> &usize { + &self.count + } +} + "#, + ); + } + + #[test] + fn test_generate_multiple_getters_from_partial_selection() { + check_assist( + generate_getter, + r#" +struct Context { + data$0: Data, + count$0: usize, + other: usize, +} + "#, + r#" +struct Context { + data: Data, + count: usize, + other: usize, +} + impl Context { fn data(&self) -> &Data { &self.data From 5bb10ae244facd131840cc66ca4e961a62eaef7c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 2 Jan 2026 19:00:55 +0800 Subject: [PATCH 37/60] Fix some TryEnum reference assists - Fix `convert_to_guarded_return` - Fix `replace_let_with_if_let` - Fix `replace_if_let_with_match` --- .../src/handlers/convert_to_guarded_return.rs | 26 +++++++++++++++++++ .../src/handlers/desugar_try_expr.rs | 2 +- .../src/handlers/replace_if_let_with_match.rs | 25 ++++++++++++++++++ .../src/handlers/replace_let_with_if_let.rs | 23 ++++++++++++++++ .../ide-completion/src/completions/postfix.rs | 2 +- .../crates/ide-db/src/ty_filter.rs | 10 ++++++- 6 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index ea5c1637b7608..dc51bf4b5b8c1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -934,6 +934,32 @@ fn foo() -> Option { None } +fn main() { + let Some(x) = foo() else { return }; +} +"#, + ); + } + + #[test] + fn convert_let_ref_stmt_inside_fn() { + check_assist( + convert_to_guarded_return, + r#" +//- minicore: option +fn foo() -> &'static Option { + &None +} + +fn main() { + let x$0 = foo(); +} +"#, + r#" +fn foo() -> &'static Option { + &None +} + fn main() { let Some(x) = foo() else { return }; } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs index 9976e34e730cc..176eb1c4944e0 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs @@ -61,7 +61,7 @@ pub(crate) fn desugar_try_expr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let expr = try_expr.expr()?; let expr_type_info = ctx.sema.type_of_expr(&expr)?; - let try_enum = TryEnum::from_ty(&ctx.sema, &expr_type_info.original)?; + let try_enum = TryEnum::from_ty_without_strip(&ctx.sema, &expr_type_info.original)?; let target = try_expr.syntax().text_range(); acc.add( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index 915dd3ffcaf0c..d2452f28c4d78 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -849,6 +849,31 @@ fn foo(x: Option) { ); } + #[test] + fn special_case_option_ref() { + check_assist( + replace_if_let_with_match, + r#" +//- minicore: option +fn foo(x: &Option) { + $0if let Some(x) = x { + println!("{}", x) + } else { + println!("none") + } +} +"#, + r#" +fn foo(x: &Option) { + match x { + Some(x) => println!("{}", x), + None => println!("none"), + } +} +"#, + ); + } + #[test] fn special_case_inverted_option() { check_assist( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs index 15977c420e642..5587f1b59c542 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs @@ -101,6 +101,29 @@ mod tests { use super::*; + #[test] + fn replace_let_try_enum_ref() { + check_assist( + replace_let_with_if_let, + r" +//- minicore: option +fn main(action: Action) { + $0let x = compute(); +} + +fn compute() -> &'static Option { &None } + ", + r" +fn main(action: Action) { + if let Some(x) = compute() { + } +} + +fn compute() -> &'static Option { &None } + ", + ) + } + #[test] fn replace_let_unknown_enum() { check_assist( diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index 8e39b0aa52552..a58592b1365b8 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -110,7 +110,7 @@ pub(crate) fn complete_postfix( postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})")) .add_to(acc, ctx.db); - let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references()); + let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty); let mut is_in_cond = false; if let Some(parent) = dot_receiver_including_refs.syntax().parent() && let Some(second_ancestor) = parent.parent() diff --git a/src/tools/rust-analyzer/crates/ide-db/src/ty_filter.rs b/src/tools/rust-analyzer/crates/ide-db/src/ty_filter.rs index 095256d8294e2..b5c43b3b369bb 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/ty_filter.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/ty_filter.rs @@ -19,8 +19,16 @@ pub enum TryEnum { impl TryEnum { const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result]; - /// Returns `Some(..)` if the provided type is an enum that implements `std::ops::Try`. + /// Returns `Some(..)` if the provided `ty.strip_references()` is an enum that implements `std::ops::Try`. pub fn from_ty(sema: &Semantics<'_, RootDatabase>, ty: &hir::Type<'_>) -> Option { + Self::from_ty_without_strip(sema, &ty.strip_references()) + } + + /// Returns `Some(..)` if the provided type is an enum that implements `std::ops::Try`. + pub fn from_ty_without_strip( + sema: &Semantics<'_, RootDatabase>, + ty: &hir::Type<'_>, + ) -> Option { let enum_ = match ty.as_adt() { Some(hir::Adt::Enum(it)) => it, _ => return None, From 94f6e7efd37f1ab2f045e4f4685076696ad690fd Mon Sep 17 00:00:00 2001 From: Raushan Kumar Date: Thu, 19 Feb 2026 04:16:52 +0000 Subject: [PATCH 38/60] fix: filter non-value definitions in path transform --- .../crates/ide-db/src/path_transform.rs | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs index d0a28e7d2f86c..16f8038f56d21 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs @@ -558,6 +558,34 @@ impl Ctx<'_> { return None; } + if matches!( + def, + hir::ModuleDef::Function(_) + | hir::ModuleDef::Trait(_) + | hir::ModuleDef::TypeAlias(_) + ) { + return None; + } + + if let hir::ModuleDef::Adt(adt) = def { + match adt { + hir::Adt::Struct(s) + if s.kind(self.source_scope.db) != hir::StructKind::Unit => + { + return None; + } + hir::Adt::Union(_) => return None, + hir::Adt::Enum(_) => return None, + _ => (), + } + } + + if let hir::ModuleDef::Variant(v) = def { + if v.kind(self.source_scope.db) != hir::StructKind::Unit { + return None; + } + } + let cfg = FindPathConfig { prefer_no_std: false, prefer_prelude: true, @@ -637,3 +665,87 @@ fn find_trait_for_assoc_item( None } + +#[cfg(test)] +mod tests { + use crate::RootDatabase; + use crate::path_transform::PathTransform; + use hir::Semantics; + use syntax::{AstNode, ast::HasName}; + use test_fixture::WithFixture; + use test_utils::assert_eq_text; + + #[test] + fn test_transform_ident_pat() { + let (db, file_id) = RootDatabase::with_single_file( + r#" +mod foo { + pub struct UnitStruct; + pub struct RecordStruct {} + pub enum Enum { UnitVariant, RecordVariant {} } + pub fn function() {} + pub const CONST: i32 = 0; + pub static STATIC: i32 = 0; + pub type Alias = i32; + pub union Union { f: i32 } +} + +mod bar { + fn anchor() {} +} + +fn main() { + use foo::*; + use foo::Enum::*; + let UnitStruct = (); + let RecordStruct = (); + let Enum = (); + let UnitVariant = (); + let RecordVariant = (); + let function = (); + let CONST = (); + let STATIC = (); + let Alias = (); + let Union = (); +} +"#, + ); + let sema = Semantics::new(&db); + let source_file = sema.parse(file_id); + + let function = source_file + .syntax() + .descendants() + .filter_map(syntax::ast::Fn::cast) + .find(|it| it.name().unwrap().text() == "main") + .unwrap(); + let source_scope = sema.scope(function.body().unwrap().syntax()).unwrap(); + + let anchor = source_file + .syntax() + .descendants() + .filter_map(syntax::ast::Fn::cast) + .find(|it| it.name().unwrap().text() == "anchor") + .unwrap(); + let target_scope = sema.scope(anchor.body().unwrap().syntax()).unwrap(); + + let transform = PathTransform::generic_transformation(&target_scope, &source_scope); + let transformed = transform.apply(function.body().unwrap().syntax()); + + let expected = r#"{ + use crate::foo::*; + use crate::foo::Enum::*; + let crate::foo::UnitStruct = (); + let RecordStruct = (); + let Enum = (); + let crate::foo::Enum::UnitVariant = (); + let RecordVariant = (); + let function = (); + let crate::foo::CONST = (); + let crate::foo::STATIC = (); + let Alias = (); + let Union = (); +}"#; + assert_eq_text!(expected, &transformed.to_string()); + } +} From c9a76c4da5857546c0880c6e0fd5267532968416 Mon Sep 17 00:00:00 2001 From: Raushan Kumar Date: Thu, 19 Feb 2026 05:06:43 +0000 Subject: [PATCH 39/60] style: fix clippy collapsible_if warning --- .../rust-analyzer/crates/ide-db/src/path_transform.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs index 16f8038f56d21..01a326a0dc63b 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs @@ -580,10 +580,10 @@ impl Ctx<'_> { } } - if let hir::ModuleDef::Variant(v) = def { - if v.kind(self.source_scope.db) != hir::StructKind::Unit { - return None; - } + if let hir::ModuleDef::Variant(v) = def + && v.kind(self.source_scope.db) != hir::StructKind::Unit + { + return None; } let cfg = FindPathConfig { From 6d80670bb2ea6e9d722641131d03be2c0eadc114 Mon Sep 17 00:00:00 2001 From: protonblu Date: Thu, 19 Feb 2026 17:22:01 +0530 Subject: [PATCH 40/60] test: fix exclude_tests_macro_refs test to use custom macro --- .../crates/ide/src/references.rs | 49 ++++--------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index cf20224c52884..39d863001a6e8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -513,61 +513,32 @@ fn test() { } #[test] - fn test_access() { + fn exclude_tests_macro_refs() { check( r#" -struct S { f$0: u32 } - -#[test] -fn test() { - let mut x = S { f: 92 }; - x.f = 92; +macro_rules! my_macro { + ($e:expr) => { $e }; } -"#, - expect![[r#" - f Field FileId(0) 11..17 11..12 - FileId(0) 61..62 read test - FileId(0) 76..77 write test - "#]], - ); - } - #[test] - fn exclude_tests_macro_refs() { - check( - r#" fn foo$0() -> i32 { 42 } fn bar() { foo(); } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn t1() { - let f = foo(); - } - - #[test] - fn t2() { - dbg!(foo()); - assert_eq!(foo(), 42); - let v = vec![foo()]; - } +#[test] +fn t2() { + my_macro!(foo()); } "#, - expect![[r#" - foo Function FileId(0) 0..22 3..6 + expect![[r#" + foo Function FileId(0) 52..74 55..58 - FileId(0) 39..42 - FileId(0) 135..138 + FileId(0) 91..94 + FileId(0) 133..136 test "#]], ); } - #[test] fn test_struct_literal_after_space() { check( From 5a3f581b1fba6660c53b91af0c00d25f353b8a36 Mon Sep 17 00:00:00 2001 From: protonblu Date: Thu, 19 Feb 2026 17:25:54 +0530 Subject: [PATCH 41/60] style: fix rustfmt formatting --- src/tools/rust-analyzer/crates/ide/src/references.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 39d863001a6e8..102eb91b74f76 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -531,7 +531,7 @@ fn t2() { my_macro!(foo()); } "#, - expect![[r#" + expect![[r#" foo Function FileId(0) 52..74 55..58 FileId(0) 91..94 From 99b50e365db0123237c709386a001112fc3a8940 Mon Sep 17 00:00:00 2001 From: protonblu Date: Thu, 19 Feb 2026 17:41:49 +0530 Subject: [PATCH 42/60] ci: rerun checks From 102f2ecc91c4130b6d572698d87d11faa1f82eef Mon Sep 17 00:00:00 2001 From: Flora Hill Date: Thu, 19 Feb 2026 23:00:29 +0000 Subject: [PATCH 43/60] Perf + Test Case RE: ShoyuVanilla --- .../crates/ide-db/src/imports/insert_use/tests.rs | 10 ++++++++++ .../crates/ide-db/src/imports/merge_imports.rs | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs index 7763d1e595d04..6c7b97458d208 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs @@ -1528,6 +1528,16 @@ fn merge_gated_imports_with_different_values() { assert_eq!(result, None); } +#[test] +fn merge_gated_imports_different_order() { + check_merge( + r#"#[cfg(a)] #[cfg(b)] use foo::bar;"#, + r#"#[cfg(b)] #[cfg(a)] use foo::baz;"#, + r#"#[cfg(a)] #[cfg(b)] use foo::{bar, baz};"#, + MergeBehavior::Crate, + ); +} + #[test] fn merge_into_existing_cfg_import() { check( diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs index d63b239ff43c7..3301719f5ce29 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs @@ -4,7 +4,7 @@ use std::cmp::Ordering; use itertools::{EitherOrBoth, Itertools}; use parser::T; use syntax::{ - Direction, SyntaxElement, algo, + Direction, SyntaxElement, ToSmolStr, algo, ast::{ self, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind, edit_in_place::Removable, make, @@ -691,10 +691,10 @@ pub fn eq_attrs( attrs0: impl Iterator, attrs1: impl Iterator, ) -> bool { - let mut attrs0: Vec<_> = attrs0.map(|attr| attr.syntax().text().to_string()).collect(); - let mut attrs1: Vec<_> = attrs1.map(|attr| attr.syntax().text().to_string()).collect(); - attrs0.sort(); - attrs1.sort(); + let mut attrs0: Vec<_> = attrs0.map(|attr| attr.syntax().text().to_smolstr()).collect(); + let mut attrs1: Vec<_> = attrs1.map(|attr| attr.syntax().text().to_smolstr()).collect(); + attrs0.sort_unstable(); + attrs1.sort_unstable(); attrs0 == attrs1 } From 01627b7441ed8aee3d0f98e9eceb541a305c11a6 Mon Sep 17 00:00:00 2001 From: jasper3108 Date: Mon, 2 Feb 2026 17:05:54 +0100 Subject: [PATCH 44/60] Support getting TypeId's Trait and vtable --- .../src/const_eval/machine.rs | 19 ++++- .../rustc_const_eval/src/interpret/mod.rs | 2 +- .../rustc_const_eval/src/interpret/util.rs | 45 +++++++++++- .../rustc_hir_analysis/src/check/intrinsic.rs | 20 ++++++ compiler/rustc_span/src/symbol.rs | 1 + library/core/src/any.rs | 66 ++++++++++++++++- library/core/src/intrinsics/mod.rs | 14 ++++ library/core/src/mem/type_info.rs | 15 ++++ library/coretests/tests/mem.rs | 1 + library/coretests/tests/mem/trait_info_of.rs | 70 +++++++++++++++++++ library/coretests/tests/mem/type_info.rs | 1 + tests/ui/reflection/trait_info_of_too_big.rs | 20 ++++++ .../reflection/trait_info_of_too_big.stderr | 27 +++++++ 13 files changed, 295 insertions(+), 6 deletions(-) create mode 100644 library/coretests/tests/mem/trait_info_of.rs create mode 100644 tests/ui/reflection/trait_info_of_too_big.rs create mode 100644 tests/ui/reflection/trait_info_of_too_big.stderr diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 3b4f7ed3261ab..1e048531ae90e 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -2,7 +2,7 @@ use std::borrow::{Borrow, Cow}; use std::fmt; use std::hash::Hash; -use rustc_abi::{Align, Size}; +use rustc_abi::{Align, FIRST_VARIANT, Size}; use rustc_ast::Mutability; use rustc_data_structures::fx::{FxHashMap, FxIndexMap, IndexEntry}; use rustc_errors::msg; @@ -21,6 +21,7 @@ use tracing::debug; use super::error::*; use crate::errors::{LongRunning, LongRunningWarn}; +use crate::interpret::util::type_implements_dyn_trait; use crate::interpret::{ self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar, @@ -601,6 +602,22 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { } } + sym::type_id_vtable => { + let tp_ty = ecx.read_type_id(&args[0])?; + let result_ty = ecx.read_type_id(&args[1])?; + + let (implements_trait, preds) = type_implements_dyn_trait(ecx, tp_ty, result_ty)?; + + if implements_trait { + let vtable_ptr = ecx.get_vtable_ptr(tp_ty, preds)?; + // Writing a non-null pointer into an `Option` will automatically make it `Some`. + ecx.write_pointer(vtable_ptr, dest)?; + } else { + // Write `None` + ecx.write_discriminant(FIRST_VARIANT, dest)?; + } + } + sym::type_of => { let ty = ecx.read_type_id(&args[0])?; ecx.write_type_info(ty, dest)?; diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs index c8ffc0ed208a7..1bf4674789e76 100644 --- a/compiler/rustc_const_eval/src/interpret/mod.rs +++ b/compiler/rustc_const_eval/src/interpret/mod.rs @@ -15,7 +15,7 @@ mod projection; mod stack; mod step; mod traits; -mod util; +pub(crate) mod util; mod validity; mod visitor; diff --git a/compiler/rustc_const_eval/src/interpret/util.rs b/compiler/rustc_const_eval/src/interpret/util.rs index 1e18a22be81c2..57a02769643b7 100644 --- a/compiler/rustc_const_eval/src/interpret/util.rs +++ b/compiler/rustc_const_eval/src/interpret/util.rs @@ -1,12 +1,51 @@ -use rustc_hir::def_id::LocalDefId; -use rustc_middle::mir; +use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_infer::traits::{Obligation, ObligationCause}; use rustc_middle::mir::interpret::{AllocInit, Allocation, GlobalAlloc, InterpResult, Pointer}; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{TyCtxt, TypeVisitable, TypeVisitableExt}; +use rustc_middle::ty::{PolyExistentialPredicate, Ty, TyCtxt, TypeVisitable, TypeVisitableExt}; +use rustc_middle::{mir, span_bug, ty}; +use rustc_trait_selection::traits::ObligationCtxt; use tracing::debug; use super::{InterpCx, MPlaceTy, MemoryKind, interp_ok, throw_inval}; use crate::const_eval::{CompileTimeInterpCx, CompileTimeMachine, InterpretationResult}; +use crate::interpret::Machine; + +/// Checks if a type implements predicates. +/// Calls `ensure_monomorphic_enough` on `ty` and `trait_ty` for you. +pub(crate) fn type_implements_dyn_trait<'tcx, M: Machine<'tcx>>( + ecx: &mut InterpCx<'tcx, M>, + ty: Ty<'tcx>, + trait_ty: Ty<'tcx>, +) -> InterpResult<'tcx, (bool, &'tcx ty::List>)> { + ensure_monomorphic_enough(ecx.tcx.tcx, ty)?; + ensure_monomorphic_enough(ecx.tcx.tcx, trait_ty)?; + + let ty::Dynamic(preds, _) = trait_ty.kind() else { + span_bug!( + ecx.find_closest_untracked_caller_location(), + "Invalid type provided to type_implements_predicates. U must be dyn Trait, got {trait_ty}." + ); + }; + + let (infcx, param_env) = ecx.tcx.infer_ctxt().build_with_typing_env(ecx.typing_env); + + let ocx = ObligationCtxt::new(&infcx); + ocx.register_obligations(preds.iter().map(|pred: PolyExistentialPredicate<'_>| { + let pred = pred.with_self_ty(ecx.tcx.tcx, ty); + // Lifetimes can only be 'static because of the bound on T + let pred = rustc_middle::ty::fold_regions(ecx.tcx.tcx, pred, |r, _| { + if r == ecx.tcx.tcx.lifetimes.re_erased { ecx.tcx.tcx.lifetimes.re_static } else { r } + }); + Obligation::new(ecx.tcx.tcx, ObligationCause::dummy(), param_env, pred) + })); + let type_impls_trait = ocx.evaluate_obligations_error_on_ambiguity().is_empty(); + // Since `assumed_wf_tys=[]` the choice of LocalDefId is irrelevant, so using the "default" + let regions_are_valid = ocx.resolve_regions(CRATE_DEF_ID, param_env, []).is_empty(); + + interp_ok((regions_are_valid && type_impls_trait, preds)) +} /// Checks whether a type contains generic parameters which must be instantiated. /// diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 5669b6793add7..a5aae7b407d6b 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -213,6 +213,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::truncf128 | sym::type_id | sym::type_id_eq + | sym::type_id_vtable | sym::type_name | sym::type_of | sym::ub_checks @@ -323,6 +324,25 @@ pub(crate) fn check_intrinsic_type( let type_id = tcx.type_of(tcx.lang_items().type_id().unwrap()).no_bound_vars().unwrap(); (0, 0, vec![type_id, type_id], tcx.types.bool) } + sym::type_id_vtable => { + let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, span); + let dyn_metadata_adt_ref = tcx.adt_def(dyn_metadata); + let dyn_metadata_args = + tcx.mk_args(&[Ty::new_ptr(tcx, tcx.types.unit, ty::Mutability::Not).into()]); + let dyn_ty = Ty::new_adt(tcx, dyn_metadata_adt_ref, dyn_metadata_args); + + let option_did = tcx.require_lang_item(LangItem::Option, span); + let option_adt_ref = tcx.adt_def(option_did); + let option_args = tcx.mk_args(&[dyn_ty.into()]); + let ret_ty = Ty::new_adt(tcx, option_adt_ref, option_args); + + ( + 0, + 0, + vec![tcx.type_of(tcx.lang_items().type_id().unwrap()).no_bound_vars().unwrap(); 2], + ret_ty, + ) + } sym::type_of => ( 0, 0, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4ae4aac7f7839..4f88b5e663e6a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2358,6 +2358,7 @@ symbols! { type_changing_struct_update, type_id, type_id_eq, + type_id_vtable, type_info, type_ir, type_ir_infer_ctxt_like, diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 42f332f7d8ba8..e5eabb280323c 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -86,7 +86,10 @@ #![stable(feature = "rust1", since = "1.0.0")] -use crate::{fmt, hash, intrinsics, ptr}; +use crate::intrinsics::{self, type_id_vtable}; +use crate::mem::transmute; +use crate::mem::type_info::{TraitImpl, TypeKind}; +use crate::{fmt, hash, ptr}; /////////////////////////////////////////////////////////////////////////////// // Any trait @@ -788,6 +791,67 @@ impl TypeId { const { intrinsics::type_id::() } } + /// Checks if the [TypeId] implements the trait. If it does it returns [TraitImpl] which can be used to build a fat pointer. + /// It can only be called at compile time. `self` must be the [TypeId] of a sized type or None will be returned. + /// + /// # Examples + /// + /// ``` + /// #![feature(type_info)] + /// use std::any::{TypeId}; + /// + /// pub trait Blah {} + /// impl Blah for u8 {} + /// + /// assert!(const { TypeId::of::().trait_info_of::() }.is_some()); + /// assert!(const { TypeId::of::().trait_info_of::() }.is_none()); + /// ``` + #[unstable(feature = "type_info", issue = "146922")] + #[rustc_const_unstable(feature = "type_info", issue = "146922")] + pub const fn trait_info_of< + T: ptr::Pointee> + ?Sized + 'static, + >( + self, + ) -> Option> { + // SAFETY: The vtable was obtained for `T`, so it is guaranteed to be `DynMetadata`. + // The intrinsic can't infer this because it is designed to work with arbitrary TypeIds. + unsafe { transmute(self.trait_info_of_trait_type_id(const { TypeId::of::() })) } + } + + /// Checks if the [TypeId] implements the trait of `trait_represented_by_type_id`. If it does it returns [TraitImpl] which can be used to build a fat pointer. + /// It can only be called at compile time. `self` must be the [TypeId] of a sized type or None will be returned. + /// + /// # Examples + /// + /// ``` + /// #![feature(type_info)] + /// use std::any::{TypeId}; + /// + /// pub trait Blah {} + /// impl Blah for u8 {} + /// + /// assert!(const { TypeId::of::().trait_info_of_trait_type_id(TypeId::of::()) }.is_some()); + /// assert!(const { TypeId::of::().trait_info_of_trait_type_id(TypeId::of::()) }.is_none()); + /// ``` + #[unstable(feature = "type_info", issue = "146922")] + #[rustc_const_unstable(feature = "type_info", issue = "146922")] + pub const fn trait_info_of_trait_type_id( + self, + trait_represented_by_type_id: TypeId, + ) -> Option> { + if self.info().size.is_none() { + return None; + } + + if matches!(trait_represented_by_type_id.info().kind, TypeKind::DynTrait(_)) + && let Some(vtable) = type_id_vtable(self, trait_represented_by_type_id) + { + Some(TraitImpl { vtable }) + } else { + None + } + } + fn as_u128(self) -> u128 { let mut bytes = [0; 16]; diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 9d5f49c88295a..acb86f9c466cf 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2864,6 +2864,20 @@ pub const unsafe fn size_of_val(ptr: *const T) -> usize; #[rustc_intrinsic_const_stable_indirect] pub const unsafe fn align_of_val(ptr: *const T) -> usize; +#[rustc_intrinsic] +#[unstable(feature = "core_intrinsics", issue = "none")] +/// Check if a type represented by a `TypeId` implements a trait represented by a `TypeId`. +/// It can only be called at compile time, the backends do +/// not implement it. If it implements the trait the dyn metadata gets returned for vtable access. +pub const fn type_id_vtable( + _id: crate::any::TypeId, + _trait: crate::any::TypeId, +) -> Option> { + panic!( + "`TypeId::trait_info_of` and `trait_info_of_trait_type_id` can only be called at compile-time" + ) +} + /// Compute the type information of a concrete type. /// It can only be called at compile time, the backends do /// not implement it. diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 18612565aeef2..cfc4266c5b167 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -3,6 +3,8 @@ use crate::any::TypeId; use crate::intrinsics::{type_id, type_of}; +use crate::marker::PointeeSized; +use crate::ptr::DynMetadata; /// Compile-time type information. #[derive(Debug)] @@ -16,6 +18,19 @@ pub struct Type { pub size: Option, } +/// Info of a trait implementation, you can retrieve the vtable with [Self::get_vtable] +#[derive(Debug, PartialEq, Eq)] +pub struct TraitImpl { + pub(crate) vtable: DynMetadata, +} + +impl TraitImpl { + /// Gets the raw vtable for type reflection mapping + pub fn get_vtable(&self) -> DynMetadata { + self.vtable + } +} + impl TypeId { /// Compute the type information of a concrete type. /// It can only be called at compile time. diff --git a/library/coretests/tests/mem.rs b/library/coretests/tests/mem.rs index 236c02d2a243a..b95e6e13063f5 100644 --- a/library/coretests/tests/mem.rs +++ b/library/coretests/tests/mem.rs @@ -1,4 +1,5 @@ mod fn_ptr; +mod trait_info_of; mod type_info; use core::mem::*; diff --git a/library/coretests/tests/mem/trait_info_of.rs b/library/coretests/tests/mem/trait_info_of.rs new file mode 100644 index 0000000000000..c723a96095815 --- /dev/null +++ b/library/coretests/tests/mem/trait_info_of.rs @@ -0,0 +1,70 @@ +use std::any::TypeId; +use std::ptr::DynMetadata; + +struct Garlic(i32); +trait Blah { + fn get_truth(&self) -> i32; +} +impl Blah for Garlic { + fn get_truth(&self) -> i32 { + self.0 * 21 + } +} + +#[test] +fn test_implements_trait() { + const { + assert!(TypeId::of::().trait_info_of::().is_some()); + assert!(TypeId::of::().trait_info_of::().is_some()); + assert!(TypeId::of::<*const Box>().trait_info_of::().is_none()); + assert!(TypeId::of::().trait_info_of_trait_type_id(TypeId::of::()).is_none()); + } +} + +#[test] +fn test_dyn_creation() { + let garlic = Garlic(2); + unsafe { + assert_eq!( + std::ptr::from_raw_parts::( + &raw const garlic, + const { TypeId::of::().trait_info_of::() }.unwrap().get_vtable() + ) + .as_ref() + .unwrap() + .get_truth(), + 42 + ); + } + + assert_eq!( + const { + TypeId::of::() + .trait_info_of_trait_type_id(TypeId::of::()) + .unwrap() + }.get_vtable(), + unsafe { + crate::mem::transmute::<_, DynMetadata<*const ()>>( + const { + TypeId::of::().trait_info_of::() + }.unwrap().get_vtable(), + ) + } + ); +} + +#[test] +fn test_incorrect_use() { + assert_eq!( + const { TypeId::of::().trait_info_of_trait_type_id(TypeId::of::()) }, + None + ); +} + +trait DstTrait {} +impl DstTrait for [i32] {} + +#[test] +fn dst_ice() { + assert!(const { TypeId::of::<[i32]>().trait_info_of::() }.is_none()); +} diff --git a/library/coretests/tests/mem/type_info.rs b/library/coretests/tests/mem/type_info.rs index 2483b4c2aacd7..dbe53fc7b0f37 100644 --- a/library/coretests/tests/mem/type_info.rs +++ b/library/coretests/tests/mem/type_info.rs @@ -3,6 +3,7 @@ use std::any::{Any, TypeId}; use std::mem::offset_of; use std::mem::type_info::{Const, Generic, GenericType, Type, TypeKind}; +use std::ptr::DynMetadata; #[test] fn test_arrays() { diff --git a/tests/ui/reflection/trait_info_of_too_big.rs b/tests/ui/reflection/trait_info_of_too_big.rs new file mode 100644 index 0000000000000..0d5224e788ac7 --- /dev/null +++ b/tests/ui/reflection/trait_info_of_too_big.rs @@ -0,0 +1,20 @@ +//@ normalize-stderr: "\[u8; [0-9]+\]" -> "[u8; N]" +//! Test for https://github.com/rust-lang/rust/pull/152003 + +#![feature(type_info)] + +use std::any::TypeId; + +trait Trait {} +impl Trait for [u8; usize::MAX] {} + +fn main() {} + +const _: () = const { + TypeId::of::<[u8; usize::MAX]>().trait_info_of_trait_type_id(TypeId::of::()); + //~^ ERROR values of the type `[u8; usize::MAX]` are too big for the target architecture +}; +const _: () = const { + TypeId::of::<[u8; usize::MAX]>().trait_info_of::(); + //~^ ERROR values of the type `[u8; usize::MAX]` are too big for the target architecture +}; diff --git a/tests/ui/reflection/trait_info_of_too_big.stderr b/tests/ui/reflection/trait_info_of_too_big.stderr new file mode 100644 index 0000000000000..fa41d3c9ec90e --- /dev/null +++ b/tests/ui/reflection/trait_info_of_too_big.stderr @@ -0,0 +1,27 @@ +error[E0080]: values of the type `[u8; usize::MAX]` are too big for the target architecture + --> $DIR/trait_info_of_too_big.rs:14:5 + | +LL | TypeId::of::<[u8; usize::MAX]>().trait_info_of_trait_type_id(TypeId::of::()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `_::{constant#0}` failed inside this call + | +note: inside `TypeId::trait_info_of_trait_type_id` + --> $SRC_DIR/core/src/any.rs:LL:COL +note: inside `type_info::::info` + --> $SRC_DIR/core/src/mem/type_info.rs:LL:COL + +error[E0080]: values of the type `[u8; usize::MAX]` are too big for the target architecture + --> $DIR/trait_info_of_too_big.rs:18:5 + | +LL | TypeId::of::<[u8; usize::MAX]>().trait_info_of::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `_::{constant#0}` failed inside this call + | +note: inside `TypeId::trait_info_of::` + --> $SRC_DIR/core/src/any.rs:LL:COL +note: inside `TypeId::trait_info_of_trait_type_id` + --> $SRC_DIR/core/src/any.rs:LL:COL +note: inside `type_info::::info` + --> $SRC_DIR/core/src/mem/type_info.rs:LL:COL + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0080`. From 7857058a6b182fba1f05f2a534bce243946f35d4 Mon Sep 17 00:00:00 2001 From: jasper3108 Date: Wed, 18 Feb 2026 17:08:47 +0100 Subject: [PATCH 45/60] nix vtable_for intrinsic --- .../src/interpret/intrinsics.rs | 45 +------------------ .../rustc_hir_analysis/src/check/intrinsic.rs | 15 ------- compiler/rustc_span/src/symbol.rs | 1 - library/core/src/any.rs | 6 ++- library/core/src/intrinsics/mod.rs | 12 ----- library/core/src/mem/type_info.rs | 2 +- library/coretests/tests/intrinsics.rs | 11 +++-- library/coretests/tests/mem/type_info.rs | 1 - .../vtable-try-as-dyn.full-debuginfo.stderr | 18 +++++++- .../vtable-try-as-dyn.no-debuginfo.stderr | 18 +++++++- 10 files changed, 48 insertions(+), 81 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index e0c75da87a4b6..fe06b0a6e0d81 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -4,19 +4,16 @@ mod simd; -use rustc_abi::{FIRST_VARIANT, FieldIdx, HasDataLayout, Size, VariantIdx}; +use rustc_abi::{FieldIdx, HasDataLayout, Size, VariantIdx}; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; use rustc_data_structures::assert_matches; use rustc_errors::msg; -use rustc_hir::def_id::CRATE_DEF_ID; -use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, read_target_uint, write_target_uint}; use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic}; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{FloatTy, PolyExistentialPredicate, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{FloatTy, Ty, TyCtxt, TypeVisitableExt}; use rustc_middle::{bug, span_bug, ty}; use rustc_span::{Symbol, sym}; -use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt}; use tracing::trace; use super::memory::MemoryKind; @@ -227,44 +224,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.write_scalar(Scalar::from_target_usize(offset, self), dest)?; } - sym::vtable_for => { - let tp_ty = instance.args.type_at(0); - let result_ty = instance.args.type_at(1); - - ensure_monomorphic_enough(tcx, tp_ty)?; - ensure_monomorphic_enough(tcx, result_ty)?; - let ty::Dynamic(preds, _) = result_ty.kind() else { - span_bug!( - self.find_closest_untracked_caller_location(), - "Invalid type provided to vtable_for::. U must be dyn Trait, got {result_ty}." - ); - }; - - let (infcx, param_env) = - self.tcx.infer_ctxt().build_with_typing_env(self.typing_env); - - let ocx = ObligationCtxt::new(&infcx); - ocx.register_obligations(preds.iter().map(|pred: PolyExistentialPredicate<'_>| { - let pred = pred.with_self_ty(tcx, tp_ty); - // Lifetimes can only be 'static because of the bound on T - let pred = ty::fold_regions(tcx, pred, |r, _| { - if r == tcx.lifetimes.re_erased { tcx.lifetimes.re_static } else { r } - }); - Obligation::new(tcx, ObligationCause::dummy(), param_env, pred) - })); - let type_impls_trait = ocx.evaluate_obligations_error_on_ambiguity().is_empty(); - // Since `assumed_wf_tys=[]` the choice of LocalDefId is irrelevant, so using the "default" - let regions_are_valid = ocx.resolve_regions(CRATE_DEF_ID, param_env, []).is_empty(); - - if regions_are_valid && type_impls_trait { - let vtable_ptr = self.get_vtable_ptr(tp_ty, preds)?; - // Writing a non-null pointer into an `Option` will automatically make it `Some`. - self.write_pointer(vtable_ptr, dest)?; - } else { - // Write `None` - self.write_discriminant(FIRST_VARIANT, dest)?; - } - } sym::variant_count => { let tp_ty = instance.args.type_at(0); let ty = match tp_ty.kind() { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index a5aae7b407d6b..9167116ca5717 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -219,7 +219,6 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::ub_checks | sym::va_copy | sym::variant_count - | sym::vtable_for | sym::wrapping_add | sym::wrapping_mul | sym::wrapping_sub @@ -695,20 +694,6 @@ pub(crate) fn check_intrinsic_type( (0, 0, vec![Ty::new_imm_ptr(tcx, tcx.types.unit)], tcx.types.usize) } - sym::vtable_for => { - let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, span); - let dyn_metadata_adt_ref = tcx.adt_def(dyn_metadata); - let dyn_metadata_args = tcx.mk_args(&[param(1).into()]); - let dyn_ty = Ty::new_adt(tcx, dyn_metadata_adt_ref, dyn_metadata_args); - - let option_did = tcx.require_lang_item(LangItem::Option, span); - let option_adt_ref = tcx.adt_def(option_did); - let option_args = tcx.mk_args(&[dyn_ty.into()]); - let ret_ty = Ty::new_adt(tcx, option_adt_ref, option_args); - - (2, 0, vec![], ret_ty) - } - // This type check is not particularly useful, but the `where` bounds // on the definition in `core` do the heavy lifting for checking it. sym::aggregate_raw_ptr => (3, 0, vec![param(1), param(2)], param(0)), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4f88b5e663e6a..f13f7e6fa4ce4 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2523,7 +2523,6 @@ symbols! { vsreg, vsx, vtable_align, - vtable_for, vtable_size, warn, wasip2, diff --git a/library/core/src/any.rs b/library/core/src/any.rs index e5eabb280323c..71a529400511c 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -1012,7 +1012,8 @@ pub const fn try_as_dyn< >( t: &T, ) -> Option<&U> { - let vtable: Option> = const { intrinsics::vtable_for::() }; + let vtable: Option> = + const { TypeId::of::().trait_info_of::().as_ref().map(TraitImpl::get_vtable) }; match vtable { Some(dyn_metadata) => { let pointer = ptr::from_raw_parts(t, dyn_metadata); @@ -1065,7 +1066,8 @@ pub const fn try_as_dyn_mut< >( t: &mut T, ) -> Option<&mut U> { - let vtable: Option> = const { intrinsics::vtable_for::() }; + let vtable: Option> = + const { TypeId::of::().trait_info_of::().as_ref().map(TraitImpl::get_vtable) }; match vtable { Some(dyn_metadata) => { let pointer = ptr::from_raw_parts_mut(t, dyn_metadata); diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index acb86f9c466cf..95b531994d92a 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2751,18 +2751,6 @@ pub unsafe fn vtable_size(ptr: *const ()) -> usize; #[rustc_intrinsic] pub unsafe fn vtable_align(ptr: *const ()) -> usize; -/// The intrinsic returns the `U` vtable for `T` if `T` can be coerced to the trait object type `U`. -/// -/// # Compile-time failures -/// Determining whether `T` can be coerced to the trait object type `U` requires trait resolution by the compiler. -/// In some cases, that resolution can exceed the recursion limit, -/// and compilation will fail instead of this function returning `None`. -#[rustc_nounwind] -#[unstable(feature = "core_intrinsics", issue = "none")] -#[rustc_intrinsic] -pub const fn vtable_for> + ?Sized>() --> Option>; - /// The size of a type in bytes. /// /// Note that, unlike most intrinsics, this is safe to call; diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index cfc4266c5b167..24ae0c6484a0c 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -26,7 +26,7 @@ pub struct TraitImpl { impl TraitImpl { /// Gets the raw vtable for type reflection mapping - pub fn get_vtable(&self) -> DynMetadata { + pub const fn get_vtable(&self) -> DynMetadata { self.vtable } } diff --git a/library/coretests/tests/intrinsics.rs b/library/coretests/tests/intrinsics.rs index c6d841b8383a8..e562f49b0a2fc 100644 --- a/library/coretests/tests/intrinsics.rs +++ b/library/coretests/tests/intrinsics.rs @@ -1,6 +1,7 @@ use core::any::TypeId; -use core::intrinsics::{assume, vtable_for}; +use core::intrinsics::assume; use std::fmt::Debug; +use std::intrinsics::type_id_vtable; use std::option::Option; use std::ptr::DynMetadata; @@ -198,15 +199,17 @@ fn carrying_mul_add_fallback_i128() { } #[test] -fn test_vtable_for() { +fn test_type_id_vtable() { #[derive(Debug)] struct A {} struct B {} - const A_VTABLE: Option> = vtable_for::(); + const A_VTABLE: Option> = + type_id_vtable(TypeId::of::(), TypeId::of::()); assert!(A_VTABLE.is_some()); - const B_VTABLE: Option> = vtable_for::(); + const B_VTABLE: Option> = + type_id_vtable(TypeId::of::(), TypeId::of::()); assert!(B_VTABLE.is_none()); } diff --git a/library/coretests/tests/mem/type_info.rs b/library/coretests/tests/mem/type_info.rs index dbe53fc7b0f37..2483b4c2aacd7 100644 --- a/library/coretests/tests/mem/type_info.rs +++ b/library/coretests/tests/mem/type_info.rs @@ -3,7 +3,6 @@ use std::any::{Any, TypeId}; use std::mem::offset_of; use std::mem::type_info::{Const, Generic, GenericType, Type, TypeKind}; -use std::ptr::DynMetadata; #[test] fn test_arrays() { diff --git a/tests/ui/limits/vtable-try-as-dyn.full-debuginfo.stderr b/tests/ui/limits/vtable-try-as-dyn.full-debuginfo.stderr index a3772e509ed67..700083069cb05 100644 --- a/tests/ui/limits/vtable-try-as-dyn.full-debuginfo.stderr +++ b/tests/ui/limits/vtable-try-as-dyn.full-debuginfo.stderr @@ -1,4 +1,20 @@ -error: values of the type `[u8; usize::MAX]` are too big for the target architecture +error[E0080]: values of the type `[u8; usize::MAX]` are too big for the target architecture + --> $SRC_DIR/core/src/any.rs:LL:COL + | + = note: evaluation of `std::any::try_as_dyn::<[u8; usize::MAX], dyn Trait>::{constant#0}` failed inside this call +note: inside `TypeId::trait_info_of::` + --> $SRC_DIR/core/src/any.rs:LL:COL +note: inside `TypeId::trait_info_of_trait_type_id` + --> $SRC_DIR/core/src/any.rs:LL:COL +note: inside `type_info::::info` + --> $SRC_DIR/core/src/mem/type_info.rs:LL:COL + +note: the above error was encountered while instantiating `fn try_as_dyn::<[u8; usize::MAX], dyn Trait>` + --> $DIR/vtable-try-as-dyn.rs:14:13 + | +LL | let _ = std::any::try_as_dyn::<[u8; usize::MAX], dyn Trait>(x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/limits/vtable-try-as-dyn.no-debuginfo.stderr b/tests/ui/limits/vtable-try-as-dyn.no-debuginfo.stderr index a3772e509ed67..700083069cb05 100644 --- a/tests/ui/limits/vtable-try-as-dyn.no-debuginfo.stderr +++ b/tests/ui/limits/vtable-try-as-dyn.no-debuginfo.stderr @@ -1,4 +1,20 @@ -error: values of the type `[u8; usize::MAX]` are too big for the target architecture +error[E0080]: values of the type `[u8; usize::MAX]` are too big for the target architecture + --> $SRC_DIR/core/src/any.rs:LL:COL + | + = note: evaluation of `std::any::try_as_dyn::<[u8; usize::MAX], dyn Trait>::{constant#0}` failed inside this call +note: inside `TypeId::trait_info_of::` + --> $SRC_DIR/core/src/any.rs:LL:COL +note: inside `TypeId::trait_info_of_trait_type_id` + --> $SRC_DIR/core/src/any.rs:LL:COL +note: inside `type_info::::info` + --> $SRC_DIR/core/src/mem/type_info.rs:LL:COL + +note: the above error was encountered while instantiating `fn try_as_dyn::<[u8; usize::MAX], dyn Trait>` + --> $DIR/vtable-try-as-dyn.rs:14:13 + | +LL | let _ = std::any::try_as_dyn::<[u8; usize::MAX], dyn Trait>(x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0080`. From b5eb4f6767b26627c363bf9f6d43197f6bca47df Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 20 Feb 2026 18:19:01 +0800 Subject: [PATCH 46/60] Do strip references for desugar_try_expr --- .../crates/ide-assists/src/handlers/desugar_try_expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs index 176eb1c4944e0..9976e34e730cc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs @@ -61,7 +61,7 @@ pub(crate) fn desugar_try_expr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let expr = try_expr.expr()?; let expr_type_info = ctx.sema.type_of_expr(&expr)?; - let try_enum = TryEnum::from_ty_without_strip(&ctx.sema, &expr_type_info.original)?; + let try_enum = TryEnum::from_ty(&ctx.sema, &expr_type_info.original)?; let target = try_expr.syntax().text_range(); acc.add( From d778d122faf5672b3bc1c4619c7218f63b504b9f Mon Sep 17 00:00:00 2001 From: protonblu Date: Fri, 20 Feb 2026 19:01:22 +0530 Subject: [PATCH 47/60] fix: generate method assist uses enclosing impl block instead of first found --- .../src/handlers/generate_function.rs | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs index ded3b0f5acb20..de52c46d57dc4 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs @@ -146,10 +146,19 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { if !is_editable_crate(target_module.krate(ctx.db()), ctx.db()) { return None; } + //Change + let enclosing_impl = ctx.find_node_at_offset::(); + let cursor_impl = enclosing_impl.filter(|impl_| { + ctx.sema.to_def(impl_).map_or(false, |def| def.self_ty(ctx.sema.db).as_adt() == Some(adt)) + }); - let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?; + let (impl_, file) = if let Some(impl_) = cursor_impl { + (Some(impl_), ctx.vfs_file_id()) + } else { + get_adt_source(ctx, &adt, fn_name.text().as_str())? + }; let target = get_method_target(ctx, &impl_, &adt)?; - + //change let function_builder = FunctionBuilder::from_method_call( ctx, &call, @@ -3205,5 +3214,45 @@ fn bar(arg: impl Fn(_) -> bool) { } "#, ); + }#[test] +fn generate_method_uses_current_impl_block() { + check_assist( + generate_function, + r" +struct Foo; + +impl Foo { + fn new() -> Self { + Foo + } +} + +impl Foo { + fn method1(&self) { + self.method2$0(42) + } +} +", + r" +struct Foo; + +impl Foo { + fn new() -> Self { + Foo } } + +impl Foo { + fn method1(&self) { + self.method2(42) + } + + fn method2(&self, arg: i32) { + ${0:todo!()} + } +} +", + ) +} +} + From aa2da37260def88070d1c6d3567b605d7677a8fa Mon Sep 17 00:00:00 2001 From: protonblu Date: Fri, 20 Feb 2026 19:21:24 +0530 Subject: [PATCH 48/60] style: apply rustfmt --- .../src/handlers/generate_function.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs index de52c46d57dc4..2c5053468bca9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs @@ -3214,11 +3214,12 @@ fn bar(arg: impl Fn(_) -> bool) { } "#, ); - }#[test] -fn generate_method_uses_current_impl_block() { - check_assist( - generate_function, - r" + } + #[test] + fn generate_method_uses_current_impl_block() { + check_assist( + generate_function, + r" struct Foo; impl Foo { @@ -3233,7 +3234,7 @@ impl Foo { } } ", - r" + r" struct Foo; impl Foo { @@ -3252,7 +3253,6 @@ impl Foo { } } ", - ) -} + ) + } } - From 7daabfd04d5ba8502fa5e82e39a9c0c4f449af62 Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Wed, 18 Feb 2026 15:10:47 -0500 Subject: [PATCH 49/60] adjust libdir for windows, use lld during enzyme build when needed --- src/bootstrap/src/core/build_steps/llvm.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index c04a8cd6b7ab2..51200450ecac7 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -23,7 +23,7 @@ use crate::core::config::{Config, TargetSelection}; use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash}; use crate::utils::exec::command; use crate::utils::helpers::{ - self, exe, get_clang_cl_resource_dir, t, unhashed_basename, up_to_date, + self, exe, get_clang_cl_resource_dir, libdir, t, unhashed_basename, up_to_date, }; use crate::{CLang, GitRepo, Kind, trace}; @@ -561,7 +561,6 @@ impl Step for Llvm { } }; - // FIXME(ZuseZ4): Do we need that for Enzyme too? // When building LLVM with LLVM_LINK_LLVM_DYLIB for macOS, an unversioned // libLLVM.dylib will be built. However, llvm-config will still look // for a versioned path like libLLVM-14.dylib. Manually create a symbolic @@ -1159,7 +1158,7 @@ impl Step for Enzyme { let llvm_version_major = llvm::get_llvm_version_major(builder, &host_llvm_config); let lib_ext = std::env::consts::DLL_EXTENSION; let libenzyme = format!("libEnzyme-{llvm_version_major}"); - let build_dir = out_dir.join("lib"); + let build_dir = out_dir.join(libdir(target)); let dylib = build_dir.join(&libenzyme).with_extension(lib_ext); trace!("checking build stamp to see if we need to rebuild enzyme artifacts"); @@ -1197,7 +1196,16 @@ impl Step for Enzyme { // hard to spot more relevant issues. let mut cflags = CcFlags::default(); cflags.push_all("-Wno-deprecated"); - configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), cflags, &[]); + + // Logic copied from `configure_llvm` + // ThinLTO is only available when building with LLVM, enabling LLD is required. + // Apple's linker ld64 supports ThinLTO out of the box though, so don't use LLD on Darwin. + let mut ldflags = LdFlags::default(); + if builder.config.llvm_thin_lto && !target.contains("apple") { + ldflags.push_all("-fuse-ld=lld"); + } + + configure_cmake(builder, target, &mut cfg, true, ldflags, cflags, &[]); // Re-use the same flags as llvm to control the level of debug information // generated by Enzyme. From 4bf4ff1617680348c1316233775df97fe65fd00e Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Thu, 19 Feb 2026 15:18:45 -0500 Subject: [PATCH 50/60] Enable autodiff in CI for mingw-llvm,linux --- src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile | 2 +- src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh | 1 + src/ci/github-actions/jobs.yml | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile b/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile index 3abca36fe70db..9691d4b099b3b 100644 --- a/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile +++ b/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile @@ -97,7 +97,7 @@ ENV RUST_CONFIGURE_ARGS \ ENV SCRIPT python3 ../x.py build --set rust.debug=true opt-dist && \ ./build/$HOSTS/stage1-tools-bin/opt-dist linux-ci -- python3 ../x.py dist \ - --host $HOSTS --target $HOSTS --include-default-paths build-manifest bootstrap + --host $HOSTS --target $HOSTS --include-default-paths build-manifest bootstrap enzyme ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=clang ENV LIBCURL_NO_PKG_CONFIG 1 diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh index c78b76a53f44c..46d34cd001a95 100755 --- a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh +++ b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh @@ -9,6 +9,7 @@ python3 ../x.py build --set rust.debug=true opt-dist --include-default-paths \ build-manifest \ bootstrap \ + enzyme \ rustc_codegen_gcc # Use GCC for building GCC components, as it seems to behave badly when built with Clang diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 88b6855784979..0687d142a3504 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -706,7 +706,7 @@ auto: # i686 has no dedicated job, build it here because this job is fast - name: dist-aarch64-llvm-mingw env: - SCRIPT: python x.py dist bootstrap --include-default-paths + SCRIPT: python x.py dist bootstrap enzyme --include-default-paths RUST_CONFIGURE_ARGS: >- --build=aarch64-pc-windows-gnullvm --target=aarch64-pc-windows-gnullvm,i686-pc-windows-gnullvm @@ -720,7 +720,7 @@ auto: - name: dist-x86_64-llvm-mingw env: - SCRIPT: python x.py dist bootstrap --include-default-paths + SCRIPT: python x.py dist bootstrap enzyme --include-default-paths RUST_CONFIGURE_ARGS: >- --build=x86_64-pc-windows-gnullvm --enable-full-tools From c033de932ec6f46ccb1fd14bf848aec0bc88eece Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Thu, 19 Feb 2026 15:22:40 -0500 Subject: [PATCH 51/60] Move aarch64-apple dist builder to dynamic llvm linking and enable autodiff in CI for it Co-authored-by: sgasho --- src/ci/github-actions/jobs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 0687d142a3504..c06c85513856b 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -504,7 +504,7 @@ auto: - name: dist-aarch64-apple env: SCRIPT: >- - ./x.py dist bootstrap + ./x.py dist bootstrap enzyme --include-default-paths --host=aarch64-apple-darwin --target=aarch64-apple-darwin @@ -513,6 +513,7 @@ auto: --enable-sanitizers --enable-profiler --set rust.jemalloc + --set llvm.link-shared=true --set rust.lto=thin --set rust.codegen-units=1 # Aarch64 tooling only needs to support macOS 11.0 and up as nothing else From ca3d3b7f719defbc5009ddc0ee56ab55f9966b64 Mon Sep 17 00:00:00 2001 From: protonblu Date: Sat, 21 Feb 2026 07:24:22 +0530 Subject: [PATCH 52/60] fix: remove redundant comment --- .../crates/ide-assists/src/handlers/generate_function.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs index 2c5053468bca9..f62eccaf1952e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs @@ -146,7 +146,7 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { if !is_editable_crate(target_module.krate(ctx.db()), ctx.db()) { return None; } - //Change + let enclosing_impl = ctx.find_node_at_offset::(); let cursor_impl = enclosing_impl.filter(|impl_| { ctx.sema.to_def(impl_).map_or(false, |def| def.self_ty(ctx.sema.db).as_adt() == Some(adt)) @@ -158,7 +158,7 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { get_adt_source(ctx, &adt, fn_name.text().as_str())? }; let target = get_method_target(ctx, &impl_, &adt)?; - //change + let function_builder = FunctionBuilder::from_method_call( ctx, &call, From fba1ca8b9f841ea5e43a0790e2e45576a74c9541 Mon Sep 17 00:00:00 2001 From: protonblu Date: Sat, 21 Feb 2026 11:03:20 +0530 Subject: [PATCH 53/60] fix: correctly parenthesize inverted condition in convert_if_to_bool_then The convert_if_to_bool_then assist was producing incorrect code when the Some branch was in the else arm and the condition was a method call expression (e.g. is_empty()). Before: if test.is_empty() { None } else { Some(()) } was incorrectly rewritten to: test.is_empty().then(|| ()) After (correct): (!test.is_empty()).then(|| ()) Root cause: the parenthesization check ran on the original condition before inversion. When invert_boolean_expression produced a PrefixExpr (e.g. !test.is_empty()), it was not being parenthesized because the check had already passed on the original MethodCallExpr. Fix: move the parenthesization check to after the inversion step so it correctly detects PrefixExpr and wraps it in parentheses. A regression test has been added to cover this case. --- .../src/handlers/convert_bool_then.rs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs index 91cee59ad8d00..d2c4ed9b5a2c3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs @@ -102,6 +102,11 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_> ast::Expr::BlockExpr(block) => unwrap_trivial_block(block), e => e, }; + let cond = if invert_cond { + invert_boolean_expression(&make, cond) + } else { + cond.clone_for_update() + }; let parenthesize = matches!( cond, @@ -123,11 +128,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_> | ast::Expr::WhileExpr(_) | ast::Expr::YieldExpr(_) ); - let cond = if invert_cond { - invert_boolean_expression(&make, cond) - } else { - cond.clone_for_update() - }; + let cond = if parenthesize { make.expr_paren(cond).into() } else { cond }; let arg_list = make.arg_list(Some(make.expr_closure(None, closure_body).into())); let mcall = make.expr_method_call(cond, make.name_ref("then"), arg_list); @@ -588,6 +589,25 @@ fn main() { None } } +", + ); + } + #[test] + fn convert_if_to_bool_then_invert_method_call() { + check_assist( + convert_if_to_bool_then, + r" +//- minicore:option +fn main() { + let test = &[()]; + let value = if$0 test.is_empty() { None } else { Some(()) }; +} +", + r" +fn main() { + let test = &[()]; + let value = (!test.is_empty()).then(|| ()); +} ", ); } From dbf337f5022d3cc8344717eb7018953c1840ff07 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 22 Feb 2026 16:58:09 +0200 Subject: [PATCH 54/60] Fix another case where we forgot to put the type param for `PartialOrd` and `PartialEq` in builtin derives --- .../crates/hir-ty/src/builtin_derive.rs | 82 ++++++++++++------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs index b7a1585c808b2..5a93c2b536064 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs @@ -35,6 +35,24 @@ fn coerce_pointee_new_type_param(trait_id: TraitId) -> TypeParamId { }) } +fn trait_args(trait_: BuiltinDeriveImplTrait, self_ty: Ty<'_>) -> GenericArgs<'_> { + match trait_ { + BuiltinDeriveImplTrait::Copy + | BuiltinDeriveImplTrait::Clone + | BuiltinDeriveImplTrait::Default + | BuiltinDeriveImplTrait::Debug + | BuiltinDeriveImplTrait::Hash + | BuiltinDeriveImplTrait::Eq + | BuiltinDeriveImplTrait::Ord => GenericArgs::new_from_slice(&[self_ty.into()]), + BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => { + GenericArgs::new_from_slice(&[self_ty.into(), self_ty.into()]) + } + BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { + panic!("`CoerceUnsized` and `DispatchFromDyn` have special generics") + } + } +} + pub(crate) fn generics_of<'db>(interner: DbInterner<'db>, id: BuiltinDeriveImplId) -> Generics { let db = interner.db; let loc = id.loc(db); @@ -95,21 +113,19 @@ pub fn impl_trait<'db>( | BuiltinDeriveImplTrait::Debug | BuiltinDeriveImplTrait::Hash | BuiltinDeriveImplTrait::Ord - | BuiltinDeriveImplTrait::Eq => { + | BuiltinDeriveImplTrait::Eq + | BuiltinDeriveImplTrait::PartialOrd + | BuiltinDeriveImplTrait::PartialEq => { let self_ty = Ty::new_adt( interner, loc.adt, GenericArgs::identity_for_item(interner, loc.adt.into()), ); - EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty])) - } - BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => { - let self_ty = Ty::new_adt( + EarlyBinder::bind(TraitRef::new_from_args( interner, - loc.adt, - GenericArgs::identity_for_item(interner, loc.adt.into()), - ); - EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty, self_ty])) + trait_id.into(), + trait_args(loc.trait_, self_ty), + )) } BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { let generic_params = GenericParams::new(db, loc.adt.into()); @@ -260,21 +276,7 @@ fn simple_trait_predicates<'db>( let param_idx = param_idx.into_raw().into_u32() + (generic_params.len_lifetimes() as u32); let param_ty = Ty::new_param(interner, param_id, param_idx); - let trait_args = match loc.trait_ { - BuiltinDeriveImplTrait::Copy - | BuiltinDeriveImplTrait::Clone - | BuiltinDeriveImplTrait::Default - | BuiltinDeriveImplTrait::Debug - | BuiltinDeriveImplTrait::Hash - | BuiltinDeriveImplTrait::Ord - | BuiltinDeriveImplTrait::Eq => GenericArgs::new_from_slice(&[param_ty.into()]), - BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => { - GenericArgs::new_from_slice(&[param_ty.into(), param_ty.into()]) - } - BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { - unreachable!() - } - }; + let trait_args = trait_args(loc.trait_, param_ty); let trait_ref = TraitRef::new_from_args(interner, trait_id.into(), trait_args); trait_ref.upcast(interner) }); @@ -285,12 +287,14 @@ fn simple_trait_predicates<'db>( &mut assoc_type_bounds, interner.db.field_types(id.into()), trait_id, + loc.trait_, ), AdtId::UnionId(id) => extend_assoc_type_bounds( interner, &mut assoc_type_bounds, interner.db.field_types(id.into()), trait_id, + loc.trait_, ), AdtId::EnumId(id) => { for &(variant_id, _, _) in &id.enum_variants(interner.db).variants { @@ -299,6 +303,7 @@ fn simple_trait_predicates<'db>( &mut assoc_type_bounds, interner.db.field_types(variant_id.into()), trait_id, + loc.trait_, ) } } @@ -320,12 +325,14 @@ fn extend_assoc_type_bounds<'db>( interner: DbInterner<'db>, assoc_type_bounds: &mut Vec>, fields: &ArenaMap>, - trait_: TraitId, + trait_id: TraitId, + trait_: BuiltinDeriveImplTrait, ) { struct ProjectionFinder<'a, 'db> { interner: DbInterner<'db>, assoc_type_bounds: &'a mut Vec>, - trait_: TraitId, + trait_id: TraitId, + trait_: BuiltinDeriveImplTrait, } impl<'db> TypeVisitor> for ProjectionFinder<'_, 'db> { @@ -334,7 +341,12 @@ fn extend_assoc_type_bounds<'db>( fn visit_ty(&mut self, t: Ty<'db>) -> Self::Result { if let TyKind::Alias(AliasTyKind::Projection, _) = t.kind() { self.assoc_type_bounds.push( - TraitRef::new(self.interner, self.trait_.into(), [t]).upcast(self.interner), + TraitRef::new_from_args( + self.interner, + self.trait_id.into(), + trait_args(self.trait_, t), + ) + .upcast(self.interner), ); } @@ -342,7 +354,7 @@ fn extend_assoc_type_bounds<'db>( } } - let mut visitor = ProjectionFinder { interner, assoc_type_bounds, trait_ }; + let mut visitor = ProjectionFinder { interner, assoc_type_bounds, trait_id, trait_ }; for (_, field) in fields.iter() { field.get().instantiate_identity().visit_with(&mut visitor); } @@ -503,10 +515,12 @@ struct MultiGenericParams<'a, T, #[pointee] U: ?Sized, const N: usize>(*const U) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Simple; -trait Trait {} +trait Trait { + type Assoc; +} #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]); +struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N], T::Assoc); "#, expect![[r#" @@ -529,41 +543,49 @@ struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]); Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Debug, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Debug, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Clone, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Clone, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Copy, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Copy, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: PartialEq<[#1]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): PartialEq<[Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. })]>, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Eq, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Eq, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: PartialOrd<[#1]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): PartialOrd<[Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. })]>, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Ord, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Ord, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) Clause(Binder { value: TraitPredicate(#1: Hash, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(Alias(Projection, AliasTy { args: [#1], def_id: TypeAliasId("Assoc"), .. }): Hash, polarity:Positive), bound_vars: [] }) "#]], ); From 71857c846624bd3286abb869db0ff84761310e16 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 22 Feb 2026 21:16:16 +0100 Subject: [PATCH 55/60] Port `#[register_tool]` to the new attribute parsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jana Dönszelmann --- .../src/attributes/crate_level.rs | 47 +++++++++++++++++++ compiler/rustc_attr_parsing/src/context.rs | 1 + .../rustc_hir/src/attrs/data_structures.rs | 3 ++ .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_passes/src/check_attr.rs | 4 +- 5 files changed, 54 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index 3b3bbf4f0c8af..149284886630d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -301,3 +301,50 @@ impl NoArgsAttributeParser for DefaultLibAllocatorParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::DefaultLibAllocator; } + +pub(crate) struct RegisterToolParser; + +impl CombineAttributeParser for RegisterToolParser { + const PATH: &[Symbol] = &[sym::register_tool]; + type Item = Ident; + const CONVERT: ConvertFn = AttributeKind::RegisterTool; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + const TEMPLATE: AttributeTemplate = template!(List: &["tool1, tool2, ..."]); + + fn extend( + cx: &mut AcceptContext<'_, '_, S>, + args: &ArgParser, + ) -> impl IntoIterator { + let ArgParser::List(list) = args else { + cx.expected_list(cx.attr_span, args); + return Vec::new(); + }; + + if list.is_empty() { + cx.warn_empty_attribute(cx.attr_span); + } + + let mut res = Vec::new(); + + for elem in list.mixed() { + let Some(elem) = elem.meta_item() else { + cx.expected_identifier(elem.span()); + continue; + }; + if let Err(arg_span) = elem.args().no_args() { + cx.expected_no_args(arg_span); + continue; + } + + let path = elem.path(); + let Some(ident) = path.word() else { + cx.expected_identifier(path.span()); + continue; + }; + + res.push(ident); + } + + res + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index b4e91ecebeeb7..d1674f6a22080 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -159,6 +159,7 @@ attribute_parsers!( Combine, Combine, Combine, + Combine, Combine, Combine, Combine, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 5d154cef66a65..68850d796c142 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1136,6 +1136,9 @@ pub enum AttributeKind { /// Represents `#[reexport_test_harness_main]` ReexportTestHarnessMain(Symbol), + /// Represents `#[register_tool]` + RegisterTool(ThinVec, Span), + /// Represents [`#[repr]`](https://doc.rust-lang.org/stable/reference/type-layout.html#representations). Repr { reprs: ThinVec<(ReprAttr, Span)>, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 0b20ea4d6a83a..68199e395e28b 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -91,6 +91,7 @@ impl AttributeKind { ProfilerRuntime => No, RecursionLimit { .. } => No, ReexportTestHarnessMain(..) => No, + RegisterTool(..) => No, Repr { .. } => No, RustcAbi { .. } => No, RustcAllocator => No, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 9af9398d78b96..34a61c88837c2 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -288,6 +288,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::ProfilerRuntime | AttributeKind::RecursionLimit { .. } | AttributeKind::ReexportTestHarnessMain(..) + | AttributeKind::RegisterTool(..) // handled below this loop and elsewhere | AttributeKind::Repr { .. } | AttributeKind::RustcAbi { .. } @@ -406,8 +407,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | sym::rustc_autodiff | sym::rustc_inherit_overflow_checks // crate-level attrs, are checked below - | sym::feature - | sym::register_tool, + | sym::feature, .. ] => {} [name, rest@..] => { From 436315d12f508c7d88f9782fc457c789a70542f5 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 22 Feb 2026 21:16:39 +0100 Subject: [PATCH 56/60] Use the new parser throughout the compiler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jana Dönszelmann --- compiler/rustc_driver_impl/src/lib.rs | 3 +- compiler/rustc_resolve/src/errors.rs | 9 ---- compiler/rustc_resolve/src/macros.rs | 50 +++++++++++-------- tests/ui/tool-attributes/invalid-tool.rs | 2 +- tests/ui/tool-attributes/invalid-tool.stderr | 10 ++-- tests/ui/tool-attributes/nested-disallowed.rs | 2 +- .../tool-attributes/nested-disallowed.stderr | 10 ++-- 7 files changed, 46 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index fc46102f6a30f..38a11b427c1f6 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -707,8 +707,9 @@ fn print_crate_info( }; let crate_name = passes::get_crate_name(sess, attrs); let lint_store = crate::unerased_lint_store(sess); - let registered_tools = rustc_resolve::registered_tools_ast(sess.dcx(), attrs); let features = rustc_expand::config::features(sess, attrs, crate_name); + let registered_tools = + rustc_resolve::registered_tools_ast(sess.dcx(), attrs, sess, &features); let lint_levels = rustc_lint::LintLevelsBuilder::crate_root( sess, &features, diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 1ca5c17856262..45c1dee9bf52b 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1201,15 +1201,6 @@ pub(crate) struct ToolWasAlreadyRegistered { pub(crate) old_ident_span: Span, } -#[derive(Diagnostic)] -#[diag("`{$tool}` only accepts identifiers")] -pub(crate) struct ToolOnlyAcceptsIdentifiers { - #[primary_span] - #[label("not an identifier")] - pub(crate) span: Span, - pub(crate) tool: Symbol, -} - #[derive(Subdiagnostic)] pub(crate) enum DefinedHere { #[label("similarly named {$candidate_descr} `{$candidate}` defined here")] diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 99cd485241546..551d89ee6022a 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -4,8 +4,9 @@ use std::mem; use std::sync::Arc; -use rustc_ast::{self as ast, Crate, NodeId, attr}; +use rustc_ast::{self as ast, Crate, DUMMY_NODE_ID, NodeId}; use rustc_ast_pretty::pprust; +use rustc_attr_parsing::AttributeParser; use rustc_errors::{Applicability, DiagCtxtHandle, StashKey}; use rustc_expand::base::{ Annotatable, DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension, @@ -15,12 +16,14 @@ use rustc_expand::compile_declarative_macro; use rustc_expand::expand::{ AstFragment, AstFragmentKind, Invocation, InvocationKind, SupportsMacroExpansion, }; -use rustc_hir::StabilityLevel; -use rustc_hir::attrs::{CfgEntry, StrippedCfgItem}; +use rustc_feature::Features; +use rustc_hir::attrs::{AttributeKind, CfgEntry, StrippedCfgItem}; use rustc_hir::def::{self, DefKind, MacroKinds, Namespace, NonMacroAttrKind}; use rustc_hir::def_id::{CrateNum, DefId, LocalDefId}; +use rustc_hir::{Attribute, StabilityLevel}; use rustc_middle::middle::stability; use rustc_middle::ty::{RegisteredTools, TyCtxt}; +use rustc_session::Session; use rustc_session::lint::builtin::{ LEGACY_DERIVE_HELPERS, OUT_OF_SCOPE_MACRO_CALLS, UNKNOWN_DIAGNOSTIC_ATTRIBUTES, UNUSED_MACRO_RULES, UNUSED_MACROS, @@ -122,35 +125,38 @@ fn fast_print_path(path: &ast::Path) -> Symbol { pub(crate) fn registered_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools { let (_, pre_configured_attrs) = &*tcx.crate_for_resolver(()).borrow(); - registered_tools_ast(tcx.dcx(), pre_configured_attrs) + registered_tools_ast(tcx.dcx(), pre_configured_attrs, tcx.sess, tcx.features()) } pub fn registered_tools_ast( dcx: DiagCtxtHandle<'_>, pre_configured_attrs: &[ast::Attribute], + sess: &Session, + features: &Features, ) -> RegisteredTools { let mut registered_tools = RegisteredTools::default(); - for attr in attr::filter_by_name(pre_configured_attrs, sym::register_tool) { - for meta_item_inner in attr.meta_item_list().unwrap_or_default() { - match meta_item_inner.ident() { - Some(ident) => { - if let Some(old_ident) = registered_tools.replace(ident) { - dcx.emit_err(errors::ToolWasAlreadyRegistered { - span: ident.span, - tool: ident, - old_ident_span: old_ident.span, - }); - } - } - None => { - dcx.emit_err(errors::ToolOnlyAcceptsIdentifiers { - span: meta_item_inner.span(), - tool: sym::register_tool, - }); - } + + if let Some(Attribute::Parsed(AttributeKind::RegisterTool(tools, _))) = + AttributeParser::parse_limited( + sess, + pre_configured_attrs, + sym::register_tool, + DUMMY_SP, + DUMMY_NODE_ID, + Some(features), + ) + { + for tool in tools { + if let Some(old_tool) = registered_tools.replace(tool) { + dcx.emit_err(errors::ToolWasAlreadyRegistered { + span: tool.span, + tool, + old_ident_span: old_tool.span, + }); } } } + // We implicitly add `rustfmt`, `clippy`, `diagnostic`, `miri` and `rust_analyzer` to known // tools, but it's not an error to register them explicitly. let predefined_tools = diff --git a/tests/ui/tool-attributes/invalid-tool.rs b/tests/ui/tool-attributes/invalid-tool.rs index 125333231d217..aec31cc7f667c 100644 --- a/tests/ui/tool-attributes/invalid-tool.rs +++ b/tests/ui/tool-attributes/invalid-tool.rs @@ -1,6 +1,6 @@ #![feature(register_tool)] #![register_tool(1)] -//~^ ERROR `register_tool` only accepts identifiers +//~^ ERROR malformed `register_tool` attribute input fn main() {} diff --git a/tests/ui/tool-attributes/invalid-tool.stderr b/tests/ui/tool-attributes/invalid-tool.stderr index deafa6d167c20..4f82e9ef5437f 100644 --- a/tests/ui/tool-attributes/invalid-tool.stderr +++ b/tests/ui/tool-attributes/invalid-tool.stderr @@ -1,8 +1,12 @@ -error: `register_tool` only accepts identifiers - --> $DIR/invalid-tool.rs:3:18 +error[E0539]: malformed `register_tool` attribute input + --> $DIR/invalid-tool.rs:3:1 | LL | #![register_tool(1)] - | ^ not an identifier + | ^^^^^^^^^^^^^^^^^-^^ + | | | + | | expected a valid identifier here + | help: must be of the form: `#![register_tool(tool1, tool2, ...)]` error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0539`. diff --git a/tests/ui/tool-attributes/nested-disallowed.rs b/tests/ui/tool-attributes/nested-disallowed.rs index 8e78042776106..87d0bf48b6c8f 100644 --- a/tests/ui/tool-attributes/nested-disallowed.rs +++ b/tests/ui/tool-attributes/nested-disallowed.rs @@ -1,4 +1,4 @@ #![feature(register_tool)] -#![register_tool(foo::bar)] //~ ERROR only accepts identifiers +#![register_tool(foo::bar)] //~ ERROR malformed `register_tool` attribute input fn main() {} diff --git a/tests/ui/tool-attributes/nested-disallowed.stderr b/tests/ui/tool-attributes/nested-disallowed.stderr index 1af73fc2f1995..e59ebd979b4c6 100644 --- a/tests/ui/tool-attributes/nested-disallowed.stderr +++ b/tests/ui/tool-attributes/nested-disallowed.stderr @@ -1,8 +1,12 @@ -error: `register_tool` only accepts identifiers - --> $DIR/nested-disallowed.rs:2:18 +error[E0539]: malformed `register_tool` attribute input + --> $DIR/nested-disallowed.rs:2:1 | LL | #![register_tool(foo::bar)] - | ^^^^^^^^ not an identifier + | ^^^^^^^^^^^^^^^^^--------^^ + | | | + | | expected a valid identifier here + | help: must be of the form: `#![register_tool(tool1, tool2, ...)]` error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0539`. From 34d616105639c373c65ec0befff0581f1997cb21 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 22 Feb 2026 22:02:20 +0100 Subject: [PATCH 57/60] Port `#[rustc_inherit_overflow_checks]` to the new attribute parsers --- .../src/attributes/rustc_internal.rs | 14 ++++++++++++++ compiler/rustc_attr_parsing/src/context.rs | 1 + compiler/rustc_hir/src/attrs/data_structures.rs | 3 +++ compiler/rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_mir_build/src/builder/mod.rs | 6 ++---- compiler/rustc_passes/src/check_attr.rs | 2 +- 6 files changed, 22 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 2bd5b9a130d1c..c18e292027517 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -175,6 +175,20 @@ impl SingleAttributeParser for RustcLegacyConstGenericsParser { } } +pub(crate) struct RustcInheritOverflowChecksParser; + +impl NoArgsAttributeParser for RustcInheritOverflowChecksParser { + const PATH: &[Symbol] = &[sym::rustc_inherit_overflow_checks]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Closure), + ]); + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcInheritOverflowChecks; +} + pub(crate) struct RustcLintOptDenyFieldAccessParser; impl SingleAttributeParser for RustcLintOptDenyFieldAccessParser { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index b4e91ecebeeb7..2233cacfaa871 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -286,6 +286,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 5d154cef66a65..484eb5d4606b8 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1274,6 +1274,9 @@ pub enum AttributeKind { /// Represents `#[rustc_if_this_changed]` RustcIfThisChanged(Span, Option), + /// Represents `#[rustc_inherit_overflow_checks]` + RustcInheritOverflowChecks, + /// Represents `#[rustc_insignificant_dtor]` RustcInsignificantDtor, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 0b20ea4d6a83a..a1c9bdba80b29 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -130,6 +130,7 @@ impl AttributeKind { RustcHasIncoherentInherentImpls => Yes, RustcHiddenTypeOfOpaques => No, RustcIfThisChanged(..) => No, + RustcInheritOverflowChecks => No, RustcInsignificantDtor => Yes, RustcIntrinsic => Yes, RustcIntrinsicConstStableIndirect => No, diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index dbd7809c2e651..c8ca7bfccc07c 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -24,7 +24,6 @@ use itertools::Itertools; use rustc_abi::{ExternAbi, FieldIdx}; use rustc_apfloat::Float; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; -use rustc_ast::attr; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sorted_map::SortedIndexMultiMap; use rustc_errors::ErrorGuaranteed; @@ -41,7 +40,7 @@ use rustc_middle::thir::{self, ExprId, LocalVarId, Param, ParamId, PatKind, Thir use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt, TypeVisitableExt, TypingMode}; use rustc_middle::{bug, span_bug}; use rustc_session::lint; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, Symbol}; use crate::builder::expr::as_place::PlaceBuilder; use crate::builder::scope::{DropKind, LintLevel}; @@ -751,11 +750,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { coroutine: Option>>, ) -> Builder<'a, 'tcx> { let tcx = infcx.tcx; - let attrs = tcx.hir_attrs(hir_id); // Some functions always have overflow checks enabled, // however, they may not get codegen'd, depending on // the settings for the crate they are codegened in. - let mut check_overflow = attr::contains_name(attrs, sym::rustc_inherit_overflow_checks); + let mut check_overflow = find_attr!(tcx.hir_attrs(hir_id), RustcInheritOverflowChecks); // Respect -C overflow-checks. check_overflow |= tcx.sess.overflow_checks(); // Constants always need overflow checks. diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 9af9398d78b96..0f323e6d298cf 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -325,6 +325,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcHasIncoherentInherentImpls | AttributeKind::RustcHiddenTypeOfOpaques | AttributeKind::RustcIfThisChanged(..) + | AttributeKind::RustcInheritOverflowChecks | AttributeKind::RustcInsignificantDtor | AttributeKind::RustcIntrinsic | AttributeKind::RustcIntrinsicConstStableIndirect @@ -404,7 +405,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | sym::rustc_on_unimplemented | sym::rustc_layout | sym::rustc_autodiff - | sym::rustc_inherit_overflow_checks // crate-level attrs, are checked below | sym::feature | sym::register_tool, From 5b87b994d805b902563c08590f3b71c5c7a9b3f3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 22 Feb 2026 22:38:46 +0100 Subject: [PATCH 58/60] fix interpreter tracing output --- compiler/rustc_const_eval/src/interpret/step.rs | 4 ++-- compiler/rustc_middle/src/mir/pretty.rs | 14 ++++++++++++-- compiler/rustc_middle/src/mir/syntax.rs | 2 +- compiler/rustc_middle/src/mir/terminator.rs | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 0dd17f109be31..126d0fc9b7a1b 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -86,7 +86,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { span = ?stmt.source_info.span, tracing_separate_thread = Empty, ) - .or_if_tracing_disabled(|| info!(stmt = ?stmt.kind)); + .or_if_tracing_disabled(|| info!("{:?}", stmt.kind)); use rustc_middle::mir::StatementKind::*; @@ -490,7 +490,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { span = ?terminator.source_info.span, tracing_separate_thread = Empty, ) - .or_if_tracing_disabled(|| info!(terminator = ?terminator.kind)); + .or_if_tracing_disabled(|| info!("{:?}", terminator.kind)); use rustc_middle::mir::TerminatorKind::*; match terminator.kind { diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index e5fe01781e96d..487a3c9c4ea22 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -802,10 +802,10 @@ impl<'de, 'tcx> MirWriter<'de, 'tcx> { } } -impl Debug for Statement<'_> { +impl Debug for StatementKind<'_> { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { use self::StatementKind::*; - match self.kind { + match *self { Assign(box (ref place, ref rv)) => write!(fmt, "{place:?} = {rv:?}"), FakeRead(box (ref cause, ref place)) => { write!(fmt, "FakeRead({cause:?}, {place:?})") @@ -844,6 +844,11 @@ impl Debug for Statement<'_> { } } } +impl Debug for Statement<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + self.kind.fmt(fmt) + } +} impl Debug for StmtDebugInfo<'_> { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { @@ -915,6 +920,11 @@ impl<'tcx> Debug for TerminatorKind<'tcx> { } } } +impl Debug for Terminator<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + self.kind.fmt(fmt) + } +} impl<'tcx> TerminatorKind<'tcx> { /// Writes the "head" part of the terminator; that is, its name and the data it uses to pick the diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 35f67460f51c0..c3da2f0394852 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -306,7 +306,7 @@ pub enum FakeBorrowKind { /// Not all of these are allowed at every [`MirPhase`]. Check the documentation there to see which /// ones you do not have to worry about. The MIR validator will generally enforce such restrictions, /// causing an ICE if they are violated. -#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] +#[derive(Clone, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] #[derive(TypeFoldable, TypeVisitable)] pub enum StatementKind<'tcx> { /// Assign statements roughly correspond to an assignment in Rust proper (`x = ...`) except diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index 1e7729bd8d657..650c44aa41fbe 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -444,7 +444,7 @@ impl AssertKind { } } -#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)] +#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)] pub struct Terminator<'tcx> { pub source_info: SourceInfo, pub kind: TerminatorKind<'tcx>, From 1b5a80cf4bce583e97e14e3d368f6d0e8f8aeb60 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 23 Feb 2026 04:45:04 +0000 Subject: [PATCH 59/60] Prepare for merging from rust-lang/rust This updates the rust-version file to c78a29473a68f07012904af11c92ecffa68fcc75. --- 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 b22c6c3869c62..b6e1b2bc55df4 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -139651428df86cf88443295542c12ea617cbb587 +c78a29473a68f07012904af11c92ecffa68fcc75 From 04e9918656647ba48877db5054aed719c3ce6897 Mon Sep 17 00:00:00 2001 From: jasper3108 Date: Mon, 23 Feb 2026 08:55:16 +0100 Subject: [PATCH 60/60] make TraitImpl unstable --- compiler/rustc_const_eval/src/const_eval/machine.rs | 3 +-- compiler/rustc_const_eval/src/interpret/mod.rs | 4 ++-- library/core/src/mem/type_info.rs | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 1e048531ae90e..5e81f03344343 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -21,12 +21,11 @@ use tracing::debug; use super::error::*; use crate::errors::{LongRunning, LongRunningWarn}; -use crate::interpret::util::type_implements_dyn_trait; use crate::interpret::{ self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar, compile_time_machine, err_inval, interp_ok, throw_exhaust, throw_inval, throw_ub, - throw_ub_custom, throw_unsup, throw_unsup_format, + throw_ub_custom, throw_unsup, throw_unsup_format, type_implements_dyn_trait, }; /// When hitting this many interpreted terminators we emit a deny by default lint diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs index 1bf4674789e76..3fbb043885715 100644 --- a/compiler/rustc_const_eval/src/interpret/mod.rs +++ b/compiler/rustc_const_eval/src/interpret/mod.rs @@ -15,7 +15,7 @@ mod projection; mod stack; mod step; mod traits; -pub(crate) mod util; +mod util; mod validity; mod visitor; @@ -38,6 +38,6 @@ use self::place::{MemPlace, Place}; pub use self::projection::{OffsetMode, Projectable}; pub use self::stack::{Frame, FrameInfo, LocalState, ReturnContinuation}; pub use self::util::EnteredTraceSpan; -pub(crate) use self::util::create_static_alloc; +pub(crate) use self::util::{create_static_alloc, type_implements_dyn_trait}; pub use self::validity::{CtfeValidationMode, RangeSet, RefTracking}; pub use self::visitor::ValueVisitor; diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 24ae0c6484a0c..2609b9767e0f9 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -20,6 +20,8 @@ pub struct Type { /// Info of a trait implementation, you can retrieve the vtable with [Self::get_vtable] #[derive(Debug, PartialEq, Eq)] +#[unstable(feature = "type_info", issue = "146922")] +#[non_exhaustive] pub struct TraitImpl { pub(crate) vtable: DynMetadata, }