From 1974d1176b06f999fd706b0ffce14c1af8262b39 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 27 Sep 2025 11:40:02 +0800 Subject: [PATCH 01/39] Fix loses label for convert_for_to_while_let Fixes: - loses label for `convert_while_to_loop` and `convert_for_to_while_let` - loses attributes for `convert_while_to_loop` and `convert_for_to_while_let` - bad indent for `convert_while_to_loop` Examples --- ```rust fn main() { #[allow(unused)] #[deny(unsafe_code)] 'a: while$0 let Some(x) = cond { foo(); break 'a; } } ``` **Before this PR**: ```rust fn main() { loop { if let Some(x) = cond { foo(); break 'a; } else { break; } } } ``` **After this PR**: ```rust fn main() { #[allow(unused)] #[deny(unsafe_code)] 'a: loop { if let Some(x) = cond { foo(); break 'a; } else { break; } } } ``` --- ```rust fn main() { let mut x = vec![1, 2, 3]; #[allow(unused)] #[deny(unsafe_code)] 'a: for $0v in x { v *= 2; break 'a; }; } ``` **Before this PR**: ```rust fn main() { let mut x = vec![1, 2, 3]; let mut tmp = x.into_iter(); while let Some(v) = tmp.next() { v *= 2; break 'a; }; } ``` **After this PR**: ```rust fn main() { let mut x = vec![1, 2, 3]; let mut tmp = x.into_iter(); #[allow(unused)] #[deny(unsafe_code)] 'a: while let Some(v) = tmp.next() { v *= 2; break 'a; }; } ``` --- .../src/handlers/convert_for_to_while_let.rs | 69 +++++- .../src/handlers/convert_while_to_loop.rs | 224 ++++++++++++++++-- .../crates/ide-assists/src/utils.rs | 24 +- .../crates/syntax/src/ast/make.rs | 2 +- 4 files changed, 292 insertions(+), 27 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_for_to_while_let.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_for_to_while_let.rs index 2d6a59a7c365..b281d5a02e53 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_for_to_while_let.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_for_to_while_let.rs @@ -1,11 +1,8 @@ -use hir::{ - Name, - sym::{self}, -}; +use hir::{Name, sym}; use ide_db::{famous_defs::FamousDefs, syntax_helpers::suggest_name}; use syntax::{ AstNode, - ast::{self, HasLoopBody, edit::IndentLevel, make, syntax_factory::SyntaxFactory}, + ast::{self, HasAttrs, HasLoopBody, edit::IndentLevel, make, syntax_factory::SyntaxFactory}, syntax_editor::Position, }; @@ -82,6 +79,18 @@ pub(crate) fn convert_for_loop_to_while_let( Some(iterable), ); let indent = IndentLevel::from_node(for_loop.syntax()); + + if let Some(label) = for_loop.label() { + let label = label.syntax().clone_for_update(); + editor.insert(Position::before(for_loop.syntax()), make.whitespace(" ")); + editor.insert(Position::before(for_loop.syntax()), label); + } + crate::utils::insert_attributes( + for_loop.syntax(), + &mut editor, + for_loop.attrs().map(|it| it.clone_for_update()), + ); + editor.insert( Position::before(for_loop.syntax()), make::tokens::whitespace(format!("\n{indent}").as_str()), @@ -186,6 +195,56 @@ fn main() { ) } + #[test] + fn each_to_for_with_label() { + check_assist( + convert_for_loop_to_while_let, + r" +fn main() { + let mut x = vec![1, 2, 3]; + 'a: for $0v in x { + v *= 2; + break 'a; + }; +}", + r" +fn main() { + let mut x = vec![1, 2, 3]; + let mut tmp = x.into_iter(); + 'a: while let Some(v) = tmp.next() { + v *= 2; + break 'a; + }; +}", + ) + } + + #[test] + fn each_to_for_with_attributes() { + check_assist( + convert_for_loop_to_while_let, + r" +fn main() { + let mut x = vec![1, 2, 3]; + #[allow(unused)] + #[deny(unsafe_code)] + for $0v in x { + v *= 2; + }; +}", + r" +fn main() { + let mut x = vec![1, 2, 3]; + let mut tmp = x.into_iter(); + #[allow(unused)] + #[deny(unsafe_code)] + while let Some(v) = tmp.next() { + v *= 2; + }; +}", + ) + } + #[test] fn each_to_for_for_in_range() { check_assist( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_while_to_loop.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_while_to_loop.rs index dbe3ee0ed603..9fd8b4b3159e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_while_to_loop.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_while_to_loop.rs @@ -1,6 +1,5 @@ use std::iter; -use either::Either; use ide_db::syntax_helpers::node_ext::is_pattern_cond; use syntax::{ AstNode, T, @@ -9,6 +8,7 @@ edit::{AstNodeEdit, IndentLevel}, make, }, + syntax_editor::{Element, Position}, }; use crate::{ @@ -44,43 +44,53 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) let while_expr = while_kw.parent().and_then(ast::WhileExpr::cast)?; let while_body = while_expr.loop_body()?; let while_cond = while_expr.condition()?; + let l_curly = while_body.stmt_list()?.l_curly_token()?; let target = while_expr.syntax().text_range(); acc.add( AssistId::refactor_rewrite("convert_while_to_loop"), "Convert while to loop", target, - |edit| { + |builder| { + let mut edit = builder.make_editor(while_expr.syntax()); let while_indent_level = IndentLevel::from_node(while_expr.syntax()); let break_block = make::block_expr( iter::once(make::expr_stmt(make::expr_break(None, None)).into()), None, ) - .indent(while_indent_level); - let block_expr = if is_pattern_cond(while_cond.clone()) { - let if_expr = make::expr_if(while_cond, while_body, Some(break_block.into())); + .indent(IndentLevel(1)); + + edit.replace_all( + while_kw.syntax_element()..=while_cond.syntax().syntax_element(), + vec![make::token(T![loop]).syntax_element()], + ); + + if is_pattern_cond(while_cond.clone()) { + let then_branch = while_body.reset_indent().indent(IndentLevel(1)); + let if_expr = make::expr_if(while_cond, then_branch, Some(break_block.into())); let stmts = iter::once(make::expr_stmt(if_expr.into()).into()); - make::block_expr(stmts, None) + let block_expr = make::block_expr(stmts, None); + edit.replace(while_body.syntax(), block_expr.indent(while_indent_level).syntax()); } else { let if_cond = invert_boolean_expression_legacy(while_cond); - let if_expr = make::expr_if(if_cond, break_block, None).syntax().clone().into(); - let elements = while_body.stmt_list().map_or_else( - || Either::Left(iter::empty()), - |stmts| { - Either::Right(stmts.syntax().children_with_tokens().filter(|node_or_tok| { - // Filter out the trailing expr - !node_or_tok - .as_node() - .is_some_and(|node| ast::Expr::can_cast(node.kind())) - })) - }, + let if_expr = make::expr_if(if_cond, break_block, None).indent(while_indent_level); + if !while_body.syntax().text().contains_char('\n') { + edit.insert( + Position::after(&l_curly), + make::tokens::whitespace(&format!("\n{while_indent_level}")), + ); + } + edit.insert_all( + Position::after(&l_curly), + vec![ + make::tokens::whitespace(&format!("\n{}", while_indent_level + 1)).into(), + if_expr.syntax().syntax_element(), + ], ); - make::hacky_block_expr(iter::once(if_expr).chain(elements), while_body.tail_expr()) }; - let replacement = make::expr_loop(block_expr.indent(while_indent_level)); - edit.replace(target, replacement.syntax().text()) + builder.add_file_edits(ctx.vfs_file_id(), edit); }, ) } @@ -115,6 +125,110 @@ fn main() { ); } + #[test] + fn convert_with_label() { + check_assist( + convert_while_to_loop, + r#" +fn main() { + 'x: while$0 cond { + foo(); + break 'x + } +} +"#, + r#" +fn main() { + 'x: loop { + if !cond { + break; + } + foo(); + break 'x + } +} +"#, + ); + + check_assist( + convert_while_to_loop, + r#" +fn main() { + 'x: while$0 let Some(x) = cond { + foo(); + break 'x + } +} +"#, + r#" +fn main() { + 'x: loop { + if let Some(x) = cond { + foo(); + break 'x + } else { + break; + } + } +} +"#, + ); + } + + #[test] + fn convert_with_attributes() { + check_assist( + convert_while_to_loop, + r#" +fn main() { + #[allow(unused)] + while$0 cond { + foo(); + break 'x + } +} +"#, + r#" +fn main() { + #[allow(unused)] + loop { + if !cond { + break; + } + foo(); + break 'x + } +} +"#, + ); + + check_assist( + convert_while_to_loop, + r#" +fn main() { + #[allow(unused)] + #[deny(unsafe_code)] + while$0 let Some(x) = cond { + foo(); + } +} +"#, + r#" +fn main() { + #[allow(unused)] + #[deny(unsafe_code)] + loop { + if let Some(x) = cond { + foo(); + } else { + break; + } + } +} +"#, + ); + } + #[test] fn convert_busy_wait() { check_assist( @@ -185,6 +299,76 @@ fn main() { ); } + #[test] + fn indentation() { + check_assist( + convert_while_to_loop, + r#" +fn main() { + { + { + while$0 cond { + foo( + "xxx", + ); + } + } + } +} +"#, + r#" +fn main() { + { + { + loop { + if !cond { + break; + } + foo( + "xxx", + ); + } + } + } +} +"#, + ); + + check_assist( + convert_while_to_loop, + r#" +fn main() { + { + { + while$0 let Some(_) = foo() { + bar( + "xxx", + ); + } + } + } +} +"#, + r#" +fn main() { + { + { + loop { + if let Some(_) = foo() { + bar( + "xxx", + ); + } else { + break; + } + } + } + } +} +"#, + ); + } + #[test] fn ignore_cursor_in_body() { check_assist_not_applicable( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 20e0302b57d7..7bca451c5e31 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -27,7 +27,7 @@ make, syntax_factory::SyntaxFactory, }, - syntax_editor::{Removable, SyntaxEditor}, + syntax_editor::{Element, Removable, SyntaxEditor}, }; use crate::{ @@ -385,6 +385,28 @@ fn invert_special_case_legacy(expr: &ast::Expr) -> Option { } } +pub(crate) fn insert_attributes( + before: impl Element, + edit: &mut SyntaxEditor, + attrs: impl IntoIterator, +) { + let mut attrs = attrs.into_iter().peekable(); + if attrs.peek().is_none() { + return; + } + let elem = before.syntax_element(); + let indent = IndentLevel::from_element(&elem); + let whitespace = format!("\n{indent}"); + edit.insert_all( + syntax::syntax_editor::Position::before(elem), + attrs + .flat_map(|attr| { + [attr.syntax().clone().into(), make::tokens::whitespace(&whitespace).into()] + }) + .collect(), + ); +} + pub(crate) fn next_prev() -> impl Iterator { [Direction::Next, Direction::Prev].into_iter() } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index 051c5835571b..dba39204e32e 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -1355,7 +1355,7 @@ pub mod tokens { pub(super) static SOURCE_FILE: LazyLock> = LazyLock::new(|| { SourceFile::parse( - "use crate::foo; const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] })\n;\n\nunsafe impl A for B where: {}", + "use crate::foo; const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] }, while loop {} {})\n;\n\nunsafe impl A for B where: {}", Edition::CURRENT, ) }); From 8e3fc73e97982ebe2ba7011eff61cee4016ee9d9 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 16 Nov 2025 17:23:32 +0800 Subject: [PATCH 02/39] Fix make::unnamed_param result a untyped_param - Add `make::untyped_param` - Add some basic make tests Example --- ```rust make::unnamed_param(make::ty("Vec")), ``` **Before this PR** ```text PARAM@0..4 IDENT_PAT@0..4 NAME@0..4 IDENT@0..4 "Vec" ``` **After this PR** ```text PARAM@0..6 PATH_TYPE@0..6 PATH@0..6 PATH_SEGMENT@0..6 NAME_REF@0..3 IDENT@0..3 "Vec" GENERIC_ARG_LIST@3..6 L_ANGLE@3..4 "<" TYPE_ARG@4..5 PATH_TYPE@4..5 PATH@4..5 PATH_SEGMENT@4..5 NAME_REF@4..5 IDENT@4..5 "T" R_ANGLE@5..6 ">" ``` --- Assist: `Generate a type alias for function with unnamed params` ```rust fn foo$0(x: Vec) {} ``` **Before this PR** ```rust type FooFn = fn(Vec); fn foo(x: Vec) {} ``` **After this PR** ```rust type FooFn = fn(Vec); fn foo(x: Vec) {} ``` --- .../crates/syntax/src/ast/make.rs | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index dba39204e32e..4a7afed61ee2 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -1016,7 +1016,19 @@ pub fn item_static( } pub fn unnamed_param(ty: ast::Type) -> ast::Param { - ast_from_text(&format!("fn f({ty}) {{ }}")) + quote! { + Param { + #ty + } + } +} + +pub fn untyped_param(pat: ast::Pat) -> ast::Param { + quote! { + Param { + #pat + } + } } pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param { @@ -1456,3 +1468,86 @@ pub fn ws(&self) -> SyntaxToken { } } } + +#[cfg(test)] +mod tests { + use expect_test::expect; + + use super::*; + + #[track_caller] + fn check(node: impl AstNode, expect: expect_test::Expect) { + let node_debug = format!("{:#?}", node.syntax()); + expect.assert_eq(&node_debug); + } + + #[test] + fn test_unnamed_param() { + check( + unnamed_param(ty("Vec")), + expect![[r#" + PARAM@0..3 + PATH_TYPE@0..3 + PATH@0..3 + PATH_SEGMENT@0..3 + NAME_REF@0..3 + IDENT@0..3 "Vec" + "#]], + ); + + check( + unnamed_param(ty("Vec")), + expect![[r#" + PARAM@0..6 + PATH_TYPE@0..6 + PATH@0..6 + PATH_SEGMENT@0..6 + NAME_REF@0..3 + IDENT@0..3 "Vec" + GENERIC_ARG_LIST@3..6 + L_ANGLE@3..4 "<" + TYPE_ARG@4..5 + PATH_TYPE@4..5 + PATH@4..5 + PATH_SEGMENT@4..5 + NAME_REF@4..5 + IDENT@4..5 "T" + R_ANGLE@5..6 ">" + "#]], + ); + } + + #[test] + fn test_untyped_param() { + check( + untyped_param(path_pat(ext::ident_path("name"))), + expect![[r#" + PARAM@0..4 + IDENT_PAT@0..4 + NAME@0..4 + IDENT@0..4 "name" + "#]], + ); + + check( + untyped_param( + range_pat( + Some(path_pat(ext::ident_path("start"))), + Some(path_pat(ext::ident_path("end"))), + ) + .into(), + ), + expect![[r#" + PARAM@0..10 + RANGE_PAT@0..10 + IDENT_PAT@0..5 + NAME@0..5 + IDENT@0..5 "start" + DOT2@5..7 ".." + IDENT_PAT@7..10 + NAME@7..10 + IDENT@7..10 "end" + "#]], + ); + } +} From 0fc34524d4688f9e34f5e661ca9504eb2d4a7b28 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Tue, 25 Nov 2025 22:37:48 +0800 Subject: [PATCH 03/39] internal: add missing method for SyntaxFactory --- .../crates/syntax/src/ast/make.rs | 2 +- .../src/ast/syntax_factory/constructors.rs | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index dba39204e32e..19019ad08d81 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -658,7 +658,7 @@ pub fn expr_if( }; expr_from_text(&format!("if {condition} {then_branch} {else_branch}")) } -pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::Expr { +pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::ForExpr { expr_from_text(&format!("for {pat} in {expr} {block}")) } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 969552392180..e0fac97f67ab 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -671,6 +671,26 @@ pub fn expr_while_loop(&self, condition: ast::Expr, body: ast::BlockExpr) -> ast ast } + pub fn expr_for_loop( + &self, + pat: ast::Pat, + iterable: ast::Expr, + body: ast::BlockExpr, + ) -> ast::ForExpr { + let ast = + make::expr_for_loop(pat.clone(), iterable.clone(), body.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(pat.syntax().clone(), ast.pat().unwrap().syntax().clone()); + builder.map_node(iterable.syntax().clone(), ast.iterable().unwrap().syntax().clone()); + builder.map_node(body.syntax().clone(), ast.loop_body().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + pub fn expr_let(&self, pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr { let ast = make::expr_let(pattern.clone(), expr.clone()).clone_for_update(); From 8b27170cb4a29262a60daa6c735b65a1d3b42765 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Sun, 30 Nov 2025 11:32:56 +0800 Subject: [PATCH 04/39] internal: migrate `convert_iter_for_each_to_for` to SyntaxEditor api --- .../handlers/convert_iter_for_each_to_for.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs index c8a244b2136d..d564555b91ea 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs @@ -3,7 +3,7 @@ use stdx::format_to; use syntax::{ AstNode, - ast::{self, HasArgList, HasLoopBody, edit_in_place::Indent, make}, + ast::{self, HasArgList, HasLoopBody, edit_in_place::Indent, syntax_factory::SyntaxFactory}, }; use crate::{AssistContext, AssistId, Assists}; @@ -57,18 +57,22 @@ pub(crate) fn convert_iter_for_each_to_for( "Replace this `Iterator::for_each` with a for loop", range, |builder| { + let make = SyntaxFactory::with_mappings(); let indent = stmt.as_ref().map_or_else(|| method.indent_level(), ast::ExprStmt::indent_level); let block = match body { - ast::Expr::BlockExpr(block) => block, - _ => make::block_expr(Vec::new(), Some(body)), - } - .clone_for_update(); + ast::Expr::BlockExpr(block) => block.clone_for_update(), + _ => make.block_expr(Vec::new(), Some(body)), + }; block.reindent_to(indent); - let expr_for_loop = make::expr_for_loop(param, receiver, block); - builder.replace(range, expr_for_loop.to_string()) + let expr_for_loop = make.expr_for_loop(param, receiver, block); + + let target_node = stmt.as_ref().map_or(method.syntax(), AstNode::syntax); + let mut editor = builder.make_editor(target_node); + editor.replace(target_node, expr_for_loop.syntax()); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } From 34f2848ef46254e144f3056de9c1594998c312fd Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 30 Nov 2025 13:42:13 +0800 Subject: [PATCH 05/39] Fix indent for toggle_ignore Example --- ```rust mod indent { #[test$0] fn test() {} } ``` **Before this PR** ```rust mod indent { #[test] #[ignore] fn test() {} } ``` And re-enable ```rust mod indent { #[test] fn test() {} } ``` **After this PR** ```rust mod indent { #[test] #[ignore] fn test() {} } ``` --- .../ide-assists/src/handlers/toggle_ignore.rs | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs index 386625b86b27..a088fb178d25 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs @@ -1,6 +1,6 @@ use syntax::{ AstNode, AstToken, - ast::{self, HasAttrs}, + ast::{self, HasAttrs, edit::AstNodeEdit}, }; use crate::{AssistContext, AssistId, Assists, utils::test_related_attribute_syn}; @@ -27,13 +27,16 @@ pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio let attr: ast::Attr = ctx.find_node_at_offset()?; let func = attr.syntax().parent().and_then(ast::Fn::cast)?; let attr = test_related_attribute_syn(&func)?; + let indent = attr.indent_level(); match has_ignore_attribute(&func) { None => acc.add( AssistId::refactor("toggle_ignore"), "Ignore this test", attr.syntax().text_range(), - |builder| builder.insert(attr.syntax().text_range().end(), "\n#[ignore]"), + |builder| { + builder.insert(attr.syntax().text_range().end(), format!("\n{indent}#[ignore]")) + }, ), Some(ignore_attr) => acc.add( AssistId::refactor("toggle_ignore"), @@ -69,13 +72,17 @@ fn test_base_case() { check_assist( toggle_ignore, r#" - #[test$0] - fn test() {} + mod indent { + #[test$0] + fn test() {} + } "#, r#" - #[test] - #[ignore] - fn test() {} + mod indent { + #[test] + #[ignore] + fn test() {} + } "#, ) } @@ -85,13 +92,17 @@ fn test_unignore() { check_assist( toggle_ignore, r#" - #[test$0] - #[ignore] - fn test() {} + mod indent { + #[test$0] + #[ignore] + fn test() {} + } "#, r#" - #[test] - fn test() {} + mod indent { + #[test] + fn test() {} + } "#, ) } From 4e1e17e80038a57354fef4dd2a72a1030606e8e0 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Mon, 22 Sep 2025 20:07:48 +0200 Subject: [PATCH 06/39] Include operator overload occurrences in SCIP index Operators were explicitly filtered out, both when filtering tokens to search definitions for and when searching for actual definitions. --- .../crates/ide/src/static_index.rs | 22 +++++++----------- .../crates/rust-analyzer/src/cli/scip.rs | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index 0cf2e15bc6f0..625cc888c84c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -10,7 +10,7 @@ documentation::Documentation, famous_defs::FamousDefs, }; -use syntax::{AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T, TextRange}; +use syntax::{AstNode, SyntaxNode, SyntaxToken, TextRange}; use crate::navigation_target::UpmappingResult; use crate::{ @@ -136,12 +136,12 @@ fn documentation_for_definition( } // FIXME: This is a weird function -fn get_definitions( - sema: &Semantics<'_, RootDatabase>, +fn get_definitions<'db>( + sema: &Semantics<'db, RootDatabase>, token: SyntaxToken, -) -> Option> { +) -> Option>), 2>> { for token in sema.descend_into_macros_exact(token) { - let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops); + let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions); if let Some(defs) = def && !defs.is_empty() { @@ -225,12 +225,6 @@ fn add_file(&mut self, file_id: FileId) { show_drop_glue: true, minicore: MiniCore::default(), }; - let tokens = tokens.filter(|token| { - matches!( - token.kind(), - IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self] - ) - }); let mut result = StaticIndexedFile { file_id, inlay_hints, folds, tokens: vec![] }; let mut add_token = |def: Definition, range: TextRange, scope_node: &SyntaxNode| { @@ -290,9 +284,9 @@ fn add_file(&mut self, file_id: FileId) { let range = token.text_range(); let node = token.parent().unwrap(); match hir::attach_db(self.db, || get_definitions(&sema, token.clone())) { - Some(it) => { - for i in it { - add_token(i, range, &node); + Some(defs) => { + for (def, _) in defs { + add_token(def, range, &node); } } None => continue, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs index fbf3082e1b89..271d2507bcfe 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs @@ -603,6 +603,29 @@ pub fn func() {} ); } + #[test] + fn operator_overload() { + check_symbol( + r#" +//- minicore: add +//- /workspace/lib.rs crate:main +use core::ops::AddAssign; + +struct S; + +impl AddAssign for S { + fn add_assign(&mut self, _rhs: Self) {} +} + +fn main() { + let mut s = S; + s +=$0 S; +} +"#, + "rust-analyzer cargo main . impl#[S][`AddAssign`]add_assign().", + ); + } + #[test] fn symbol_for_trait() { check_symbol( From 7fa80495eadbcde54a59fb8a894b04aaa321db5a Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Mon, 1 Dec 2025 22:05:24 +0800 Subject: [PATCH 07/39] internal: add missing method for `SyntaxFactory` --- .../src/ast/syntax_factory/constructors.rs | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 969552392180..9847d639d613 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -71,6 +71,189 @@ pub fn type_param( ast } + + pub fn path_from_text(&self, text: &str) -> ast::Path { + make::path_from_text(text).clone_for_update() + } + + pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr { + let ast::Expr::FieldExpr(ast) = + make::expr_field(receiver.clone(), field).clone_for_update() + else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(receiver.syntax().clone(), ast.expr().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + + pub fn impl_trait( + &self, + attrs: impl IntoIterator, + is_unsafe: bool, + trait_gen_params: Option, + trait_gen_args: Option, + type_gen_params: Option, + type_gen_args: Option, + is_negative: bool, + path_type: ast::Type, + ty: ast::Type, + trait_where_clause: Option, + ty_where_clause: Option, + body: Option, + ) -> ast::Impl { + let (attrs, attrs_input) = iterator_input(attrs); + let ast = make::impl_trait( + attrs, + is_unsafe, + trait_gen_params.clone(), + trait_gen_args.clone(), + type_gen_params.clone(), + type_gen_args.clone(), + is_negative, + path_type.clone(), + ty.clone(), + trait_where_clause.clone(), + ty_where_clause.clone(), + body.clone(), + ) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(attrs_input, ast.attrs().map(|attr| attr.syntax().clone())); + if let Some(trait_gen_params) = trait_gen_params { + builder.map_node( + trait_gen_params.syntax().clone(), + ast.generic_param_list().unwrap().syntax().clone(), + ); + } + builder.map_node(path_type.syntax().clone(), ast.trait_().unwrap().syntax().clone()); + builder.map_node(ty.syntax().clone(), ast.self_ty().unwrap().syntax().clone()); + if let Some(ty_where_clause) = ty_where_clause { + builder.map_node( + ty_where_clause.syntax().clone(), + ast.where_clause().unwrap().syntax().clone(), + ); + } + if let Some(body) = body { + builder.map_node( + body.syntax().clone(), + ast.assoc_item_list().unwrap().syntax().clone(), + ); + } + builder.finish(&mut mapping); + } + + ast + } + + pub fn ty_alias( + &self, + attrs: impl IntoIterator, + ident: &str, + generic_param_list: Option, + type_param_bounds: Option, + where_clause: Option, + assignment: Option<(ast::Type, Option)>, + ) -> ast::TypeAlias { + let (attrs, attrs_input) = iterator_input(attrs); + let ast = make::ty_alias( + attrs, + ident, + generic_param_list.clone(), + type_param_bounds.clone(), + where_clause.clone(), + assignment.clone(), + ) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(attrs_input, ast.attrs().map(|attr| attr.syntax().clone())); + if let Some(generic_param_list) = generic_param_list { + builder.map_node( + generic_param_list.syntax().clone(), + ast.generic_param_list().unwrap().syntax().clone(), + ); + } + if let Some(type_param_bounds) = type_param_bounds { + builder.map_node( + type_param_bounds.syntax().clone(), + ast.type_bound_list().unwrap().syntax().clone(), + ); + } + if let Some(where_clause) = where_clause { + builder.map_node( + where_clause.syntax().clone(), + ast.where_clause().unwrap().syntax().clone(), + ); + } + if let Some((ty, _)) = assignment { + builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone()); + } + builder.finish(&mut mapping); + } + + ast + } + + pub fn param_list( + &self, + self_param: Option, + params: impl IntoIterator, + ) -> ast::ParamList { + let (params, input) = iterator_input(params); + let ast = make::param_list(self_param.clone(), params).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(self_param) = self_param { + if let Some(new_self_param) = ast.self_param() { + builder.map_node(self_param.syntax().clone(), new_self_param.syntax().clone()); + } + } + builder.map_children(input, ast.params().map(|p| p.syntax().clone())); + builder.finish(&mut mapping); + } + + ast + } + + pub fn const_param(&self, name: ast::Name, ty: ast::Type) -> ast::ConstParam { + let ast = make::const_param(name.clone(), ty.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + + pub fn generic_param_list( + &self, + params: impl IntoIterator, + ) -> ast::GenericParamList { + let (params, input) = iterator_input(params); + let ast = make::generic_param_list(params).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(input, ast.generic_params().map(|p| p.syntax().clone())); + builder.finish(&mut mapping); + } + + ast + } + pub fn path_segment(&self, name_ref: ast::NameRef) -> ast::PathSegment { let ast = make::path_segment(name_ref.clone()).clone_for_update(); From 98d0421a714108d0de9095fadc8dd3c71f26a230 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Thu, 4 Dec 2025 23:27:19 +0800 Subject: [PATCH 08/39] internal: migrate `generate_delegate_trait` to SyntaxEditor api --- .../src/handlers/generate_delegate_trait.rs | 445 ++++++++++-------- 1 file changed, 250 insertions(+), 195 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs index e87dde5b8e42..03642756219a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -14,15 +14,15 @@ }; use itertools::Itertools; use syntax::{ - AstNode, Edition, NodeOrToken, SmolStr, SyntaxKind, ToSmolStr, + AstNode, Edition, SmolStr, SyntaxElement, SyntaxKind, ToSmolStr, ast::{ self, AssocItem, GenericArgList, GenericParamList, HasAttrs, HasGenericArgs, HasGenericParams, HasName, HasTypeBounds, HasVisibility as astHasVisibility, Path, WherePred, edit::{self, AstNodeEdit}, - make, + syntax_factory::SyntaxFactory, }, - ted::{self, Position}, + syntax_editor::SyntaxEditor, }; // Assist: generate_delegate_trait @@ -169,10 +169,15 @@ enum Delegee { } impl Delegee { + fn trait_(&self) -> &hir::Trait { + match self { + Delegee::Bound(it) | Delegee::Impls(it, _) => it, + } + } + fn signature(&self, db: &dyn HirDatabase, edition: Edition) -> String { let mut s = String::new(); - - let (Delegee::Bound(it) | Delegee::Impls(it, _)) = self; + let it = self.trait_(); for m in it.module(db).path_to_root(db).iter().rev() { if let Some(name) = m.name(db) { @@ -201,15 +206,12 @@ pub(crate) fn delegate(&self, field: Field, acc: &mut Assists, ctx: &AssistConte let db = ctx.db(); for (index, delegee) in field.impls.iter().enumerate() { - let trait_ = match delegee { - Delegee::Bound(b) => b, - Delegee::Impls(i, _) => i, - }; + let trait_ = delegee.trait_(); // Skip trait that has `Self` type, which cannot be delegated // // See [`test_self_ty`] - if has_self_type(*trait_, ctx).is_some() { + if has_self_type(*trait_, ctx) { continue; } @@ -254,9 +256,10 @@ fn generate_impl( delegee: &Delegee, edition: Edition, ) -> Option { + let make = SyntaxFactory::without_mappings(); let db = ctx.db(); let ast_strukt = &strukt.strukt; - let strukt_ty = make::ty_path(make::ext::ident_path(&strukt.name.to_string())); + let strukt_ty = make.ty_path(make.ident_path(&strukt.name.to_string())).into(); let strukt_params = ast_strukt.generic_param_list(); match delegee { @@ -264,7 +267,7 @@ fn generate_impl( let bound_def = ctx.sema.source(delegee.to_owned())?.value; let bound_params = bound_def.generic_param_list(); - let delegate = make::impl_trait( + let delegate = make.impl_trait( None, delegee.is_unsafe(db), bound_params.clone(), @@ -272,33 +275,28 @@ fn generate_impl( strukt_params.clone(), strukt_params.map(|params| params.to_generic_args()), delegee.is_auto(db), - make::ty(&delegee.name(db).display_no_db(edition).to_smolstr()), + make.ty(&delegee.name(db).display_no_db(edition).to_smolstr()), strukt_ty, bound_def.where_clause(), ast_strukt.where_clause(), None, - ) - .clone_for_update(); + ); // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths let qualified_path_type = - make::path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); + make.path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); - let delegate_assoc_items = delegate.get_or_create_assoc_item_list(); - if let Some(ai) = bound_def.assoc_item_list() { + // Collect assoc items + let assoc_items: Option> = bound_def.assoc_item_list().map(|ai| { ai.assoc_items() .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) - .for_each(|item| { - let assoc = process_assoc_item( - item.clone_for_update(), - qualified_path_type.clone(), - field_name, - ); - if let Some(assoc) = assoc { - delegate_assoc_items.add_item(assoc); - } - }); - }; + .filter_map(|item| { + process_assoc_item(item, qualified_path_type.clone(), field_name) + }) + .collect() + }); + + let delegate = finalize_delegate(&delegate, assoc_items, false)?; let target_scope = ctx.sema.scope(strukt.strukt.syntax())?; let source_scope = ctx.sema.scope(bound_def.syntax())?; @@ -324,7 +322,7 @@ fn generate_impl( .and_then(|wc| rename_strukt_args(ctx, ast_strukt, &wc, &args)); (field_ty, where_clause) } - None => (field_ty.clone_for_update(), None), + None => (field_ty.clone(), None), }; // 2) Handle instantiated generics in `field_ty`. @@ -347,38 +345,38 @@ fn generate_impl( ); // 2.2) Generate generic args applied on impl. - let transform_args = generate_args_for_impl( + let (transform_args, trait_gen_params) = generate_args_for_impl( old_impl_params, &old_impl.self_ty()?, &field_ty, - &trait_gen_params, + trait_gen_params, &old_impl_trait_args, ); // 2.3) Instantiate generics with `transform_impl`, this step also // remove unused params. - let trait_gen_args = old_impl.trait_()?.generic_arg_list().and_then(|trait_args| { - let trait_args = &mut trait_args.clone_for_update(); - if let Some(new_args) = transform_impl( - ctx, - ast_strukt, - &old_impl, - &transform_args, - trait_args.clone_subtree(), - ) { - *trait_args = new_args.clone_subtree(); - Some(new_args) - } else { - None - } - }); + let trait_gen_args = + old_impl.trait_()?.generic_arg_list().and_then(|mut trait_args| { + let trait_args = &mut trait_args; + if let Some(new_args) = transform_impl( + ctx, + ast_strukt, + &old_impl, + &transform_args, + trait_args.clone_subtree(), + ) { + *trait_args = new_args.clone_subtree(); + Some(new_args) + } else { + None + } + }); let type_gen_args = strukt_params.clone().map(|params| params.to_generic_args()); - let path_type = - make::ty(&trait_.name(db).display_no_db(edition).to_smolstr()).clone_for_update(); + let path_type = make.ty(&trait_.name(db).display_no_db(edition).to_smolstr()); let path_type = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type)?; // 3) Generate delegate trait impl - let delegate = make::impl_trait( + let delegate = make.impl_trait( None, trait_.is_unsafe(db), trait_gen_params, @@ -388,34 +386,27 @@ fn generate_impl( trait_.is_auto(db), path_type, strukt_ty, - old_impl.where_clause().map(|wc| wc.clone_for_update()), + old_impl.where_clause(), ty_where_clause, None, - ) - .clone_for_update(); + ); // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths let qualified_path_type = - make::path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); + make.path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); - // 4) Transform associated items in delegte trait impl - let delegate_assoc_items = delegate.get_or_create_assoc_item_list(); - for item in old_impl - .get_or_create_assoc_item_list() - .assoc_items() - .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) - { - let item = item.clone_for_update(); - let item = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item)?; + // 4) Transform associated items in delegate trait impl + let assoc_items: Option> = old_impl.assoc_item_list().map(|ail| { + ail.assoc_items() + .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) + .filter_map(|item| { + let item = + transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item)?; + process_assoc_item(item, qualified_path_type.clone(), field_name) + }) + .collect() + }); - let assoc = process_assoc_item(item, qualified_path_type.clone(), field_name)?; - delegate_assoc_items.add_item(assoc); - } - - // 5) Remove useless where clauses - if let Some(wc) = delegate.where_clause() { - remove_useless_where_clauses(&delegate.trait_()?, &delegate.self_ty()?, wc); - } - Some(delegate) + finalize_delegate(&delegate, assoc_items, true) } } } @@ -446,6 +437,35 @@ fn transform_impl( N::cast(transform.apply(syntax.syntax())) } +/// Extracts the name from a generic parameter. +fn generic_param_name(param: &ast::GenericParam) -> Option { + match param { + ast::GenericParam::TypeParam(t) => t.name().map(|n| n.to_string()), + ast::GenericParam::ConstParam(c) => c.name().map(|n| n.to_string()), + ast::GenericParam::LifetimeParam(l) => l.lifetime().map(|lt| lt.to_string()), + } +} + +/// Filters generic params, keeping only those whose names are not in `names_to_remove`. +fn filter_generic_params( + gpl: ast::GenericParamList, + names_to_remove: &FxHashSet, +) -> Option { + let remaining_params: Vec<_> = gpl + .generic_params() + .filter(|param| { + generic_param_name(param).is_none_or(|name| !names_to_remove.contains(&name)) + }) + .collect(); + + if remaining_params.is_empty() { + None + } else { + let make = SyntaxFactory::without_mappings(); + Some(make.generic_param_list(remaining_params)) + } +} + fn remove_instantiated_params( self_ty: &ast::Type, old_impl_params: Option, @@ -454,10 +474,8 @@ fn remove_instantiated_params( match self_ty { ast::Type::PathType(path_type) => { old_impl_params.and_then(|gpl| { - // Remove generic parameters in field_ty (which is instantiated). - let new_gpl = gpl.clone_for_update(); - - path_type + // Collect generic args that should be removed (instantiated params) + let args_to_remove: FxHashSet = path_type .path()? .segments() .filter_map(|seg| seg.generic_arg_list()) @@ -466,16 +484,25 @@ fn remove_instantiated_params( // it shouldn't be removed now, which will be instantiated in // later `path_transform` .filter(|arg| !old_trait_args.contains(&arg.to_string())) - .for_each(|arg| new_gpl.remove_generic_arg(&arg)); - (new_gpl.generic_params().count() > 0).then_some(new_gpl) + .map(|arg| arg.to_string()) + .collect(); + + filter_generic_params(gpl, &args_to_remove) }) } _ => old_impl_params, } } -fn remove_useless_where_clauses(trait_ty: &ast::Type, self_ty: &ast::Type, wc: ast::WhereClause) { - let live_generics = [trait_ty, self_ty] +fn remove_useless_where_clauses(editor: &mut SyntaxEditor, delegate: &ast::Impl) { + let Some(wc) = delegate.where_clause() else { + return; + }; + let (Some(trait_ty), Some(self_ty)) = (delegate.trait_(), delegate.self_ty()) else { + return; + }; + + let live_generics = [&trait_ty, &self_ty] .into_iter() .flat_map(|ty| ty.generic_arg_list()) .flat_map(|gal| gal.generic_args()) @@ -484,36 +511,78 @@ fn remove_useless_where_clauses(trait_ty: &ast::Type, self_ty: &ast::Type, wc: a // Keep where-clauses that have generics after substitution, and remove the // rest. - let has_live_generics = |pred: &WherePred| { + let has_no_live_generics = |pred: &WherePred| { pred.syntax() .descendants_with_tokens() .filter_map(|e| e.into_token()) .any(|e| e.kind() == SyntaxKind::IDENT && live_generics.contains(&e.to_string())) .not() }; - wc.predicates().filter(has_live_generics).for_each(|pred| wc.remove_predicate(pred)); - if wc.predicates().count() == 0 { - // Remove useless whitespaces - [syntax::Direction::Prev, syntax::Direction::Next] - .into_iter() - .flat_map(|dir| { - wc.syntax() - .siblings_with_tokens(dir) - .skip(1) - .take_while(|node_or_tok| node_or_tok.kind() == SyntaxKind::WHITESPACE) - }) - .for_each(ted::remove); + let predicates_to_remove: Vec<_> = wc.predicates().filter(has_no_live_generics).collect(); + let remaining_predicates = wc.predicates().count() - predicates_to_remove.len(); - ted::insert( - ted::Position::after(wc.syntax()), - NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)), - ); - // Remove where clause - ted::remove(wc.syntax()); + if remaining_predicates == 0 { + // Remove the entire where clause + editor.delete(wc.syntax().clone()); + } else { + // Remove only the useless predicates + for pred in predicates_to_remove { + // Also remove the comma before or after the predicate + if let Some(previous) = pred.syntax().prev_sibling() { + // Remove from after previous sibling to predicate (inclusive) + if let Some(start) = previous.next_sibling_or_token() { + let end: SyntaxElement = pred.syntax().clone().into(); + editor.delete_all(start..=end); + } + } else if let Some(next) = pred.syntax().next_sibling() { + // Remove from predicate to before next sibling (exclusive) + if let Some(end) = next.prev_sibling_or_token() { + let start: SyntaxElement = pred.syntax().clone().into(); + editor.delete_all(start..=end); + } + } else { + editor.delete(pred.syntax().clone()); + } + } } } +/// Finalize the delegate impl by: +/// 1. Replacing the assoc_item_list with new items (if any) +/// 2. Removing useless where clauses +fn finalize_delegate( + delegate: &ast::Impl, + assoc_items: Option>, + remove_where_clauses: bool, +) -> Option { + let has_items = assoc_items.as_ref().is_some_and(|items| !items.is_empty()); + + if !has_items && !remove_where_clauses { + return Some(delegate.clone()); + } + + let mut editor = SyntaxEditor::new(delegate.syntax().clone_subtree()); + + // 1. Replace assoc_item_list if we have new items + if let Some(items) = assoc_items + && !items.is_empty() + { + let new_assoc_item_list = + syntax::ast::make::assoc_item_list(Some(items)).clone_for_update(); + if let Some(old_list) = delegate.assoc_item_list() { + editor.replace(old_list.syntax(), new_assoc_item_list.syntax()); + } + } + + // 2. Remove useless where clauses + if remove_where_clauses { + remove_useless_where_clauses(&mut editor, delegate); + } + + ast::Impl::cast(editor.finish().new_root().clone()) +} + // Generate generic args that should be apply to current impl. // // For example, say we have implementation `impl Trait for B`, @@ -524,10 +593,13 @@ fn generate_args_for_impl( old_impl_gpl: Option, self_ty: &ast::Type, field_ty: &ast::Type, - trait_params: &Option, + trait_params: Option, old_trait_args: &FxHashSet, -) -> Option { - let old_impl_args = old_impl_gpl.map(|gpl| gpl.to_generic_args().generic_args())?; +) -> (Option, Option) { + let Some(old_impl_args) = old_impl_gpl.map(|gpl| gpl.to_generic_args().generic_args()) else { + return (None, trait_params); + }; + // Create pairs of the args of `self_ty` and corresponding `field_ty` to // form the substitution list let mut arg_substs = FxHashMap::default(); @@ -542,6 +614,8 @@ fn generate_args_for_impl( } } + let mut params_to_remove = FxHashSet::default(); + let args = old_impl_args .map(|old_arg| { arg_substs.get(&old_arg.to_string()).map_or_else( @@ -549,14 +623,18 @@ fn generate_args_for_impl( |replace_with| { // The old_arg will be replaced, so it becomes redundant if trait_params.is_some() && old_trait_args.contains(&old_arg.to_string()) { - trait_params.as_ref().unwrap().remove_generic_arg(&old_arg) + params_to_remove.insert(old_arg.to_string()); } replace_with.clone() }, ) }) .collect_vec(); - args.is_empty().not().then(|| make::generic_arg_list(args)) + + let make = SyntaxFactory::without_mappings(); + let result = args.is_empty().not().then(|| make.generic_arg_list(args, false)); + let trait_params = trait_params.and_then(|gpl| filter_generic_params(gpl, ¶ms_to_remove)); + (result, trait_params) } fn rename_strukt_args( @@ -570,41 +648,37 @@ fn rename_strukt_args( { let hir_strukt = ctx.sema.to_struct_def(strukt)?; let hir_adt = hir::Adt::from(hir_strukt); - - let item = item.clone_for_update(); let scope = ctx.sema.scope(item.syntax())?; let transform = PathTransform::adt_transformation(&scope, &scope, hir_adt, args.clone()); N::cast(transform.apply(item.syntax())) } -fn has_self_type(trait_: hir::Trait, ctx: &AssistContext<'_>) -> Option<()> { - let trait_source = ctx.sema.source(trait_)?.value; - trait_source - .syntax() - .descendants_with_tokens() - .filter_map(|e| e.into_token()) - .find(|e| e.kind() == SyntaxKind::SELF_TYPE_KW) - .map(|_| ()) +fn has_self_type(trait_: hir::Trait, ctx: &AssistContext<'_>) -> bool { + ctx.sema + .source(trait_) + .and_then(|src| { + src.value + .syntax() + .descendants_with_tokens() + .filter_map(|e| e.into_token()) + .find(|e| e.kind() == SyntaxKind::SELF_TYPE_KW) + }) + .is_some() } fn resolve_name_conflicts( strukt_params: Option, old_impl_params: &Option, ) -> Option { + let make = SyntaxFactory::without_mappings(); match (strukt_params, old_impl_params) { (Some(old_strukt_params), Some(old_impl_params)) => { - let params = make::generic_param_list(std::iter::empty()).clone_for_update(); + let mut new_params: Vec = Vec::new(); for old_strukt_param in old_strukt_params.generic_params() { // Get old name from `strukt` - let name = SmolStr::from(match &old_strukt_param { - ast::GenericParam::ConstParam(c) => c.name()?.to_string(), - ast::GenericParam::LifetimeParam(l) => { - l.lifetime()?.lifetime_ident_token()?.to_string() - } - ast::GenericParam::TypeParam(t) => t.name()?.to_string(), - }); + let name = SmolStr::from(generic_param_name(&old_strukt_param)?); // The new name cannot be conflicted with generics in trait, and the renamed names. let param_list_to_names = |param_list: &GenericParamList| { @@ -613,8 +687,9 @@ fn resolve_name_conflicts( p => Some(p.to_string()), }) }; + let new_params_list = make.generic_param_list(new_params.clone()); let existing_names = param_list_to_names(old_impl_params) - .chain(param_list_to_names(¶ms)) + .chain(param_list_to_names(&new_params_list)) .collect_vec(); let mut name_generator = suggest_name::NameGenerator::new_with_names( existing_names.iter().map(|s| s.as_str()), @@ -623,25 +698,21 @@ fn resolve_name_conflicts( match old_strukt_param { ast::GenericParam::ConstParam(c) => { if let Some(const_ty) = c.ty() { - let const_param = make::const_param(make::name(&name), const_ty); - params.add_generic_param(ast::GenericParam::ConstParam( - const_param.clone_for_update(), - )); + let const_param = make.const_param(make.name(&name), const_ty); + new_params.push(ast::GenericParam::ConstParam(const_param)); } } p @ ast::GenericParam::LifetimeParam(_) => { - params.add_generic_param(p.clone_for_update()); + new_params.push(p.clone_for_update()); } ast::GenericParam::TypeParam(t) => { let type_bounds = t.type_bound_list(); - let type_param = make::type_param(make::name(&name), type_bounds); - params.add_generic_param(ast::GenericParam::TypeParam( - type_param.clone_for_update(), - )); + let type_param = make.type_param(make.name(&name), type_bounds); + new_params.push(ast::GenericParam::TypeParam(type_param)); } } } - Some(params) + Some(make.generic_param_list(new_params)) } (Some(old_strukt_gpl), None) => Some(old_strukt_gpl), _ => None, @@ -666,7 +737,8 @@ fn process_assoc_item( } fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> Option { - let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str()); + let make = SyntaxFactory::without_mappings(); + let path_expr_segment = make.path_from_text(item.name()?.to_string().as_str()); // We want rhs of the const assignment to be a qualified path // The general case for const assignment can be found [here](`https://doc.rust-lang.org/reference/items/constant-items.html`) @@ -674,15 +746,14 @@ fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> Option // >::ConstName; // FIXME : We can't rely on `make::path_qualified` for now but it would be nice to replace the following with it. // make::path_qualified(qual_path_ty, path_expr_segment.as_single_segment().unwrap()); - let qualified_path = qualified_path(qual_path_ty, path_expr_segment); - let inner = make::item_const( + let qualified_path = make.path_from_text(&format!("{qual_path_ty}::{path_expr_segment}")); + let inner = make.item_const( item.attrs(), item.visibility(), item.name()?, item.ty()?, - make::expr_path(qualified_path), - ) - .clone_for_update(); + make.expr_path(qualified_path), + ); Some(AssocItem::Const(inner)) } @@ -692,59 +763,46 @@ fn func_assoc_item( qual_path_ty: Path, base_name: &str, ) -> Option { - let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str()); - let qualified_path = qualified_path(qual_path_ty, path_expr_segment); + let make = SyntaxFactory::without_mappings(); + let path_expr_segment = make.path_from_text(item.name()?.to_string().as_str()); + let qualified_path = make.path_from_text(&format!("{qual_path_ty}::{path_expr_segment}")); let call = match item.param_list() { // Methods and funcs should be handled separately. // We ask if the func has a `self` param. Some(l) => match l.self_param() { Some(slf) => { - let mut self_kw = make::expr_path(make::path_from_text("self")); - self_kw = make::expr_field(self_kw, base_name); + let self_kw = make.expr_path(make.path_from_text("self")); + let self_kw = make.expr_field(self_kw, base_name).into(); let tail_expr_self = match slf.kind() { ast::SelfParamKind::Owned => self_kw, - ast::SelfParamKind::Ref => make::expr_ref(self_kw, false), - ast::SelfParamKind::MutRef => make::expr_ref(self_kw, true), + ast::SelfParamKind::Ref => make.expr_ref(self_kw, false), + ast::SelfParamKind::MutRef => make.expr_ref(self_kw, true), }; - let param_count = l.params().count(); - let args = convert_param_list_to_arg_list(l).clone_for_update(); - let pos_after_l_paren = Position::after(args.l_paren_token()?); - if param_count > 0 { - // Add SelfParam and a TOKEN::COMMA - ted::insert_all_raw( - pos_after_l_paren, - vec![ - NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()), - NodeOrToken::Token(make::token(SyntaxKind::COMMA)), - NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)), - ], - ); - } else { - // Add SelfParam only - ted::insert_raw( - pos_after_l_paren, - NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()), - ); - } + // Build argument list with self expression prepended + let other_args = convert_param_list_to_arg_list(l); + let all_args: Vec = + std::iter::once(tail_expr_self).chain(other_args.args()).collect(); + let args = make.arg_list(all_args); - make::expr_call(make::expr_path(qualified_path), args) - } - None => { - make::expr_call(make::expr_path(qualified_path), convert_param_list_to_arg_list(l)) + make.expr_call(make.expr_path(qualified_path), args).into() } + None => make + .expr_call(make.expr_path(qualified_path), convert_param_list_to_arg_list(l)) + .into(), }, - None => make::expr_call( - make::expr_path(qualified_path), - convert_param_list_to_arg_list(make::param_list(None, Vec::new())), - ), - } - .clone_for_update(); + None => make + .expr_call( + make.expr_path(qualified_path), + convert_param_list_to_arg_list(make.param_list(None, Vec::new())), + ) + .into(), + }; - let body = make::block_expr(vec![], Some(call.into())).clone_for_update(); - let func = make::fn_( + let body = make.block_expr(vec![], Some(call)); + let func = make.fn_( item.attrs(), item.visibility(), item.name()?, @@ -757,35 +815,32 @@ fn func_assoc_item( item.const_token().is_some(), item.unsafe_token().is_some(), item.gen_token().is_some(), - ) - .clone_for_update(); + ); Some(AssocItem::Fn(func.indent(edit::IndentLevel(1)))) } fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option { - let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str()); - let qualified_path = qualified_path(qual_path_ty, path_expr_segment); - let ty = make::ty_path(qualified_path); + let make = SyntaxFactory::without_mappings(); + let path_expr_segment = make.path_from_text(item.name()?.to_string().as_str()); + let qualified_path = make.path_from_text(&format!("{qual_path_ty}::{path_expr_segment}")); + let ty = make.ty_path(qualified_path).into(); let ident = item.name()?.to_string(); - let alias = make::ty_alias( - item.attrs(), - ident.as_str(), - item.generic_param_list(), - None, - item.where_clause(), - Some((ty, None)), - ) - .indent(edit::IndentLevel(1)); + let alias = make + .ty_alias( + item.attrs(), + ident.as_str(), + item.generic_param_list(), + None, + item.where_clause(), + Some((ty, None)), + ) + .indent(edit::IndentLevel(1)); Some(AssocItem::TypeAlias(alias)) } -fn qualified_path(qual_path_ty: ast::Path, path_expr_seg: ast::Path) -> ast::Path { - make::path_from_text(&format!("{qual_path_ty}::{path_expr_seg}")) -} - #[cfg(test)] mod test { From b405964c94fd40e7783ac5b9a2b1954509437213 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Thu, 4 Dec 2025 23:30:01 +0800 Subject: [PATCH 09/39] minor: fmt & clippy --- .../crates/syntax/src/ast/syntax_factory/constructors.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 9847d639d613..8efad0368a9d 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -71,7 +71,6 @@ pub fn type_param( ast } - pub fn path_from_text(&self, text: &str) -> ast::Path { make::path_from_text(text).clone_for_update() } @@ -213,10 +212,10 @@ pub fn param_list( if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); - if let Some(self_param) = self_param { - if let Some(new_self_param) = ast.self_param() { - builder.map_node(self_param.syntax().clone(), new_self_param.syntax().clone()); - } + if let Some(self_param) = self_param + && let Some(new_self_param) = ast.self_param() + { + builder.map_node(self_param.syntax().clone(), new_self_param.syntax().clone()); } builder.map_children(input, ast.params().map(|p| p.syntax().clone())); builder.finish(&mut mapping); From 573d6a5b9b473407dee1a1a98efda03ccf5fc921 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:56:52 +0000 Subject: [PATCH 10/39] Bump jws in /editors/code Bumps and [jws](https://github.com/brianloveswords/node-jws). These dependencies needed to be updated together. Updates `jws` from 3.2.2 to 3.2.3 - [Release notes](https://github.com/brianloveswords/node-jws/releases) - [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianloveswords/node-jws/compare/v3.2.2...v3.2.3) Updates `jws` from 4.0.0 to 4.0.1 - [Release notes](https://github.com/brianloveswords/node-jws/releases) - [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianloveswords/node-jws/compare/v3.2.2...v3.2.3) --- updated-dependencies: - dependency-name: jws dependency-version: 3.2.3 dependency-type: indirect - dependency-name: jws dependency-version: 4.0.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../editors/code/package-lock.json | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index d49d19fbe10c..00d83e906848 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -1486,6 +1486,7 @@ "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", @@ -1869,6 +1870,7 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2838,6 +2840,7 @@ "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" } @@ -3319,6 +3322,7 @@ "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", @@ -4406,6 +4410,7 @@ "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" } @@ -4508,25 +4513,25 @@ } }, "node_modules/jsonwebtoken/node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "dev": true, "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "dev": true, "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -4544,25 +4549,25 @@ } }, "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dev": true, "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dev": true, "license": "MIT", "dependencies": { - "jwa": "^2.0.0", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -6673,6 +6678,7 @@ "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 e2d9d4981e6545ed28aa81452e864ceac5100020 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 5 Dec 2025 12:53:10 +0200 Subject: [PATCH 11/39] Do not create stale expressions in body lowering --- .../crates/hir-def/src/expr_store/lower.rs | 169 ++++++++---------- .../src/handlers/mutability_errors.rs | 6 +- 2 files changed, 75 insertions(+), 100 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index 26a50b53251f..77930c49ce9a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -434,7 +434,7 @@ pub struct ExprCollector<'db> { current_try_block_label: Option, label_ribs: Vec, - current_binding_owner: Option, + unowned_bindings: Vec, awaitable_context: Option, } @@ -536,7 +536,7 @@ pub fn new( current_try_block_label: None, is_lowering_coroutine: false, label_ribs: Vec::new(), - current_binding_owner: None, + unowned_bindings: Vec::new(), awaitable_context: None, current_block_legacy_macro_defs_count: FxHashMap::default(), outer_impl_trait: false, @@ -1062,12 +1062,10 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { Some(ast::BlockModifier::Const(_)) => { self.with_label_rib(RibKind::Constant, |this| { this.with_awaitable_block(Awaitable::No("constant block"), |this| { - let (result_expr_id, prev_binding_owner) = - this.initialize_binding_owner(syntax_ptr); - let inner_expr = this.collect_block(e); - this.store.exprs[result_expr_id] = Expr::Const(inner_expr); - this.current_binding_owner = prev_binding_owner; - result_expr_id + this.with_binding_owner(|this| { + let inner_expr = this.collect_block(e); + this.alloc_expr(Expr::Const(inner_expr), syntax_ptr) + }) }) }) } @@ -1278,64 +1276,65 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { } } ast::Expr::ClosureExpr(e) => self.with_label_rib(RibKind::Closure, |this| { - let (result_expr_id, prev_binding_owner) = - this.initialize_binding_owner(syntax_ptr); - let mut args = Vec::new(); - let mut arg_types = Vec::new(); - if let Some(pl) = e.param_list() { - let num_params = pl.params().count(); - args.reserve_exact(num_params); - arg_types.reserve_exact(num_params); - for param in pl.params() { - let pat = this.collect_pat_top(param.pat()); - let type_ref = - param.ty().map(|it| this.lower_type_ref_disallow_impl_trait(it)); - args.push(pat); - arg_types.push(type_ref); + this.with_binding_owner(|this| { + let mut args = Vec::new(); + let mut arg_types = Vec::new(); + if let Some(pl) = e.param_list() { + let num_params = pl.params().count(); + args.reserve_exact(num_params); + arg_types.reserve_exact(num_params); + for param in pl.params() { + let pat = this.collect_pat_top(param.pat()); + let type_ref = + param.ty().map(|it| this.lower_type_ref_disallow_impl_trait(it)); + args.push(pat); + arg_types.push(type_ref); + } } - } - let ret_type = e - .ret_type() - .and_then(|r| r.ty()) - .map(|it| this.lower_type_ref_disallow_impl_trait(it)); + let ret_type = e + .ret_type() + .and_then(|r| r.ty()) + .map(|it| this.lower_type_ref_disallow_impl_trait(it)); - let prev_is_lowering_coroutine = mem::take(&mut this.is_lowering_coroutine); - let prev_try_block_label = this.current_try_block_label.take(); + let prev_is_lowering_coroutine = mem::take(&mut this.is_lowering_coroutine); + let prev_try_block_label = this.current_try_block_label.take(); - let awaitable = if e.async_token().is_some() { - Awaitable::Yes - } else { - Awaitable::No("non-async closure") - }; - let body = - this.with_awaitable_block(awaitable, |this| this.collect_expr_opt(e.body())); - - let closure_kind = if this.is_lowering_coroutine { - let movability = if e.static_token().is_some() { - Movability::Static + let awaitable = if e.async_token().is_some() { + Awaitable::Yes } else { - Movability::Movable + Awaitable::No("non-async closure") }; - ClosureKind::Coroutine(movability) - } else if e.async_token().is_some() { - ClosureKind::Async - } else { - ClosureKind::Closure - }; - let capture_by = - if e.move_token().is_some() { CaptureBy::Value } else { CaptureBy::Ref }; - this.is_lowering_coroutine = prev_is_lowering_coroutine; - this.current_binding_owner = prev_binding_owner; - this.current_try_block_label = prev_try_block_label; - this.store.exprs[result_expr_id] = Expr::Closure { - args: args.into(), - arg_types: arg_types.into(), - ret_type, - body, - closure_kind, - capture_by, - }; - result_expr_id + let body = this + .with_awaitable_block(awaitable, |this| this.collect_expr_opt(e.body())); + + let closure_kind = if this.is_lowering_coroutine { + let movability = if e.static_token().is_some() { + Movability::Static + } else { + Movability::Movable + }; + ClosureKind::Coroutine(movability) + } else if e.async_token().is_some() { + ClosureKind::Async + } else { + ClosureKind::Closure + }; + let capture_by = + if e.move_token().is_some() { CaptureBy::Value } else { CaptureBy::Ref }; + this.is_lowering_coroutine = prev_is_lowering_coroutine; + this.current_try_block_label = prev_try_block_label; + this.alloc_expr( + Expr::Closure { + args: args.into(), + arg_types: arg_types.into(), + ret_type, + body, + closure_kind, + capture_by, + }, + syntax_ptr, + ) + }) }), ast::Expr::BinExpr(e) => { let op = e.op_kind(); @@ -1371,11 +1370,7 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { let initializer = self.collect_expr_opt(initializer); let repeat = self.with_label_rib(RibKind::Constant, |this| { if let Some(repeat) = repeat { - let syntax_ptr = AstPtr::new(&repeat); - this.collect_as_a_binding_owner_bad( - |this| this.collect_expr(repeat), - syntax_ptr, - ) + this.with_binding_owner(|this| this.collect_expr(repeat)) } else { this.missing_expr() } @@ -1632,31 +1627,13 @@ fn collect_tuple( } } - fn initialize_binding_owner( - &mut self, - syntax_ptr: AstPtr, - ) -> (ExprId, Option) { - let result_expr_id = self.alloc_expr(Expr::Missing, syntax_ptr); - let prev_binding_owner = self.current_binding_owner.take(); - self.current_binding_owner = Some(result_expr_id); - - (result_expr_id, prev_binding_owner) - } - - /// FIXME: This function is bad. It will produce a dangling `Missing` expr which wastes memory. Currently - /// it is used only for const blocks and repeat expressions, which are also hacky and ideally should have - /// their own body. Don't add more usage for this function so that we can remove this function after - /// separating those bodies. - fn collect_as_a_binding_owner_bad( - &mut self, - job: impl FnOnce(&mut ExprCollector<'_>) -> ExprId, - syntax_ptr: AstPtr, - ) -> ExprId { - let (id, prev_owner) = self.initialize_binding_owner(syntax_ptr); - let tmp = job(self); - self.store.exprs[id] = mem::replace(&mut self.store.exprs[tmp], Expr::Missing); - self.current_binding_owner = prev_owner; - id + fn with_binding_owner(&mut self, create_expr: impl FnOnce(&mut Self) -> ExprId) -> ExprId { + let prev_unowned_bindings_len = self.unowned_bindings.len(); + let expr_id = create_expr(self); + for binding in self.unowned_bindings.drain(prev_unowned_bindings_len..) { + self.store.binding_owners.insert(binding, expr_id); + } + expr_id } /// Desugar `try { ; }` into `': { ; ::std::ops::Try::from_output() }`, @@ -2368,11 +2345,7 @@ fn collect_pat(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> PatI ast::Pat::ConstBlockPat(const_block_pat) => { if let Some(block) = const_block_pat.block_expr() { let expr_id = self.with_label_rib(RibKind::Constant, |this| { - let syntax_ptr = AstPtr::new(&block.clone().into()); - this.collect_as_a_binding_owner_bad( - |this| this.collect_block(block), - syntax_ptr, - ) + this.with_binding_owner(|this| this.collect_block(block)) }); Pat::ConstBlock(expr_id) } else { @@ -3376,9 +3349,7 @@ fn alloc_binding( hygiene: HygieneId, ) -> BindingId { let binding = self.store.bindings.alloc(Binding { name, mode, problems: None, hygiene }); - if let Some(owner) = self.current_binding_owner { - self.store.binding_owners.insert(binding, owner); - } + self.unowned_bindings.push(binding); binding } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs index 18280a4addec..2887a32825db 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs @@ -995,6 +995,10 @@ fn f() { } "#, ); + // FIXME: There should be no "unused variable" here, and there should be a mutability error, + // but our MIR infra is horribly broken and due to the order in which expressions are lowered + // there is no `StorageLive` for `x` in the closure (in fact, `x` should not even be a variable + // of the closure, the environment should be, but as I said, our MIR infra is horribly broken). check_diagnostics( r#" //- minicore: copy, fn @@ -1003,8 +1007,8 @@ fn f() { || { || { let x = 2; + // ^ 💡 warn: unused variable || { || { x = 5; } } - //^^^^^ 💡 error: cannot mutate immutable variable `x` } } }; From 1b396d7b7beb1b53dabe91dfc417d16b96c7671e Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Fri, 5 Dec 2025 20:27:30 +0800 Subject: [PATCH 12/39] minor: add missing SyntaxFactory::assoc_item_list --- .../src/handlers/generate_delegate_trait.rs | 8 ++++---- .../src/ast/syntax_factory/constructors.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs index 03642756219a..ab350e975df1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -296,7 +296,7 @@ fn generate_impl( .collect() }); - let delegate = finalize_delegate(&delegate, assoc_items, false)?; + let delegate = finalize_delegate(&make, &delegate, assoc_items, false)?; let target_scope = ctx.sema.scope(strukt.strukt.syntax())?; let source_scope = ctx.sema.scope(bound_def.syntax())?; @@ -406,7 +406,7 @@ fn generate_impl( .collect() }); - finalize_delegate(&delegate, assoc_items, true) + finalize_delegate(&make, &delegate, assoc_items, true) } } } @@ -552,6 +552,7 @@ fn remove_useless_where_clauses(editor: &mut SyntaxEditor, delegate: &ast::Impl) /// 1. Replacing the assoc_item_list with new items (if any) /// 2. Removing useless where clauses fn finalize_delegate( + make: &SyntaxFactory, delegate: &ast::Impl, assoc_items: Option>, remove_where_clauses: bool, @@ -568,8 +569,7 @@ fn finalize_delegate( if let Some(items) = assoc_items && !items.is_empty() { - let new_assoc_item_list = - syntax::ast::make::assoc_item_list(Some(items)).clone_for_update(); + let new_assoc_item_list = make.assoc_item_list(items); if let Some(old_list) = delegate.assoc_item_list() { editor.replace(old_list.syntax(), new_assoc_item_list.syntax()); } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 8efad0368a9d..560dd77662ca 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -1454,6 +1454,23 @@ pub fn fn_( ast } + pub fn assoc_item_list( + &self, + items: impl IntoIterator, + ) -> ast::AssocItemList { + let (items, input) = iterator_input(items); + let items_vec: Vec<_> = items.into_iter().collect(); + let ast = make::assoc_item_list(Some(items_vec)).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(input, ast.assoc_items().map(|item| item.syntax().clone())); + builder.finish(&mut mapping); + } + + ast + } + pub fn attr_outer(&self, meta: ast::Meta) -> ast::Attr { let ast = make::attr_outer(meta.clone()).clone_for_update(); From 2122285ff9a52830c5458860ec36d7878bb91439 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 5 Dec 2025 23:15:58 +0800 Subject: [PATCH 13/39] Fix not complete `format!("{{{$0")` and underscore Example --- ```rust fn main() { let foobar = 1; format_args!("{{{f$0"); } ``` **Before this PR** No complete **After this PR** ```rust fn main() { let foobar = 1; format_args!("{{{foobar"); } ``` --- ```rust fn main() { let foo_bar = 1; format_args!("{foo_$0}"); } ``` **Before this PR** No complete **After this PR** ```rust fn main() { let foo_bar = 1; format_args!("{foo_bar}"); } ``` --- .../src/completions/format_string.rs | 94 +++++++++++++++++-- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs index 5ae65b05bc42..eaacd8d1a8fe 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs @@ -22,13 +22,8 @@ pub(crate) fn format_string( let cursor_in_lit = cursor - lit_start; let prefix = &original.text()[..cursor_in_lit.into()]; - let braces = prefix.char_indices().rev().skip_while(|&(_, c)| c.is_alphanumeric()).next_tuple(); - let brace_offset = match braces { - // escaped brace - Some(((_, '{'), (_, '{'))) => return, - Some(((idx, '{'), _)) => lit_start + TextSize::from(idx as u32 + 1), - _ => return, - }; + let Some(brace_offset) = unescaped_brace(prefix) else { return }; + let brace_offset = lit_start + brace_offset + TextSize::of('{'); let source_range = TextRange::new(brace_offset, cursor); ctx.locals.iter().sorted_by_key(|&(k, _)| k.clone()).for_each(|(name, _)| { @@ -59,6 +54,15 @@ pub(crate) fn format_string( }); } +fn unescaped_brace(prefix: &str) -> Option { + let is_ident_char = |ch: char| ch.is_alphanumeric() || ch == '_'; + prefix + .trim_end_matches(is_ident_char) + .strip_suffix('{') + .filter(|it| it.chars().rev().take_while(|&ch| ch == '{').count() % 2 == 0) + .map(|s| TextSize::new(s.len() as u32)) +} + #[cfg(test)] mod tests { use expect_test::expect; @@ -96,6 +100,82 @@ fn main() { ); } + #[test] + fn no_completion_after_escaped() { + check_no_kw( + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("{{f$0"); +} +"#, + expect![[]], + ); + check_no_kw( + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("some text {{{{f$0"); +} +"#, + expect![[]], + ); + } + + #[test] + fn completes_unescaped_after_escaped() { + check_edit( + "foobar", + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("{{{f$0"); +} +"#, + r#" +fn main() { + let foobar = 1; + format_args!("{{{foobar"); +} +"#, + ); + check_edit( + "foobar", + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("{{{{{f$0"); +} +"#, + r#" +fn main() { + let foobar = 1; + format_args!("{{{{{foobar"); +} +"#, + ); + check_edit( + "foobar", + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("}}{f$0"); +} +"#, + r#" +fn main() { + let foobar = 1; + format_args!("}}{foobar"); +} +"#, + ); + } + #[test] fn completes_locals() { check_edit( From 3e33ed8fbb1759f4e252bea9ec9d0c0118321a00 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 7 Dec 2025 20:49:22 +0800 Subject: [PATCH 14/39] Fix pub in enum variant field for no_such_field Example --- ```rust //- /main.rs mod foo; fn main() { foo::Foo::Variant { bar: 3, $0baz: false}; } //- /foo.rs pub enum Foo { Variant { bar: i32 } } ``` **Before this PR** ```rust pub enum Foo { Variant { bar: i32, pub(crate) baz: bool } } ``` **After this PR** ```rust pub enum Foo { Variant { bar: i32, baz: bool } } ``` --- .../src/handlers/no_such_field.rs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs index 0edab5e0b3b1..bcfe3a8aa5ce 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs @@ -102,7 +102,8 @@ fn missing_record_expr_field_fixes( let indent = IndentLevel::from_node(last_field_syntax); let mut new_field = new_field.to_string(); - if usage_file_id != def_file_id { + // FIXME: check submodule instead of FileId + if usage_file_id != def_file_id && !matches!(def_id, hir::VariantDef::Variant(_)) { new_field = format!("pub(crate) {new_field}"); } new_field = format!("\n{indent}{new_field}"); @@ -357,6 +358,34 @@ pub struct Foo { ) } + #[test] + fn test_add_enum_variant_field_in_other_file_from_usage() { + check_fix( + r#" +//- /main.rs +mod foo; + +fn main() { + foo::Foo::Variant { bar: 3, $0baz: false}; +} +//- /foo.rs +pub enum Foo { + Variant { + bar: i32 + } +} +"#, + r#" +pub enum Foo { + Variant { + bar: i32, + baz: bool + } +} +"#, + ) + } + #[test] fn test_tuple_field_on_record_struct() { check_no_fix( From aec8ce4693a3bfca7ec09cd94386689b730743f8 Mon Sep 17 00:00:00 2001 From: benodiwal Date: Mon, 8 Dec 2025 04:41:05 +0530 Subject: [PATCH 15/39] fix: fixed Impl display to show trait generic args --- .../rust-analyzer/crates/hir/src/display.rs | 7 ++- .../crates/ide/src/hover/tests.rs | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index 07e61a83c440..afdac484a117 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -192,9 +192,10 @@ fn write_impl_header<'db>(impl_: &Impl, f: &mut HirFormatter<'_, 'db>) -> Result let def_id = GenericDefId::ImplId(impl_.id); write_generic_params(def_id, f)?; - if let Some(trait_) = impl_.trait_(db) { - let trait_data = db.trait_signature(trait_.id); - write!(f, " {} for", trait_data.name.display(db, f.edition()))?; + if let Some(trait_ref) = impl_.trait_ref(db) { + f.write_char(' ')?; + trait_ref.hir_fmt(f)?; + f.write_str(" for")?; } f.write_char(' ')?; diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index 071eacf6604c..5330b7eb9941 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11169,3 +11169,60 @@ fn foo() { "#]], ); } + +#[test] +fn hover_trait_impl_shows_generic_args() { + // Single generic arg + check( + r#" +trait Foo { + fn foo(&self) {} +} + +impl Foo<()> for T { + fn fo$0o(&self) {} +} + +fn bar() { + ().foo(); +} +"#, + expect![[r#" + *foo* + + ```rust + ra_test_fixture + ``` + + ```rust + impl Foo<()> for T + fn foo(&self) + ``` + "#]], + ); + + // Multiple generic args + check( + r#" +trait Foo { + fn foo(&self) {} +} + +impl Foo for T { + fn fo$0o(&self) {} +} +"#, + expect![[r#" + *foo* + + ```rust + ra_test_fixture + ``` + + ```rust + impl Foo for T + fn foo(&self) + ``` + "#]], + ); +} From d40aad9c2daa6d9b4d0475747cfe5183ff76e187 Mon Sep 17 00:00:00 2001 From: benodiwal Date: Mon, 8 Dec 2025 11:57:31 +0530 Subject: [PATCH 16/39] fix: updated to use hir-def representation --- src/tools/rust-analyzer/crates/hir/src/display.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index afdac484a117..d0d8c4877d21 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -192,9 +192,10 @@ fn write_impl_header<'db>(impl_: &Impl, f: &mut HirFormatter<'_, 'db>) -> Result let def_id = GenericDefId::ImplId(impl_.id); write_generic_params(def_id, f)?; - if let Some(trait_ref) = impl_.trait_ref(db) { + let impl_data = db.impl_signature(impl_.id); + if let Some(target_trait) = &impl_data.target_trait { f.write_char(' ')?; - trait_ref.hir_fmt(f)?; + hir_display_with_store(&impl_data.store[target_trait.path], &impl_data.store).hir_fmt(f)?; f.write_str(" for")?; } From d828665b5fdb9aeab1ff8be518923818f7be2932 Mon Sep 17 00:00:00 2001 From: Urgau <3616612+Urgau@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:19:22 +0100 Subject: [PATCH 17/39] Remove `[no-mentions]` handler in our triagebot config https://github.blog/changelog/2025-11-07-removing-notifications-for-mentions-in-commit-messages/ --- src/tools/rust-analyzer/triagebot.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tools/rust-analyzer/triagebot.toml b/src/tools/rust-analyzer/triagebot.toml index c9862495bc0c..ac4efd0a24bc 100644 --- a/src/tools/rust-analyzer/triagebot.toml +++ b/src/tools/rust-analyzer/triagebot.toml @@ -25,6 +25,3 @@ labels = ["has-merge-commits", "S-waiting-on-author"] # Canonicalize issue numbers to avoid closing the wrong issue when upstreaming this subtree [canonicalize-issue-links] - -# Prevents mentions in commits to avoid users being spammed -[no-mentions] From ef9928781a40b84fa3e768ea0da393e47352bfee Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 9 Dec 2025 12:34:35 +0800 Subject: [PATCH 18/39] Add complex tests for assist generate_fn_type_alias --- .../src/handlers/generate_fn_type_alias.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs index 0b7eca2290f6..7fd94b4bedc8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs @@ -269,6 +269,22 @@ fn generate_fn_alias_unnamed_generics_bounds() { ); } + #[test] + fn generate_fn_alias_unnamed_complex_types() { + check_assist_by_label( + generate_fn_type_alias, + r#" +fn fo$0o(x: Vec) {} +"#, + r#" +type ${0:FooFn} = fn(Vec); + +fn foo(x: Vec) {} +"#, + ParamStyle::Unnamed.label(), + ); + } + #[test] fn generate_fn_alias_unnamed_self() { check_assist_by_label( @@ -405,6 +421,22 @@ fn generate_fn_alias_named_generics_bounds() { ); } + #[test] + fn generate_fn_alias_named_complex_types() { + check_assist_by_label( + generate_fn_type_alias, + r#" +fn fo$0o(x: Vec) {} +"#, + r#" +type ${0:FooFn} = fn(x: Vec); + +fn foo(x: Vec) {} +"#, + ParamStyle::Named.label(), + ); + } + #[test] fn generate_fn_alias_named_self() { check_assist_by_label( From 676550b308e78097dcf852747bedfadca1dc91ea Mon Sep 17 00:00:00 2001 From: benodiwal Date: Tue, 9 Dec 2025 18:49:55 +0530 Subject: [PATCH 19/39] fix: resolve const generic param-env panic in type projection --- .../hir-ty/src/infer/closure/analysis.rs | 2 + .../rust-analyzer/crates/hir-ty/src/layout.rs | 4 +- .../rust-analyzer/crates/hir-ty/src/mir.rs | 3 +- .../crates/hir-ty/src/mir/borrowck.rs | 11 +++- .../crates/hir-ty/src/mir/eval.rs | 1 + .../crates/hir-ty/src/mir/lower/tests.rs | 59 +++++++++++++++++++ 6 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs index d6d63891bb06..308c01865ae3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs @@ -43,6 +43,7 @@ fn ty(&self, ctx: &mut InferenceContext<'_, 'db>) -> Ty<'db> { for p in &self.projections { ty = p.projected_ty( &ctx.table.infer_ctxt, + ctx.table.param_env, ty, |_, _, _| { unreachable!("Closure field only happens in MIR"); @@ -839,6 +840,7 @@ fn restrict_precision_for_unsafe(&mut self) { for (i, p) in capture.place.projections.iter().enumerate() { ty = p.projected_ty( &self.table.infer_ctxt, + self.table.param_env, ty, |_, _, _| { unreachable!("Closure field only happens in MIR"); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs index 565063fb138c..4b20d6eb3220 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs @@ -25,7 +25,7 @@ consteval::try_const_usize, db::HirDatabase, next_solver::{ - DbInterner, GenericArgs, ParamEnv, Ty, TyKind, TypingMode, + DbInterner, GenericArgs, Ty, TyKind, TypingMode, infer::{DbInternerInferExt, traits::ObligationCause}, }, }; @@ -170,7 +170,7 @@ pub fn layout_of_ty_query<'db>( let cx = LayoutCx::new(dl); let infer_ctxt = interner.infer_ctxt().build(TypingMode::PostAnalysis); let cause = ObligationCause::dummy(); - let ty = infer_ctxt.at(&cause, ParamEnv::empty()).deeply_normalize(ty).unwrap_or(ty); + let ty = infer_ctxt.at(&cause, trait_env.param_env).deeply_normalize(ty).unwrap_or(ty); let result = match ty.kind() { TyKind::Adt(def, args) => { match def.inner().id { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs index 3cafb6aa2503..836c20a43348 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs @@ -157,6 +157,7 @@ impl<'db, V: PartialEq> ProjectionElem<'db, V> { pub fn projected_ty( &self, infcx: &InferCtxt<'db>, + env: ParamEnv<'db>, mut base: Ty<'db>, closure_field: impl FnOnce(InternedClosureId, GenericArgs<'db>, usize) -> Ty<'db>, krate: Crate, @@ -173,8 +174,6 @@ pub fn projected_ty( if matches!(base.kind(), TyKind::Alias(..)) { let mut ocx = ObligationCtxt::new(infcx); - // FIXME: we should get this from caller - let env = ParamEnv::empty(); match ocx.structurally_normalize_ty(&ObligationCause::dummy(), env, base) { Ok(it) => base = it, Err(_) => return Ty::new_error(interner, ErrorGuaranteed), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs index 4d76a9f3fb3d..b39c9bc06559 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs @@ -106,7 +106,7 @@ pub fn borrowck_query<'db>( // FIXME(next-solver): Opaques. let infcx = interner.infer_ctxt().build(typing_mode); res.push(BorrowckResult { - mutability_of_locals: mutability_of_locals(&infcx, &body), + mutability_of_locals: mutability_of_locals(&infcx, env, &body), moved_out_of_ref: moved_out_of_ref(&infcx, env, &body), partially_moved: partially_moved(&infcx, env, &body), borrow_regions: borrow_regions(db, &body), @@ -146,6 +146,7 @@ fn moved_out_of_ref<'db>( } ty = proj.projected_ty( infcx, + env, ty, make_fetch_closure_field(db), body.owner.module(db).krate(db), @@ -242,6 +243,7 @@ fn partially_moved<'db>( for proj in p.projection.lookup(&body.projection_store) { ty = proj.projected_ty( infcx, + env, ty, make_fetch_closure_field(db), body.owner.module(db).krate(db), @@ -374,6 +376,7 @@ enum ProjectionCase { fn place_case<'db>( infcx: &InferCtxt<'db>, + env: ParamEnv<'db>, body: &MirBody<'db>, lvalue: &Place<'db>, ) -> ProjectionCase { @@ -395,6 +398,7 @@ fn place_case<'db>( } ty = proj.projected_ty( infcx, + env, ty, make_fetch_closure_field(db), body.owner.module(db).krate(db), @@ -535,6 +539,7 @@ fn record_usage_for_operand<'db>( fn mutability_of_locals<'db>( infcx: &InferCtxt<'db>, + env: ParamEnv<'db>, body: &MirBody<'db>, ) -> ArenaMap, MutabilityReason> { let db = infcx.interner.db; @@ -547,7 +552,7 @@ fn mutability_of_locals<'db>( for statement in &block.statements { match &statement.kind { StatementKind::Assign(place, value) => { - match place_case(infcx, body, place) { + match place_case(infcx, env, body, place) { ProjectionCase::Direct => { if ever_init_map.get(place.local).copied().unwrap_or_default() { push_mut_span(place.local, statement.span, &mut result); @@ -596,7 +601,7 @@ fn mutability_of_locals<'db>( }, p, ) = value - && place_case(infcx, body, p) != ProjectionCase::Indirect + && place_case(infcx, env, body, p) != ProjectionCase::Indirect { push_mut_span(p.local, statement.span, &mut result); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index 35b45174c209..3b4913cae3fb 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -722,6 +722,7 @@ fn projected_ty(&self, ty: Ty<'db>, proj: PlaceElem<'db>) -> Ty<'db> { let (ty, proj) = pair; let r = proj.projected_ty( &self.infcx, + self.param_env.param_env, ty, |c, subst, f| { let InternedClosure(def, _) = self.db.lookup_intern_closure(c); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/tests.rs index 357f617a21e1..73399dab7fbd 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/tests.rs @@ -1,3 +1,4 @@ +use hir_def::DefWithBodyId; use test_fixture::WithFixture; use crate::{db::HirDatabase, setup_tracing, test_db::TestDB}; @@ -49,3 +50,61 @@ fn foo() { "#, ); } + +fn check_borrowck(#[rust_analyzer::rust_fixture] ra_fixture: &str) { + let _tracing = setup_tracing(); + let (db, file_ids) = TestDB::with_many_files(ra_fixture); + crate::attach_db(&db, || { + let file_id = *file_ids.last().unwrap(); + let module_id = db.module_for_file(file_id.file_id(&db)); + let def_map = module_id.def_map(&db); + let scope = &def_map[module_id].scope; + + let mut bodies: Vec = Vec::new(); + + for decl in scope.declarations() { + if let hir_def::ModuleDefId::FunctionId(f) = decl { + bodies.push(f.into()); + } + } + + for impl_id in scope.impls() { + let impl_items = impl_id.impl_items(&db); + for (_, item) in impl_items.items.iter() { + if let hir_def::AssocItemId::FunctionId(f) = item { + bodies.push((*f).into()); + } + } + } + + for body in bodies { + let _ = db.borrowck(body); + } + }) +} + +#[test] +fn regression_21173_const_generic_impl_with_assoc_type() { + check_borrowck( + r#" +pub trait Tr { + type Assoc; + fn f(&self, handle: Self::Assoc) -> i32; +} + +pub struct ConstGeneric; + +impl Tr for &ConstGeneric { + type Assoc = AssocTy; + + fn f(&self, a: Self::Assoc) -> i32 { + a.x + } +} + +pub struct AssocTy { + x: i32, +} + "#, + ); +} From fc5a6832eb6e2498bfcc56ec95391841ed06fca7 Mon Sep 17 00:00:00 2001 From: Cole Kauder-McMurrich Date: Wed, 10 Dec 2025 02:14:43 -0500 Subject: [PATCH 20/39] fix: is_transmutable always panicking --- .../rust-analyzer/crates/hir-ty/src/next_solver/solver.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs index b5ed770e161d..859e26e37641 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs @@ -225,7 +225,9 @@ fn is_transmutable( _src: Ty<'db>, _assume: ::Const, ) -> Result { - unimplemented!() + // It's better to return some value while not fully implement + // then panic in the mean time + Ok(Certainty::Yes) } fn evaluate_const( From 225c5e08661ac58ddf054611f2d702969cce0e9e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 9 Dec 2025 09:01:12 +0100 Subject: [PATCH 21/39] Revert "Turn `BlockLoc` into a tracked struct" This reverts commit 64cabd87be97bb1fa9ac15a77e8fba64d06d426b. --- .../rust-analyzer/crates/hir-def/src/db.rs | 15 +++-- .../crates/hir-def/src/expr_store/lower.rs | 4 +- .../src/expr_store/tests/body/block.rs | 52 +-------------- .../crates/hir-def/src/find_path.rs | 66 +++++++++---------- .../crates/hir-def/src/import_map.rs | 6 +- .../crates/hir-def/src/item_tree.rs | 8 +-- .../rust-analyzer/crates/hir-def/src/lib.rs | 61 ++++++----------- .../crates/hir-def/src/nameres.rs | 32 ++++----- .../crates/hir-def/src/resolver.rs | 4 +- .../crates/hir-def/src/visibility.rs | 30 ++++----- .../rust-analyzer/crates/hir-ty/src/infer.rs | 23 ++++--- .../crates/hir-ty/src/method_resolution.rs | 49 +++++++------- src/tools/rust-analyzer/crates/hir/src/lib.rs | 4 +- .../hir/src/semantics/child_by_source.rs | 2 +- 14 files changed, 144 insertions(+), 212 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs index 98df8d0ff445..ccd4bc9be84a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -8,12 +8,12 @@ use triomphe::Arc; use crate::{ - AssocItemId, AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, EnumVariantId, - EnumVariantLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, ExternCrateLoc, FunctionId, - FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalFieldId, Macro2Id, Macro2Loc, MacroExpander, - MacroId, MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, ProcMacroId, ProcMacroLoc, StaticId, - StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, - UnionLoc, UseId, UseLoc, VariantId, + AssocItemId, AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, + EnumVariantId, EnumVariantLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, ExternCrateLoc, + FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalFieldId, Macro2Id, Macro2Loc, + MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, ProcMacroId, + ProcMacroLoc, StaticId, StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, + TypeAliasLoc, UnionId, UnionLoc, UseId, UseLoc, VariantId, attrs::AttrFlags, expr_store::{ Body, BodySourceMap, ExpressionStore, ExpressionStoreSourceMap, scope::ExprScopes, @@ -82,6 +82,9 @@ pub trait InternDatabase: RootQueryDb { #[salsa::interned] fn intern_macro_rules(&self, loc: MacroRulesLoc) -> MacroRulesId; // endregion: items + + #[salsa::interned] + fn intern_block(&self, loc: BlockLoc) -> BlockId; } #[query_group::query_group] diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index f374dd2cc9ef..df4cff0b8a17 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -31,7 +31,7 @@ use tt::TextRange; use crate::{ - AdtId, BlockId, BlockIdLt, DefWithBodyId, FunctionId, GenericDefId, ImplId, MacroId, + AdtId, BlockId, BlockLoc, DefWithBodyId, FunctionId, GenericDefId, ImplId, MacroId, ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro, attrs::AttrFlags, builtin_type::BuiltinUint, @@ -2114,7 +2114,7 @@ fn collect_block_( ) -> ExprId { let block_id = self.expander.ast_id_map().ast_id_for_block(&block).map(|file_local_id| { let ast_id = self.expander.in_file(file_local_id); - unsafe { BlockIdLt::new(self.db, ast_id, self.module).to_static() } + self.db.intern_block(BlockLoc { ast_id, module: self.module }) }); let (module, def_map) = diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs index 2d60f44092c5..836a079e777f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs @@ -195,55 +195,9 @@ fn f() { Id(1c00), ), block: Some( - BlockIdLt { - [salsa id]: Id(3c01), - ast_id: InFileWrapper { - file_id: FileId( - EditionedFileIdData { - editioned_file_id: EditionedFileId( - 0, - Edition2024, - ), - krate: Crate( - Id(1c00), - ), - }, - ), - value: FileAstId::(ErasedFileAstId { kind: BlockExpr, index: 0, hash: F9BF }), - }, - module: ModuleIdLt { - [salsa id]: Id(3002), - krate: Crate( - Id(1c00), - ), - block: Some( - BlockIdLt { - [salsa id]: Id(3c00), - ast_id: InFileWrapper { - file_id: FileId( - EditionedFileIdData { - editioned_file_id: EditionedFileId( - 0, - Edition2024, - ), - krate: Crate( - Id(1c00), - ), - }, - ), - value: FileAstId::(ErasedFileAstId { kind: BlockExpr, index: 0, hash: C181 }), - }, - module: ModuleIdLt { - [salsa id]: Id(3000), - krate: Crate( - Id(1c00), - ), - block: None, - }, - }, - ), - }, - }, + BlockId( + 3c01, + ), ), }"#]], ); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs index cc0594f00d61..5d1cac8e93c4 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs @@ -12,7 +12,7 @@ use rustc_hash::FxHashSet; use crate::{ - FindPathConfig, ModuleDefId, ModuleIdLt, + FindPathConfig, ModuleDefId, ModuleId, db::DefDatabase, item_scope::ItemInNs, nameres::DefMap, @@ -24,7 +24,7 @@ pub fn find_path( db: &dyn DefDatabase, item: ItemInNs, - from: ModuleIdLt<'_>, + from: ModuleId, mut prefix_kind: PrefixKind, ignore_local_imports: bool, mut cfg: FindPathConfig, @@ -102,14 +102,14 @@ struct FindPathCtx<'db> { cfg: FindPathConfig, ignore_local_imports: bool, is_std_item: bool, - from: ModuleIdLt<'db>, + from: ModuleId, from_crate: Crate, - crate_root: ModuleIdLt<'db>, + crate_root: ModuleId, from_def_map: &'db DefMap, fuel: Cell, } -/// Attempts to find a path to refer to the given `item` visible from the `from` ModuleIdLt<'_> +/// Attempts to find a path to refer to the given `item` visible from the `from` ModuleId fn find_path_inner(ctx: &FindPathCtx<'_>, item: ItemInNs, max_len: usize) -> Option { // - if the item is a module, jump straight to module search if !ctx.is_std_item @@ -157,10 +157,10 @@ fn find_path_inner(ctx: &FindPathCtx<'_>, item: ItemInNs, max_len: usize) -> Opt } #[tracing::instrument(skip_all)] -fn find_path_for_module<'db>( - ctx: &'db FindPathCtx<'db>, - visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>, - module_id: ModuleIdLt<'db>, +fn find_path_for_module( + ctx: &FindPathCtx<'_>, + visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>, + module_id: ModuleId, maybe_extern: bool, max_len: usize, ) -> Option { @@ -217,7 +217,7 @@ fn find_path_for_module<'db>( ctx.db, ctx.from_def_map, ctx.from, - ItemInNs::Types(unsafe { module_id.to_static() }.into()), + ItemInNs::Types(module_id.into()), ctx.ignore_local_imports, ); if let Some(scope_name) = scope_name { @@ -244,7 +244,7 @@ fn find_path_for_module<'db>( } // - if the module is in the prelude, return it by that path - let item = ItemInNs::Types(unsafe { module_id.to_static() }.into()); + let item = ItemInNs::Types(module_id.into()); if let Some(choice) = find_in_prelude(ctx.db, ctx.from_def_map, item, ctx.from) { return Some(choice); } @@ -257,10 +257,10 @@ fn find_path_for_module<'db>( best_choice } -fn find_in_scope<'db>( - db: &'db dyn DefDatabase, +fn find_in_scope( + db: &dyn DefDatabase, def_map: &DefMap, - from: ModuleIdLt<'db>, + from: ModuleId, item: ItemInNs, ignore_local_imports: bool, ) -> Option { @@ -278,7 +278,7 @@ fn find_in_prelude( db: &dyn DefDatabase, local_def_map: &DefMap, item: ItemInNs, - from: ModuleIdLt<'_>, + from: ModuleId, ) -> Option { let (prelude_module, _) = local_def_map.prelude()?; let prelude_def_map = prelude_module.def_map(db); @@ -310,8 +310,8 @@ fn find_in_prelude( fn is_kw_kind_relative_to_from( db: &dyn DefDatabase, def_map: &DefMap, - item: ModuleIdLt<'_>, - from: ModuleIdLt<'_>, + item: ModuleId, + from: ModuleId, ) -> Option { if item.krate(db) != from.krate(db) || item.block(db).is_some() || from.block(db).is_some() { return None; @@ -332,9 +332,9 @@ fn is_kw_kind_relative_to_from( } #[tracing::instrument(skip_all)] -fn calculate_best_path<'db>( - ctx: &'db FindPathCtx<'db>, - visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>, +fn calculate_best_path( + ctx: &FindPathCtx<'_>, + visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>, item: ItemInNs, max_len: usize, best_choice: &mut Option, @@ -372,9 +372,9 @@ fn calculate_best_path<'db>( } } -fn find_in_sysroot<'db>( - ctx: &'db FindPathCtx<'db>, - visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>, +fn find_in_sysroot( + ctx: &FindPathCtx<'_>, + visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>, item: ItemInNs, max_len: usize, best_choice: &mut Option, @@ -418,9 +418,9 @@ fn find_in_sysroot<'db>( }); } -fn find_in_dep<'db>( - ctx: &'db FindPathCtx<'db>, - visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>, +fn find_in_dep( + ctx: &FindPathCtx<'_>, + visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>, item: ItemInNs, max_len: usize, best_choice: &mut Option, @@ -461,9 +461,9 @@ fn find_in_dep<'db>( } } -fn calculate_best_path_local<'db>( - ctx: &'db FindPathCtx<'db>, - visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>, +fn calculate_best_path_local( + ctx: &FindPathCtx<'_>, + visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>, item: ItemInNs, max_len: usize, best_choice: &mut Option, @@ -558,11 +558,11 @@ fn path_kind_len(kind: PathKind) -> usize { } /// Finds locations in `from.krate` from which `item` can be imported by `from`. -fn find_local_import_locations<'db>( - ctx: &'db FindPathCtx<'db>, +fn find_local_import_locations( + ctx: &FindPathCtx<'_>, item: ItemInNs, - visited_modules: &mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>, - mut cb: impl FnMut(&mut FxHashSet<(ItemInNs, ModuleIdLt<'db>)>, &Name, ModuleIdLt<'db>), + visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>, + mut cb: impl FnMut(&mut FxHashSet<(ItemInNs, ModuleId)>, &Name, ModuleId), ) { let _p = tracing::info_span!("find_local_import_locations").entered(); let db = ctx.db; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs index 433aead77adb..6c5d226cac1b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs @@ -496,7 +496,7 @@ mod tests { use expect_test::{Expect, expect}; use test_fixture::WithFixture; - use crate::{ItemContainerId, Lookup, ModuleIdLt, nameres::assoc::TraitItems, test_db::TestDB}; + use crate::{ItemContainerId, Lookup, nameres::assoc::TraitItems, test_db::TestDB}; use super::*; @@ -628,8 +628,8 @@ fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { expect.assert_eq(&actual) } - fn render_path<'db>(db: &'db dyn DefDatabase, info: &ImportInfo) -> String { - let mut module: ModuleIdLt<'db> = info.container; + fn render_path(db: &dyn DefDatabase, info: &ImportInfo) -> String { + let mut module = info.container; let mut segments = vec![&info.name]; let def_map = module.def_map(db); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs index 1228d1999bcb..2a104fff2b92 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs @@ -58,7 +58,7 @@ use thin_vec::ThinVec; use triomphe::Arc; -use crate::{BlockId, db::DefDatabase}; +use crate::{BlockId, Lookup, db::DefDatabase}; pub(crate) use crate::item_tree::{ attrs::*, @@ -150,10 +150,10 @@ pub(crate) fn block_item_tree_query(db: &dyn DefDatabase, block: BlockId) -> Arc let _p = tracing::info_span!("block_item_tree_query", ?block).entered(); static EMPTY: OnceLock> = OnceLock::new(); - let ast_id = block.ast_id(db); - let block = ast_id.to_node(db); + let loc = block.lookup(db); + let block = loc.ast_id.to_node(db); - let ctx = lower::Ctx::new(db, ast_id.file_id); + let ctx = lower::Ctx::new(db, loc.ast_id.file_id); let mut item_tree = ctx.lower_block(&block); let ItemTree { top_level, top_attrs, attrs, vis, big_data, small_data } = &item_tree; if small_data.is_empty() 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 e58cb7bad705..97af8ad93def 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -420,31 +420,13 @@ pub struct ProcMacroLoc { impl_intern!(ProcMacroId, ProcMacroLoc, intern_proc_macro, lookup_intern_proc_macro); impl_loc!(ProcMacroLoc, id: Fn, container: ModuleId); -#[salsa_macros::tracked(debug)] -#[derive(PartialOrd, Ord)] -pub struct BlockIdLt<'db> { +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct BlockLoc { pub ast_id: AstId, /// The containing module. - pub module: ModuleIdLt<'db>, -} -pub type BlockId = BlockIdLt<'static>; - -impl BlockIdLt<'_> { - /// # Safety - /// - /// The caller must ensure that the `ModuleId` is not leaked outside of query computations. - pub unsafe fn to_static(self) -> BlockId { - unsafe { std::mem::transmute(self) } - } -} -impl BlockId { - /// # Safety - /// - /// The caller must ensure that the `BlockId` comes from the given database. - pub unsafe fn to_db<'db>(self, _db: &'db dyn DefDatabase) -> BlockIdLt<'db> { - unsafe { std::mem::transmute(self) } - } + pub module: ModuleId, } +impl_intern!(BlockId, BlockLoc, intern_block, lookup_intern_block); #[salsa_macros::tracked(debug)] #[derive(PartialOrd, Ord)] @@ -454,26 +436,34 @@ pub struct ModuleIdLt<'db> { /// If this `ModuleId` was derived from a `DefMap` for a block expression, this stores the /// `BlockId` of that block expression. If `None`, this module is part of the crate-level /// `DefMap` of `krate`. - pub block: Option>, + pub block: Option, } pub type ModuleId = ModuleIdLt<'static>; -impl<'db> ModuleIdLt<'db> { +impl ModuleIdLt<'_> { /// # Safety /// /// The caller must ensure that the `ModuleId` is not leaked outside of query computations. pub unsafe fn to_static(self) -> ModuleId { unsafe { std::mem::transmute(self) } } +} +impl ModuleId { + /// # Safety + /// + /// The caller must ensure that the `ModuleId` comes from the given database. + pub unsafe fn to_db<'db>(self, _db: &'db dyn DefDatabase) -> ModuleIdLt<'db> { + unsafe { std::mem::transmute(self) } + } - pub fn def_map(self, db: &'db dyn DefDatabase) -> &'db DefMap { + pub fn def_map(self, db: &dyn DefDatabase) -> &DefMap { match self.block(db) { Some(block) => block_def_map(db, block), None => crate_def_map(db, self.krate(db)), } } - pub(crate) fn local_def_map(self, db: &'db dyn DefDatabase) -> (&'db DefMap, &'db LocalDefMap) { + pub(crate) fn local_def_map(self, db: &dyn DefDatabase) -> (&DefMap, &LocalDefMap) { match self.block(db) { Some(block) => (block_def_map(db, block), self.only_local_def_map(db)), None => { @@ -483,15 +473,15 @@ pub(crate) fn local_def_map(self, db: &'db dyn DefDatabase) -> (&'db DefMap, &'d } } - pub(crate) fn only_local_def_map(self, db: &'db dyn DefDatabase) -> &'db LocalDefMap { + pub(crate) fn only_local_def_map(self, db: &dyn DefDatabase) -> &LocalDefMap { crate_local_def_map(db, self.krate(db)).local(db) } - pub fn crate_def_map(self, db: &'db dyn DefDatabase) -> &'db DefMap { + pub fn crate_def_map(self, db: &dyn DefDatabase) -> &DefMap { crate_def_map(db, self.krate(db)) } - pub fn name(self, db: &'db dyn DefDatabase) -> Option { + pub fn name(self, db: &dyn DefDatabase) -> Option { let def_map = self.def_map(db); let parent = def_map[self].parent?; def_map[parent].children.iter().find_map(|(name, module_id)| { @@ -501,24 +491,15 @@ pub fn name(self, db: &'db dyn DefDatabase) -> Option { /// Returns the module containing `self`, either the parent `mod`, or the module (or block) containing /// the block, if `self` corresponds to a block expression. - pub fn containing_module(self, db: &'db dyn DefDatabase) -> Option> { + pub fn containing_module(self, db: &dyn DefDatabase) -> Option { self.def_map(db).containing_module(self) } - pub fn is_block_module(self, db: &'db dyn DefDatabase) -> bool { + pub fn is_block_module(self, db: &dyn DefDatabase) -> bool { self.block(db).is_some() && self.def_map(db).root_module_id() == self } } -impl ModuleId { - /// # Safety - /// - /// The caller must ensure that the `ModuleId` comes from the given database. - pub unsafe fn to_db<'db>(self, _db: &'db dyn DefDatabase) -> ModuleIdLt<'db> { - unsafe { std::mem::transmute(self) } - } -} - impl HasModule for ModuleId { #[inline] fn module(&self, _db: &dyn DefDatabase) -> ModuleId { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index a85237662c43..3f29619bcb62 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -75,7 +75,7 @@ use tt::TextRange; use crate::{ - AstId, BlockId, BlockIdLt, ExternCrateId, FunctionId, FxIndexMap, Lookup, MacroCallStyles, + AstId, BlockId, BlockLoc, ExternCrateId, FunctionId, FxIndexMap, Lookup, MacroCallStyles, MacroExpander, MacroId, ModuleId, ModuleIdLt, ProcMacroId, UseId, db::DefDatabase, item_scope::{BuiltinShadowMode, ItemScope}, @@ -247,12 +247,12 @@ struct BlockInfo { parent: ModuleId, } -impl std::ops::Index> for DefMap { +impl std::ops::Index for DefMap { type Output = ModuleData; - fn index(&self, id: ModuleIdLt<'_>) -> &ModuleData { + fn index(&self, id: ModuleId) -> &ModuleData { self.modules - .get(&unsafe { id.to_static() }) + .get(&id) .unwrap_or_else(|| panic!("ModuleId not found in ModulesMap {:#?}: {id:#?}", self.root)) } } @@ -400,10 +400,8 @@ pub(crate) fn crate_local_def_map(db: &dyn DefDatabase, crate_id: Crate) -> DefM } #[salsa_macros::tracked(returns(ref))] -pub fn block_def_map<'db>(db: &'db dyn DefDatabase, block_id: BlockIdLt<'db>) -> DefMap { - let block_id = unsafe { block_id.to_static() }; - let ast_id = block_id.ast_id(db); - let module = unsafe { block_id.module(db).to_static() }; +pub fn block_def_map(db: &dyn DefDatabase, block_id: BlockId) -> DefMap { + let BlockLoc { ast_id, module } = block_id.lookup(db); let visibility = Visibility::Module(module, VisibilityExplicitness::Implicit); let module_data = @@ -559,7 +557,7 @@ pub fn parent(&self) -> Option { /// Returns the module containing `local_mod`, either the parent `mod`, or the module (or block) containing /// the block, if `self` corresponds to a block expression. - pub fn containing_module(&self, local_mod: ModuleIdLt<'_>) -> Option { + pub fn containing_module(&self, local_mod: ModuleId) -> Option { match self[local_mod].parent { Some(parent) => Some(parent), None => self.block.map(|BlockInfo { parent, .. }| parent), @@ -664,11 +662,11 @@ pub(crate) fn resolve_path_locally( /// /// If `f` returns `Some(val)`, iteration is stopped and `Some(val)` is returned. If `f` returns /// `None`, iteration continues. - pub(crate) fn with_ancestor_maps<'db, T>( + pub(crate) fn with_ancestor_maps( &self, - db: &'db dyn DefDatabase, - local_mod: ModuleIdLt<'db>, - f: &mut dyn FnMut(&DefMap, ModuleIdLt<'db>) -> Option, + db: &dyn DefDatabase, + local_mod: ModuleId, + f: &mut dyn FnMut(&DefMap, ModuleId) -> Option, ) -> Option { if let Some(it) = f(self, local_mod) { return Some(it); @@ -854,13 +852,11 @@ fn deref_mut(&mut self) -> &mut Self::Target { } } -impl Index> for ModulesMap { +impl Index for ModulesMap { type Output = ModuleData; - fn index(&self, id: ModuleIdLt<'_>) -> &ModuleData { - self.inner - .get(&unsafe { id.to_static() }) - .unwrap_or_else(|| panic!("ModuleId not found in ModulesMap: {id:#?}")) + fn index(&self, id: ModuleId) -> &ModuleData { + self.inner.get(&id).unwrap_or_else(|| panic!("ModuleId not found in ModulesMap: {id:#?}")) } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs index 45d5dc9fcd27..263f603a0bfb 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -881,7 +881,7 @@ fn append_expr_scope<'db>( })); if let Some(block) = expr_scopes.block(scope_id) { let def_map = block_def_map(db, block); - let local_def_map = block.module(db).only_local_def_map(db); + let local_def_map = block.lookup(db).module.only_local_def_map(db); resolver.scopes.push(Scope::BlockScope(ModuleItemMap { def_map, local_def_map, @@ -1087,7 +1087,7 @@ fn resolver_for_scope_<'db>( for scope in scope_chain.into_iter().rev() { if let Some(block) = scopes.block(scope) { let def_map = block_def_map(db, block); - let local_def_map = block.module(db).only_local_def_map(db); + let local_def_map = block.lookup(db).module.only_local_def_map(db); // Using `DefMap::ROOT` is okay here since inside modules other than the root, // there can't directly be expressions. r = r.push_block_scope(def_map, local_def_map, def_map.root); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs index 95554c63b97d..a1645de6ec23 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs @@ -9,8 +9,8 @@ use triomphe::Arc; use crate::{ - AssocItemId, HasModule, ItemContainerId, LocalFieldId, ModuleId, ModuleIdLt, TraitId, - VariantId, db::DefDatabase, nameres::DefMap, resolver::HasResolver, src::HasSource, + AssocItemId, HasModule, ItemContainerId, LocalFieldId, ModuleId, TraitId, VariantId, + db::DefDatabase, nameres::DefMap, resolver::HasResolver, src::HasSource, }; pub use crate::item_tree::{RawVisibility, VisibilityExplicitness}; @@ -41,13 +41,9 @@ pub(crate) fn is_visible_from_other_crate(self) -> bool { } #[tracing::instrument(skip_all)] - pub fn is_visible_from<'db>( - self, - db: &'db dyn DefDatabase, - from_module: ModuleIdLt<'db>, - ) -> bool { + pub fn is_visible_from(self, db: &dyn DefDatabase, from_module: ModuleId) -> bool { let to_module = match self { - Visibility::Module(m, _) => unsafe { m.to_db(db) }, + Visibility::Module(m, _) => m, Visibility::PubCrate(krate) => return from_module.krate(db) == krate, Visibility::Public => return true, }; @@ -63,11 +59,11 @@ pub fn is_visible_from<'db>( Self::is_visible_from_def_map_(db, def_map, to_module, from_module) } - pub(crate) fn is_visible_from_def_map<'db>( + pub(crate) fn is_visible_from_def_map( self, - db: &'db dyn DefDatabase, - def_map: &'db DefMap, - from_module: ModuleIdLt<'db>, + db: &dyn DefDatabase, + def_map: &DefMap, + from_module: ModuleId, ) -> bool { if cfg!(debug_assertions) { _ = def_map.modules[from_module]; @@ -93,11 +89,11 @@ pub(crate) fn is_visible_from_def_map<'db>( Self::is_visible_from_def_map_(db, def_map, to_module, from_module) } - fn is_visible_from_def_map_<'db>( - db: &'db dyn DefDatabase, - def_map: &'db DefMap, - mut to_module: ModuleIdLt<'db>, - mut from_module: ModuleIdLt<'db>, + fn is_visible_from_def_map_( + db: &dyn DefDatabase, + def_map: &DefMap, + mut to_module: ModuleId, + mut from_module: ModuleId, ) -> bool { debug_assert_eq!(to_module.krate(db), def_map.krate()); // `to_module` might be the root module of a block expression. Those have the same diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index 70868e4b95aa..cafe0329692c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -653,16 +653,19 @@ pub fn type_of_expr_or_pat(&self, id: ExprOrPatId) -> Option> { } pub fn type_of_expr_with_adjust(&self, id: ExprId) -> Option> { match self.expr_adjustments.get(&id).and_then(|adjustments| { - adjustments.iter().rfind(|adj| { - // https://github.com/rust-lang/rust/blob/67819923ac8ea353aaa775303f4c3aacbf41d010/compiler/rustc_mir_build/src/thir/cx/expr.rs#L140 - !matches!( - adj, - Adjustment { - kind: Adjust::NeverToAny, - target, - } if target.is_never() - ) - }) + adjustments + .iter() + .filter(|adj| { + // https://github.com/rust-lang/rust/blob/67819923ac8ea353aaa775303f4c3aacbf41d010/compiler/rustc_mir_build/src/thir/cx/expr.rs#L140 + !matches!( + adj, + Adjustment { + kind: Adjust::NeverToAny, + target, + } if target.is_never() + ) + }) + .next_back() }) { Some(adjustment) => Some(adjustment.target), None => self.type_of_expr.get(id).copied(), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs index d9cfe6d84c2b..868ae00329b3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs @@ -13,8 +13,8 @@ use base_db::Crate; use hir_def::{ - AssocItemId, BlockIdLt, ConstId, FunctionId, GenericParamId, HasModule, ImplId, - ItemContainerId, ModuleId, TraitId, + AssocItemId, BlockId, ConstId, FunctionId, GenericParamId, HasModule, ImplId, ItemContainerId, + ModuleId, TraitId, attrs::AttrFlags, expr_store::path::GenericArgs as HirGenericArgs, hir::ExprId, @@ -558,9 +558,9 @@ pub struct InherentImpls { } #[salsa::tracked] -impl<'db> InherentImpls { +impl InherentImpls { #[salsa::tracked(returns(ref))] - pub fn for_crate(db: &'db dyn HirDatabase, krate: Crate) -> Self { + pub fn for_crate(db: &dyn HirDatabase, krate: Crate) -> Self { let _p = tracing::info_span!("inherent_impls_in_crate_query", ?krate).entered(); let crate_def_map = crate_def_map(db, krate); @@ -569,7 +569,7 @@ pub fn for_crate(db: &'db dyn HirDatabase, krate: Crate) -> Self { } #[salsa::tracked(returns(ref))] - pub fn for_block(db: &'db dyn HirDatabase, block: BlockIdLt<'db>) -> Option> { + pub fn for_block(db: &dyn HirDatabase, block: BlockId) -> Option> { let _p = tracing::info_span!("inherent_impls_in_block_query").entered(); let block_def_map = block_def_map(db, block); @@ -627,13 +627,13 @@ pub fn for_self_ty(&self, self_ty: &SimplifiedType) -> &[ImplId] { self.map.get(self_ty).map(|it| &**it).unwrap_or_default() } - pub fn for_each_crate_and_block<'db>( - db: &'db dyn HirDatabase, + pub fn for_each_crate_and_block( + db: &dyn HirDatabase, krate: Crate, - block: Option>, + block: Option, for_each: &mut dyn FnMut(&InherentImpls), ) { - let blocks = std::iter::successors(block, |block| block.module(db).block(db)); + let blocks = std::iter::successors(block, |block| block.loc(db).module.block(db)); blocks.filter_map(|block| Self::for_block(db, block).as_deref()).for_each(&mut *for_each); for_each(Self::for_crate(db, krate)); } @@ -670,9 +670,9 @@ pub struct TraitImpls { } #[salsa::tracked] -impl<'db> TraitImpls { +impl TraitImpls { #[salsa::tracked(returns(ref))] - pub fn for_crate(db: &'db dyn HirDatabase, krate: Crate) -> Arc { + pub fn for_crate(db: &dyn HirDatabase, krate: Crate) -> Arc { let _p = tracing::info_span!("inherent_impls_in_crate_query", ?krate).entered(); let crate_def_map = crate_def_map(db, krate); @@ -681,7 +681,7 @@ pub fn for_crate(db: &'db dyn HirDatabase, krate: Crate) -> Arc { } #[salsa::tracked(returns(ref))] - pub fn for_block(db: &'db dyn HirDatabase, block: BlockIdLt<'db>) -> Option> { + pub fn for_block(db: &dyn HirDatabase, block: BlockId) -> Option> { let _p = tracing::info_span!("inherent_impls_in_block_query").entered(); let block_def_map = block_def_map(db, block); @@ -690,7 +690,7 @@ pub fn for_block(db: &'db dyn HirDatabase, block: BlockIdLt<'db>) -> Option Box<[Arc]> { + pub fn for_crate_and_deps(db: &dyn HirDatabase, krate: Crate) -> Box<[Arc]> { krate.transitive_deps(db).iter().map(|&dep| Self::for_crate(db, dep).clone()).collect() } } @@ -792,23 +792,23 @@ pub fn for_self_ty(&self, self_ty: &SimplifiedType, mut callback: impl FnMut(&[I } } - pub fn for_each_crate_and_block<'db>( - db: &'db dyn HirDatabase, + pub fn for_each_crate_and_block( + db: &dyn HirDatabase, krate: Crate, - block: Option>, + block: Option, for_each: &mut dyn FnMut(&TraitImpls), ) { - let blocks = std::iter::successors(block, |block| block.module(db).block(db)); + let blocks = std::iter::successors(block, |block| block.loc(db).module.block(db)); blocks.filter_map(|block| Self::for_block(db, block).as_deref()).for_each(&mut *for_each); Self::for_crate_and_deps(db, krate).iter().map(|it| &**it).for_each(for_each); } /// Like [`Self::for_each_crate_and_block()`], but takes in account two blocks, one for a trait and one for a self type. - pub fn for_each_crate_and_block_trait_and_type<'db>( - db: &'db dyn HirDatabase, + pub fn for_each_crate_and_block_trait_and_type( + db: &dyn HirDatabase, krate: Crate, - type_block: Option>, - trait_block: Option>, + type_block: Option, + trait_block: Option, for_each: &mut dyn FnMut(&TraitImpls), ) { let in_self_and_deps = TraitImpls::for_crate_and_deps(db, krate); @@ -819,11 +819,10 @@ pub fn for_each_crate_and_block_trait_and_type<'db>( // that means there can't be duplicate impls; if they meet, we stop the search of the deeper block. // This breaks when they are equal (both will stop immediately), therefore we handle this case // specifically. - let blocks_iter = |block: Option>| { - std::iter::successors(block, |block| block.module(db).block(db)) + let blocks_iter = |block: Option| { + std::iter::successors(block, |block| block.loc(db).module.block(db)) }; - let for_each_block = |current_block: Option>, - other_block: Option>| { + let for_each_block = |current_block: Option, other_block: Option| { blocks_iter(current_block) .take_while(move |&block| { other_block.is_none_or(|other_block| other_block != block) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index e57f031f009a..a50a736ccd0e 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -590,7 +590,7 @@ pub fn nearest_non_block_module(self, db: &dyn HirDatabase) -> Module { while id.is_block_module(db) { id = id.containing_module(db).expect("block without parent module"); } - Module { id: unsafe { id.to_static() } } + Module { id } } pub fn path_to_root(self, db: &dyn HirDatabase) -> Vec { @@ -4352,7 +4352,7 @@ pub fn all_for_type<'db>( module.block(db), &mut |impls| extend_with_impls(impls.for_self_ty(&simplified_ty)), ); - std::iter::successors(module.block(db), |block| block.module(db).block(db)) + std::iter::successors(module.block(db), |block| block.loc(db).module.block(db)) .filter_map(|block| TraitImpls::for_block(db, block).as_deref()) .for_each(|impls| impls.for_self_ty(&simplified_ty, &mut extend_with_impls)); for &krate in &**db.all_crates() { diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs index d924aaa25ddc..c1f72debe54f 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics/child_by_source.rs @@ -226,7 +226,7 @@ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: Hi // All block expressions are merged into the same map, because they logically all add // inner items to the containing `DefWithBodyId`. def_map[def_map.root].scope.child_by_source_to(db, res, file_id); - res[keys::BLOCK].insert(block.ast_id(db).to_ptr(db), block); + res[keys::BLOCK].insert(block.lookup(db).ast_id.to_ptr(db), block); } } } From f65d2df44808a4a110a3447fb8e0b6099a49188e Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 10 Dec 2025 18:35:13 +0800 Subject: [PATCH 22/39] Fix assist `and` -> `and_then` parameter - And fix fallback parentheses Example --- ```rust fn foo() { let foo = Some("foo"); return foo.and$0(Some("bar")); } ``` **Before this PR** ```rust fn foo() { let foo = Some("foo"); return foo.and_then(|| Some("bar")); } ``` **After this PR** ```rust fn foo() { let foo = Some("foo"); return foo.and_then(|it| Some("bar")); } ``` --- ```rust struct Func { f: fn() -> i32 } fn foo() { let foo = true; let func = Func { f: || 2 }; let x = foo.then$0(func.f); } ``` **Before this PR** ```text request handler panicked: Failed to make ast node `syntax::ast::generated::nodes::CallExpr` from text const C: () = func.f(); ``` **After this PR** ```rust struct Func { f: fn() -> i32 } fn foo() { let foo = true; let func = Func { f: || 2 }; let x = foo.then_some((func.f)()); } ``` --- .../src/handlers/replace_method_eager_lazy.rs | 93 +++++++++++++++++-- 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs index c85ec734c07a..6ca3e26ca018 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs @@ -1,4 +1,5 @@ -use ide_db::assists::AssistId; +use hir::Semantics; +use ide_db::{RootDatabase, assists::AssistId, defs::Definition}; use syntax::{ AstNode, ast::{self, Expr, HasArgList, make}, @@ -60,8 +61,8 @@ pub(crate) fn replace_with_lazy_method(acc: &mut Assists, ctx: &AssistContext<'_ format!("Replace {method_name} with {method_name_lazy}"), call.syntax().text_range(), |builder| { + let closured = into_closure(&last_arg, &method_name_lazy); builder.replace(method_name.syntax().text_range(), method_name_lazy); - let closured = into_closure(&last_arg); builder.replace_ast(last_arg, closured); }, ) @@ -79,7 +80,7 @@ fn lazy_method_name(name: &str) -> String { } } -fn into_closure(param: &Expr) -> Expr { +fn into_closure(param: &Expr, name_lazy: &str) -> Expr { (|| { if let ast::Expr::CallExpr(call) = param { if call.arg_list()?.args().count() == 0 { Some(call.expr()?) } else { None } @@ -87,7 +88,11 @@ fn into_closure(param: &Expr) -> Expr { None } })() - .unwrap_or_else(|| make::expr_closure(None, param.clone()).into()) + .unwrap_or_else(|| { + let pats = (name_lazy == "and_then") + .then(|| make::untyped_param(make::ext::simple_ident_pat(make::name("it")).into())); + make::expr_closure(pats, param.clone()).into() + }) } // Assist: replace_with_eager_method @@ -146,21 +151,39 @@ pub(crate) fn replace_with_eager_method(acc: &mut Assists, ctx: &AssistContext<' call.syntax().text_range(), |builder| { builder.replace(method_name.syntax().text_range(), method_name_eager); - let called = into_call(&last_arg); + let called = into_call(&last_arg, &ctx.sema); builder.replace_ast(last_arg, called); }, ) } -fn into_call(param: &Expr) -> Expr { +fn into_call(param: &Expr, sema: &Semantics<'_, RootDatabase>) -> Expr { (|| { if let ast::Expr::ClosureExpr(closure) = param { - if closure.param_list()?.params().count() == 0 { Some(closure.body()?) } else { None } + let mut params = closure.param_list()?.params(); + match params.next() { + Some(_) if params.next().is_none() => { + let params = sema.resolve_expr_as_callable(param)?.params(); + let used_param = Definition::Local(params.first()?.as_local(sema.db)?) + .usages(sema) + .at_least_one(); + if used_param { None } else { Some(closure.body()?) } + } + None => Some(closure.body()?), + Some(_) => None, + } } else { None } })() - .unwrap_or_else(|| make::expr_call(param.clone(), make::arg_list(Vec::new())).into()) + .unwrap_or_else(|| { + let callable = if needs_parens_in_call(param) { + make::expr_paren(param.clone()).into() + } else { + param.clone() + }; + make::expr_call(callable, make::arg_list(Vec::new())).into() + }) } fn eager_method_name(name: &str) -> Option<&str> { @@ -177,6 +200,12 @@ fn ends_is(name: &str, end: &str) -> bool { name.strip_suffix(end).is_some_and(|s| s.is_empty() || s.ends_with('_')) } +fn needs_parens_in_call(param: &Expr) -> bool { + let call = make::expr_call(make::ext::expr_unit(), make::arg_list(Vec::new())); + let callable = call.expr().expect("invalid make call"); + param.needs_parens_in_place_of(call.syntax(), callable.syntax()) +} + #[cfg(test)] mod tests { use crate::tests::check_assist; @@ -333,7 +362,7 @@ fn foo() { r#" fn foo() { let foo = Some("foo"); - return foo.and_then(|| Some("bar")); + return foo.and_then(|it| Some("bar")); } "#, ) @@ -347,7 +376,7 @@ fn replace_and_then_with_and() { //- minicore: option, fn fn foo() { let foo = Some("foo"); - return foo.and_then$0(|| Some("bar")); + return foo.and_then$0(|it| Some("bar")); } "#, r#" @@ -359,6 +388,26 @@ fn foo() { ) } + #[test] + fn replace_and_then_with_and_used_param() { + check_assist( + replace_with_eager_method, + r#" +//- minicore: option, fn +fn foo() { + let foo = Some("foo"); + return foo.and_then$0(|it| Some(it.strip_suffix("bar"))); +} +"#, + r#" +fn foo() { + let foo = Some("foo"); + return foo.and((|it| Some(it.strip_suffix("bar")))()); +} +"#, + ) + } + #[test] fn replace_then_some_with_then() { check_assist( @@ -395,6 +444,30 @@ fn foo() { let foo = true; let x = foo.then_some(2); } +"#, + ) + } + + #[test] + fn replace_then_with_then_some_needs_parens() { + check_assist( + replace_with_eager_method, + r#" +//- minicore: option, fn, bool_impl +struct Func { f: fn() -> i32 } +fn foo() { + let foo = true; + let func = Func { f: || 2 }; + let x = foo.then$0(func.f); +} +"#, + r#" +struct Func { f: fn() -> i32 } +fn foo() { + let foo = true; + let func = Func { f: || 2 }; + let x = foo.then_some((func.f)()); +} "#, ) } From fd8f92d94ad4ba990e3b289edf4518318a585d2a Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 10 Dec 2025 15:23:07 +0200 Subject: [PATCH 23/39] Move `format_args!()` lowering to a separate file It's growing out of control. --- .../crates/hir-def/src/expr_store/lower.rs | 689 +---------------- .../src/expr_store/lower/format_args.rs | 699 ++++++++++++++++++ 2 files changed, 702 insertions(+), 686 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index 0b2af134c8ca..3448b734ff10 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -2,6 +2,7 @@ //! representation. mod asm; +mod format_args; mod generics; mod path; @@ -19,7 +20,7 @@ use rustc_hash::FxHashMap; use stdx::never; use syntax::{ - AstNode, AstPtr, AstToken as _, SyntaxNodePtr, + AstNode, AstPtr, SyntaxNodePtr, ast::{ self, ArrayExprKind, AstChildren, BlockExpr, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasName, HasTypeBounds, IsString, RangeItem, @@ -34,7 +35,6 @@ AdtId, BlockId, BlockLoc, DefWithBodyId, FunctionId, GenericDefId, ImplId, MacroId, ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro, attrs::AttrFlags, - builtin_type::BuiltinUint, db::DefDatabase, expr_store::{ Body, BodySourceMap, ExprPtr, ExpressionStore, ExpressionStoreBuilder, @@ -47,13 +47,7 @@ hir::{ Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind, Expr, ExprId, Item, Label, LabelId, Literal, MatchArm, Movability, OffsetOf, Pat, PatId, - RecordFieldPat, RecordLitField, Statement, - format_args::{ - self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, - FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, - FormatPlaceholder, FormatSign, FormatTrait, - }, - generics::GenericParams, + RecordFieldPat, RecordLitField, Statement, generics::GenericParams, }, item_scope::BuiltinShadowMode, item_tree::FieldsShape, @@ -2608,7 +2602,6 @@ fn with_opt_labeled_rib( } // endregion: labels - // region: format fn expand_macros_to_string(&mut self, expr: ast::Expr) -> Option<(ast::String, bool)> { let m = match expr { ast::Expr::MacroExpr(m) => m, @@ -2628,676 +2621,6 @@ fn expand_macros_to_string(&mut self, expr: ast::Expr) -> Option<(ast::String, b Some((exp, false)) } - fn collect_format_args( - &mut self, - f: ast::FormatArgsExpr, - syntax_ptr: AstPtr, - ) -> ExprId { - let mut args = FormatArgumentsCollector::default(); - f.args().for_each(|arg| { - args.add(FormatArgument { - kind: match arg.name() { - Some(name) => FormatArgumentKind::Named(name.as_name()), - None => FormatArgumentKind::Normal, - }, - expr: self.collect_expr_opt(arg.expr()), - }); - }); - let template = f.template(); - let fmt_snippet = template.as_ref().and_then(|it| match it { - ast::Expr::Literal(literal) => match literal.kind() { - ast::LiteralKind::String(s) => Some(s.text().to_owned()), - _ => None, - }, - _ => None, - }); - let mut mappings = vec![]; - let (fmt, hygiene) = match template.and_then(|template| { - self.expand_macros_to_string(template.clone()).map(|it| (it, template)) - }) { - Some(((s, is_direct_literal), template)) => { - let call_ctx = self.expander.call_syntax_ctx(); - let hygiene = self.hygiene_id_for(s.syntax().text_range()); - let fmt = format_args::parse( - &s, - fmt_snippet, - args, - is_direct_literal, - |name, range| { - let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name))); - if let Some(range) = range { - self.store - .template_map - .get_or_insert_with(Default::default) - .implicit_capture_to_source - .insert( - expr_id, - self.expander.in_file((AstPtr::new(&template), range)), - ); - } - if !hygiene.is_root() { - self.store.ident_hygiene.insert(expr_id.into(), hygiene); - } - expr_id - }, - |name, span| { - if let Some(span) = span { - mappings.push((span, name)) - } - }, - call_ctx, - ); - (fmt, hygiene) - } - None => ( - FormatArgs { - template: Default::default(), - arguments: args.finish(), - orphans: Default::default(), - }, - HygieneId::ROOT, - ), - }; - - // Create a list of all _unique_ (argument, format trait) combinations. - // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] - let mut argmap = FxIndexSet::default(); - for piece in fmt.template.iter() { - let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; - if let Ok(index) = placeholder.argument.index { - argmap.insert((index, ArgumentType::Format(placeholder.format_trait))); - } - } - - let lit_pieces = fmt - .template - .iter() - .enumerate() - .filter_map(|(i, piece)| { - match piece { - FormatArgsPiece::Literal(s) => { - Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone())))) - } - &FormatArgsPiece::Placeholder(_) => { - // Inject empty string before placeholders when not already preceded by a literal piece. - if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) - { - Some(self.alloc_expr_desugared(Expr::Literal(Literal::String( - Symbol::empty(), - )))) - } else { - None - } - } - } - }) - .collect(); - let lit_pieces = - self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: lit_pieces })); - let lit_pieces = self.alloc_expr_desugared(Expr::Ref { - expr: lit_pieces, - rawness: Rawness::Ref, - mutability: Mutability::Shared, - }); - let format_options = { - // Generate: - // &[format_spec_0, format_spec_1, format_spec_2] - let elements = fmt - .template - .iter() - .filter_map(|piece| { - let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; - Some(self.make_format_spec(placeholder, &mut argmap)) - }) - .collect(); - let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements })); - self.alloc_expr_desugared(Expr::Ref { - expr: array, - rawness: Rawness::Ref, - mutability: Mutability::Shared, - }) - }; - - // Assume that rustc version >= 1.89.0 iff lang item `format_arguments` exists - // but `format_unsafe_arg` does not - let lang_items = self.lang_items(); - let fmt_args = lang_items.FormatArguments; - let fmt_unsafe_arg = lang_items.FormatUnsafeArg; - let use_format_args_since_1_89_0 = fmt_args.is_some() && fmt_unsafe_arg.is_none(); - - let idx = if use_format_args_since_1_89_0 { - self.collect_format_args_impl(syntax_ptr, fmt, argmap, lit_pieces, format_options) - } else { - self.collect_format_args_before_1_89_0_impl( - syntax_ptr, - fmt, - argmap, - lit_pieces, - format_options, - ) - }; - - self.store - .template_map - .get_or_insert_with(Default::default) - .format_args_to_captures - .insert(idx, (hygiene, mappings)); - idx - } - - /// `format_args!` expansion implementation for rustc versions < `1.89.0` - fn collect_format_args_before_1_89_0_impl( - &mut self, - syntax_ptr: AstPtr, - fmt: FormatArgs, - argmap: FxIndexSet<(usize, ArgumentType)>, - lit_pieces: ExprId, - format_options: ExprId, - ) -> ExprId { - let arguments = &*fmt.arguments.arguments; - - let args = if arguments.is_empty() { - let expr = self - .alloc_expr_desugared(Expr::Array(Array::ElementList { elements: Box::default() })); - self.alloc_expr_desugared(Expr::Ref { - expr, - rawness: Rawness::Ref, - mutability: Mutability::Shared, - }) - } else { - // Generate: - // &match (&arg0, &arg1, &…) { - // args => [ - // ::new_display(args.0), - // ::new_lower_hex(args.1), - // ::new_debug(args.0), - // … - // ] - // } - let args = argmap - .iter() - .map(|&(arg_index, ty)| { - let arg = self.alloc_expr_desugared(Expr::Ref { - expr: arguments[arg_index].expr, - rawness: Rawness::Ref, - mutability: Mutability::Shared, - }); - self.make_argument(arg, ty) - }) - .collect(); - let array = - self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); - self.alloc_expr_desugared(Expr::Ref { - expr: array, - rawness: Rawness::Ref, - mutability: Mutability::Shared, - }) - }; - - // Generate: - // ::new_v1_formatted( - // lit_pieces, - // args, - // format_options, - // unsafe { ::core::fmt::UnsafeArg::new() } - // ) - - let lang_items = self.lang_items(); - let new_v1_formatted = self.ty_rel_lang_path( - lang_items.FormatArguments, - Name::new_symbol_root(sym::new_v1_formatted), - ); - let unsafe_arg_new = - self.ty_rel_lang_path(lang_items.FormatUnsafeArg, Name::new_symbol_root(sym::new)); - let new_v1_formatted = - self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path)); - - let unsafe_arg_new = - self.alloc_expr_desugared(unsafe_arg_new.map_or(Expr::Missing, Expr::Path)); - let unsafe_arg_new = - self.alloc_expr_desugared(Expr::Call { callee: unsafe_arg_new, args: Box::default() }); - let mut unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe { - id: None, - statements: Box::new([]), - tail: Some(unsafe_arg_new), - }); - if !fmt.orphans.is_empty() { - unsafe_arg_new = self.alloc_expr_desugared(Expr::Block { - id: None, - // We collect the unused expressions here so that we still infer them instead of - // dropping them out of the expression tree. We cannot store them in the `Unsafe` - // block because then unsafe blocks within them will get a false "unused unsafe" - // diagnostic (rustc has a notion of builtin unsafe blocks, but we don't). - statements: fmt - .orphans - .into_iter() - .map(|expr| Statement::Expr { expr, has_semi: true }) - .collect(), - tail: Some(unsafe_arg_new), - label: None, - }); - } - - self.alloc_expr( - Expr::Call { - callee: new_v1_formatted, - args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]), - }, - syntax_ptr, - ) - } - - /// `format_args!` expansion implementation for rustc versions >= `1.89.0`, - /// especially since [this PR](https://github.com/rust-lang/rust/pull/140748) - fn collect_format_args_impl( - &mut self, - syntax_ptr: AstPtr, - fmt: FormatArgs, - argmap: FxIndexSet<(usize, ArgumentType)>, - lit_pieces: ExprId, - format_options: ExprId, - ) -> ExprId { - let arguments = &*fmt.arguments.arguments; - - let (let_stmts, args) = if arguments.is_empty() { - ( - // Generate: - // [] - vec![], - self.alloc_expr_desugared(Expr::Array(Array::ElementList { - elements: Box::default(), - })), - ) - } else if argmap.len() == 1 && arguments.len() == 1 { - // Only one argument, so we don't need to make the `args` tuple. - // - // Generate: - // super let args = [::new_display(&arg)]; - let args = argmap - .iter() - .map(|&(arg_index, ty)| { - let ref_arg = self.alloc_expr_desugared(Expr::Ref { - expr: arguments[arg_index].expr, - rawness: Rawness::Ref, - mutability: Mutability::Shared, - }); - self.make_argument(ref_arg, ty) - }) - .collect(); - let args = - self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); - let args_name = Name::new_symbol_root(sym::args); - let args_binding = self.alloc_binding( - args_name.clone(), - BindingAnnotation::Unannotated, - HygieneId::ROOT, - ); - let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); - self.add_definition_to_binding(args_binding, args_pat); - // TODO: We don't have `super let` yet. - let let_stmt = Statement::Let { - pat: args_pat, - type_ref: None, - initializer: Some(args), - else_branch: None, - }; - (vec![let_stmt], self.alloc_expr_desugared(Expr::Path(args_name.into()))) - } else { - // Generate: - // super let args = (&arg0, &arg1, &...); - let args_name = Name::new_symbol_root(sym::args); - let args_binding = self.alloc_binding( - args_name.clone(), - BindingAnnotation::Unannotated, - HygieneId::ROOT, - ); - let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); - self.add_definition_to_binding(args_binding, args_pat); - let elements = arguments - .iter() - .map(|arg| { - self.alloc_expr_desugared(Expr::Ref { - expr: arg.expr, - rawness: Rawness::Ref, - mutability: Mutability::Shared, - }) - }) - .collect(); - let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements }); - // TODO: We don't have `super let` yet - let let_stmt1 = Statement::Let { - pat: args_pat, - type_ref: None, - initializer: Some(args_tuple), - else_branch: None, - }; - - // Generate: - // super let args = [ - // ::new_display(args.0), - // ::new_lower_hex(args.1), - // ::new_debug(args.0), - // … - // ]; - let args = argmap - .iter() - .map(|&(arg_index, ty)| { - let args_ident_expr = - self.alloc_expr_desugared(Expr::Path(args_name.clone().into())); - let arg = self.alloc_expr_desugared(Expr::Field { - expr: args_ident_expr, - name: Name::new_tuple_field(arg_index), - }); - self.make_argument(arg, ty) - }) - .collect(); - let array = - self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); - let args_binding = self.alloc_binding( - args_name.clone(), - BindingAnnotation::Unannotated, - HygieneId::ROOT, - ); - let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); - self.add_definition_to_binding(args_binding, args_pat); - let let_stmt2 = Statement::Let { - pat: args_pat, - type_ref: None, - initializer: Some(array), - else_branch: None, - }; - (vec![let_stmt1, let_stmt2], self.alloc_expr_desugared(Expr::Path(args_name.into()))) - }; - - // Generate: - // &args - let args = self.alloc_expr_desugared(Expr::Ref { - expr: args, - rawness: Rawness::Ref, - mutability: Mutability::Shared, - }); - - let call_block = { - // Generate: - // unsafe { - // ::new_v1_formatted( - // lit_pieces, - // args, - // format_options, - // ) - // } - - let new_v1_formatted = self.ty_rel_lang_path( - self.lang_items().FormatArguments, - Name::new_symbol_root(sym::new_v1_formatted), - ); - let new_v1_formatted = - self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path)); - let args = [lit_pieces, args, format_options]; - let call = self - .alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() }); - - Expr::Unsafe { id: None, statements: Box::default(), tail: Some(call) } - }; - - if !let_stmts.is_empty() { - // Generate: - // { - // super let … - // super let … - // ::new_…(…) - // } - let call = self.alloc_expr_desugared(call_block); - self.alloc_expr( - Expr::Block { - id: None, - statements: let_stmts.into(), - tail: Some(call), - label: None, - }, - syntax_ptr, - ) - } else { - self.alloc_expr(call_block, syntax_ptr) - } - } - - /// Generate a hir expression for a format_args placeholder specification. - /// - /// Generates - /// - /// ```text - /// ::…, // alignment - /// …u32, // flags - /// , // width - /// , // precision - /// ) - /// ``` - fn make_format_spec( - &mut self, - placeholder: &FormatPlaceholder, - argmap: &mut FxIndexSet<(usize, ArgumentType)>, - ) -> ExprId { - let lang_items = self.lang_items(); - let position = match placeholder.argument.index { - Ok(arg_index) => { - let (i, _) = - argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); - self.alloc_expr_desugared(Expr::Literal(Literal::Uint( - i as u128, - Some(BuiltinUint::Usize), - ))) - } - Err(_) => self.missing_expr(), - }; - let &FormatOptions { - ref width, - ref precision, - alignment, - fill, - sign, - alternate, - zero_pad, - debug_hex, - } = &placeholder.format_options; - - let precision_expr = self.make_count(precision, argmap); - let width_expr = self.make_count(width, argmap); - - if self.krate.workspace_data(self.db).is_atleast_187() { - // These need to match the constants in library/core/src/fmt/rt.rs. - let align = match alignment { - Some(FormatAlignment::Left) => 0, - Some(FormatAlignment::Right) => 1, - Some(FormatAlignment::Center) => 2, - None => 3, - }; - // This needs to match `Flag` in library/core/src/fmt/rt.rs. - let flags = fill.unwrap_or(' ') as u32 - | ((sign == Some(FormatSign::Plus)) as u32) << 21 - | ((sign == Some(FormatSign::Minus)) as u32) << 22 - | (alternate as u32) << 23 - | (zero_pad as u32) << 24 - | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25 - | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26 - | (width.is_some() as u32) << 27 - | (precision.is_some() as u32) << 28 - | align << 29 - | 1 << 31; // Highest bit always set. - let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( - flags as u128, - Some(BuiltinUint::U32), - ))); - - let position = - RecordLitField { name: Name::new_symbol_root(sym::position), expr: position }; - let flags = RecordLitField { name: Name::new_symbol_root(sym::flags), expr: flags }; - let precision = RecordLitField { - name: Name::new_symbol_root(sym::precision), - expr: precision_expr, - }; - let width = - RecordLitField { name: Name::new_symbol_root(sym::width), expr: width_expr }; - self.alloc_expr_desugared(Expr::RecordLit { - path: self.lang_path(lang_items.FormatPlaceholder).map(Box::new), - fields: Box::new([position, flags, precision, width]), - spread: None, - }) - } else { - let format_placeholder_new = { - let format_placeholder_new = self.ty_rel_lang_path( - lang_items.FormatPlaceholder, - Name::new_symbol_root(sym::new), - ); - match format_placeholder_new { - Some(path) => self.alloc_expr_desugared(Expr::Path(path)), - None => self.missing_expr(), - } - }; - // This needs to match `Flag` in library/core/src/fmt/rt.rs. - let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32) - | (((sign == Some(FormatSign::Minus)) as u32) << 1) - | ((alternate as u32) << 2) - | ((zero_pad as u32) << 3) - | (((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4) - | (((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5); - let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( - flags as u128, - Some(BuiltinUint::U32), - ))); - let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' ')))); - let align = { - let align = self.ty_rel_lang_path( - lang_items.FormatAlignment, - match alignment { - Some(FormatAlignment::Left) => Name::new_symbol_root(sym::Left), - Some(FormatAlignment::Right) => Name::new_symbol_root(sym::Right), - Some(FormatAlignment::Center) => Name::new_symbol_root(sym::Center), - None => Name::new_symbol_root(sym::Unknown), - }, - ); - match align { - Some(path) => self.alloc_expr_desugared(Expr::Path(path)), - None => self.missing_expr(), - } - }; - self.alloc_expr_desugared(Expr::Call { - callee: format_placeholder_new, - args: Box::new([position, fill, align, flags, precision_expr, width_expr]), - }) - } - } - - /// Generate a hir expression for a format_args Count. - /// - /// Generates: - /// - /// ```text - /// ::Is(…) - /// ``` - /// - /// or - /// - /// ```text - /// ::Param(…) - /// ``` - /// - /// or - /// - /// ```text - /// ::Implied - /// ``` - fn make_count( - &mut self, - count: &Option, - argmap: &mut FxIndexSet<(usize, ArgumentType)>, - ) -> ExprId { - let lang_items = self.lang_items(); - match count { - Some(FormatCount::Literal(n)) => { - let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( - *n as u128, - // FIXME: Change this to Some(BuiltinUint::U16) once we drop support for toolchains < 1.88 - None, - ))); - let count_is = match self - .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Is)) - { - Some(count_is) => self.alloc_expr_desugared(Expr::Path(count_is)), - None => self.missing_expr(), - }; - self.alloc_expr_desugared(Expr::Call { callee: count_is, args: Box::new([args]) }) - } - Some(FormatCount::Argument(arg)) => { - if let Ok(arg_index) = arg.index { - let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); - - let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( - i as u128, - Some(BuiltinUint::Usize), - ))); - let count_param = match self - .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Param)) - { - Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), - None => self.missing_expr(), - }; - self.alloc_expr_desugared(Expr::Call { - callee: count_param, - args: Box::new([args]), - }) - } else { - // FIXME: This drops arg causing it to potentially not be resolved/type checked - // when typing? - self.missing_expr() - } - } - None => match self - .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Implied)) - { - Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), - None => self.missing_expr(), - }, - } - } - - /// Generate a hir expression representing an argument to a format_args invocation. - /// - /// Generates: - /// - /// ```text - /// ::new_…(arg) - /// ``` - fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { - use ArgumentType::*; - use FormatTrait::*; - - let new_fn = match self.ty_rel_lang_path( - self.lang_items().FormatArgument, - Name::new_symbol_root(match ty { - Format(Display) => sym::new_display, - Format(Debug) => sym::new_debug, - Format(LowerExp) => sym::new_lower_exp, - Format(UpperExp) => sym::new_upper_exp, - Format(Octal) => sym::new_octal, - Format(Pointer) => sym::new_pointer, - Format(Binary) => sym::new_binary, - Format(LowerHex) => sym::new_lower_hex, - Format(UpperHex) => sym::new_upper_hex, - Usize => sym::from_usize, - }), - ) { - Some(new_fn) => self.alloc_expr_desugared(Expr::Path(new_fn)), - None => self.missing_expr(), - }; - self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) }) - } - - // endregion: format - fn lang_path(&self, lang: Option>) -> Option { Some(Path::LangItem(lang?.into(), None)) } @@ -3424,12 +2747,6 @@ fn comma_follows_token(t: Option) -> bool { .is_some_and(|it| it.kind() == syntax::T![,]) } -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -enum ArgumentType { - Format(FormatTrait), - Usize, -} - /// This function find the AST fragment that corresponds to an `AssociatedTypeBinding` in the HIR. pub fn hir_assoc_type_binding_to_ast( segment_args: &ast::GenericArgList, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs new file mode 100644 index 000000000000..aa7bf0e4e244 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs @@ -0,0 +1,699 @@ +//! Lowering of `format_args!()`. + +use base_db::FxIndexSet; +use hir_expand::name::{AsName, Name}; +use intern::{Symbol, sym}; +use syntax::{ + AstPtr, AstToken as _, + ast::{self, HasName}, +}; + +use crate::{ + builtin_type::BuiltinUint, + expr_store::{HygieneId, lower::ExprCollector, path::Path}, + hir::{ + Array, BindingAnnotation, Expr, ExprId, Literal, Pat, RecordLitField, Statement, + format_args::{ + self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, + FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, + FormatPlaceholder, FormatSign, FormatTrait, + }, + }, + type_ref::{Mutability, Rawness}, +}; + +impl<'db> ExprCollector<'db> { + pub(super) fn collect_format_args( + &mut self, + f: ast::FormatArgsExpr, + syntax_ptr: AstPtr, + ) -> ExprId { + let mut args = FormatArgumentsCollector::default(); + f.args().for_each(|arg| { + args.add(FormatArgument { + kind: match arg.name() { + Some(name) => FormatArgumentKind::Named(name.as_name()), + None => FormatArgumentKind::Normal, + }, + expr: self.collect_expr_opt(arg.expr()), + }); + }); + let template = f.template(); + let fmt_snippet = template.as_ref().and_then(|it| match it { + ast::Expr::Literal(literal) => match literal.kind() { + ast::LiteralKind::String(s) => Some(s.text().to_owned()), + _ => None, + }, + _ => None, + }); + let mut mappings = vec![]; + let (fmt, hygiene) = match template.and_then(|template| { + self.expand_macros_to_string(template.clone()).map(|it| (it, template)) + }) { + Some(((s, is_direct_literal), template)) => { + let call_ctx = self.expander.call_syntax_ctx(); + let hygiene = self.hygiene_id_for(s.syntax().text_range()); + let fmt = format_args::parse( + &s, + fmt_snippet, + args, + is_direct_literal, + |name, range| { + let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name))); + if let Some(range) = range { + self.store + .template_map + .get_or_insert_with(Default::default) + .implicit_capture_to_source + .insert( + expr_id, + self.expander.in_file((AstPtr::new(&template), range)), + ); + } + if !hygiene.is_root() { + self.store.ident_hygiene.insert(expr_id.into(), hygiene); + } + expr_id + }, + |name, span| { + if let Some(span) = span { + mappings.push((span, name)) + } + }, + call_ctx, + ); + (fmt, hygiene) + } + None => ( + FormatArgs { + template: Default::default(), + arguments: args.finish(), + orphans: Default::default(), + }, + HygieneId::ROOT, + ), + }; + + // Create a list of all _unique_ (argument, format trait) combinations. + // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] + let mut argmap = FxIndexSet::default(); + for piece in fmt.template.iter() { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + if let Ok(index) = placeholder.argument.index { + argmap.insert((index, ArgumentType::Format(placeholder.format_trait))); + } + } + + let lit_pieces = fmt + .template + .iter() + .enumerate() + .filter_map(|(i, piece)| { + match piece { + FormatArgsPiece::Literal(s) => { + Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone())))) + } + &FormatArgsPiece::Placeholder(_) => { + // Inject empty string before placeholders when not already preceded by a literal piece. + if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) + { + Some(self.alloc_expr_desugared(Expr::Literal(Literal::String( + Symbol::empty(), + )))) + } else { + None + } + } + } + }) + .collect(); + let lit_pieces = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: lit_pieces })); + let lit_pieces = self.alloc_expr_desugared(Expr::Ref { + expr: lit_pieces, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + let format_options = { + // Generate: + // &[format_spec_0, format_spec_1, format_spec_2] + let elements = fmt + .template + .iter() + .filter_map(|piece| { + let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; + Some(self.make_format_spec(placeholder, &mut argmap)) + }) + .collect(); + let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + + // Assume that rustc version >= 1.89.0 iff lang item `format_arguments` exists + // but `format_unsafe_arg` does not + let lang_items = self.lang_items(); + let fmt_args = lang_items.FormatArguments; + let fmt_unsafe_arg = lang_items.FormatUnsafeArg; + let use_format_args_since_1_89_0 = fmt_args.is_some() && fmt_unsafe_arg.is_none(); + + let idx = if use_format_args_since_1_89_0 { + self.collect_format_args_impl(syntax_ptr, fmt, argmap, lit_pieces, format_options) + } else { + self.collect_format_args_before_1_89_0_impl( + syntax_ptr, + fmt, + argmap, + lit_pieces, + format_options, + ) + }; + + self.store + .template_map + .get_or_insert_with(Default::default) + .format_args_to_captures + .insert(idx, (hygiene, mappings)); + idx + } + + /// `format_args!` expansion implementation for rustc versions < `1.89.0` + fn collect_format_args_before_1_89_0_impl( + &mut self, + syntax_ptr: AstPtr, + fmt: FormatArgs, + argmap: FxIndexSet<(usize, ArgumentType)>, + lit_pieces: ExprId, + format_options: ExprId, + ) -> ExprId { + let arguments = &*fmt.arguments.arguments; + + let args = if arguments.is_empty() { + let expr = self + .alloc_expr_desugared(Expr::Array(Array::ElementList { elements: Box::default() })); + self.alloc_expr_desugared(Expr::Ref { + expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + } else { + // Generate: + // &match (&arg0, &arg1, &…) { + // args => [ + // ::new_display(args.0), + // ::new_lower_hex(args.1), + // ::new_debug(args.0), + // … + // ] + // } + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let arg = self.alloc_expr_desugared(Expr::Ref { + expr: arguments[arg_index].expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + self.make_argument(arg, ty) + }) + .collect(); + let array = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + + // Generate: + // ::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // unsafe { ::core::fmt::UnsafeArg::new() } + // ) + + let lang_items = self.lang_items(); + let new_v1_formatted = self.ty_rel_lang_path( + lang_items.FormatArguments, + Name::new_symbol_root(sym::new_v1_formatted), + ); + let unsafe_arg_new = + self.ty_rel_lang_path(lang_items.FormatUnsafeArg, Name::new_symbol_root(sym::new)); + let new_v1_formatted = + self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path)); + + let unsafe_arg_new = + self.alloc_expr_desugared(unsafe_arg_new.map_or(Expr::Missing, Expr::Path)); + let unsafe_arg_new = + self.alloc_expr_desugared(Expr::Call { callee: unsafe_arg_new, args: Box::default() }); + let mut unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe { + id: None, + statements: Box::new([]), + tail: Some(unsafe_arg_new), + }); + if !fmt.orphans.is_empty() { + unsafe_arg_new = self.alloc_expr_desugared(Expr::Block { + id: None, + // We collect the unused expressions here so that we still infer them instead of + // dropping them out of the expression tree. We cannot store them in the `Unsafe` + // block because then unsafe blocks within them will get a false "unused unsafe" + // diagnostic (rustc has a notion of builtin unsafe blocks, but we don't). + statements: fmt + .orphans + .into_iter() + .map(|expr| Statement::Expr { expr, has_semi: true }) + .collect(), + tail: Some(unsafe_arg_new), + label: None, + }); + } + + self.alloc_expr( + Expr::Call { + callee: new_v1_formatted, + args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]), + }, + syntax_ptr, + ) + } + + /// `format_args!` expansion implementation for rustc versions >= `1.89.0`, + /// especially since [this PR](https://github.com/rust-lang/rust/pull/140748) + fn collect_format_args_impl( + &mut self, + syntax_ptr: AstPtr, + fmt: FormatArgs, + argmap: FxIndexSet<(usize, ArgumentType)>, + lit_pieces: ExprId, + format_options: ExprId, + ) -> ExprId { + let arguments = &*fmt.arguments.arguments; + + let (let_stmts, args) = if arguments.is_empty() { + ( + // Generate: + // [] + vec![], + self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: Box::default(), + })), + ) + } else if argmap.len() == 1 && arguments.len() == 1 { + // Only one argument, so we don't need to make the `args` tuple. + // + // Generate: + // super let args = [::new_display(&arg)]; + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let ref_arg = self.alloc_expr_desugared(Expr::Ref { + expr: arguments[arg_index].expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + self.make_argument(ref_arg, ty) + }) + .collect(); + let args = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); + let args_name = Name::new_symbol_root(sym::args); + let args_binding = self.alloc_binding( + args_name.clone(), + BindingAnnotation::Unannotated, + HygieneId::ROOT, + ); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + // TODO: We don't have `super let` yet. + let let_stmt = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(args), + else_branch: None, + }; + (vec![let_stmt], self.alloc_expr_desugared(Expr::Path(args_name.into()))) + } else { + // Generate: + // super let args = (&arg0, &arg1, &...); + let args_name = Name::new_symbol_root(sym::args); + let args_binding = self.alloc_binding( + args_name.clone(), + BindingAnnotation::Unannotated, + HygieneId::ROOT, + ); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + let elements = arguments + .iter() + .map(|arg| { + self.alloc_expr_desugared(Expr::Ref { + expr: arg.expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }) + .collect(); + let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements }); + // TODO: We don't have `super let` yet + let let_stmt1 = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(args_tuple), + else_branch: None, + }; + + // Generate: + // super let args = [ + // ::new_display(args.0), + // ::new_lower_hex(args.1), + // ::new_debug(args.0), + // … + // ]; + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let args_ident_expr = + self.alloc_expr_desugared(Expr::Path(args_name.clone().into())); + let arg = self.alloc_expr_desugared(Expr::Field { + expr: args_ident_expr, + name: Name::new_tuple_field(arg_index), + }); + self.make_argument(arg, ty) + }) + .collect(); + let array = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); + let args_binding = self.alloc_binding( + args_name.clone(), + BindingAnnotation::Unannotated, + HygieneId::ROOT, + ); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + let let_stmt2 = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(array), + else_branch: None, + }; + (vec![let_stmt1, let_stmt2], self.alloc_expr_desugared(Expr::Path(args_name.into()))) + }; + + // Generate: + // &args + let args = self.alloc_expr_desugared(Expr::Ref { + expr: args, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + + let call_block = { + // Generate: + // unsafe { + // ::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // ) + // } + + let new_v1_formatted = self.ty_rel_lang_path( + self.lang_items().FormatArguments, + Name::new_symbol_root(sym::new_v1_formatted), + ); + let new_v1_formatted = + self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path)); + let args = [lit_pieces, args, format_options]; + let call = self + .alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() }); + + Expr::Unsafe { id: None, statements: Box::default(), tail: Some(call) } + }; + + if !let_stmts.is_empty() { + // Generate: + // { + // super let … + // super let … + // ::new_…(…) + // } + let call = self.alloc_expr_desugared(call_block); + self.alloc_expr( + Expr::Block { + id: None, + statements: let_stmts.into(), + tail: Some(call), + label: None, + }, + syntax_ptr, + ) + } else { + self.alloc_expr(call_block, syntax_ptr) + } + } + + /// Generate a hir expression for a format_args placeholder specification. + /// + /// Generates + /// + /// ```text + /// ::…, // alignment + /// …u32, // flags + /// , // width + /// , // precision + /// ) + /// ``` + fn make_format_spec( + &mut self, + placeholder: &FormatPlaceholder, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + let lang_items = self.lang_items(); + let position = match placeholder.argument.index { + Ok(arg_index) => { + let (i, _) = + argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); + self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))) + } + Err(_) => self.missing_expr(), + }; + let &FormatOptions { + ref width, + ref precision, + alignment, + fill, + sign, + alternate, + zero_pad, + debug_hex, + } = &placeholder.format_options; + + let precision_expr = self.make_count(precision, argmap); + let width_expr = self.make_count(width, argmap); + + if self.krate.workspace_data(self.db).is_atleast_187() { + // These need to match the constants in library/core/src/fmt/rt.rs. + let align = match alignment { + Some(FormatAlignment::Left) => 0, + Some(FormatAlignment::Right) => 1, + Some(FormatAlignment::Center) => 2, + None => 3, + }; + // This needs to match `Flag` in library/core/src/fmt/rt.rs. + let flags = fill.unwrap_or(' ') as u32 + | ((sign == Some(FormatSign::Plus)) as u32) << 21 + | ((sign == Some(FormatSign::Minus)) as u32) << 22 + | (alternate as u32) << 23 + | (zero_pad as u32) << 24 + | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25 + | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26 + | (width.is_some() as u32) << 27 + | (precision.is_some() as u32) << 28 + | align << 29 + | 1 << 31; // Highest bit always set. + let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + flags as u128, + Some(BuiltinUint::U32), + ))); + + let position = + RecordLitField { name: Name::new_symbol_root(sym::position), expr: position }; + let flags = RecordLitField { name: Name::new_symbol_root(sym::flags), expr: flags }; + let precision = RecordLitField { + name: Name::new_symbol_root(sym::precision), + expr: precision_expr, + }; + let width = + RecordLitField { name: Name::new_symbol_root(sym::width), expr: width_expr }; + self.alloc_expr_desugared(Expr::RecordLit { + path: self.lang_path(lang_items.FormatPlaceholder).map(Box::new), + fields: Box::new([position, flags, precision, width]), + spread: None, + }) + } else { + let format_placeholder_new = { + let format_placeholder_new = self.ty_rel_lang_path( + lang_items.FormatPlaceholder, + Name::new_symbol_root(sym::new), + ); + match format_placeholder_new { + Some(path) => self.alloc_expr_desugared(Expr::Path(path)), + None => self.missing_expr(), + } + }; + // This needs to match `Flag` in library/core/src/fmt/rt.rs. + let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32) + | (((sign == Some(FormatSign::Minus)) as u32) << 1) + | ((alternate as u32) << 2) + | ((zero_pad as u32) << 3) + | (((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4) + | (((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5); + let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + flags as u128, + Some(BuiltinUint::U32), + ))); + let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' ')))); + let align = { + let align = self.ty_rel_lang_path( + lang_items.FormatAlignment, + match alignment { + Some(FormatAlignment::Left) => Name::new_symbol_root(sym::Left), + Some(FormatAlignment::Right) => Name::new_symbol_root(sym::Right), + Some(FormatAlignment::Center) => Name::new_symbol_root(sym::Center), + None => Name::new_symbol_root(sym::Unknown), + }, + ); + match align { + Some(path) => self.alloc_expr_desugared(Expr::Path(path)), + None => self.missing_expr(), + } + }; + self.alloc_expr_desugared(Expr::Call { + callee: format_placeholder_new, + args: Box::new([position, fill, align, flags, precision_expr, width_expr]), + }) + } + } + + /// Generate a hir expression for a format_args Count. + /// + /// Generates: + /// + /// ```text + /// ::Is(…) + /// ``` + /// + /// or + /// + /// ```text + /// ::Param(…) + /// ``` + /// + /// or + /// + /// ```text + /// ::Implied + /// ``` + fn make_count( + &mut self, + count: &Option, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + let lang_items = self.lang_items(); + match count { + Some(FormatCount::Literal(n)) => { + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + *n as u128, + // FIXME: Change this to Some(BuiltinUint::U16) once we drop support for toolchains < 1.88 + None, + ))); + let count_is = match self + .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Is)) + { + Some(count_is) => self.alloc_expr_desugared(Expr::Path(count_is)), + None => self.missing_expr(), + }; + self.alloc_expr_desugared(Expr::Call { callee: count_is, args: Box::new([args]) }) + } + Some(FormatCount::Argument(arg)) => { + if let Ok(arg_index) = arg.index { + let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); + + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))); + let count_param = match self + .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Param)) + { + Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), + None => self.missing_expr(), + }; + self.alloc_expr_desugared(Expr::Call { + callee: count_param, + args: Box::new([args]), + }) + } else { + // FIXME: This drops arg causing it to potentially not be resolved/type checked + // when typing? + self.missing_expr() + } + } + None => match self + .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Implied)) + { + Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), + None => self.missing_expr(), + }, + } + } + + /// Generate a hir expression representing an argument to a format_args invocation. + /// + /// Generates: + /// + /// ```text + /// ::new_…(arg) + /// ``` + fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { + use ArgumentType::*; + use FormatTrait::*; + + let new_fn = match self.ty_rel_lang_path( + self.lang_items().FormatArgument, + Name::new_symbol_root(match ty { + Format(Display) => sym::new_display, + Format(Debug) => sym::new_debug, + Format(LowerExp) => sym::new_lower_exp, + Format(UpperExp) => sym::new_upper_exp, + Format(Octal) => sym::new_octal, + Format(Pointer) => sym::new_pointer, + Format(Binary) => sym::new_binary, + Format(LowerHex) => sym::new_lower_hex, + Format(UpperHex) => sym::new_upper_hex, + Usize => sym::from_usize, + }), + ) { + Some(new_fn) => self.alloc_expr_desugared(Expr::Path(new_fn)), + None => self.missing_expr(), + }; + self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) }) + } +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum ArgumentType { + Format(FormatTrait), + Usize, +} From 99df3375e33f9da54764d87764ada1fb6783087a Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Thu, 11 Dec 2025 00:35:48 +0900 Subject: [PATCH 24/39] fix: Support dyn compatibility for old toolchains without `MetaSized` --- .../crates/hir-ty/src/dyn_compatibility.rs | 24 ++++---- .../hir-ty/src/tests/regression/new_solver.rs | 56 +++++++++++++++++++ 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs index 506c4abc8377..64b15eb017a6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs @@ -427,9 +427,14 @@ fn receiver_is_dispatchable<'db>( }; let meta_sized_did = lang_items.MetaSized; - let Some(meta_sized_did) = meta_sized_did else { - return false; - }; + + // TODO: This is for supporting dyn compatibility for toolchains doesn't contain `MetaSized` + // trait. Uncomment and short circuit here once `MINIMUM_SUPPORTED_TOOLCHAIN_VERSION` + // become > 1.88.0 + // + // let Some(meta_sized_did) = meta_sized_did else { + // return false; + // }; // Type `U` // FIXME: That seems problematic to fake a generic param like that? @@ -450,17 +455,16 @@ fn receiver_is_dispatchable<'db>( }); let trait_predicate = TraitRef::new_from_args(interner, trait_.into(), args); - let meta_sized_predicate = - TraitRef::new(interner, meta_sized_did.into(), [unsized_self_ty]); + let meta_sized_predicate = meta_sized_did + .map(|did| TraitRef::new(interner, did.into(), [unsized_self_ty]).upcast(interner)); ParamEnv { clauses: Clauses::new_from_iter( interner, - generic_predicates.iter_identity_copied().chain([ - unsize_predicate.upcast(interner), - trait_predicate.upcast(interner), - meta_sized_predicate.upcast(interner), - ]), + generic_predicates + .iter_identity_copied() + .chain([unsize_predicate.upcast(interner), trait_predicate.upcast(interner)]) + .chain(meta_sized_predicate), ), } }; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs index 5c1f85cb2a95..e11cc85e7ff1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs @@ -226,6 +226,62 @@ fn debug(_: &dyn Foo) {} impl Foo for i32 {} +fn main() { + debug(&1); +}"#, + ); + + // toolchains <= 1.88.0, before sized-hierarchy. + check_no_mismatches( + r#" +#[lang = "sized"] +pub trait Sized {} + +#[lang = "unsize"] +pub trait Unsize {} + +#[lang = "coerce_unsized"] +pub trait CoerceUnsized {} + +impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<&'a mut U> for &'a mut T {} + +impl<'a, 'b: 'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<&'a U> for &'b mut T {} + +impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<*mut U> for &'a mut T {} + +impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<*const U> for &'a mut T {} + +impl<'a, 'b: 'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} + +impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<*const U> for &'a T {} + +impl, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} + +impl, U: ?Sized> CoerceUnsized<*const U> for *mut T {} + +impl, U: ?Sized> CoerceUnsized<*const U> for *const T {} + +#[lang = "dispatch_from_dyn"] +pub trait DispatchFromDyn {} + +impl<'a, T: ?Sized + Unsize, U: ?Sized> DispatchFromDyn<&'a U> for &'a T {} + +impl<'a, T: ?Sized + Unsize, U: ?Sized> DispatchFromDyn<&'a mut U> for &'a mut T {} + +impl, U: ?Sized> DispatchFromDyn<*const U> for *const T {} + +impl, U: ?Sized> DispatchFromDyn<*mut U> for *mut T {} + +trait Foo { + fn bar(&self) -> u32 { + 0xCAFE + } +} + +fn debug(_: &dyn Foo) {} + +impl Foo for i32 {} + fn main() { debug(&1); }"#, From 8747b0abf1576144f49bbb84d47705e92417543d Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Thu, 11 Dec 2025 00:36:28 +0900 Subject: [PATCH 25/39] Bring back `MINIMUM_SUPPORTED_TOOLCHAIN_VERSION` to `1.78.0` --- src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs index 3dea21e56485..6ae527abb1ff 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs @@ -14,7 +14,7 @@ /// Any toolchain less than this version will likely not work with rust-analyzer built from this revision. pub const MINIMUM_SUPPORTED_TOOLCHAIN_VERSION: semver::Version = semver::Version { major: 1, - minor: 90, + minor: 78, patch: 0, pre: semver::Prerelease::EMPTY, build: semver::BuildMetadata::EMPTY, From c0bf494d24ad7fe8aed76320286b105cf90be4c0 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 10 Dec 2025 18:42:22 +0200 Subject: [PATCH 26/39] Support the new lowering of format_args!() --- .../crates/hir-def/src/expr_store/lower.rs | 32 +- .../src/expr_store/lower/format_args.rs | 461 +++++++++++++++--- .../hir-def/src/expr_store/tests/body.rs | 84 +++- .../crates/test-utils/src/minicore.rs | 51 +- 4 files changed, 523 insertions(+), 105 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index 3448b734ff10..0b25812f261b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -432,6 +432,8 @@ pub struct ExprCollector<'db> { awaitable_context: Option, krate: base_db::Crate, + + name_generator_index: usize, } #[derive(Clone, Debug)] @@ -537,9 +539,16 @@ pub fn new( current_block_legacy_macro_defs_count: FxHashMap::default(), outer_impl_trait: false, krate, + name_generator_index: 0, } } + fn generate_new_name(&mut self) -> Name { + let index = self.name_generator_index; + self.name_generator_index += 1; + Name::generate_new_name(index) + } + #[inline] pub(crate) fn lang_items(&self) -> &'db LangItems { self.lang_items.get_or_init(|| crate::lang_item::lang_items(self.db, self.def_map.krate())) @@ -1638,9 +1647,8 @@ fn with_binding_owner(&mut self, create_expr: impl FnOnce(&mut Self) -> ExprId) /// and save the `` to use it as a break target for desugaring of the `?` operator. fn desugar_try_block(&mut self, e: BlockExpr) -> ExprId { let try_from_output = self.lang_path(self.lang_items().TryTraitFromOutput); - let label = self.alloc_label_desugared(Label { - name: Name::generate_new_name(self.store.labels.len()), - }); + let label = self.generate_new_name(); + let label = self.alloc_label_desugared(Label { name: label }); let old_label = self.current_try_block_label.replace(label); let ptr = AstPtr::new(&e).upcast(); @@ -1768,7 +1776,7 @@ fn collect_for_loop(&mut self, syntax_ptr: AstPtr, e: ast::ForExpr) - this.collect_expr_opt(e.loop_body().map(|it| it.into())) }), }; - let iter_name = Name::generate_new_name(self.store.exprs.len()); + let iter_name = self.generate_new_name(); let iter_expr = self.alloc_expr(Expr::Path(Path::from(iter_name.clone())), syntax_ptr); let iter_expr_mut = self.alloc_expr( Expr::Ref { expr: iter_expr, rawness: Rawness::Ref, mutability: Mutability::Mut }, @@ -1829,7 +1837,7 @@ fn collect_try_operator(&mut self, syntax_ptr: AstPtr, e: ast::TryExp let try_branch = self.alloc_expr(try_branch.map_or(Expr::Missing, Expr::Path), syntax_ptr); let expr = self .alloc_expr(Expr::Call { callee: try_branch, args: Box::new([operand]) }, syntax_ptr); - let continue_name = Name::generate_new_name(self.store.bindings.len()); + let continue_name = self.generate_new_name(); let continue_binding = self.alloc_binding( continue_name.clone(), BindingAnnotation::Unannotated, @@ -1847,7 +1855,7 @@ fn collect_try_operator(&mut self, syntax_ptr: AstPtr, e: ast::TryExp guard: None, expr: self.alloc_expr(Expr::Path(Path::from(continue_name)), syntax_ptr), }; - let break_name = Name::generate_new_name(self.store.bindings.len()); + let break_name = self.generate_new_name(); let break_binding = self.alloc_binding(break_name.clone(), BindingAnnotation::Unannotated, HygieneId::ROOT); let break_bpat = self.alloc_pat_desugared(Pat::Bind { id: break_binding, subpat: None }); @@ -2628,9 +2636,17 @@ fn lang_path(&self, lang: Option>) -> Option { fn ty_rel_lang_path( &self, lang: Option>, - relative_name: Name, + relative_name: Symbol, ) -> Option { - Some(Path::LangItem(lang?.into(), Some(relative_name))) + Some(Path::LangItem(lang?.into(), Some(Name::new_symbol_root(relative_name)))) + } + + fn ty_rel_lang_path_expr( + &self, + lang: Option>, + relative_name: Symbol, + ) -> Expr { + self.ty_rel_lang_path(lang, relative_name).map_or(Expr::Missing, Expr::Path) } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs index aa7bf0e4e244..7efc9a956c1c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs @@ -19,6 +19,7 @@ FormatPlaceholder, FormatSign, FormatTrait, }, }, + lang_item::LangItemTarget, type_ref::{Mutability, Rawness}, }; @@ -94,6 +95,347 @@ pub(super) fn collect_format_args( ), }; + let idx = if self.lang_items().FormatCount.is_none() { + self.collect_format_args_after_1_93_0_impl(syntax_ptr, fmt) + } else { + self.collect_format_args_before_1_93_0_impl(syntax_ptr, fmt) + }; + + self.store + .template_map + .get_or_insert_with(Default::default) + .format_args_to_captures + .insert(idx, (hygiene, mappings)); + idx + } + + fn collect_format_args_after_1_93_0_impl( + &mut self, + syntax_ptr: AstPtr, + fmt: FormatArgs, + ) -> ExprId { + let lang_items = self.lang_items(); + + // Create a list of all _unique_ (argument, format trait) combinations. + // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] + // + // We use usize::MAX for arguments that don't exist, because that can never be a valid index + // into the arguments array. + let mut argmap = FxIndexSet::default(); + + let mut incomplete_lit = String::new(); + + let mut implicit_arg_index = 0; + + let mut bytecode = Vec::new(); + + let template = if fmt.template.is_empty() { + // Treat empty templates as a single literal piece (with an empty string), + // so we produce `from_str("")` for those. + &[FormatArgsPiece::Literal(sym::__empty)][..] + } else { + &fmt.template[..] + }; + + // See library/core/src/fmt/mod.rs for the format string encoding format. + + for (i, piece) in template.iter().enumerate() { + match piece { + FormatArgsPiece::Literal(sym) => { + // Coalesce adjacent literal pieces. + if let Some(FormatArgsPiece::Literal(_)) = template.get(i + 1) { + incomplete_lit.push_str(sym.as_str()); + continue; + } + let mut s = if incomplete_lit.is_empty() { + sym.as_str() + } else { + incomplete_lit.push_str(sym.as_str()); + &incomplete_lit + }; + + // If this is the last piece and was the only piece, that means + // there are no placeholders and the entire format string is just a literal. + // + // In that case, we can just use `from_str`. + if i + 1 == template.len() && bytecode.is_empty() { + // Generate: + // ::from_str("meow") + let from_str = self.ty_rel_lang_path_desugared_expr( + lang_items.FormatArguments, + sym::from_str, + ); + let sym = + if incomplete_lit.is_empty() { sym.clone() } else { Symbol::intern(s) }; + let s = self.alloc_expr_desugared(Expr::Literal(Literal::String(sym))); + let from_str = self.alloc_expr( + Expr::Call { callee: from_str, args: Box::new([s]) }, + syntax_ptr, + ); + return if !fmt.arguments.arguments.is_empty() { + // With an incomplete format string (e.g. only an opening `{`), it's possible for `arguments` + // to be non-empty when reaching this code path. + self.alloc_expr( + Expr::Block { + id: None, + statements: fmt + .arguments + .arguments + .iter() + .map(|arg| Statement::Expr { + expr: arg.expr, + has_semi: true, + }) + .collect(), + tail: Some(from_str), + label: None, + }, + syntax_ptr, + ) + } else { + from_str + }; + } + + // Encode the literal in chunks of up to u16::MAX bytes, split at utf-8 boundaries. + while !s.is_empty() { + let len = s.floor_char_boundary(usize::from(u16::MAX)); + if len < 0x80 { + bytecode.push(len as u8); + } else { + bytecode.push(0x80); + bytecode.extend_from_slice(&(len as u16).to_le_bytes()); + } + bytecode.extend(&s.as_bytes()[..len]); + s = &s[len..]; + } + + incomplete_lit.clear(); + } + FormatArgsPiece::Placeholder(p) => { + // Push the start byte and remember its index so we can set the option bits later. + let i = bytecode.len(); + bytecode.push(0xC0); + + let position = match &p.argument.index { + &Ok(it) => it, + Err(_) => usize::MAX, + }; + let position = argmap + .insert_full((position, ArgumentType::Format(p.format_trait))) + .0 as u64; + + // This needs to match the constants in library/core/src/fmt/mod.rs. + let o = &p.format_options; + let align = match o.alignment { + Some(FormatAlignment::Left) => 0, + Some(FormatAlignment::Right) => 1, + Some(FormatAlignment::Center) => 2, + None => 3, + }; + let default_flags = 0x6000_0020; + let flags: u32 = o.fill.unwrap_or(' ') as u32 + | ((o.sign == Some(FormatSign::Plus)) as u32) << 21 + | ((o.sign == Some(FormatSign::Minus)) as u32) << 22 + | (o.alternate as u32) << 23 + | (o.zero_pad as u32) << 24 + | ((o.debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25 + | ((o.debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26 + | (o.width.is_some() as u32) << 27 + | (o.precision.is_some() as u32) << 28 + | align << 29; + if flags != default_flags { + bytecode[i] |= 1; + bytecode.extend_from_slice(&flags.to_le_bytes()); + if let Some(val) = &o.width { + let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap); + // Only encode if nonzero; zero is the default. + if indirect || val != 0 { + bytecode[i] |= 1 << 1 | (indirect as u8) << 4; + bytecode.extend_from_slice(&val.to_le_bytes()); + } + } + if let Some(val) = &o.precision { + let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap); + // Only encode if nonzero; zero is the default. + if indirect || val != 0 { + bytecode[i] |= 1 << 2 | (indirect as u8) << 5; + bytecode.extend_from_slice(&val.to_le_bytes()); + } + } + } + if implicit_arg_index != position { + bytecode[i] |= 1 << 3; + bytecode.extend_from_slice(&(position as u16).to_le_bytes()); + } + implicit_arg_index = position + 1; + } + } + } + + assert!(incomplete_lit.is_empty()); + + // Zero terminator. + bytecode.push(0); + + // Ensure all argument indexes actually fit in 16 bits, as we truncated them to 16 bits before. + if argmap.len() > u16::MAX as usize { + // FIXME: Emit an error. + // ctx.dcx().span_err(macsp, "too many format arguments"); + } + + let arguments = &fmt.arguments.arguments[..]; + + let (mut statements, args) = if arguments.is_empty() { + // Generate: + // [] + ( + Vec::new(), + self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: Box::new([]), + })), + ) + } else { + // Generate: + // super let args = (&arg0, &arg1, &…); + let args_name = self.generate_new_name(); + let args_path = Path::from(args_name.clone()); + let args_binding = self.alloc_binding( + args_name.clone(), + BindingAnnotation::Unannotated, + HygieneId::ROOT, + ); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + let elements = arguments + .iter() + .map(|arg| { + self.alloc_expr_desugared(Expr::Ref { + expr: arg.expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }) + .collect(); + let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements }); + // FIXME: Make this a `super let` when we have this statement. + let let_statement_1 = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(args_tuple), + else_branch: None, + }; + + // Generate: + // super let args = [ + // ::new_display(args.0), + // ::new_lower_hex(args.1), + // ::new_debug(args.0), + // … + // ]; + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let args_ident_expr = self.alloc_expr_desugared(Expr::Path(args_path.clone())); + let arg = self.alloc_expr_desugared(Expr::Field { + expr: args_ident_expr, + name: Name::new_tuple_field(arg_index), + }); + self.make_argument(arg, ty) + }) + .collect(); + let args = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); + let args_binding = + self.alloc_binding(args_name, BindingAnnotation::Unannotated, HygieneId::ROOT); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + // FIXME: Make this a `super let` when we have this statement. + let let_statement_2 = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(args), + else_branch: None, + }; + ( + vec![let_statement_1, let_statement_2], + self.alloc_expr_desugared(Expr::Path(args_path)), + ) + }; + + // Generate: + // unsafe { + // ::new(b"…", &args) + // } + let template = self + .alloc_expr_desugared(Expr::Literal(Literal::ByteString(bytecode.into_boxed_slice()))); + let call = { + let new = self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new); + let args = self.alloc_expr_desugared(Expr::Ref { + expr: args, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + self.alloc_expr_desugared(Expr::Call { callee: new, args: Box::new([template, args]) }) + }; + let call = self.alloc_expr( + Expr::Unsafe { id: None, statements: Box::new([]), tail: Some(call) }, + syntax_ptr, + ); + + // We collect the unused expressions here so that we still infer them instead of + // dropping them out of the expression tree. We cannot store them in the `Unsafe` + // block because then unsafe blocks within them will get a false "unused unsafe" + // diagnostic (rustc has a notion of builtin unsafe blocks, but we don't). + statements + .extend(fmt.orphans.into_iter().map(|expr| Statement::Expr { expr, has_semi: true })); + + if !statements.is_empty() { + // Generate: + // { + // super let … + // super let … + // ::new(…) + // } + self.alloc_expr( + Expr::Block { + id: None, + statements: statements.into_boxed_slice(), + tail: Some(call), + label: None, + }, + syntax_ptr, + ) + } else { + call + } + } + + /// Get the value for a `width` or `precision` field. + /// + /// Returns the value and whether it is indirect (an indexed argument) or not. + fn make_count_after_1_93_0( + &self, + count: &FormatCount, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> (bool, u16) { + match count { + FormatCount::Literal(n) => (false, *n), + FormatCount::Argument(arg) => { + let index = match &arg.index { + &Ok(it) => it, + Err(_) => usize::MAX, + }; + (true, argmap.insert_full((index, ArgumentType::Usize)).0 as u16) + } + } + } + + fn collect_format_args_before_1_93_0_impl( + &mut self, + syntax_ptr: AstPtr, + fmt: FormatArgs, + ) -> ExprId { // Create a list of all _unique_ (argument, format trait) combinations. // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] let mut argmap = FxIndexSet::default(); @@ -160,8 +502,14 @@ pub(super) fn collect_format_args( let fmt_unsafe_arg = lang_items.FormatUnsafeArg; let use_format_args_since_1_89_0 = fmt_args.is_some() && fmt_unsafe_arg.is_none(); - let idx = if use_format_args_since_1_89_0 { - self.collect_format_args_impl(syntax_ptr, fmt, argmap, lit_pieces, format_options) + if use_format_args_since_1_89_0 { + self.collect_format_args_after_1_89_0_impl( + syntax_ptr, + fmt, + argmap, + lit_pieces, + format_options, + ) } else { self.collect_format_args_before_1_89_0_impl( syntax_ptr, @@ -170,14 +518,7 @@ pub(super) fn collect_format_args( lit_pieces, format_options, ) - }; - - self.store - .template_map - .get_or_insert_with(Default::default) - .format_args_to_captures - .insert(idx, (hygiene, mappings)); - idx + } } /// `format_args!` expansion implementation for rustc versions < `1.89.0` @@ -238,17 +579,10 @@ fn collect_format_args_before_1_89_0_impl( // ) let lang_items = self.lang_items(); - let new_v1_formatted = self.ty_rel_lang_path( - lang_items.FormatArguments, - Name::new_symbol_root(sym::new_v1_formatted), - ); - let unsafe_arg_new = - self.ty_rel_lang_path(lang_items.FormatUnsafeArg, Name::new_symbol_root(sym::new)); let new_v1_formatted = - self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path)); - + self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new_v1_formatted); let unsafe_arg_new = - self.alloc_expr_desugared(unsafe_arg_new.map_or(Expr::Missing, Expr::Path)); + self.ty_rel_lang_path_desugared_expr(lang_items.FormatUnsafeArg, sym::new); let unsafe_arg_new = self.alloc_expr_desugared(Expr::Call { callee: unsafe_arg_new, args: Box::default() }); let mut unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe { @@ -284,7 +618,7 @@ fn collect_format_args_before_1_89_0_impl( /// `format_args!` expansion implementation for rustc versions >= `1.89.0`, /// especially since [this PR](https://github.com/rust-lang/rust/pull/140748) - fn collect_format_args_impl( + fn collect_format_args_after_1_89_0_impl( &mut self, syntax_ptr: AstPtr, fmt: FormatArgs, @@ -422,12 +756,10 @@ fn collect_format_args_impl( // ) // } - let new_v1_formatted = self.ty_rel_lang_path( + let new_v1_formatted = self.ty_rel_lang_path_desugared_expr( self.lang_items().FormatArguments, - Name::new_symbol_root(sym::new_v1_formatted), + sym::new_v1_formatted, ); - let new_v1_formatted = - self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path)); let args = [lit_pieces, args, format_options]; let call = self .alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() }); @@ -499,8 +831,8 @@ fn make_format_spec( debug_hex, } = &placeholder.format_options; - let precision_expr = self.make_count(precision, argmap); - let width_expr = self.make_count(width, argmap); + let precision_expr = self.make_count_before_1_93_0(precision, argmap); + let width_expr = self.make_count_before_1_93_0(width, argmap); if self.krate.workspace_data(self.db).is_atleast_187() { // These need to match the constants in library/core/src/fmt/rt.rs. @@ -542,16 +874,8 @@ fn make_format_spec( spread: None, }) } else { - let format_placeholder_new = { - let format_placeholder_new = self.ty_rel_lang_path( - lang_items.FormatPlaceholder, - Name::new_symbol_root(sym::new), - ); - match format_placeholder_new { - Some(path) => self.alloc_expr_desugared(Expr::Path(path)), - None => self.missing_expr(), - } - }; + let format_placeholder_new = + self.ty_rel_lang_path_desugared_expr(lang_items.FormatPlaceholder, sym::new); // This needs to match `Flag` in library/core/src/fmt/rt.rs. let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32) | (((sign == Some(FormatSign::Minus)) as u32) << 1) @@ -564,21 +888,15 @@ fn make_format_spec( Some(BuiltinUint::U32), ))); let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' ')))); - let align = { - let align = self.ty_rel_lang_path( - lang_items.FormatAlignment, - match alignment { - Some(FormatAlignment::Left) => Name::new_symbol_root(sym::Left), - Some(FormatAlignment::Right) => Name::new_symbol_root(sym::Right), - Some(FormatAlignment::Center) => Name::new_symbol_root(sym::Center), - None => Name::new_symbol_root(sym::Unknown), - }, - ); - match align { - Some(path) => self.alloc_expr_desugared(Expr::Path(path)), - None => self.missing_expr(), - } - }; + let align = self.ty_rel_lang_path_desugared_expr( + lang_items.FormatAlignment, + match alignment { + Some(FormatAlignment::Left) => sym::Left, + Some(FormatAlignment::Right) => sym::Right, + Some(FormatAlignment::Center) => sym::Center, + None => sym::Unknown, + }, + ); self.alloc_expr_desugared(Expr::Call { callee: format_placeholder_new, args: Box::new([position, fill, align, flags, precision_expr, width_expr]), @@ -605,7 +923,7 @@ fn make_format_spec( /// ```text /// ::Implied /// ``` - fn make_count( + fn make_count_before_1_93_0( &mut self, count: &Option, argmap: &mut FxIndexSet<(usize, ArgumentType)>, @@ -618,12 +936,8 @@ fn make_count( // FIXME: Change this to Some(BuiltinUint::U16) once we drop support for toolchains < 1.88 None, ))); - let count_is = match self - .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Is)) - { - Some(count_is) => self.alloc_expr_desugared(Expr::Path(count_is)), - None => self.missing_expr(), - }; + let count_is = + self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Is); self.alloc_expr_desugared(Expr::Call { callee: count_is, args: Box::new([args]) }) } Some(FormatCount::Argument(arg)) => { @@ -634,12 +948,8 @@ fn make_count( i as u128, Some(BuiltinUint::Usize), ))); - let count_param = match self - .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Param)) - { - Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), - None => self.missing_expr(), - }; + let count_param = + self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Param); self.alloc_expr_desugared(Expr::Call { callee: count_param, args: Box::new([args]), @@ -650,9 +960,7 @@ fn make_count( self.missing_expr() } } - None => match self - .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Implied)) - { + None => match self.ty_rel_lang_path(lang_items.FormatCount, sym::Implied) { Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), None => self.missing_expr(), }, @@ -670,9 +978,9 @@ fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { use ArgumentType::*; use FormatTrait::*; - let new_fn = match self.ty_rel_lang_path( + let new_fn = self.ty_rel_lang_path_desugared_expr( self.lang_items().FormatArgument, - Name::new_symbol_root(match ty { + match ty { Format(Display) => sym::new_display, Format(Debug) => sym::new_debug, Format(LowerExp) => sym::new_lower_exp, @@ -683,13 +991,18 @@ fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { Format(LowerHex) => sym::new_lower_hex, Format(UpperHex) => sym::new_upper_hex, Usize => sym::from_usize, - }), - ) { - Some(new_fn) => self.alloc_expr_desugared(Expr::Path(new_fn)), - None => self.missing_expr(), - }; + }, + ); self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) }) } + + fn ty_rel_lang_path_desugared_expr( + &mut self, + lang: Option>, + relative_name: Symbol, + ) -> ExprId { + self.alloc_expr_desugared(self.ty_rel_lang_path_expr(lang, relative_name)) + } } #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs index 22ade4387582..7e48ca8f4558 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs @@ -161,9 +161,9 @@ fn main() { match builtin#lang(into_iter)( 0..10, ) { - mut 11 => loop { + mut 0 => loop { match builtin#lang(next)( - &mut 11, + &mut 0, ) { builtin#lang(None) => break, builtin#lang(Some)(ident) => { @@ -261,10 +261,10 @@ fn main() { } #[test] -fn desugar_builtin_format_args() { +fn desugar_builtin_format_args_before_1_93_0() { let (db, body, def) = lower( r#" -//- minicore: fmt +//- minicore: fmt_before_1_93_0 fn main() { let are = "are"; let count = 10; @@ -343,6 +343,59 @@ fn main() { .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) } +#[test] +fn desugar_builtin_format_args() { + let (db, body, def) = lower( + r#" +//- minicore: fmt +fn main() { + let are = "are"; + let count = 10; + builtin#format_args("\u{1b}hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", orphan = (), last = "!"); + builtin#format_args("hello world"); + builtin#format_args("hello world", orphan = ()); +} +"#, + ); + + expect![[r#" + fn main() { + let are = "are"; + let count = 10; + { + let 0 = (&"fancy", &(), &"!", &count, &are, ); + let 0 = [ + builtin#lang(Argument::new_display)( + 0.3, + ), builtin#lang(Argument::new_display)( + 0.0, + ), builtin#lang(Argument::new_debug)( + 0.4, + ), builtin#lang(Argument::new_display)( + 0.2, + ), + ]; + (); + unsafe { + builtin#lang(Arguments::new)( + "\x07\x1bhello \xc3 \x00\x00i\x02\x00\x01 \xc0\r friends, we \xc0\x01 \xc8\x01\x00\xc8\x03\x00\x00", + &0, + ) + } + }; + builtin#lang(Arguments::from_str)( + "hello world", + ); + { + (); + builtin#lang(Arguments::from_str)( + "hello world", + ) + }; + }"#]] + .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) +} + #[test] fn test_macro_hygiene() { let (db, body, def) = lower( @@ -382,27 +435,16 @@ pub(crate) fn new(message: impl Into) -> SsrError { fn main() { _ = ra_test_fixture::error::SsrError::new( { - let args = [ + let 0 = (&node.text(), ); + let 0 = [ builtin#lang(Argument::new_display)( - &node.text(), + 0.0, ), ]; unsafe { - builtin#lang(Arguments::new_v1_formatted)( - &[ - "Failed to resolve path `", "`", - ], - &args, - &[ - builtin#lang(Placeholder::new)( - 0usize, - ' ', - builtin#lang(Alignment::Unknown), - 0u32, - builtin#lang(Count::Implied), - builtin#lang(Count::Implied), - ), - ], + builtin#lang(Arguments::new)( + "\x18Failed to resolve path `\xc0\x01`\x00", + &0, ) } }, diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs index 0fe17e3075de..b7c09391ec37 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs @@ -34,7 +34,8 @@ //! eq: sized //! error: fmt //! fmt: option, result, transmute, coerce_unsized, copy, clone, derive -//! fmt_before_1_89_0: fmt +//! fmt_before_1_93_0: fmt +//! fmt_before_1_89_0: fmt_before_1_93_0 //! fn: sized, tuple //! from: sized, result //! future: pin @@ -1259,6 +1260,7 @@ pub enum Alignment { Unknown, } + // region:fmt_before_1_93_0 #[lang = "format_count"] pub enum Count { Is(usize), @@ -1288,6 +1290,7 @@ pub const fn new( Placeholder { position, fill, align, flags, precision, width } } } + // endregion:fmt_before_1_93_0 // region:fmt_before_1_89_0 #[lang = "format_unsafe_arg"] @@ -1303,6 +1306,7 @@ pub unsafe fn new() -> Self { // endregion:fmt_before_1_89_0 } + // region:fmt_before_1_93_0 #[derive(Copy, Clone)] #[lang = "format_arguments"] pub struct Arguments<'a> { @@ -1341,6 +1345,14 @@ pub unsafe fn new_v1_formatted( } // endregion:!fmt_before_1_89_0 + pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> { + Self::from_str(s) + } + + pub const fn from_str(s: &'static str) -> Arguments<'a> { + Arguments { pieces: &[s], fmt: None, args: &[] } + } + pub const fn as_str(&self) -> Option<&'static str> { match (self.pieces, self.args) { ([], []) => Some(""), @@ -1349,6 +1361,41 @@ pub const fn as_str(&self) -> Option<&'static str> { } } } + // endregion:fmt_before_1_93_0 + + // region:!fmt_before_1_93_0 + #[lang = "format_arguments"] + #[derive(Copy, Clone)] + pub struct Arguments<'a> { + // This is a non-faithful representation of `core::fmt::Arguments`, because the real one + // is too complex for minicore. + message: Option<&'a str>, + } + + impl<'a> Arguments<'a> { + pub unsafe fn new( + _template: &'a [u8; N], + _args: &'a [rt::Argument<'a>; M], + ) -> Arguments<'a> { + Arguments { message: None } + } + + pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> { + Arguments { message: Some(s) } + } + + pub const fn from_str(s: &'static str) -> Arguments<'a> { + Arguments { message: Some(s) } + } + + pub fn as_str(&self) -> Option<&'static str> { + match self.message { + Some(s) => unsafe { Some(&*(s as *const str)) }, + None => None, + } + } + } + // endregion:!fmt_before_1_93_0 // region:derive pub(crate) mod derive { @@ -1817,7 +1864,7 @@ pub const fn panic_fmt(fmt: crate::fmt::Arguments<'_>) -> ! { #[lang = "panic"] pub const fn panic(expr: &'static str) -> ! { - panic_fmt(crate::fmt::Arguments::new_const(&[expr])) + panic_fmt(crate::fmt::Arguments::from_str(expr)) } } // endregion:panic From 8f9d86226dd67c6ff486b4220f26c65234c323fa Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 10 Dec 2025 22:27:21 +0200 Subject: [PATCH 27/39] Support `#[feature(associated_type_defaults)]` --- .../crates/hir-ty/src/next_solver/interner.rs | 9 +- .../crates/hir-ty/src/next_solver/solver.rs | 85 ++++++++++--------- .../crates/hir-ty/src/tests/traits.rs | 20 +++++ 3 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs index 2edf442a8cc6..8b24a20a5bed 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs @@ -1104,14 +1104,7 @@ fn variances_of(self, def_id: Self::DefId) -> Self::VariancesOf { fn type_of(self, def_id: Self::DefId) -> EarlyBinder { match def_id { - SolverDefId::TypeAliasId(id) => { - use hir_def::Lookup; - match id.lookup(self.db()).container { - ItemContainerId::ImplId(it) => it, - _ => panic!("assoc ty value should be in impl"), - }; - self.db().ty(id.into()) - } + SolverDefId::TypeAliasId(id) => self.db().ty(id.into()), SolverDefId::AdtId(id) => self.db().ty(id.into()), // FIXME(next-solver): This uses the types of `query mir_borrowck` in rustc. // diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs index 859e26e37641..40a3f17cf169 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs @@ -177,45 +177,52 @@ fn fetch_eligible_assoc_item( impl_id: ImplIdWrapper, ) -> Result, ErrorGuaranteed> { let impl_items = impl_id.0.impl_items(self.0.interner.db()); - let id = match trait_assoc_def_id { - SolverDefId::TypeAliasId(trait_assoc_id) => { - let trait_assoc_data = self.0.interner.db.type_alias_signature(trait_assoc_id); - impl_items - .items - .iter() - .find_map(|(impl_assoc_name, impl_assoc_id)| { - if let AssocItemId::TypeAliasId(impl_assoc_id) = *impl_assoc_id - && *impl_assoc_name == trait_assoc_data.name - { - Some(impl_assoc_id) - } else { - None - } - }) - .map(SolverDefId::TypeAliasId) - } - SolverDefId::ConstId(trait_assoc_id) => { - let trait_assoc_data = self.0.interner.db.const_signature(trait_assoc_id); - let trait_assoc_name = trait_assoc_data - .name - .as_ref() - .expect("unnamed consts should not get passed to the solver"); - impl_items - .items - .iter() - .find_map(|(impl_assoc_name, impl_assoc_id)| { - if let AssocItemId::ConstId(impl_assoc_id) = *impl_assoc_id - && impl_assoc_name == trait_assoc_name - { - Some(impl_assoc_id) - } else { - None - } - }) - .map(SolverDefId::ConstId) - } - _ => panic!("Unexpected SolverDefId"), - }; + let id = + match trait_assoc_def_id { + SolverDefId::TypeAliasId(trait_assoc_id) => { + let trait_assoc_data = self.0.interner.db.type_alias_signature(trait_assoc_id); + impl_items + .items + .iter() + .find_map(|(impl_assoc_name, impl_assoc_id)| { + if let AssocItemId::TypeAliasId(impl_assoc_id) = *impl_assoc_id + && *impl_assoc_name == trait_assoc_data.name + { + Some(impl_assoc_id) + } else { + None + } + }) + .or_else(|| { + if trait_assoc_data.ty.is_some() { Some(trait_assoc_id) } else { None } + }) + .map(SolverDefId::TypeAliasId) + } + SolverDefId::ConstId(trait_assoc_id) => { + let trait_assoc_data = self.0.interner.db.const_signature(trait_assoc_id); + let trait_assoc_name = trait_assoc_data + .name + .as_ref() + .expect("unnamed consts should not get passed to the solver"); + impl_items + .items + .iter() + .find_map(|(impl_assoc_name, impl_assoc_id)| { + if let AssocItemId::ConstId(impl_assoc_id) = *impl_assoc_id + && impl_assoc_name == trait_assoc_name + { + Some(impl_assoc_id) + } else { + None + } + }) + .or_else(|| { + if trait_assoc_data.has_body() { Some(trait_assoc_id) } else { None } + }) + .map(SolverDefId::ConstId) + } + _ => panic!("Unexpected SolverDefId"), + }; Ok(id) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index 677e35775dd7..a54c0a799dfc 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -5079,3 +5079,23 @@ fn foo(base_layer_two: &dyn BaseLayerOne) { "#, ); } + +#[test] +fn default_assoc_types() { + check_types( + r#" +trait Trait { + type Assoc = (T, U); + fn method(self) -> Self::Assoc { loop {} } +} + +struct Struct(T); +impl Trait<((), T)> for Struct {} + +fn foo(v: Struct) { + v.method(); + // ^^^^^^^^^^ (((), f32), i32) +} + "#, + ); +} From 6d512195a9b00cbf6a00d1b18863f7ed5a5fe89a Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 10 Dec 2025 22:45:20 +0200 Subject: [PATCH 28/39] `#[rustc_deprecated_safe_2024]` can also come as `#[rustc_deprecated_safe_2024(audit_that = "reason")]` --- src/tools/rust-analyzer/crates/hir-def/src/attrs.rs | 3 +++ .../crates/ide-diagnostics/src/handlers/missing_unsafe.rs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index 607638f32b4e..febc794b5a05 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -155,6 +155,9 @@ fn match_attr_flags(attr_flags: &mut AttrFlags, attr: Meta) -> ControlFlow { extract_rustc_skip_during_method_dispatch(attr_flags, tt) } + "rustc_deprecated_safe_2024" => { + attr_flags.insert(AttrFlags::RUSTC_DEPRECATED_SAFE_2024) + } _ => {} }, 2 => match path.segments[0].text() { diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs index 59215f34a581..1abb50144d34 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs @@ -296,8 +296,12 @@ fn no_missing_unsafe_diagnostic_with_deprecated_safe_2024() { #[rustc_deprecated_safe_2024] fn set_var() {} +#[rustc_deprecated_safe_2024(audit_that = "something")] +fn set_var2() {} + fn main() { set_var(); + set_var2(); } "#, ); From ad25b20bdbc7a4aa301f20090519873d34f219a1 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 12 Dec 2025 00:09:31 +0200 Subject: [PATCH 29/39] Fix Clippy Needed because there is a new Clippy - Rust 1.92.0 was released. --- .../rust-analyzer/crates/hir-ty/src/infer.rs | 23 ++++++++----------- .../hir-ty/src/next_solver/infer/traits.rs | 4 ---- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index cafe0329692c..70868e4b95aa 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -653,19 +653,16 @@ pub fn type_of_expr_or_pat(&self, id: ExprOrPatId) -> Option> { } pub fn type_of_expr_with_adjust(&self, id: ExprId) -> Option> { match self.expr_adjustments.get(&id).and_then(|adjustments| { - adjustments - .iter() - .filter(|adj| { - // https://github.com/rust-lang/rust/blob/67819923ac8ea353aaa775303f4c3aacbf41d010/compiler/rustc_mir_build/src/thir/cx/expr.rs#L140 - !matches!( - adj, - Adjustment { - kind: Adjust::NeverToAny, - target, - } if target.is_never() - ) - }) - .next_back() + adjustments.iter().rfind(|adj| { + // https://github.com/rust-lang/rust/blob/67819923ac8ea353aaa775303f4c3aacbf41d010/compiler/rustc_mir_build/src/thir/cx/expr.rs#L140 + !matches!( + adj, + Adjustment { + kind: Adjust::NeverToAny, + target, + } if target.is_never() + ) + }) }) { Some(adjustment) => Some(adjustment.target), None => self.type_of_expr.get(id).copied(), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/traits.rs index 4f000c24cc73..3409de17a122 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/traits.rs @@ -36,10 +36,6 @@ pub struct ObligationCause { } impl ObligationCause { - #[expect( - clippy::new_without_default, - reason = "`new` is temporary, eventually we will provide span etc. here" - )] #[inline] pub fn new() -> ObligationCause { ObligationCause { _private: () } From 901ab2b4a8a90114c471e553b91bd40bdb20d231 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 11 Dec 2025 23:54:54 +0200 Subject: [PATCH 30/39] Fix a panic in `ast::TypeBound::kind()` --- .../crates/hir-def/src/expr_store/lower.rs | 3 ++- .../hir-def/src/expr_store/tests/signatures.rs | 12 ++++++++++++ .../rust-analyzer/crates/syntax/src/ast/node_ext.rs | 11 +++++++---- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index 0b2af134c8ca..e72de6d68297 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -949,7 +949,8 @@ fn lower_type_bound( node: ast::TypeBound, impl_trait_lower_fn: ImplTraitLowerFn<'_>, ) -> TypeBound { - match node.kind() { + let Some(kind) = node.kind() else { return TypeBound::Error }; + match kind { ast::TypeBoundKind::PathType(binder, path_type) => { let binder = match binder.and_then(|it| it.generic_param_list()) { Some(gpl) => gpl diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/signatures.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/signatures.rs index 2dac4e7fc84b..f1db00cf6a67 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/signatures.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/signatures.rs @@ -197,3 +197,15 @@ fn allowed3(Param[1]) "#]], ); } + +#[test] +fn regression_21138() { + lower_and_print( + r#" +fn foo(v: for<'a> Trait1 + Trait2) {} + "#, + expect![[r#" + fn foo(dyn for<'a> Trait1 + Trait2) {...} + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs index 901d17bb1491..b872221bf711 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs @@ -813,13 +813,16 @@ pub enum TypeBoundKind { } impl ast::TypeBound { - pub fn kind(&self) -> TypeBoundKind { + pub fn kind(&self) -> Option { if let Some(path_type) = support::children(self.syntax()).next() { - TypeBoundKind::PathType(self.for_binder(), path_type) + Some(TypeBoundKind::PathType(self.for_binder(), path_type)) + } else if let Some(for_binder) = support::children::(&self.syntax).next() { + let Some(ast::Type::PathType(path_type)) = for_binder.ty() else { return None }; + Some(TypeBoundKind::PathType(for_binder.for_binder(), path_type)) } else if let Some(args) = self.use_bound_generic_args() { - TypeBoundKind::Use(args) + Some(TypeBoundKind::Use(args)) } else if let Some(lifetime) = self.lifetime() { - TypeBoundKind::Lifetime(lifetime) + Some(TypeBoundKind::Lifetime(lifetime)) } else { unreachable!() } From 31258db8e224654244cad95c557bd76d8e71d4f9 Mon Sep 17 00:00:00 2001 From: tris203 Date: Thu, 11 Dec 2025 22:33:58 +0000 Subject: [PATCH 31/39] fix(lsp): handle dynamic registration for didSave Update server capabilities to set `save` to `None` when the client supports dynamic registration for `didSaveTextDocument`. This prevents redundant static registration and aligns with LSP specification. --- .../crates/rust-analyzer/src/lsp/capabilities.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs index f94e7486ff8f..d6a694be9121 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs @@ -37,7 +37,11 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { change: Some(TextDocumentSyncKind::INCREMENTAL), will_save: None, will_save_wait_until: None, - save: Some(SaveOptions::default().into()), + save: if config.caps().did_save_text_document_dynamic_registration() { + None + } else { + Some(SaveOptions::default().into()) + }, })), hover_provider: Some(HoverProviderCapability::Simple(true)), completion_provider: Some(CompletionOptions { From a3dd6be7624247174961e8dfe2e3dcb9ce763d70 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 12 Dec 2025 18:55:49 +0800 Subject: [PATCH 32/39] Reorder add_return_type assist This assist is often before inline and is very inconvenient Usually, incomplete expression statements are written at the tail of the block, but they are not the return value of the block ```rust fn foo() { Some(2).$0and(optb) } ``` ```text 1. Add this function's return type 2. Inline `and` 3. Qualify `and` method call 4. Replace and with and_then ``` --- src/tools/rust-analyzer/crates/ide-assists/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs index 16b5684c16a4..47cb4c8e74cb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -247,7 +247,6 @@ pub(crate) fn all() -> &'static [Handler] { add_label_to_loop::add_label_to_loop, add_lifetime_to_type::add_lifetime_to_type, add_missing_match_arms::add_missing_match_arms, - add_return_type::add_return_type, add_turbo_fish::add_turbo_fish, apply_demorgan::apply_demorgan_iterator, apply_demorgan::apply_demorgan, @@ -392,6 +391,7 @@ pub(crate) fn all() -> &'static [Handler] { // used as a tie-breaker. add_missing_impl_members::add_missing_impl_members, add_missing_impl_members::add_missing_default_members, + add_return_type::add_return_type, // replace_string_with_char::replace_string_with_char, replace_string_with_char::replace_char_with_string, From 67c0f741ef1176c60340d3cd1736b170264edcc7 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 13 Dec 2025 20:12:08 +0200 Subject: [PATCH 33/39] Use a generated name in old format_args lowering, instead of `args` To prevent it from clashing with other names. --- .../hir-def/src/expr_store/lower/format_args.rs | 4 ++-- .../crates/hir-def/src/expr_store/tests/body.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs index 7efc9a956c1c..4bbfc5b144f1 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs @@ -655,7 +655,7 @@ fn collect_format_args_after_1_89_0_impl( .collect(); let args = self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); - let args_name = Name::new_symbol_root(sym::args); + let args_name = self.generate_new_name(); let args_binding = self.alloc_binding( args_name.clone(), BindingAnnotation::Unannotated, @@ -674,7 +674,7 @@ fn collect_format_args_after_1_89_0_impl( } else { // Generate: // super let args = (&arg0, &arg1, &...); - let args_name = Name::new_symbol_root(sym::args); + let args_name = self.generate_new_name(); let args_binding = self.alloc_binding( args_name.clone(), BindingAnnotation::Unannotated, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs index 7e48ca8f4558..504c310684d6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs @@ -278,16 +278,16 @@ fn main() { let are = "are"; let count = 10; { - let args = (&"fancy", &(), &"!", &count, &are, ); - let args = [ + let 0 = (&"fancy", &(), &"!", &count, &are, ); + let 0 = [ builtin#lang(Argument::new_display)( - args.3, + 0.3, ), builtin#lang(Argument::new_display)( - args.0, + 0.0, ), builtin#lang(Argument::new_debug)( - args.4, + 0.4, ), builtin#lang(Argument::new_display)( - args.2, + 0.2, ), ]; unsafe { @@ -295,7 +295,7 @@ fn main() { &[ "\u{1b}hello ", " ", " friends, we ", " ", "", ], - &args, + &0, &[ builtin#lang(Placeholder::new)( 0usize, From 189a794a50ae964b59b157c90af4862ce6285fd8 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 14 Dec 2025 07:40:41 +0800 Subject: [PATCH 34/39] Fix bind_unused_param applicable on closure Example --- ```rust fn foo() { let _ = |$0x| 2; } ``` **Before this PR** ```rust fn foo() { let _ = x; let _ = |x| 2; } ``` **After this PR** Assist not applicable --- .../ide-assists/src/handlers/bind_unused_param.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs index 1b24f7fe7ffb..771e80bb9231 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs @@ -33,7 +33,7 @@ pub(crate) fn bind_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> O return None; } - let func = param.syntax().ancestors().find_map(ast::Fn::cast)?; + let func = param.syntax().ancestors().nth(2).and_then(ast::Fn::cast)?; let stmt_list = func.body()?.stmt_list()?; let l_curly_range = stmt_list.l_curly_token()?.text_range(); let r_curly_range = stmt_list.r_curly_token()?.text_range(); @@ -176,6 +176,18 @@ fn keep_underscore_used() { bind_unused_param, r#" fn foo($0_x: i32, y: i32) {} +"#, + ); + } + + #[test] + fn not_applicable_closure() { + check_assist_not_applicable( + bind_unused_param, + r#" +fn foo() { + let _ = |$0x| 2; +} "#, ); } From 8bd369063e35abe3519f34227ed0653dfa424b19 Mon Sep 17 00:00:00 2001 From: Jesung Yang Date: Sun, 14 Dec 2025 03:25:08 +0000 Subject: [PATCH 35/39] fix: respect rustc's lint attribute application order Reverse the order of returned lint attributes for a `SyntaxNode` to match rustc's behavior. When multiple lint attributes are present, rustc overrides earlier ones with the last defined attribute. The previous iteration order was incorrect, causing earlier attributes to override the later ones. --- .../rust-analyzer/crates/hir/src/semantics.rs | 2 +- .../src/handlers/incorrect_case.rs | 3 +- .../src/handlers/unused_variables.rs | 55 +++++++++++++++++++ .../crates/ide-diagnostics/src/lib.rs | 10 +--- 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index ffb518b1e66f..b15e642daae7 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -267,7 +267,7 @@ pub fn lint_attrs( &self, krate: Crate, item: ast::AnyHasAttrs, - ) -> impl Iterator { + ) -> impl DoubleEndedIterator { let mut cfg_options = None; let cfg_options = || *cfg_options.get_or_insert_with(|| krate.id.cfg_options(self.db)); let mut result = Vec::new(); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs index fdc426c32c7b..4a12c5a26d45 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -1000,7 +1000,8 @@ fn BAD_CASE() {} // ^^^^^^^^^^^^ 💡 error: Module `OtherBadCase` should have snake_case name, e.g. `other_bad_case` //- /BAD_CASE/OtherBadCase.rs -#![deny(non_snake_case)] +#![allow(non_snake_case)] +#![deny(non_snake_case)] // The lint level has been overridden. fn FOO() {} // ^^^ 💡 error: Function `FOO` should have snake_case name, e.g. `foo` diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unused_variables.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unused_variables.rs index 84e63acbc04f..b7ec8fa53fa7 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unused_variables.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unused_variables.rs @@ -182,6 +182,61 @@ fn main2() { ); } + #[test] + fn apply_last_lint_attribute_when_multiple_are_present() { + check_diagnostics( + r#" +#![allow(unused_variables)] +#![warn(unused_variables)] +#![deny(unused_variables)] + +fn main() { + let x = 2; + //^ 💡 error: unused variable + + #[deny(unused_variables)] + #[warn(unused_variables)] + #[allow(unused_variables)] + let y = 0; +} +"#, + ); + } + + #[test] + fn prefer_closest_ancestor_lint_attribute() { + check_diagnostics( + r#" +#![allow(unused_variables)] + +fn main() { + #![warn(unused_variables)] + + #[deny(unused_variables)] + let x = 2; + //^ 💡 error: unused variable +} + +#[warn(unused_variables)] +fn main2() { + #[deny(unused_variables)] + let x = 2; + //^ 💡 error: unused variable +} + +#[warn(unused_variables)] +fn main3() { + let x = 2; + //^ 💡 warn: unused variable +} + +fn main4() { + let x = 2; +} +"#, + ); + } + #[test] fn fix_unused_variable() { check_fix( diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs index fe04bd175c96..343c28e8f9a4 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -643,19 +643,13 @@ fn find_outline_mod_lint_severity( let mod_def = sema.to_module_def(&mod_node)?; let module_source_file = sema.module_definition_node(mod_def); - let mut result = None; let lint_groups = lint_groups(&diag.code, edition); lint_attrs( sema, krate, ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"), ) - .for_each(|(lint, severity)| { - if lint_groups.contains(&lint) { - result = Some(severity); - } - }); - result + .find_map(|(lint, severity)| lint_groups.contains(&lint).then_some(severity)) } fn lint_severity_at( @@ -682,7 +676,7 @@ fn lint_attrs( krate: hir::Crate, ancestor: ast::AnyHasAttrs, ) -> impl Iterator { - sema.lint_attrs(krate, ancestor).map(|(lint_attr, lint)| { + sema.lint_attrs(krate, ancestor).rev().map(|(lint_attr, lint)| { let severity = match lint_attr { hir::LintAttr::Allow | hir::LintAttr::Expect => Severity::Allow, hir::LintAttr::Warn => Severity::Warning, From 701bb2fee1ca3708401a5426d31817cf3fa6bda8 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 12 Dec 2025 12:32:52 +0100 Subject: [PATCH 36/39] minor: Emit `WorkspaceDiagnosticRefresh` when flycheck finished --- .../crates/rust-analyzer/src/main_loop.rs | 16 ++++++++++++---- .../rust-analyzer/tests/slow-tests/support.rs | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 77dedf12720c..1a1c0182f87a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -437,11 +437,17 @@ fn handle_event(&mut self, event: Event) { } } Event::Flycheck(message) => { - let _p = tracing::info_span!("GlobalState::handle_event/flycheck").entered(); - self.handle_flycheck_msg(message); + let mut cargo_finished = false; + self.handle_flycheck_msg(message, &mut cargo_finished); // Coalesce many flycheck updates into a single loop turn while let Ok(message) = self.flycheck_receiver.try_recv() { - self.handle_flycheck_msg(message); + self.handle_flycheck_msg(message, &mut cargo_finished); + } + if cargo_finished { + self.send_request::( + (), + |_, _| (), + ); } } Event::TestResult(message) => { @@ -1109,7 +1115,7 @@ fn handle_cargo_test_msg(&mut self, message: CargoTestMessage) { } } - fn handle_flycheck_msg(&mut self, message: FlycheckMessage) { + fn handle_flycheck_msg(&mut self, message: FlycheckMessage, cargo_finished: &mut bool) { match message { FlycheckMessage::AddDiagnostic { id, @@ -1167,6 +1173,7 @@ fn handle_flycheck_msg(&mut self, message: FlycheckMessage) { flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)), flycheck::Progress::DidCancel => { self.last_flycheck_error = None; + *cargo_finished = true; (Progress::End, None) } flycheck::Progress::DidFailToRestart(err) => { @@ -1177,6 +1184,7 @@ fn handle_flycheck_msg(&mut self, message: FlycheckMessage) { flycheck::Progress::DidFinish(result) => { self.last_flycheck_error = result.err().map(|err| format!("cargo check failed to start: {err}")); + *cargo_finished = true; (Progress::End, None) } }; 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 b1b428e7068d..195ad226ae0c 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 @@ -48,6 +48,7 @@ pub(crate) fn with_fixture(fixture: &str) -> Project<'_> { "enable": false, }, }, + "checkOnSave": false, "procMacro": { "enable": false, } From aecd399ac8466744ca53005499ddaf374db2ed8c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 7 Dec 2025 14:50:24 +0100 Subject: [PATCH 37/39] internal: Give `FileSymbol` it's `'db` lifetime --- .../rust-analyzer/crates/base-db/src/lib.rs | 22 ++++++ src/tools/rust-analyzer/crates/hir/src/db.rs | 37 --------- .../rust-analyzer/crates/hir/src/symbols.rs | 23 ++++-- .../crates/ide-db/src/symbol_index.rs | 77 +++++++++++-------- .../ide-db/src/test_data/test_doc_alias.txt | 7 ++ .../test_symbol_index_collection.txt | 33 ++++++++ .../test_symbols_exclude_imports.txt | 1 + .../test_data/test_symbols_with_imports.txt | 2 + .../crates/ide/src/navigation_target.rs | 2 +- 9 files changed, 129 insertions(+), 75 deletions(-) diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index 97938924100b..0e2255ba3fb9 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -59,6 +59,28 @@ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { }; } +/// # SAFETY +/// +/// `old_pointer` must be valid for unique writes +pub unsafe fn unsafe_update_eq(old_pointer: *mut T, new_value: T) -> bool +where + T: PartialEq, +{ + // SAFETY: Caller obligation + let old_ref: &mut T = unsafe { &mut *old_pointer }; + + if *old_ref != new_value { + *old_ref = new_value; + true + } else { + // Subtle but important: Eq impls can be buggy or define equality + // in surprising ways. If it says that the value has not changed, + // we do not modify the existing value, and thus do not have to + // update the revision, as downstream code will not see the new value. + false + } +} + pub const DEFAULT_FILE_TEXT_LRU_CAP: u16 = 16; pub const DEFAULT_PARSE_LRU_CAP: u16 = 128; pub const DEFAULT_BORROWCK_LRU_CAP: u16 = 2024; diff --git a/src/tools/rust-analyzer/crates/hir/src/db.rs b/src/tools/rust-analyzer/crates/hir/src/db.rs index 64d97b3f2a23..3021ccf4029c 100644 --- a/src/tools/rust-analyzer/crates/hir/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir/src/db.rs @@ -4,42 +4,5 @@ //! //! But we need this for at least LRU caching at the query level. pub use hir_def::db::DefDatabase; -// AttrsQuery, BlockDefMapQuery, BlockItemTreeQuery, BlockItemTreeWithSourceMapQuery, BodyQuery, -// BodyWithSourceMapQuery, ConstDataQuery, ConstVisibilityQuery, CrateDefMapQuery, -// CrateLangItemsQuery, CrateNotableTraitsQuery, CrateSupportsNoStdQuery, DefDatabase, -// DefDatabaseStorage, EnumDataQuery, EnumVariantDataWithDiagnosticsQuery, -// ExpandProcAttrMacrosQuery, ExprScopesQuery, ExternCrateDeclDataQuery, FieldVisibilitiesQuery, -// FieldsAttrsQuery, FieldsAttrsSourceMapQuery, FileItemTreeQuery, FileItemTreeWithSourceMapQuery, -// FunctionDataQuery, FunctionVisibilityQuery, GenericParamsQuery, -// GenericParamsWithSourceMapQuery, ImplItemsWithDiagnosticsQuery, ImportMapQuery, -// IncludeMacroInvocQuery, InternAnonymousConstQuery, InternBlockQuery, InternConstQuery, -// InternDatabase, InternDatabaseStorage, InternEnumQuery, InternExternBlockQuery, -// InternExternCrateQuery, InternFunctionQuery, InternImplQuery, InternInTypeConstQuery, -// InternMacro2Query, InternMacroRulesQuery, InternProcMacroQuery, InternStaticQuery, -// InternStructQuery, InternTraitAliasQuery, InternTraitQuery, InternTypeAliasQuery, -// InternUnionQuery, InternUseQuery, LangItemQuery, Macro2DataQuery, MacroDefQuery, -// MacroRulesDataQuery, NotableTraitsInDepsQuery, ProcMacroDataQuery, StaticDataQuery, -// StructDataWithDiagnosticsQuery, TraitAliasDataQuery, TraitItemsWithDiagnosticsQuery, -// TypeAliasDataQuery, UnionDataWithDiagnosticsQuery, -// }; pub use hir_expand::db::ExpandDatabase; -// AstIdMapQuery, DeclMacroExpanderQuery, ExpandDatabase, ExpandDatabaseStorage, -// ExpandProcMacroQuery, InternMacroCallQuery, InternSyntaxContextQuery, MacroArgQuery, -// ParseMacroExpansionErrorQuery, ParseMacroExpansionQuery, ProcMacroSpanQuery, ProcMacrosQuery, -// RealSpanMapQuery, pub use hir_ty::db::HirDatabase; -// AdtDatumQuery, AdtVarianceQuery, AssociatedTyDataQuery, AssociatedTyValueQuery, BorrowckQuery, -// CallableItemSignatureQuery, ConstEvalDiscriminantQuery, ConstEvalQuery, ConstEvalStaticQuery, -// ConstParamTyQuery, DynCompatibilityOfTraitQuery, FieldTypesQuery, FnDefDatumQuery, -// FnDefVarianceQuery, GenericDefaultsQuery, GenericPredicatesForParamQuery, -// GenericPredicatesQuery, GenericPredicatesWithoutParentQuery, HirDatabase, HirDatabaseStorage, -// ImplDatumQuery, ImplSelfTyQuery, ImplTraitQuery, IncoherentInherentImplCratesQuery, InferQuery, -// InherentImplsInBlockQuery, InherentImplsInCrateQuery, InternCallableDefQuery, -// InternClosureQuery, InternCoroutineQuery, InternImplTraitIdQuery, InternLifetimeParamIdQuery, -// InternTypeOrConstParamIdQuery, LayoutOfAdtQuery, LayoutOfTyQuery, LookupImplMethodQuery, -// MirBodyForClosureQuery, MirBodyQuery, MonomorphizedMirBodyForClosureQuery, -// MonomorphizedMirBodyQuery, ProgramClausesForChalkEnvQuery, ReturnTypeImplTraitsQuery, -// TargetDataLayoutQuery, TraitDatumQuery, TraitEnvironmentQuery, TraitImplsInBlockQuery, -// TraitImplsInCrateQuery, TraitImplsInDepsQuery, TraitSolveQuery, TyQuery, -// TypeAliasImplTraitsQuery, ValueTyQuery, -// }; diff --git a/src/tools/rust-analyzer/crates/hir/src/symbols.rs b/src/tools/rust-analyzer/crates/hir/src/symbols.rs index a9320fdda7e1..073142670d2a 100644 --- a/src/tools/rust-analyzer/crates/hir/src/symbols.rs +++ b/src/tools/rust-analyzer/crates/hir/src/symbols.rs @@ -1,5 +1,7 @@ //! File symbol extraction. +use std::marker::PhantomData; + use base_db::FxIndexSet; use either::Either; use hir_def::{ @@ -25,7 +27,7 @@ /// The actual data that is stored in the index. It should be as compact as /// possible. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FileSymbol { +pub struct FileSymbol<'db> { pub name: Symbol, pub def: ModuleDef, pub loc: DeclarationLocation, @@ -35,6 +37,7 @@ pub struct FileSymbol { pub is_assoc: bool, pub is_import: bool, pub do_not_complete: Complete, + _marker: PhantomData<&'db ()>, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -61,9 +64,9 @@ struct SymbolCollectorWork { parent: Option, } -pub struct SymbolCollector<'a> { - db: &'a dyn HirDatabase, - symbols: FxIndexSet, +pub struct SymbolCollector<'db> { + db: &'db dyn HirDatabase, + symbols: FxIndexSet>, work: Vec, current_container_name: Option, collect_pub_only: bool, @@ -83,10 +86,10 @@ pub fn new(db: &'a dyn HirDatabase, collect_pub_only: bool) -> Self { } pub fn new_module( - db: &dyn HirDatabase, + db: &'a dyn HirDatabase, module: Module, collect_pub_only: bool, - ) -> Box<[FileSymbol]> { + ) -> Box<[FileSymbol<'a>]> { let mut symbol_collector = SymbolCollector::new(db, collect_pub_only); symbol_collector.collect(module); symbol_collector.finish() @@ -105,7 +108,7 @@ pub fn collect(&mut self, module: Module) { } } - pub fn finish(self) -> Box<[FileSymbol]> { + pub fn finish(self) -> Box<[FileSymbol<'a>]> { self.symbols.into_iter().collect() } @@ -217,6 +220,7 @@ fn collect_from_module(&mut self, module_id: ModuleId) { is_assoc: false, is_import: true, do_not_complete: Complete::Yes, + _marker: PhantomData, }); }; @@ -251,6 +255,7 @@ fn collect_from_module(&mut self, module_id: ModuleId) { is_assoc: false, is_import: false, do_not_complete: Complete::Yes, + _marker: PhantomData, }); }; @@ -428,6 +433,7 @@ fn push_decl( is_assoc, is_import: false, do_not_complete, + _marker: PhantomData, }); } } @@ -441,6 +447,7 @@ fn push_decl( is_assoc, is_import: false, do_not_complete, + _marker: PhantomData, }); do_not_complete @@ -474,6 +481,7 @@ fn push_module(&mut self, module_id: ModuleId, name: &Name) { is_assoc: false, is_import: false, do_not_complete, + _marker: PhantomData, }); } } @@ -487,6 +495,7 @@ fn push_module(&mut self, module_id: ModuleId, name: &Name) { is_assoc: false, is_import: false, do_not_complete, + _marker: PhantomData, }); } } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs index c5ead45a20ae..e15d0b33bbaa 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs @@ -37,6 +37,7 @@ }; use rayon::prelude::*; use rustc_hash::FxHashSet; +use salsa::Update; use crate::RootDatabase; @@ -118,7 +119,7 @@ pub struct LocalRoots { } /// The symbol indices of modules that make up a given crate. -pub fn crate_symbols(db: &dyn HirDatabase, krate: Crate) -> Box<[&SymbolIndex]> { +pub fn crate_symbols(db: &dyn HirDatabase, krate: Crate) -> Box<[&SymbolIndex<'_>]> { let _p = tracing::info_span!("crate_symbols").entered(); krate.modules(db).into_iter().map(|module| SymbolIndex::module_symbols(db, module)).collect() } @@ -148,7 +149,7 @@ pub fn crate_symbols(db: &dyn HirDatabase, krate: Crate) -> Box<[&SymbolIndex]> // | Editor | Shortcut | // |---------|-----------| // | VS Code | Ctrl+T -pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec { +pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec> { let _p = tracing::info_span!("world_symbols", query = ?query.query).entered(); let indices: Vec<_> = if query.libs { @@ -170,9 +171,7 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec { crates .par_iter() .for_each_with(db.clone(), |snap, &krate| _ = crate_symbols(snap, krate.into())); - let indices: Vec<_> = - crates.into_iter().map(|krate| crate_symbols(db, krate.into())).collect(); - indices.iter().flat_map(|indices| indices.iter().cloned()).collect() + crates.into_iter().flat_map(|krate| Vec::from(crate_symbols(db, krate.into()))).collect() }; let mut res = vec![]; @@ -184,24 +183,27 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec { } #[derive(Default)] -pub struct SymbolIndex { - symbols: Box<[FileSymbol]>, +pub struct SymbolIndex<'db> { + symbols: Box<[FileSymbol<'db>]>, map: fst::Map>, } -impl SymbolIndex { +impl<'db> SymbolIndex<'db> { /// The symbol index for a given source root within library_roots. - pub fn library_symbols(db: &dyn HirDatabase, source_root_id: SourceRootId) -> &SymbolIndex { + pub fn library_symbols( + db: &'db dyn HirDatabase, + source_root_id: SourceRootId, + ) -> &'db SymbolIndex<'db> { // FIXME: #[salsa::interned] struct InternedSourceRootId { id: SourceRootId, } #[salsa::tracked(returns(ref))] - fn library_symbols( - db: &dyn HirDatabase, - source_root_id: InternedSourceRootId<'_>, - ) -> SymbolIndex { + fn library_symbols<'db>( + db: &'db dyn HirDatabase, + source_root_id: InternedSourceRootId<'db>, + ) -> SymbolIndex<'db> { let _p = tracing::info_span!("library_symbols").entered(); // We call this without attaching because this runs in parallel, so we need to attach here. @@ -224,7 +226,7 @@ fn library_symbols( /// The symbol index for a given module. These modules should only be in source roots that /// are inside local_roots. - pub fn module_symbols(db: &dyn HirDatabase, module: Module) -> &SymbolIndex { + pub fn module_symbols(db: &dyn HirDatabase, module: Module) -> &SymbolIndex<'_> { // FIXME: #[salsa::interned] struct InternedModuleId { @@ -232,7 +234,10 @@ struct InternedModuleId { } #[salsa::tracked(returns(ref))] - fn module_symbols(db: &dyn HirDatabase, module: InternedModuleId<'_>) -> SymbolIndex { + fn module_symbols<'db>( + db: &'db dyn HirDatabase, + module: InternedModuleId<'db>, + ) -> SymbolIndex<'db> { let _p = tracing::info_span!("module_symbols").entered(); // We call this without attaching because this runs in parallel, so we need to attach here. @@ -250,29 +255,41 @@ fn module_symbols(db: &dyn HirDatabase, module: InternedModuleId<'_>) -> SymbolI } } -impl fmt::Debug for SymbolIndex { +impl fmt::Debug for SymbolIndex<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SymbolIndex").field("n_symbols", &self.symbols.len()).finish() } } -impl PartialEq for SymbolIndex { - fn eq(&self, other: &SymbolIndex) -> bool { +impl PartialEq for SymbolIndex<'_> { + fn eq(&self, other: &SymbolIndex<'_>) -> bool { self.symbols == other.symbols } } -impl Eq for SymbolIndex {} +impl Eq for SymbolIndex<'_> {} -impl Hash for SymbolIndex { +impl Hash for SymbolIndex<'_> { fn hash(&self, hasher: &mut H) { self.symbols.hash(hasher) } } -impl SymbolIndex { - fn new(mut symbols: Box<[FileSymbol]>) -> SymbolIndex { - fn cmp(lhs: &FileSymbol, rhs: &FileSymbol) -> Ordering { +unsafe impl Update for SymbolIndex<'_> { + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + let this = unsafe { &mut *old_pointer }; + if *this == new_value { + false + } else { + *this = new_value; + true + } + } +} + +impl<'db> SymbolIndex<'db> { + fn new(mut symbols: Box<[FileSymbol<'db>]>) -> SymbolIndex<'db> { + fn cmp(lhs: &FileSymbol<'_>, rhs: &FileSymbol<'_>) -> Ordering { let lhs_chars = lhs.name.as_str().chars().map(|c| c.to_ascii_lowercase()); let rhs_chars = rhs.name.as_str().chars().map(|c| c.to_ascii_lowercase()); lhs_chars.cmp(rhs_chars) @@ -318,7 +335,7 @@ pub fn len(&self) -> usize { } pub fn memory_size(&self) -> usize { - self.map.as_fst().size() + self.symbols.len() * size_of::() + self.map.as_fst().size() + self.symbols.len() * size_of::>() } fn range_to_map_value(start: usize, end: usize) -> u64 { @@ -336,10 +353,10 @@ fn map_value_to_range(value: u64) -> (usize, usize) { } impl Query { - pub(crate) fn search<'sym, T>( + pub(crate) fn search<'db, T>( self, - indices: &'sym [&SymbolIndex], - cb: impl FnMut(&'sym FileSymbol) -> ControlFlow, + indices: &[&'db SymbolIndex<'db>], + cb: impl FnMut(&'db FileSymbol<'db>) -> ControlFlow, ) -> Option { let _p = tracing::info_span!("symbol_index::Query::search").entered(); let mut op = fst::map::OpBuilder::new(); @@ -371,11 +388,11 @@ pub(crate) fn search<'sym, T>( } } - fn search_maps<'sym, T>( + fn search_maps<'db, T>( &self, - indices: &'sym [&SymbolIndex], + indices: &[&'db SymbolIndex<'db>], mut stream: fst::map::Union<'_>, - mut cb: impl FnMut(&'sym FileSymbol) -> ControlFlow, + mut cb: impl FnMut(&'db FileSymbol<'db>) -> ControlFlow, ) -> Option { let ignore_underscore_prefixed = !self.query.starts_with("__"); while let Some((_, indexed_values)) = stream.next() { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt index c8a9231fa99e..5783d97564d0 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt @@ -39,6 +39,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "Struct", @@ -73,6 +74,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "mul1", @@ -107,6 +109,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "mul2", @@ -141,6 +144,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "s1", @@ -175,6 +179,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "s1", @@ -209,6 +214,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "s2", @@ -243,6 +249,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, ], ), diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt index cc879cf84fc6..953bc73da9d8 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -39,6 +39,7 @@ is_assoc: true, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "Alias", @@ -71,6 +72,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "B", @@ -105,6 +107,7 @@ is_assoc: true, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "CONST", @@ -137,6 +140,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "CONST_WITH_INNER", @@ -169,6 +173,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "Enum", @@ -203,6 +208,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "ItemLikeMacro", @@ -237,6 +243,7 @@ is_assoc: false, is_import: true, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "Macro", @@ -271,6 +278,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "STATIC", @@ -303,6 +311,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "Struct", @@ -337,6 +346,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "StructFromMacro", @@ -371,6 +381,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "StructInFn", @@ -407,6 +418,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "StructInNamedConst", @@ -443,6 +455,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "StructInUnnamedConst", @@ -477,6 +490,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "StructT", @@ -511,6 +525,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "Trait", @@ -543,6 +558,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "Trait", @@ -577,6 +593,7 @@ is_assoc: false, is_import: true, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "Union", @@ -611,6 +628,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "a_mod", @@ -643,6 +661,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "b_mod", @@ -675,6 +694,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "define_struct", @@ -709,6 +729,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "generic_impl_fn", @@ -743,6 +764,7 @@ is_assoc: true, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "impl_fn", @@ -777,6 +799,7 @@ is_assoc: true, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "macro_rules_macro", @@ -811,6 +834,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "main", @@ -843,6 +867,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "really_define_struct", @@ -877,6 +902,7 @@ is_assoc: false, is_import: true, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "trait_fn", @@ -911,6 +937,7 @@ is_assoc: true, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, ], ), @@ -954,6 +981,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, ], ), @@ -995,6 +1023,7 @@ is_assoc: false, is_import: true, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "IsThisJustATrait", @@ -1029,6 +1058,7 @@ is_assoc: false, is_import: true, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "StructInModB", @@ -1063,6 +1093,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "SuperItemLikeMacro", @@ -1097,6 +1128,7 @@ is_assoc: false, is_import: true, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "ThisStruct", @@ -1131,6 +1163,7 @@ is_assoc: false, is_import: true, do_not_complete: Yes, + _marker: PhantomData<&()>, }, ], ), diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt index dc512fe1b87a..6f5f8f889c7d 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt @@ -32,5 +32,6 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, ] diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt index 0b2522775d4d..5d3fe4d2658d 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt @@ -32,6 +32,7 @@ is_assoc: false, is_import: false, do_not_complete: Yes, + _marker: PhantomData<&()>, }, FileSymbol { name: "Foo", @@ -66,5 +67,6 @@ is_assoc: false, is_import: true, do_not_complete: Yes, + _marker: PhantomData<&()>, }, ] diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs index 67472507f66e..29530ed02bb6 100644 --- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs +++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs @@ -225,7 +225,7 @@ pub(crate) fn from_syntax( } } -impl TryToNav for FileSymbol { +impl<'db> TryToNav for FileSymbol<'db> { fn try_to_nav( &self, sema: &Semantics<'_, RootDatabase>, From 9dcd2efaa32782e1f5ec8cd689085d2f5ef9c2d8 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 15 Dec 2025 04:25:42 +0000 Subject: [PATCH 38/39] Prepare for merging from rust-lang/rust This updates the rust-version file to 0208ee09be465f69005a7a12c28d5eccac7d5f34. --- 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 7a84872f266d..dcf82c94aaee 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -dfe1b8c97bcde283102f706d5dcdc3649e5e12e3 +0208ee09be465f69005a7a12c28d5eccac7d5f34 From 964d2922ff8873ea227a6157a0d93e1006a8c28f Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 15 Dec 2025 04:30:49 +0000 Subject: [PATCH 39/39] Format code --- src/tools/rust-analyzer/crates/ide-completion/src/lib.rs | 1 - src/tools/rust-analyzer/crates/parser/src/lib.rs | 4 ++-- src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs | 1 - src/tools/rust-analyzer/crates/project-model/src/lib.rs | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs index c9d5971cd0e3..33ab43fa614a 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs @@ -2,7 +2,6 @@ // It's useful to refer to code that is private in doc comments. #![allow(rustdoc::private_intra_doc_links)] - #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #[cfg(feature = "in-rust-tree")] diff --git a/src/tools/rust-analyzer/crates/parser/src/lib.rs b/src/tools/rust-analyzer/crates/parser/src/lib.rs index 81cdc188012c..4478bf4e3733 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lib.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lib.rs @@ -24,9 +24,9 @@ #[cfg(not(feature = "in-rust-tree"))] extern crate ra_ap_rustc_lexer as rustc_lexer; #[cfg(feature = "in-rust-tree")] -extern crate rustc_lexer; -#[cfg(feature = "in-rust-tree")] extern crate rustc_driver as _; +#[cfg(feature = "in-rust-tree")] +extern crate rustc_lexer; mod event; mod frontmatter; diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs index 8e1faca00a1b..85b250eddfd4 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs @@ -11,7 +11,6 @@ feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span) )] #![allow(internal_features)] - #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #[cfg(feature = "in-rust-tree")] diff --git a/src/tools/rust-analyzer/crates/project-model/src/lib.rs b/src/tools/rust-analyzer/crates/project-model/src/lib.rs index 0d89e13ed374..3414b52d4514 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/lib.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/lib.rs @@ -17,7 +17,6 @@ // It's useful to refer to code that is private in doc comments. #![allow(rustdoc::private_intra_doc_links)] - #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #[cfg(feature = "in-rust-tree")]