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 2d6a59a7c365c..b281d5a02e536 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 dbe3ee0ed6039..9fd8b4b3159ea 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 @@ use syntax::{ 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 20e0302b57d70..7bca451c5e31f 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 @@ use syntax::{ 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 051c5835571bc..dba39204e32ee 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 dba39204e32ee..4a7afed61ee26 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 mod tokens { } } } + +#[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 dba39204e32ee..19019ad08d819 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 9695523921808..e0fac97f67abf 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 @@ impl SyntaxFactory { 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 c8a244b2136da..d564555b91ea1 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 ide_db::famous_defs::FamousDefs; 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 386625b86b271..a088fb178d257 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 @@ mod tests { 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 @@ mod tests { 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 0cf2e15bc6f02..625cc888c84cb 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 @@ use ide_db::{ 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 @@ impl StaticIndex<'_> { 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 @@ impl StaticIndex<'_> { 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 fbf3082e1b897..271d2507bcfea 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 mod example_mod { ); } + #[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 9695523921808..9847d639d6136 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 @@ impl SyntaxFactory { 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 | 449 ++++++++++-------- 1 file changed, 252 insertions(+), 197 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 e87dde5b8e427..03642756219a1 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 ide_db::{ }; 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 @@ impl Struct { 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_()?)); - - // 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)?; - - let assoc = process_assoc_item(item, qualified_path_type.clone(), field_name)?; - delegate_assoc_items.add_item(assoc); - } + make.path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); - // 5) Remove useless where clauses - if let Some(wc) = delegate.where_clause() { - remove_useless_where_clauses(&delegate.trait_()?, &delegate.self_ty()?, wc); - } - Some(delegate) + // 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() + }); + + 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,34 +511,76 @@ 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); - ted::insert( - ted::Position::after(wc.syntax()), - NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)), - ); - // Remove where clause - ted::remove(wc.syntax()); + let predicates_to_remove: Vec<_> = wc.predicates().filter(has_no_live_generics).collect(); + let remaining_predicates = wc.predicates().count() - predicates_to_remove.len(); + + 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. @@ -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 @@ where { 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 9847d639d6136..8efad0368a9d9 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 @@ impl SyntaxFactory { ast } - pub fn path_from_text(&self, text: &str) -> ast::Path { make::path_from_text(text).clone_for_update() } @@ -213,10 +212,10 @@ impl SyntaxFactory { 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 d49d19fbe10cb..00d83e9068482 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 | 167 ++++++++---------- .../src/handlers/mutability_errors.rs | 6 +- 2 files changed, 74 insertions(+), 99 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 26a50b53251f6..77930c49ce9ab 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { } } 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 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 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 { - Movability::Movable + ClosureKind::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 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { } } - 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl ExprCollector<'_> { 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 18280a4addec9..2887a32825db4 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 fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 { } "#, ); + // 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 03642756219a1..ab350e975df16 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 8efad0368a9d9..560dd77662ca4 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 @@ impl SyntaxFactory { 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 5ae65b05bc42e..eaacd8d1a8fef 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 0edab5e0b3b19..bcfe3a8aa5ce1 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 07e61a83c4401..afdac484a1170 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 071eacf6604c8..5330b7eb99418 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 afdac484a1170..d0d8c4877d21f 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 c9862495bc0c6..ac4efd0a24bcb 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 0b7eca2290f62..7fd94b4bedc88 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 foo(a: A, b: B) -> i32 { return 42; } ); } + #[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 foo(a: A, b: B) -> i32 { return 42; } ); } + #[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 d6d63891bb066..308c01865ae37 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 @@ impl<'db> HirPlace<'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 @@ impl<'db> InferenceContext<'_, 'db> { 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 565063fb138c4..4b20d6eb32208 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 @@ use crate::{ 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 3cafb6aa2503b..836c20a433485 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 @@ impl<'db, V: PartialEq> ProjectionElem<'db, V> { 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 4d76a9f3fb3d7..b39c9bc06559e 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 35b45174c2092..3b4913cae3fb7 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 @@ impl<'db> Evaluator<'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 357f617a21e13..73399dab7fbd0 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 b5ed770e161dd..859e26e37641d 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 @@ impl<'db> SolverDelegate for SolverContext<'db> { _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 98df8d0ff4451..ccd4bc9be84a9 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 la_arena::ArenaMap; 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 f374dd2cc9efe..df4cff0b8a17b 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 triomphe::Arc; 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 @@ impl<'db> ExprCollector<'db> { ) -> 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 2d60f44092c5f..836a079e777f2 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 cc0594f00d613..5d1cac8e93c49 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 intern::sym; use rustc_hash::FxHashSet; use crate::{ - FindPathConfig, ModuleDefId, ModuleIdLt, + FindPathConfig, ModuleDefId, ModuleId, db::DefDatabase, item_scope::ItemInNs, nameres::DefMap, @@ -24,7 +24,7 @@ use crate::{ 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 433aead77adbb..6c5d226cac1bf 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 @@ mod tests { 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 1228d1999bcb5..2a104fff2b92c 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 syntax::{SyntaxKind, ast, match_ast}; 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 e58cb7bad7057..97af8ad93def3 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 @@ impl<'db> ModuleIdLt<'db> { } } - 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 @@ impl<'db> ModuleIdLt<'db> { /// 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 a85237662c437..3f29619bcb629 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 triomphe::Arc; 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 @@ impl DefMap { /// 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 @@ impl DefMap { /// /// 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 @@ impl DerefMut for ModulesMap { } } -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 45d5dc9fcd271..263f603a0bfbb 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 @@ impl<'db> Resolver<'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 95554c63b97d8..a1645de6ec236 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 syntax::ast::{self, HasVisibility}; 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 @@ impl Visibility { } #[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 @@ impl Visibility { 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 @@ impl Visibility { 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 70868e4b95aa4..cafe0329692cd 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 @@ impl<'db> InferenceResult<'db> { } 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 d9cfe6d84c2b7..868ae00329b3b 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 tracing::{debug, instrument}; 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 @@ impl<'db> InherentImpls { } #[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 @@ impl InherentImpls { 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 @@ impl<'db> TraitImpls { } #[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 @@ impl<'db> TraitImpls { } #[salsa::tracked(returns(ref))] - pub fn for_crate_and_deps(db: &'db dyn HirDatabase, krate: Crate) -> 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 @@ impl TraitImpls { } } - 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 @@ impl TraitImpls { // 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 e57f031f009a5..a50a736ccd0eb 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 @@ impl 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 @@ impl Impl { 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 d924aaa25ddc3..c1f72debe54fe 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 @@ impl ChildBySource for DefWithBodyId { // 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 c85ec734c07a5..6ca3e26ca0187 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 foo() { //- 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 0b2af134c8cac..3448b734ff10f 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 intern::{Symbol, sym}; 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 @@ use crate::{ 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 @@ use crate::{ 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 @@ impl<'db> ExprCollector<'db> { } // 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 @@ impl<'db> ExprCollector<'db> { 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 0000000000000..aa7bf0e4e244c --- /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 506c4abc83772..64b15eb017a6d 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 5c1f85cb2a951..e11cc85e7ff17 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 3dea21e56485e..6ae527abb1ff1 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 @@ extern crate ra_ap_rustc_type_ir as rustc_type_ir; /// 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 3448b734ff10f..0b25812f261bf 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { /// 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 aa7bf0e4e244c..7efc9a956c1c4 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 @@ use crate::{ FormatPlaceholder, FormatSign, FormatTrait, }, }, + lang_item::LangItemTarget, type_ref::{Mutability, Rawness}, }; @@ -94,6 +95,347 @@ impl<'db> ExprCollector<'db> { ), }; + 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { // ) 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 @@ impl<'db> ExprCollector<'db> { /// `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 @@ impl<'db> ExprCollector<'db> { // ) // } - 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { /// ```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 @@ impl<'db> ExprCollector<'db> { // 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 @@ impl<'db> ExprCollector<'db> { 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 22ade43875821..7e48ca8f45585 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 @@ impl 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 0fe17e3075de8..b7c09391ec379 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 mod fmt { Unknown, } + // region:fmt_before_1_93_0 #[lang = "format_count"] pub enum Count { Is(usize), @@ -1288,6 +1290,7 @@ pub mod fmt { 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 mod fmt { // 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 mod fmt { } // 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 mod fmt { } } } + // 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 @@ mod panicking { #[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 2edf442a8cc62..8b24a20a5bed4 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 @@ impl<'db> Interner for DbInterner<'db> { 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 859e26e37641d..40a3f17cf169e 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 @@ impl<'db> SolverDelegate for SolverContext<'db> { 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 677e35775dd78..a54c0a799dfc9 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 607638f32b4e6..febc794b5a05f 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 59215f34a5812..1abb50144d34f 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 main() { #[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 cafe0329692cd..70868e4b95aa4 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 @@ impl<'db> InferenceResult<'db> { } 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 4f000c24cc73c..3409de17a1223 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 0b2af134c8cac..e72de6d68297f 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 @@ impl<'db> ExprCollector<'db> { 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 2dac4e7fc84b6..f1db00cf6a67d 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(baz: impl Baz>) {} "#]], ); } + +#[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 901d17bb14911..b872221bf7113 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 f94e7486ff8f1..d6a694be9121e 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 16b5684c16a47..47cb4c8e74cb0 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 @@ mod handlers { 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 @@ mod handlers { // 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 7efc9a956c1c4..4bbfc5b144f16 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 @@ impl<'db> ExprCollector<'db> { .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 @@ impl<'db> ExprCollector<'db> { } 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 7e48ca8f45585..504c310684d65 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 1b24f7fe7ffba..771e80bb92312 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 foo(x: i32, $0y: i32) { y; } 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 ffb518b1e66f4..b15e642daae76 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 @@ impl Semantics<'_, DB> { &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 fdc426c32c7bf..4a12c5a26d455 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 @@ mod OtherBadCase; // ^^^^^^^^^^^^ 💡 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 84e63acbc04f0..b7ec8fa53fa72 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 fe04bd175c96e..343c28e8f9a48 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 77dedf12720ce..1a1c0182f87ad 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 @@ impl GlobalState { } } 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 @@ impl GlobalState { } } - 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 @@ impl GlobalState { 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 @@ impl GlobalState { 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 b1b428e7068dc..195ad226ae0c5 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 @@ impl 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 97938924100be..0e2255ba3fb9c 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 @@ macro_rules! impl_intern_key { }; } +/// # 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 64d97b3f2a238..3021ccf4029c0 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 a9320fdda7e17..073142670d2af 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 @@ use crate::{HasCrate, Module, ModuleDef, Semantics}; /// 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 @@ impl<'a> SymbolCollector<'a> { } 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 @@ impl<'a> SymbolCollector<'a> { } } - pub fn finish(self) -> Box<[FileSymbol]> { + pub fn finish(self) -> Box<[FileSymbol<'a>]> { self.symbols.into_iter().collect() } @@ -217,6 +220,7 @@ impl<'a> SymbolCollector<'a> { is_assoc: false, is_import: true, do_not_complete: Complete::Yes, + _marker: PhantomData, }); }; @@ -251,6 +255,7 @@ impl<'a> SymbolCollector<'a> { is_assoc: false, is_import: false, do_not_complete: Complete::Yes, + _marker: PhantomData, }); }; @@ -428,6 +433,7 @@ impl<'a> SymbolCollector<'a> { is_assoc, is_import: false, do_not_complete, + _marker: PhantomData, }); } } @@ -441,6 +447,7 @@ impl<'a> SymbolCollector<'a> { is_assoc, is_import: false, do_not_complete, + _marker: PhantomData, }); do_not_complete @@ -474,6 +481,7 @@ impl<'a> SymbolCollector<'a> { is_assoc: false, is_import: false, do_not_complete, + _marker: PhantomData, }); } } @@ -487,6 +495,7 @@ impl<'a> SymbolCollector<'a> { 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 c5ead45a20ae4..e15d0b33bbaa5 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 hir::{ }; 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 @@ impl SymbolIndex { /// 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 @@ impl SymbolIndex { } #[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 @@ impl SymbolIndex { } } -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 @@ impl SymbolIndex { } 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 @@ impl SymbolIndex { } 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 @@ impl Query { } } - 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 c8a9231fa99ea..5783d97564d03 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 cc879cf84fc66..953bc73da9d84 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 dc512fe1b87a0..6f5f8f889c7dd 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 0b2522775d4d0..5d3fe4d2658d6 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 67472507f66e7..29530ed02bb6d 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 @@ impl NavigationTarget { } } -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 7a84872f266d1..dcf82c94aaeed 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 c9d5971cd0e39..33ab43fa614a4 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 81cdc188012c9..4478bf4e37333 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 8e1faca00a1bd..85b250eddfd43 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 0d89e13ed3742..3414b52d4514f 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")]