From 9b6afcfeadc21d2283a1a8f5eb9bfdab646c9f88 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 3 Sep 2025 07:28:32 +0800 Subject: [PATCH 001/101] Fix indent for convert_closure_to_fn Example --- ```rust fn foo() { { let closure = |$0| match () { () => {}, }; closure(); } } ``` **Before this PR**: ```rust fn foo() { { fn closure() { match () { () => {}, } } closure(); } } ``` **After this PR**: ```rust fn foo() { { fn closure() { match () { () => {}, } } closure(); } } ``` --- .../src/handlers/convert_closure_to_fn.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs index 5f526ec899404..03f4e4749dbd0 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs @@ -220,7 +220,7 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_>) } let body = if wrap_body_in_block { - make::block_expr([], Some(body)) + make::block_expr([], Some(body.reset_indent().indent(1.into()))) } else { ast::BlockExpr::cast(body.syntax().clone()).unwrap() }; @@ -969,6 +969,32 @@ fn foo() { } closure(); } +"#, + ); + check_assist( + convert_closure_to_fn, + r#" +//- minicore: copy +fn foo() { + { + let closure = |$0| match () { + () => {}, + }; + closure(); + } +} +"#, + r#" +fn foo() { + { + fn closure() { + match () { + () => {}, + } + } + closure(); + } +} "#, ); } From 9bc8e4eb09aab27c024b52fb461bcf3d8fa143a8 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 3 Sep 2025 22:18:01 +0800 Subject: [PATCH 002/101] Add applicable on LetExpr for unwrap_tuple Example --- ```rust fn main() { if $0let (foo, bar) = ("Foo", "Bar") { code(); } } ``` -> ```rust fn main() { if let foo = "Foo" && let bar = "Bar" { code(); } } ``` --- .../ide-assists/src/handlers/unwrap_tuple.rs | 64 +++++++++++++------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs index 46f3e85e12346..2345075a52be6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs @@ -1,3 +1,6 @@ +use std::iter; + +use either::Either; use syntax::{ AstNode, T, ast::{self, edit::AstNodeEdit}, @@ -24,11 +27,16 @@ use crate::{AssistContext, AssistId, Assists}; // ``` pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let let_kw = ctx.find_token_syntax_at_offset(T![let])?; - let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?; - let indent_level = let_stmt.indent_level().0 as usize; - let pat = let_stmt.pat()?; - let ty = let_stmt.ty(); - let init = let_stmt.initializer()?; + let let_stmt = let_kw.parent().and_then(Either::::cast)?; + let mut indent_level = let_stmt.indent_level(); + let pat = either::for_both!(&let_stmt, it => it.pat())?; + let (ty, init, prefix, suffix) = match &let_stmt { + Either::Left(let_stmt) => (let_stmt.ty(), let_stmt.initializer()?, "", ";"), + Either::Right(let_expr) => { + indent_level = indent_level + 1; + (None, let_expr.expr()?, "&& ", "") + } + }; // This only applies for tuple patterns, types, and initializers. let tuple_pat = match pat { @@ -60,25 +68,19 @@ pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option "Unwrap tuple", let_kw.text_range(), |edit| { - let indents = " ".repeat(indent_level); + let mut decls = String::new(); // If there is an ascribed type, insert that type for each declaration, // otherwise, omit that type. - if let Some(tys) = tuple_ty { - let mut zipped_decls = String::new(); - for (pat, ty, expr) in - itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields()) - { - zipped_decls.push_str(&format!("{indents}let {pat}: {ty} = {expr};\n")) - } - edit.replace(parent.text_range(), zipped_decls.trim()); - } else { - let mut zipped_decls = String::new(); - for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) { - zipped_decls.push_str(&format!("{indents}let {pat} = {expr};\n")); - } - edit.replace(parent.text_range(), zipped_decls.trim()); + let tys = + tuple_ty.into_iter().flat_map(|it| it.fields().map(Some)).chain(iter::repeat(None)); + for (pat, ty, expr) in itertools::izip!(tuple_pat.fields(), tys, tuple_init.fields()) { + let ty = ty.map_or_else(String::new, |ty| format!(": {ty}")); + decls.push_str(&format!("{prefix}let {pat}{ty} = {expr}{suffix}\n{indent_level}")) } + + let s = decls.trim(); + edit.replace(parent.text_range(), s.strip_prefix(prefix).unwrap_or(s)); }, ) } @@ -123,6 +125,28 @@ fn main() { ); } + #[test] + fn unwrap_tuples_in_let_expr() { + check_assist( + unwrap_tuple, + r#" +fn main() { + if $0let (foo, bar) = ("Foo", "Bar") { + code(); + } +} +"#, + r#" +fn main() { + if let foo = "Foo" + && let bar = "Bar" { + code(); + } +} +"#, + ); + } + #[test] fn unwrap_tuple_with_types() { check_assist( From 293e8d33a2b6279d1a82fad44a4321a77705fd9f Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 5 Sep 2025 15:56:48 +0800 Subject: [PATCH 003/101] Fix not applicable on empty struct for no_such_field Example --- ```rust fn main() { Foo { bar$0: false }; } struct Foo {} ``` -> ```rust fn main() { Foo { bar: false }; } struct Foo { bar: bool, } ``` --- .../src/handlers/no_such_field.rs | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) 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..9ae99faa00a44 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 @@ -97,24 +97,36 @@ fn missing_record_expr_field_fixes( make::ty(&new_field_type.display_source_code(sema.db, module.into(), true).ok()?), ); - let last_field = record_fields.fields().last()?; - let last_field_syntax = last_field.syntax(); - let indent = IndentLevel::from_node(last_field_syntax); + let (indent, offset, postfix, needs_comma) = + if let Some(last_field) = record_fields.fields().last() { + let indent = IndentLevel::from_node(last_field.syntax()); + let offset = last_field.syntax().text_range().end(); + let needs_comma = !last_field.to_string().ends_with(','); + (indent, offset, String::new(), needs_comma) + } else { + let indent = IndentLevel::from_node(record_fields.syntax()); + let offset = record_fields.l_curly_token()?.text_range().end(); + let postfix = if record_fields.syntax().text().contains_char('\n') { + ",".into() + } else { + format!(",\n{indent}") + }; + (indent + 1, offset, postfix, false) + }; let mut new_field = new_field.to_string(); if usage_file_id != def_file_id { new_field = format!("pub(crate) {new_field}"); } - new_field = format!("\n{indent}{new_field}"); + new_field = format!("\n{indent}{new_field}{postfix}"); - let needs_comma = !last_field_syntax.to_string().ends_with(','); if needs_comma { new_field = format!(",{new_field}"); } let source_change = SourceChange::from_text_edit( def_file_id.file_id(sema.db), - TextEdit::insert(last_field_syntax.text_range().end(), new_field), + TextEdit::insert(offset, new_field), ); return Some(vec![fix( @@ -333,6 +345,44 @@ struct Foo { ) } + #[test] + fn test_add_field_from_usage_with_empty_struct() { + check_fix( + r" +fn main() { + Foo { bar$0: false }; +} +struct Foo {} +", + r" +fn main() { + Foo { bar: false }; +} +struct Foo { + bar: bool, +} +", + ); + + check_fix( + r" +fn main() { + Foo { bar$0: false }; +} +struct Foo { +} +", + r" +fn main() { + Foo { bar: false }; +} +struct Foo { + bar: bool, +} +", + ); + } + #[test] fn test_add_field_in_other_file_from_usage() { check_fix( From 04b232a2576eaebd233815a6a670f3ae800d9827 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 7 Sep 2025 16:13:49 +0800 Subject: [PATCH 004/101] Add wrap multiple attr for wrap_unwrap_cfg_attr Example --- ```rust pub struct Test { $0#[foo] #[bar]$0 test: u32, } ``` -> ```rust pub struct Test { #[cfg_attr($0, foo, bar)] test: u32, } ``` --- .../src/handlers/wrap_unwrap_cfg_attr.rs | 138 +++++++++++++----- 1 file changed, 101 insertions(+), 37 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs index 7d5740b748bef..36df4af31d5e2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs @@ -2,7 +2,7 @@ use ide_db::source_change::SourceChangeBuilder; use itertools::Itertools; use syntax::{ NodeOrToken, SyntaxToken, T, TextRange, algo, - ast::{self, AstNode, make, syntax_factory::SyntaxFactory}, + ast::{self, AstNode, edit::AstNodeEdit, make, syntax_factory::SyntaxFactory}, }; use crate::{AssistContext, AssistId, Assists}; @@ -27,7 +27,7 @@ use crate::{AssistContext, AssistId, Assists}; enum WrapUnwrapOption { WrapDerive { derive: TextRange, attr: ast::Attr }, - WrapAttr(ast::Attr), + WrapAttr(Vec), } /// Attempts to get the derive attribute from a derive attribute list @@ -102,9 +102,9 @@ fn attempt_get_derive(attr: ast::Attr, ident: SyntaxToken) -> WrapUnwrapOption { if ident.parent().and_then(ast::TokenTree::cast).is_none() || !attr.simple_name().map(|v| v.eq("derive")).unwrap_or_default() { - WrapUnwrapOption::WrapAttr(attr) + WrapUnwrapOption::WrapAttr(vec![attr]) } else { - attempt_attr().unwrap_or(WrapUnwrapOption::WrapAttr(attr)) + attempt_attr().unwrap_or_else(|| WrapUnwrapOption::WrapAttr(vec![attr])) } } pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { @@ -118,13 +118,27 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) - Some(attempt_get_derive(attr, ident)) } - (Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(attr)), + (Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(vec![attr])), _ => None, } } else { let covering_element = ctx.covering_element(); match covering_element { - NodeOrToken::Node(node) => ast::Attr::cast(node).map(WrapUnwrapOption::WrapAttr), + NodeOrToken::Node(node) => { + if let Some(attr) = ast::Attr::cast(node.clone()) { + Some(WrapUnwrapOption::WrapAttr(vec![attr])) + } else { + let attrs = node + .children() + .filter(|it| it.text_range().intersect(ctx.selection_trimmed()).is_some()) + .map(ast::Attr::cast) + .collect::>>()?; + if attrs.is_empty() { + return None; + } + Some(WrapUnwrapOption::WrapAttr(attrs)) + } + } NodeOrToken::Token(ident) if ident.kind() == syntax::T![ident] => { let attr = ident.parent_ancestors().find_map(ast::Attr::cast)?; Some(attempt_get_derive(attr, ident)) @@ -133,10 +147,12 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) - } }?; match option { - WrapUnwrapOption::WrapAttr(attr) if attr.simple_name().as_deref() == Some("cfg_attr") => { - unwrap_cfg_attr(acc, attr) - } - WrapUnwrapOption::WrapAttr(attr) => wrap_cfg_attr(acc, ctx, attr), + WrapUnwrapOption::WrapAttr(attrs) => match &attrs[..] { + [attr] if attr.simple_name().as_deref() == Some("cfg_attr") => { + unwrap_cfg_attr(acc, attrs.into_iter().next().unwrap()) + } + _ => wrap_cfg_attrs(acc, ctx, attrs), + }, WrapUnwrapOption::WrapDerive { derive, attr } => wrap_derive(acc, ctx, attr, derive), } } @@ -220,40 +236,51 @@ fn wrap_derive( ); Some(()) } -fn wrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>, attr: ast::Attr) -> Option<()> { - let range = attr.syntax().text_range(); - let path = attr.path()?; +fn wrap_cfg_attrs(acc: &mut Assists, ctx: &AssistContext<'_>, attrs: Vec) -> Option<()> { + let (first_attr, last_attr) = (attrs.first()?, attrs.last()?); + let range = first_attr.syntax().text_range().cover(last_attr.syntax().text_range()); + let path_attrs = + attrs.iter().map(|attr| Some((attr.path()?, attr.clone()))).collect::>>()?; let handle_source_change = |edit: &mut SourceChangeBuilder| { let make = SyntaxFactory::with_mappings(); - let mut editor = edit.make_editor(attr.syntax()); - let mut raw_tokens = - vec![NodeOrToken::Token(make.token(T![,])), NodeOrToken::Token(make.whitespace(" "))]; - path.syntax().descendants_with_tokens().for_each(|it| { - if let NodeOrToken::Token(token) = it { - raw_tokens.push(NodeOrToken::Token(token)); - } - }); - if let Some(meta) = attr.meta() { - if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) { - raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); - raw_tokens.push(NodeOrToken::Token(eq)); - raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); + let mut editor = edit.make_editor(first_attr.syntax()); + let mut raw_tokens = vec![]; + for (path, attr) in path_attrs { + raw_tokens.extend([ + NodeOrToken::Token(make.token(T![,])), + NodeOrToken::Token(make.whitespace(" ")), + ]); + path.syntax().descendants_with_tokens().for_each(|it| { + if let NodeOrToken::Token(token) = it { + raw_tokens.push(NodeOrToken::Token(token)); + } + }); + if let Some(meta) = attr.meta() { + if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) { + raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); + raw_tokens.push(NodeOrToken::Token(eq)); + raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); - expr.syntax().descendants_with_tokens().for_each(|it| { - if let NodeOrToken::Token(token) = it { - raw_tokens.push(NodeOrToken::Token(token)); - } - }); - } else if let Some(tt) = meta.token_tree() { - raw_tokens.extend(tt.token_trees_and_tokens()); + expr.syntax().descendants_with_tokens().for_each(|it| { + if let NodeOrToken::Token(token) = it { + raw_tokens.push(NodeOrToken::Token(token)); + } + }); + } else if let Some(tt) = meta.token_tree() { + raw_tokens.extend(tt.token_trees_and_tokens()); + } } } let meta = make.meta_token_tree(make.ident_path("cfg_attr"), make.token_tree(T!['('], raw_tokens)); - let cfg_attr = - if attr.excl_token().is_some() { make.attr_inner(meta) } else { make.attr_outer(meta) }; + let cfg_attr = if first_attr.excl_token().is_some() { + make.attr_inner(meta) + } else { + make.attr_outer(meta) + }; - editor.replace(attr.syntax(), cfg_attr.syntax()); + let syntax_range = first_attr.syntax().clone().into()..=last_attr.syntax().clone().into(); + editor.replace_all(syntax_range, vec![cfg_attr.syntax().clone().into()]); if let Some(snippet_cap) = ctx.config.snippet_cap && let Some(first_meta) = @@ -332,7 +359,8 @@ fn unwrap_cfg_attr(acc: &mut Assists, attr: ast::Attr) -> Option<()> { return None; } let handle_source_change = |f: &mut SourceChangeBuilder| { - let inner_attrs = inner_attrs.iter().map(|it| it.to_string()).join("\n"); + let inner_attrs = + inner_attrs.iter().map(|it| it.to_string()).join(&format!("\n{}", attr.indent_level())); f.replace(range, inner_attrs); }; acc.add( @@ -414,6 +442,42 @@ mod tests { } "#, ); + check_assist( + wrap_unwrap_cfg_attr, + r#" + pub struct Test { + #[other_attr] + $0#[foo] + #[bar]$0 + #[other_attr] + test: u32, + } + "#, + r#" + pub struct Test { + #[other_attr] + #[cfg_attr($0, foo, bar)] + #[other_attr] + test: u32, + } + "#, + ); + check_assist( + wrap_unwrap_cfg_attr, + r#" + pub struct Test { + #[cfg_attr(debug_assertions$0, foo, bar)] + test: u32, + } + "#, + r#" + pub struct Test { + #[foo] + #[bar] + test: u32, + } + "#, + ); } #[test] fn to_from_eq_attr() { From 2b3e3190a22650fe0874e482afb3de1ab36d678b Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 8 Sep 2025 14:09:48 +0800 Subject: [PATCH 005/101] Add nested lifetime support for add_lifetime_to_type Changes: - Add nested lifetime support - Add explicit infer lifetime support - Change assist type to `quickfix` Example --- ```rust struct Foo { a: &$0i32, b: &'_ i32, c: (&i32, Bar<'_>), } ``` **Before this PR**: ```rust struct Foo<'a> { a: &'a i32, b: &'_ i32, c: (&i32, Bar<'_>), } ``` **After this PR**: ```rust struct Foo<'a> { a: &'a i32, b: &'a i32, c: (&'a i32, Bar<'a>), } ``` --- .../src/handlers/add_lifetime_to_type.rs | 115 +++++++++++------- 1 file changed, 68 insertions(+), 47 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs index 27dbdcf2c4d57..265ee3d2d4e71 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs @@ -1,4 +1,7 @@ -use syntax::ast::{self, AstNode, HasGenericParams, HasName}; +use syntax::{ + SyntaxKind, SyntaxNode, SyntaxToken, + ast::{self, AstNode, HasGenericParams, HasName}, +}; use crate::{AssistContext, AssistId, Assists}; @@ -21,7 +24,7 @@ use crate::{AssistContext, AssistId, Assists}; // ``` pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let ref_type_focused = ctx.find_node_at_offset::()?; - if ref_type_focused.lifetime().is_some() { + if ref_type_focused.lifetime().is_some_and(|lifetime| lifetime.text() != "'_") { return None; } @@ -34,10 +37,10 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) - return None; } - let ref_types = fetch_borrowed_types(&node)?; + let changes = fetch_borrowed_types(&node)?; let target = node.syntax().text_range(); - acc.add(AssistId::generate("add_lifetime_to_type"), "Add lifetime", target, |builder| { + acc.add(AssistId::quick_fix("add_lifetime_to_type"), "Add lifetime", target, |builder| { match node.generic_param_list() { Some(gen_param) => { if let Some(left_angle) = gen_param.l_angle_token() { @@ -51,16 +54,21 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) - } } - for ref_type in ref_types { - if let Some(amp_token) = ref_type.amp_token() { - builder.insert(amp_token.text_range().end(), "'a "); + for change in changes { + match change { + Change::Replace(it) => { + builder.replace(it.text_range(), "'a"); + } + Change::Insert(it) => { + builder.insert(it.text_range().end(), "'a "); + } } } }) } -fn fetch_borrowed_types(node: &ast::Adt) -> Option> { - let ref_types: Vec = match node { +fn fetch_borrowed_types(node: &ast::Adt) -> Option> { + let ref_types: Vec<_> = match node { ast::Adt::Enum(enum_) => { let variant_list = enum_.variant_list()?; variant_list @@ -79,55 +87,50 @@ fn fetch_borrowed_types(node: &ast::Adt) -> Option> { } ast::Adt::Union(un) => { let record_field_list = un.record_field_list()?; - record_field_list - .fields() - .filter_map(|r_field| { - if let ast::Type::RefType(ref_type) = r_field.ty()? - && ref_type.lifetime().is_none() - { - return Some(ref_type); - } - - None - }) - .collect() + find_ref_types_from_field_list(&record_field_list.into())? } }; if ref_types.is_empty() { None } else { Some(ref_types) } } -fn find_ref_types_from_field_list(field_list: &ast::FieldList) -> Option> { - let ref_types: Vec = match field_list { - ast::FieldList::RecordFieldList(record_list) => record_list - .fields() - .filter_map(|f| { - if let ast::Type::RefType(ref_type) = f.ty()? - && ref_type.lifetime().is_none() - { - return Some(ref_type); - } - - None - }) - .collect(), - ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list - .fields() - .filter_map(|f| { - if let ast::Type::RefType(ref_type) = f.ty()? - && ref_type.lifetime().is_none() - { - return Some(ref_type); - } - - None - }) - .collect(), +fn find_ref_types_from_field_list(field_list: &ast::FieldList) -> Option> { + let ref_types: Vec<_> = match field_list { + ast::FieldList::RecordFieldList(record_list) => { + record_list.fields().flat_map(|f| infer_lifetimes(f.syntax())).collect() + } + ast::FieldList::TupleFieldList(tuple_field_list) => { + tuple_field_list.fields().flat_map(|f| infer_lifetimes(f.syntax())).collect() + } }; if ref_types.is_empty() { None } else { Some(ref_types) } } +enum Change { + Replace(SyntaxToken), + Insert(SyntaxToken), +} + +fn infer_lifetimes(node: &SyntaxNode) -> Vec { + node.children() + .filter(|it| !matches!(it.kind(), SyntaxKind::FN_PTR_TYPE | SyntaxKind::TYPE_BOUND_LIST)) + .flat_map(|it| { + infer_lifetimes(&it) + .into_iter() + .chain(ast::Lifetime::cast(it.clone()).and_then(|lt| { + lt.lifetime_ident_token().filter(|lt| lt.text() == "'_").map(Change::Replace) + })) + .chain( + ast::RefType::cast(it) + .filter(|ty| ty.lifetime().is_none()) + .and_then(|ty| ty.amp_token()) + .map(Change::Insert), + ) + }) + .collect() +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -164,6 +167,24 @@ mod tests { check_assist_not_applicable(add_lifetime_to_type, r#"struct Foo { a: &'a$0 i32 }"#); } + #[test] + fn add_lifetime_to_nested_types() { + check_assist( + add_lifetime_to_type, + r#"struct Foo { a: &$0i32, b: &(&i32, fn(&str) -> &str) }"#, + r#"struct Foo<'a> { a: &'a i32, b: &'a (&'a i32, fn(&str) -> &str) }"#, + ); + } + + #[test] + fn add_lifetime_to_explicit_infer_lifetime() { + check_assist( + add_lifetime_to_type, + r#"struct Foo { a: &'_ $0i32, b: &'_ (&'_ i32, fn(&str) -> &str) }"#, + r#"struct Foo<'a> { a: &'a i32, b: &'a (&'a i32, fn(&str) -> &str) }"#, + ); + } + #[test] fn add_lifetime_to_enum() { check_assist( From 5cd99fb215e58a881900e1c25af15e0bd7707023 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 29 Sep 2025 18:12:38 +0800 Subject: [PATCH 006/101] Add fixes for non_exhaustive_let diagnostic Example --- ```rust fn foo() { let None$0 = Some(5); } ``` -> ```rust fn foo() { let None = Some(5) else { return }; } ``` --- .../src/handlers/non_exhaustive_let.rs | 185 +++++++++++++++++- 1 file changed, 178 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs index c86ecd2f03b93..346a59ed528dd 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs @@ -1,4 +1,11 @@ -use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; +use either::Either; +use hir::Semantics; +use ide_db::text_edit::TextEdit; +use ide_db::ty_filter::TryEnum; +use ide_db::{RootDatabase, source_change::SourceChange}; +use syntax::{AstNode, ast}; + +use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, fix}; // Diagnostic: non-exhaustive-let // @@ -15,11 +22,68 @@ pub(crate) fn non_exhaustive_let( d.pat.map(Into::into), ) .stable() + .with_fixes(fixes(&ctx.sema, d)) +} +fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::NonExhaustiveLet) -> Option> { + let root = sema.parse_or_expand(d.pat.file_id); + let pat = d.pat.value.to_node(&root); + let let_stmt = ast::LetStmt::cast(pat.syntax().parent()?)?; + let early_node = let_stmt.syntax().ancestors().find_map(AstNode::cast)?; + let early_text = early_text(sema, &early_node); + + if let_stmt.let_else().is_some() { + return None; + } + + let file_id = d.pat.file_id.file_id()?.file_id(sema.db); + let semicolon = if let_stmt.semicolon_token().is_none() { ";" } else { "" }; + let else_block = format!(" else {{ {early_text} }}{semicolon}"); + let insert_offset = let_stmt + .semicolon_token() + .map(|it| it.text_range().start()) + .unwrap_or_else(|| let_stmt.syntax().text_range().end()); + + let source_change = + SourceChange::from_text_edit(file_id, TextEdit::insert(insert_offset, else_block)); + let target = sema.original_range(let_stmt.syntax()).range; + Some(vec![fix("add_let_else_block", "Add let-else block", source_change, target)]) +} + +fn early_text( + sema: &Semantics<'_, RootDatabase>, + early_node: &Either>, +) -> &'static str { + match early_node { + Either::Left(_any_loop) => "continue", + Either::Right(Either::Left(fn_)) => sema + .to_def(fn_) + .map(|fn_def| fn_def.ret_type(sema.db)) + .map(|ty| return_text(&ty, sema)) + .unwrap_or("return"), + Either::Right(Either::Right(closure)) => closure + .body() + .and_then(|expr| sema.type_of_expr(&expr)) + .map(|ty| return_text(&ty.adjusted(), sema)) + .unwrap_or("return"), + } +} + +fn return_text(ty: &hir::Type<'_>, sema: &Semantics<'_, RootDatabase>) -> &'static str { + if ty.is_unit() { + "return" + } else if let Some(try_enum) = TryEnum::from_ty(sema, ty) { + match try_enum { + TryEnum::Option => "return None", + TryEnum::Result => "return Err($0)", + } + } else { + "return $0" + } } #[cfg(test)] mod tests { - use crate::tests::check_diagnostics; + use crate::tests::{check_diagnostics, check_fix}; #[test] fn option_nonexhaustive() { @@ -28,7 +92,7 @@ mod tests { //- minicore: option fn main() { let None = Some(5); - //^^^^ error: non-exhaustive pattern: `Some(_)` not covered + //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered } "#, ); @@ -54,7 +118,7 @@ fn main() { fn main() { '_a: { let None = Some(5); - //^^^^ error: non-exhaustive pattern: `Some(_)` not covered + //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered } } "#, @@ -66,7 +130,7 @@ fn main() { fn main() { let _ = async { let None = Some(5); - //^^^^ error: non-exhaustive pattern: `Some(_)` not covered + //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered }; } "#, @@ -78,7 +142,7 @@ fn main() { fn main() { unsafe { let None = Some(5); - //^^^^ error: non-exhaustive pattern: `Some(_)` not covered + //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered } } "#, @@ -101,7 +165,7 @@ fn test(x: Result) { //- minicore: result fn test(x: Result) { let Ok(_y) = x; - //^^^^^^ error: non-exhaustive pattern: `Err(_)` not covered + //^^^^^^ 💡 error: non-exhaustive pattern: `Err(_)` not covered } "#, ); @@ -132,6 +196,113 @@ fn foo(v: Enum<()>) { ); } + #[test] + fn fix_return_in_loop() { + check_fix( + r#" +//- minicore: option +fn foo() { + while cond { + let None$0 = Some(5); + } +} +"#, + r#" +fn foo() { + while cond { + let None = Some(5) else { continue }; + } +} +"#, + ); + } + + #[test] + fn fix_return_in_fn() { + check_fix( + r#" +//- minicore: option +fn foo() { + let None$0 = Some(5); +} +"#, + r#" +fn foo() { + let None = Some(5) else { return }; +} +"#, + ); + } + + #[test] + fn fix_return_in_incomplete_let() { + check_fix( + r#" +//- minicore: option +fn foo() { + let None$0 = Some(5) +} +"#, + r#" +fn foo() { + let None = Some(5) else { return }; +} +"#, + ); + } + + #[test] + fn fix_return_in_closure() { + check_fix( + r#" +//- minicore: option +fn foo() -> Option<()> { + let _f = || { + let None$0 = Some(5); + }; +} +"#, + r#" +fn foo() -> Option<()> { + let _f = || { + let None = Some(5) else { return }; + }; +} +"#, + ); + } + + #[test] + fn fix_return_try_in_fn() { + check_fix( + r#" +//- minicore: option +fn foo() -> Option<()> { + let None$0 = Some(5); +} +"#, + r#" +fn foo() -> Option<()> { + let None = Some(5) else { return None }; +} +"#, + ); + + check_fix( + r#" +//- minicore: option, result +fn foo() -> Result<(), i32> { + let None$0 = Some(5); +} +"#, + r#" +fn foo() -> Result<(), i32> { + let None = Some(5) else { return Err($0) }; +} +"#, + ); + } + #[test] fn regression_20259() { check_diagnostics( From c55a2249cd3014c3f14a782c292adf3bee32f7f9 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 26 Oct 2025 17:16:47 +0800 Subject: [PATCH 007/101] Support unmap macro expansion --- .../src/handlers/non_exhaustive_let.rs | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs index 346a59ed528dd..bc10e82854f5c 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs @@ -24,24 +24,30 @@ pub(crate) fn non_exhaustive_let( .stable() .with_fixes(fixes(&ctx.sema, d)) } + fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::NonExhaustiveLet) -> Option> { let root = sema.parse_or_expand(d.pat.file_id); let pat = d.pat.value.to_node(&root); let let_stmt = ast::LetStmt::cast(pat.syntax().parent()?)?; - let early_node = let_stmt.syntax().ancestors().find_map(AstNode::cast)?; + let early_node = + sema.ancestors_with_macros(let_stmt.syntax().clone()).find_map(AstNode::cast)?; let early_text = early_text(sema, &early_node); if let_stmt.let_else().is_some() { return None; } - - let file_id = d.pat.file_id.file_id()?.file_id(sema.db); + let hir::FileRangeWrapper { file_id, range } = sema.original_range_opt(let_stmt.syntax())?; + let insert_offset = if let Some(semicolon) = let_stmt.semicolon_token() + && let Some(token) = sema.parse(file_id).syntax().token_at_offset(range.end()).left_biased() + && token.kind() == semicolon.kind() + { + token.text_range().start() + } else { + range.end() + }; let semicolon = if let_stmt.semicolon_token().is_none() { ";" } else { "" }; let else_block = format!(" else {{ {early_text} }}{semicolon}"); - let insert_offset = let_stmt - .semicolon_token() - .map(|it| it.text_range().start()) - .unwrap_or_else(|| let_stmt.syntax().text_range().end()); + let file_id = file_id.file_id(sema.db); let source_change = SourceChange::from_text_edit(file_id, TextEdit::insert(insert_offset, else_block)); @@ -234,6 +240,29 @@ fn foo() { ); } + #[test] + fn fix_return_in_macro_expanded() { + check_fix( + r#" +//- minicore: option +macro_rules! identity { ($($t:tt)*) => { $($t)* }; } +fn foo() { + identity! { + let None$0 = Some(5); + } +} +"#, + r#" +macro_rules! identity { ($($t:tt)*) => { $($t)* }; } +fn foo() { + identity! { + let None = Some(5) else { return }; + } +} +"#, + ); + } + #[test] fn fix_return_in_incomplete_let() { check_fix( From 440cb96934cbc71daee8b6c0cde9271090ed3bde Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 7 Nov 2025 21:55:50 +0800 Subject: [PATCH 008/101] Support WhileExpr and ForExpr for add_label_to_loop Example --- ```rust fn main() { for$0 _ in 0..5 { break; continue; } } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn main() { 'l: for _ in 0..5 { break 'l; continue 'l; } } ``` --- .../src/handlers/add_label_to_loop.rs | 81 ++++++++++++++++++- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs index b84ad24cfcef3..ece927fce2cd8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs @@ -35,9 +35,9 @@ use crate::{AssistContext, AssistId, Assists}; // } // ``` pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let loop_kw = ctx.find_token_syntax_at_offset(T![loop])?; - let loop_expr = loop_kw.parent().and_then(ast::LoopExpr::cast)?; - if loop_expr.label().is_some() { + let loop_expr = ctx.find_node_at_offset::()?; + let loop_kw = loop_token(&loop_expr)?; + if loop_expr.label().is_some() || !loop_kw.text_range().contains_inclusive(ctx.offset()) { return None; } @@ -80,6 +80,14 @@ pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> O ) } +fn loop_token(loop_expr: &ast::AnyHasLoopBody) -> Option { + loop_expr + .syntax() + .children_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| matches!(it.kind(), T![for] | T![loop] | T![while])) +} + fn insert_label_after_token( editor: &mut SyntaxEditor, make: &SyntaxFactory, @@ -123,6 +131,48 @@ fn main() { ); } + #[test] + fn add_label_to_while_expr() { + check_assist( + add_label_to_loop, + r#" +fn main() { + while$0 true { + break; + continue; + } +}"#, + r#" +fn main() { + ${1:'l}: while true { + break ${2:'l}; + continue ${0:'l}; + } +}"#, + ); + } + + #[test] + fn add_label_to_for_expr() { + check_assist( + add_label_to_loop, + r#" +fn main() { + for$0 _ in 0..5 { + break; + continue; + } +}"#, + r#" +fn main() { + ${1:'l}: for _ in 0..5 { + break ${2:'l}; + continue ${0:'l}; + } +}"#, + ); + } + #[test] fn add_label_to_outer_loop() { check_assist( @@ -191,6 +241,31 @@ fn main() { break 'l; continue 'l; } +}"#, + ); + } + + #[test] + fn do_not_add_label_if_outside_keyword() { + check_assist_not_applicable( + add_label_to_loop, + r#" +fn main() { + 'l: loop {$0 + break 'l; + continue 'l; + } +}"#, + ); + + check_assist_not_applicable( + add_label_to_loop, + r#" +fn main() { + 'l: while true {$0 + break 'l; + continue 'l; + } }"#, ); } From f4334bb19c847eb9e37d42a20c153ea81e74ee80 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 23 Dec 2025 16:49:57 +0800 Subject: [PATCH 009/101] stdx indent and dedent utils --- .../rust-analyzer/crates/stdx/src/lib.rs | 57 +++++++++++++++++-- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/stdx/src/lib.rs b/src/tools/rust-analyzer/crates/stdx/src/lib.rs index 5fa0074163713..bda933bb4032f 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/lib.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/lib.rs @@ -207,12 +207,7 @@ pub fn trim_indent(mut text: &str) -> String { if text.starts_with('\n') { text = &text[1..]; } - let indent = text - .lines() - .filter(|it| !it.trim().is_empty()) - .map(|it| it.len() - it.trim_start().len()) - .min() - .unwrap_or(0); + let indent = indent_of(text); text.split_inclusive('\n') .map( |line| { @@ -222,6 +217,25 @@ pub fn trim_indent(mut text: &str) -> String { .collect() } +#[must_use] +fn indent_of(text: &str) -> usize { + text.lines() + .filter(|it| !it.trim().is_empty()) + .map(|it| it.len() - it.trim_start().len()) + .min() + .unwrap_or(0) +} + +#[must_use] +pub fn dedent_by(spaces: usize, text: &str) -> String { + text.split_inclusive('\n') + .map(|line| { + let trimmed = line.trim_start_matches(' '); + if line.len() - trimmed.len() <= spaces { trimmed } else { &line[spaces..] } + }) + .collect() +} + pub fn equal_range_by(slice: &[T], mut key: F) -> ops::Range where F: FnMut(&T) -> Ordering, @@ -352,6 +366,37 @@ mod tests { ); } + #[test] + fn test_dedent() { + assert_eq!(dedent_by(0, ""), ""); + assert_eq!(dedent_by(1, ""), ""); + assert_eq!(dedent_by(2, ""), ""); + assert_eq!(dedent_by(0, "foo"), "foo"); + assert_eq!(dedent_by(2, "foo"), "foo"); + assert_eq!(dedent_by(2, " foo"), "foo"); + assert_eq!(dedent_by(2, " foo"), " foo"); + assert_eq!(dedent_by(2, " foo\nbar"), " foo\nbar"); + assert_eq!(dedent_by(2, "foo\n bar"), "foo\n bar"); + assert_eq!(dedent_by(2, "foo\n\n bar"), "foo\n\n bar"); + assert_eq!(dedent_by(2, "foo\n.\n bar"), "foo\n.\n bar"); + assert_eq!(dedent_by(2, "foo\n .\n bar"), "foo\n.\n bar"); + assert_eq!(dedent_by(2, "foo\n .\n bar"), "foo\n .\n bar"); + } + + #[test] + fn test_indent_of() { + assert_eq!(indent_of(""), 0); + assert_eq!(indent_of(" "), 0); + assert_eq!(indent_of(" x"), 1); + assert_eq!(indent_of(" x\n"), 1); + assert_eq!(indent_of(" x\ny"), 0); + assert_eq!(indent_of(" x\n y"), 1); + assert_eq!(indent_of(" x\n y"), 1); + assert_eq!(indent_of(" x\n y"), 2); + assert_eq!(indent_of(" x\n y\n"), 2); + assert_eq!(indent_of(" x\n\n y\n"), 2); + } + #[test] fn test_replace() { #[track_caller] From ad4e1c2fb27802f54806286d4fc486c5d11392fa Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 23 Dec 2025 16:50:25 +0800 Subject: [PATCH 010/101] Fix postfix completion indentation compensation Editor adds the current indentation to the content of the code snippet This PR dedentation is used to offset the editor snippet indentation Example --- ```rust fn foo(x: Option, y: Option) { let _f = || { x .and(y) .map(|it| it+2) .$0 }; } ``` **Before this PR** ```rust fn foo(x: Option, y: Option) { let _f = || { let $0 = x .and(y) .map(|it| it+2); }; } ``` **After this PR** ```rust fn foo(x: Option, y: Option) { let _f = || { let $0 = x .and(y) .map(|it| it+2); }; } ``` --- .../ide-completion/src/completions/postfix.rs | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index 7f67ef848ecec..250e1472e9a35 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -355,12 +355,20 @@ fn get_receiver_text( range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.')) } let file_text = sema.db.file_text(range.file_id.file_id(sema.db)); - let mut text = file_text.text(sema.db)[range.range].to_owned(); + let text = file_text.text(sema.db); + let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.start())]); + let mut text = stdx::dedent_by(indent_spaces, &text[range.range]); // The receiver texts should be interpreted as-is, as they are expected to be // normal Rust expressions. escape_snippet_bits(&mut text); - text + return text; + + fn indent_of_tail_line(text: &str) -> usize { + let tail_line = text.rsplit_once('\n').map_or(text, |(_, s)| s); + let trimmed = tail_line.trim_start_matches(' '); + tail_line.len() - trimmed.len() + } } /// Escapes `\` and `$` so that they don't get interpreted as snippet-specific constructs. @@ -977,9 +985,9 @@ use core::ops::ControlFlow; fn main() { ControlFlow::Break(match true { - true => "\${1:placeholder}", - false => "\\\$", - }) + true => "\${1:placeholder}", + false => "\\\$", +}) } "#, ); @@ -1219,4 +1227,31 @@ fn foo() { "#, ); } + + #[test] + fn snippet_dedent() { + check_edit( + "let", + r#" +//- minicore: option +fn foo(x: Option, y: Option) { + let _f = || { + x + .and(y) + .map(|it| it+2) + .$0 + }; +} +"#, + r#" +fn foo(x: Option, y: Option) { + let _f = || { + let $0 = x + .and(y) + .map(|it| it+2); + }; +} +"#, + ); + } } From 430468fdbfb5d3972aafe9bcd196b5a7fc484159 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 25 Dec 2025 15:06:45 +0800 Subject: [PATCH 011/101] Fix tuple struct pat expected type Example --- ```rust struct Foo(Option); fn foo(x: Foo) -> Foo { match x { Foo($0) => () } } ``` **Before this PR** ```rust ty: Foo, name: ? ``` **After this PR** ```rust ty: Option, name: ? ``` --- .../ide-completion/src/context/analysis.rs | 10 +++++ .../ide-completion/src/context/tests.rs | 44 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index ce26d3806cc4c..0528a94dac65b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -761,6 +761,16 @@ fn expected_type_and_name<'db>( let ty = sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original); (ty, None) }, + ast::TupleStructPat(it) => { + let fields = it.path().and_then(|path| match sema.resolve_path(&path)? { + hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) => Some(adt.as_struct()?.fields(sema.db)), + hir::PathResolution::Def(hir::ModuleDef::Variant(variant)) => Some(variant.fields(sema.db)), + _ => None, + }); + let nr = it.fields().take_while(|it| it.syntax().text_range().end() <= token.text_range().start()).count(); + let ty = fields.and_then(|fields| Some(fields.get(nr)?.ty(sema.db).to_type(sema.db))); + (ty, None) + }, ast::Fn(it) => { cov_mark::hit!(expected_type_fn_ret_with_leading_char); cov_mark::hit!(expected_type_fn_ret_without_leading_char); diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs index 09a9b6f112f07..3169a16e2e746 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs @@ -287,6 +287,50 @@ fn foo() -> Foo { ); } +#[test] +fn expected_type_tuple_struct_pat() { + check_expected_type_and_name( + r#" +//- minicore: option +struct Foo(Option); +fn foo(x: Foo) -> Foo { + match x { Foo($0) => () } +} +"#, + expect![[r#"ty: Option, name: ?"#]], + ); + + check_expected_type_and_name( + r#" +struct Foo(i32, u32, f32); +fn foo(x: Foo) -> Foo { + match x { Foo($0) => () } +} +"#, + expect![[r#"ty: i32, name: ?"#]], + ); + + check_expected_type_and_name( + r#" +struct Foo(i32, u32, f32); +fn foo(x: Foo) -> Foo { + match x { Foo(num,$0) => () } +} +"#, + expect![[r#"ty: u32, name: ?"#]], + ); + + check_expected_type_and_name( + r#" +struct Foo(i32, u32, f32); +fn foo(x: Foo) -> Foo { + match x { Foo(num,$0,float) => () } +} +"#, + expect![[r#"ty: u32, name: ?"#]], + ); +} + #[test] fn expected_type_if_let_without_leading_char() { cov_mark::check!(expected_type_if_let_without_leading_char); From e04b950e1231b33d7dc951494b66f4315192bc81 Mon Sep 17 00:00:00 2001 From: dfireBird Date: Sat, 3 Jan 2026 10:37:57 +0530 Subject: [PATCH 012/101] change test_name placeholder to executable_arg fix replacing target on lib target kind --- .../crates/rust-analyzer/src/config.rs | 12 +-- .../crates/rust-analyzer/src/target_spec.rs | 85 +++++++++++++------ .../docs/book/src/configuration_generated.md | 12 +-- .../rust-analyzer/editors/code/package.json | 6 +- 4 files changed, 76 insertions(+), 39 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index e39569e108de4..444bbd1b26835 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -909,18 +909,18 @@ config_data! { /// Override the command used for bench runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically + /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically /// replace the package name, target option (such as `--bin` or `--example`), the target name and - /// the test name (name of test function or test mod path). + /// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). runnables_bench_overrideCommand: Option> = None, /// Command to be executed instead of 'cargo' for runnables. runnables_command: Option = None, /// Override the command used for bench runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically + /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically /// replace the package name, target option (such as `--bin` or `--example`), the target name and - /// the test name (name of test function or test mod path). + /// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). runnables_doctest_overrideCommand: Option> = None, /// Additional arguments to be passed to cargo for runnables such as /// tests or binaries. For example, it may be `--release`. @@ -938,9 +938,9 @@ config_data! { /// Override the command used for test runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically + /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically /// replace the package name, target option (such as `--bin` or `--example`), the target name and - /// the test name (name of test function or test mod path). + /// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). runnables_test_overrideCommand: Option> = None, /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index e0f95a7830ea8..6d9adc2d8d8a6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -119,38 +119,21 @@ impl CargoTargetSpec { let extra_test_binary_args = config.extra_test_binary_args; let mut cargo_args = Vec::new(); - let mut executable_args = Vec::new(); + let executable_args = Self::executable_args_for(kind, extra_test_binary_args); match kind { - RunnableKind::Test { test_id, attr } => { + RunnableKind::Test { .. } => { cargo_args.push(config.test_command); - executable_args.push(test_id.to_string()); - if let TestId::Path(_) = test_id { - executable_args.push("--exact".to_owned()); - } - executable_args.extend(extra_test_binary_args); - if attr.ignore { - executable_args.push("--ignored".to_owned()); - } } - RunnableKind::TestMod { path } => { + RunnableKind::TestMod { .. } => { cargo_args.push(config.test_command); - executable_args.push(path.clone()); - executable_args.extend(extra_test_binary_args); } - RunnableKind::Bench { test_id } => { + RunnableKind::Bench { .. } => { cargo_args.push(config.bench_command); - executable_args.push(test_id.to_string()); - if let TestId::Path(_) = test_id { - executable_args.push("--exact".to_owned()); - } - executable_args.extend(extra_test_binary_args); } - RunnableKind::DocTest { test_id } => { + RunnableKind::DocTest { .. } => { cargo_args.push("test".to_owned()); cargo_args.push("--doc".to_owned()); - executable_args.push(test_id.to_string()); - executable_args.extend(extra_test_binary_args); } RunnableKind::Bin => { let subcommand = match spec { @@ -243,16 +226,70 @@ impl CargoTargetSpec { TargetKind::BuildScript | TargetKind::Other => "", }; + let target = |kind, target| match kind { + TargetKind::Bin | TargetKind::Test | TargetKind::Bench | TargetKind::Example => target, + _ => "", + }; + let replace_placeholders = |arg: String| match &spec { Some(spec) => arg .replace("${package}", &spec.package) .replace("${target_arg}", target_arg(spec.target_kind)) - .replace("${target}", &spec.target) + .replace("${target}", target(spec.target_kind, &spec.target)) .replace("${test_name}", &test_name), _ => arg, }; - args.map(|args| args.into_iter().map(replace_placeholders).collect()) + let extra_test_binary_args = config.extra_test_binary_args; + let executable_args = Self::executable_args_for(kind, extra_test_binary_args); + + args.map(|mut args| { + let exec_args_idx = args.iter().position(|a| a == "${executable_args}"); + + if let Some(idx) = exec_args_idx { + args.splice(idx..idx + 1, executable_args); + } + + args.into_iter().map(replace_placeholders).filter(|a| !a.trim().is_empty()).collect() + }) + } + + fn executable_args_for( + kind: &RunnableKind, + extra_test_binary_args: impl IntoIterator, + ) -> Vec { + let mut executable_args = Vec::new(); + + match kind { + RunnableKind::Test { test_id, attr } => { + executable_args.push(test_id.to_string()); + if let TestId::Path(_) = test_id { + executable_args.push("--exact".to_owned()); + } + executable_args.extend(extra_test_binary_args); + if attr.ignore { + executable_args.push("--ignored".to_owned()); + } + } + RunnableKind::TestMod { path } => { + executable_args.push(path.clone()); + executable_args.extend(extra_test_binary_args); + } + RunnableKind::Bench { test_id } => { + executable_args.push(test_id.to_string()); + if let TestId::Path(_) = test_id { + executable_args.push("--exact".to_owned()); + } + executable_args.extend(extra_test_binary_args); + } + RunnableKind::DocTest { test_id } => { + executable_args.push(test_id.to_string()); + executable_args.extend(extra_test_binary_args); + } + RunnableKind::Bin => {} + } + + executable_args } pub(crate) fn push_to(self, buf: &mut Vec, kind: &RunnableKind) { diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index 58b6363345279..811f995493a65 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -1362,9 +1362,9 @@ Default: `null` Override the command used for bench runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically +Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically replace the package name, target option (such as `--bin` or `--example`), the target name and -the test name (name of test function or test mod path). +the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). ## rust-analyzer.runnables.command {#runnables.command} @@ -1381,9 +1381,9 @@ Default: `null` Override the command used for bench runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically +Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically replace the package name, target option (such as `--bin` or `--example`), the target name and -the test name (name of test function or test mod path). +the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). ## rust-analyzer.runnables.extraArgs {#runnables.extraArgs} @@ -1426,9 +1426,9 @@ Default: `null` Override the command used for test runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically +Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically replace the package name, target option (such as `--bin` or `--example`), the target name and -the test name (name of test function or test mod path). +the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). ## rust-analyzer.rustc.source {#rustc.source} diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 2157cbd486535..498077ddb51a7 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -2840,7 +2840,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.bench.overrideCommand": { - "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).", + "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", "default": null, "type": [ "null", @@ -2869,7 +2869,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.doctest.overrideCommand": { - "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).", + "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", "default": null, "type": [ "null", @@ -2923,7 +2923,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.test.overrideCommand": { - "markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).", + "markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", "default": null, "type": [ "null", From b21e115e2cc6193838cdc499e127debcffe4d62a Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 14:04:32 +1100 Subject: [PATCH 013/101] Add a test for parsing BuildData and RunnableData json --- .../crates/project-model/src/tests.rs | 6 ++++ .../test_data/labeled-project.json | 31 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json diff --git a/src/tools/rust-analyzer/crates/project-model/src/tests.rs b/src/tools/rust-analyzer/crates/project-model/src/tests.rs index a03ed562e1be5..395cea6f76e61 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/tests.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/tests.rs @@ -192,6 +192,12 @@ fn rust_project_hello_world_project_model() { ); } +#[test] +fn rust_project_labeled_project_model() { + // This just needs to parse. + _ = load_rust_project("labeled-project.json"); +} + #[test] fn rust_project_cfg_groups() { let (crate_graph, _proc_macros) = load_rust_project("cfg-groups.json"); diff --git a/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json b/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json new file mode 100644 index 0000000000000..50f66739cfdf4 --- /dev/null +++ b/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json @@ -0,0 +1,31 @@ +{ + "sysroot_src": null, + "crates": [ + { + "display_name": "hello_world", + "root_module": "$ROOT$src/lib.rs", + "edition": "2018", + "deps": [], + "is_workspace_member": true, + "build": { + "label": "//:hello_world", + "build_file": "$ROOT$BUILD", + "target_kind": "bin" + } + } + ], + "runnables": [ + { + "kind": "run", + "program": "bazel", + "args": ["run", "{label}"], + "cwd": "$ROOT$" + }, + { + "kind": "flycheck", + "program": "$ROOT$custom-flychecker.sh", + "args": ["{label}"], + "cwd": "$ROOT$" + } + ] +} From 557df27ade4759f3e7d0c5c8ea8aed6cdf75a597 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 10:07:36 +1100 Subject: [PATCH 014/101] Privatise some fields and structs in project_json These are not used outside of the project-model crate, ie. instantly converted to other structures. --- .../crates/project-model/src/project_json.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index cbbb95cc468fe..cd13a208a4a48 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -460,23 +460,23 @@ enum EditionData { } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] -pub struct BuildData { +struct BuildData { label: String, build_file: Utf8PathBuf, target_kind: TargetKindData, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct RunnableData { - pub program: String, - pub args: Vec, - pub cwd: Utf8PathBuf, - pub kind: RunnableKindData, +struct RunnableData { + program: String, + args: Vec, + cwd: Utf8PathBuf, + kind: RunnableKindData, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -pub enum RunnableKindData { +enum RunnableKindData { Flycheck, Check, Run, @@ -490,7 +490,7 @@ pub enum RunnableKindData { #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -pub enum TargetKindData { +enum TargetKindData { Bin, /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...). Lib, From b801b88405cf5911ad8196fc2ed67233d4bb2b51 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 10:06:47 +1100 Subject: [PATCH 015/101] Add unknown runnable kind for forwards compatibility --- .../crates/project-model/src/project_json.rs | 9 +++++++++ .../crates/project-model/test_data/labeled-project.json | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index 6938010cbd708..cbbb95cc468fe 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -368,6 +368,9 @@ pub enum RunnableKind { /// Template for checking a target, emitting rustc JSON diagnostics. /// May include {label} which will get the label from the `build` section of a crate. Flycheck, + + /// For forwards-compatibility, i.e. old rust-analyzer binary with newer workspace discovery tools + Unknown, } #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] @@ -478,6 +481,11 @@ pub enum RunnableKindData { Check, Run, TestOne, + + /// For forwards-compatibility, i.e. old rust-analyzer binary with newer workspace discovery tools + #[allow(unused)] + #[serde(other)] + Unknown, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] @@ -546,6 +554,7 @@ impl From for RunnableKind { RunnableKindData::Run => RunnableKind::Run, RunnableKindData::TestOne => RunnableKind::TestOne, RunnableKindData::Flycheck => RunnableKind::Flycheck, + RunnableKindData::Unknown => RunnableKind::Unknown, } } } diff --git a/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json b/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json index 50f66739cfdf4..5c0e1f33979ec 100644 --- a/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json +++ b/src/tools/rust-analyzer/crates/project-model/test_data/labeled-project.json @@ -26,6 +26,12 @@ "program": "$ROOT$custom-flychecker.sh", "args": ["{label}"], "cwd": "$ROOT$" + }, + { + "kind": "we-ignore-unknown-runnable-kinds-for-forwards-compatibility", + "program": "abc", + "args": ["{label}"], + "cwd": "$ROOT$" } ] } From f8455e26bdbd27dd4f970e39d87aaa1d84b3ae87 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 10:07:36 +1100 Subject: [PATCH 016/101] A comment to help ProjectJsonData stay backwards-compatible Makes clear to future editors of this code that they should add #[serde(default)] to new fields. --- .../rust-analyzer/crates/project-model/src/project_json.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index cd13a208a4a48..e3a4feec8d926 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -383,6 +383,8 @@ pub struct ProjectJsonData { crates: Vec, #[serde(default)] runnables: Vec, + // + // New fields should be Option or #[serde(default)]. This applies to most of this datastructure. } #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Default)] From ff365fd4a4eb9b7c7a59c514b240912ed710d3dd Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 11:07:21 +1100 Subject: [PATCH 017/101] Refactor the runnable_args implementation to be more compact I'm about to complete the match statement here with more of the same. Once you write the exact same code 5 times, it's time for a helper function. --- .../crates/rust-analyzer/src/target_spec.rs | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index b8d9acc02a328..c87eefc6955ab 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -6,7 +6,7 @@ use cargo_metadata::PackageId; use cfg::{CfgAtom, CfgExpr}; use hir::sym; use ide::{Cancellable, Crate, FileId, RunnableKind, TestId}; -use project_model::project_json::Runnable; +use project_model::project_json::{self, Runnable}; use project_model::{CargoFeatures, ManifestPath, TargetKind}; use rustc_hash::FxHashSet; use triomphe::Arc; @@ -72,44 +72,36 @@ pub(crate) struct ProjectJsonTargetSpec { } impl ProjectJsonTargetSpec { + fn find_replace_runnable( + &self, + kind: project_json::RunnableKind, + replacer: &dyn Fn(&Self, &str) -> String, + ) -> Option { + for runnable in &self.shell_runnables { + if runnable.kind == kind { + let mut runnable = runnable.clone(); + + let replaced_args: Vec<_> = + runnable.args.iter().map(|arg| replacer(self, arg)).collect(); + runnable.args = replaced_args; + + return Some(runnable); + } + } + + None + } + pub(crate) fn runnable_args(&self, kind: &RunnableKind) -> Option { match kind { - RunnableKind::Bin => { - for runnable in &self.shell_runnables { - if matches!(runnable.kind, project_model::project_json::RunnableKind::Run) { - let mut runnable = runnable.clone(); - - let replaced_args: Vec<_> = runnable - .args - .iter() - .map(|arg| arg.replace("{label}", &self.label)) - .collect(); - runnable.args = replaced_args; - - return Some(runnable); - } - } - - None - } + RunnableKind::Bin => self + .find_replace_runnable(project_json::RunnableKind::Run, &|this, arg| { + arg.replace("{label}", &this.label) + }), RunnableKind::Test { test_id, .. } => { - for runnable in &self.shell_runnables { - if matches!(runnable.kind, project_model::project_json::RunnableKind::TestOne) { - let mut runnable = runnable.clone(); - - let replaced_args: Vec<_> = runnable - .args - .iter() - .map(|arg| arg.replace("{test_id}", &test_id.to_string())) - .map(|arg| arg.replace("{label}", &self.label)) - .collect(); - runnable.args = replaced_args; - - return Some(runnable); - } - } - - None + self.find_replace_runnable(project_json::RunnableKind::Run, &|this, arg| { + arg.replace("{label}", &this.label).replace("{test_id}", &test_id.to_string()) + }) } RunnableKind::TestMod { .. } => None, RunnableKind::Bench { .. } => None, From 91819d05f63488c0db7df48fa8212b6eccb4e843 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 10:34:16 +1100 Subject: [PATCH 018/101] Restore a deleted comment that was quite informative Was deleted when this code was moved in PR 18043. --- src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs | 2 ++ 1 file changed, 2 insertions(+) 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 62a3b3a17bdfc..ba817609d3ff2 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 @@ -1180,6 +1180,8 @@ impl GlobalState { } => self.diagnostics.clear_check_older_than_for_package(id, package_id, generation), FlycheckMessage::Progress { id, progress } => { let format_with_id = |user_facing_command: String| { + // When we're running multiple flychecks, we have to include a disambiguator in + // the title, or the editor complains. Note that this is a user-facing string. if self.flycheck.len() == 1 { user_facing_command } else { From f8d046d87deb9fd77fc35e42506091aee34cb8da Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 9 Jan 2026 11:12:20 +1100 Subject: [PATCH 019/101] Support more runnable kinds for project json Allows project JSON users to run a whole module of tests, benchmarks, doctests. --- .../crates/project-model/src/project_json.rs | 21 +++++++++++++++++++ .../crates/rust-analyzer/src/target_spec.rs | 17 ++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index cbbb95cc468fe..201c59039bef1 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -365,6 +365,21 @@ pub enum RunnableKind { /// May include {test_id} which will get the test clicked on by the user. TestOne, + /// Run tests matching a pattern (in RA, usually a path::to::module::of::tests) + /// May include {label} which will get the label from the `build` section of a crate. + /// May include {test_pattern} which will get the test module clicked on by the user. + TestMod, + + /// Run a single doctest + /// May include {label} which will get the label from the `build` section of a crate. + /// May include {test_id} which will get the doctest clicked on by the user. + DocTestOne, + + /// Run a single benchmark + /// May include {label} which will get the label from the `build` section of a crate. + /// May include {bench_id} which will get the benchmark clicked on by the user. + BenchOne, + /// Template for checking a target, emitting rustc JSON diagnostics. /// May include {label} which will get the label from the `build` section of a crate. Flycheck, @@ -481,6 +496,9 @@ pub enum RunnableKindData { Check, Run, TestOne, + TestMod, + DocTestOne, + BenchOne, /// For forwards-compatibility, i.e. old rust-analyzer binary with newer workspace discovery tools #[allow(unused)] @@ -553,6 +571,9 @@ impl From for RunnableKind { RunnableKindData::Check => RunnableKind::Check, RunnableKindData::Run => RunnableKind::Run, RunnableKindData::TestOne => RunnableKind::TestOne, + RunnableKindData::TestMod => RunnableKind::TestMod, + RunnableKindData::DocTestOne => RunnableKind::DocTestOne, + RunnableKindData::BenchOne => RunnableKind::BenchOne, RunnableKindData::Flycheck => RunnableKind::Flycheck, RunnableKindData::Unknown => RunnableKind::Unknown, } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index c87eefc6955ab..b96786cae9359 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -103,9 +103,20 @@ impl ProjectJsonTargetSpec { arg.replace("{label}", &this.label).replace("{test_id}", &test_id.to_string()) }) } - RunnableKind::TestMod { .. } => None, - RunnableKind::Bench { .. } => None, - RunnableKind::DocTest { .. } => None, + RunnableKind::TestMod { path } => self + .find_replace_runnable(project_json::RunnableKind::TestMod, &|this, arg| { + arg.replace("{label}", &this.label).replace("{test_pattern}", path) + }), + RunnableKind::Bench { test_id } => { + self.find_replace_runnable(project_json::RunnableKind::BenchOne, &|this, arg| { + arg.replace("{label}", &this.label).replace("{bench_id}", &test_id.to_string()) + }) + } + RunnableKind::DocTest { test_id } => { + self.find_replace_runnable(project_json::RunnableKind::DocTestOne, &|this, arg| { + arg.replace("{label}", &this.label).replace("{test_id}", &test_id.to_string()) + }) + } } } } From 8ec0c96d8c8cc83552a65413cb8d8ea647dcd25b Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 29 Aug 2025 18:20:41 +0800 Subject: [PATCH 020/101] Add partial selection for merge_imports Example --- **Input**: ```rust use std::fmt::Error; $0use std::fmt::Display; use std::fmt::Debug; use std::fmt::Write; use$0 std::fmt::Result; ``` **Before this PR**: ```rust use std::fmt::Error; use std::fmt::{Debug, Display, Write}; use std::fmt::Result; ``` **After this PR**: ```rust use std::fmt::Error; use std::fmt::{Debug, Display, Result, Write}; ``` --- .../ide-assists/src/handlers/merge_imports.rs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs index 9ba73d23dd243..42bc05811fd14 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs @@ -49,8 +49,9 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio SyntaxElement::Node(n) => n, SyntaxElement::Token(t) => t.parent()?, }; - let mut selected_nodes = - parent_node.children().filter(|it| selection_range.contains_range(it.text_range())); + let mut selected_nodes = parent_node.children().filter(|it| { + selection_range.intersect(it.text_range()).is_some_and(|it| !it.is_empty()) + }); let first_selected = selected_nodes.next()?; let edits = match_ast! { @@ -677,6 +678,25 @@ use std::fmt::Result; ); } + #[test] + fn merge_partial_selection_uses() { + cov_mark::check!(merge_with_selected_use_item_neighbors); + check_assist( + merge_imports, + r" +use std::fmt::Error; +$0use std::fmt::Display; +use std::fmt::Debug; +use std::fmt::Write; +use$0 std::fmt::Result; +", + r" +use std::fmt::Error; +use std::fmt::{Debug, Display, Result, Write}; +", + ); + } + #[test] fn merge_selection_use_trees() { cov_mark::check!(merge_with_selected_use_tree_neighbors); From 529c83d0cd4fa4ee2f002b322257cd5e16959a0d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 15 Jan 2026 16:55:20 +0800 Subject: [PATCH 021/101] Add applicable on let-else branch for unwrap_block Example --- ```rust fn main() { let Some(2) = None else {$0 return; }; } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn main() { return; } ``` --- .../ide-assists/src/handlers/unwrap_block.rs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs index e4f5e3523bd2a..e029d7884fd52 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs @@ -45,6 +45,7 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option ast::LoopExpr(it) => it.syntax().clone(), ast::WhileExpr(it) => it.syntax().clone(), ast::MatchArm(it) => it.parent_match().syntax().clone(), + ast::LetElse(it) => it.syntax().parent()?, ast::LetStmt(it) => { replacement = wrap_let(&it, replacement); prefer_container = Some(it.syntax().clone()); @@ -556,6 +557,40 @@ fn main() { ); } + #[test] + fn simple_let_else() { + check_assist( + unwrap_block, + r#" +fn main() { + let Some(2) = None else {$0 + return; + }; +} +"#, + r#" +fn main() { + return; +} +"#, + ); + check_assist( + unwrap_block, + r#" +fn main() { + let Some(2) = None else {$0 + return + }; +} +"#, + r#" +fn main() { + return +} +"#, + ); + } + #[test] fn unwrap_match_arm() { check_assist( From f62b2f3b4dc85bac50b4b6f517587f479e79f9e1 Mon Sep 17 00:00:00 2001 From: Claire Fan Date: Mon, 23 Feb 2026 18:47:11 +0800 Subject: [PATCH 022/101] [BPF] add target feature allows-misaligned-mem-access --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 5 +++- compiler/rustc_target/src/target_features.rs | 6 +++-- .../crates/hir-ty/src/target_feature.rs | 1 + tests/assembly-llvm/bpf_unaligned.rs | 27 +++++++++++++++++++ tests/auxiliary/minicore.rs | 4 +++ tests/codegen-llvm/bpf-allows-unaligned.rs | 11 ++++++++ tests/ui/check-cfg/target_feature.stderr | 1 + 7 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 tests/assembly-llvm/bpf_unaligned.rs create mode 100644 tests/codegen-llvm/bpf-allows-unaligned.rs diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index fbb582fe86018..66bd6dfffec11 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -252,7 +252,10 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option Some(LLVMFeature::new("fullfp16")), s => Some(LLVMFeature::new(s)), }, - + Arch::Bpf => match s { + "allows-misaligned-mem-access" if major < 22 => None, + s => Some(LLVMFeature::new(s)), + }, // Filter out features that are not supported by the current LLVM version Arch::LoongArch32 | Arch::LoongArch64 => match s { "32s" if major < 21 => None, diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index c3de7e257662f..8a87cee911179 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -756,8 +756,10 @@ static WASM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ // tidy-alphabetical-end ]; -const BPF_FEATURES: &[(&str, Stability, ImpliedFeatures)] = - &[("alu32", Unstable(sym::bpf_target_feature), &[])]; +const BPF_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ + ("alu32", Unstable(sym::bpf_target_feature), &[]), + ("allows-misaligned-mem-access", Unstable(sym::bpf_target_feature), &[]), +]; static CSKY_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ // tidy-alphabetical-start diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/target_feature.rs b/src/tools/rust-analyzer/crates/hir-ty/src/target_feature.rs index 2bd675ba124e4..29a933f922630 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/target_feature.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/target_feature.rs @@ -217,6 +217,7 @@ const TARGET_FEATURE_IMPLICATIONS_RAW: &[(&str, &[&str])] = &[ ("relaxed-simd", &["simd128"]), // BPF ("alu32", &[]), + ("allows-misaligned-mem-access", &[]), // CSKY ("10e60", &["7e10"]), ("2e3", &["e2"]), diff --git a/tests/assembly-llvm/bpf_unaligned.rs b/tests/assembly-llvm/bpf_unaligned.rs new file mode 100644 index 0000000000000..466c3e411ec2c --- /dev/null +++ b/tests/assembly-llvm/bpf_unaligned.rs @@ -0,0 +1,27 @@ +//@ add-minicore +//@ assembly-output: emit-asm +//@ compile-flags: --target bpfel-unknown-none -C target_feature=+allows-misaligned-mem-access +//@ min-llvm-version: 22 +//@ needs-llvm-components: bpf +#![feature(no_core)] +#![crate_type = "rlib"] +#![no_core] + +extern crate minicore; +use minicore::*; + +// CHECK-LABEL: test_load_i64: +// CHECK: r0 = *(u64 *)(r1 + 0) +#[no_mangle] +pub unsafe fn test_load_i64(p: *const u64) -> u64 { + let mut tmp: u64 = 0; + copy_nonoverlapping(p as *const u8, &mut tmp as *mut u64 as *mut u8, 8); + tmp +} + +// CHECK-LABEL: test_store_i64: +// CHECK: *(u64 *)(r1 + 0) = r2 +#[no_mangle] +pub unsafe fn test_store_i64(p: *mut u64, v: u64) { + copy_nonoverlapping(&v as *const u64 as *const u8, p as *mut u8, 8); +} diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 3b3472340e737..11f0c9927cdbd 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -275,6 +275,10 @@ trait Drop { fn drop(&mut self); } +#[rustc_nounwind] +#[rustc_intrinsic] +pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); + pub mod mem { #[rustc_nounwind] #[rustc_intrinsic] diff --git a/tests/codegen-llvm/bpf-allows-unaligned.rs b/tests/codegen-llvm/bpf-allows-unaligned.rs new file mode 100644 index 0000000000000..c7a70d5b2e502 --- /dev/null +++ b/tests/codegen-llvm/bpf-allows-unaligned.rs @@ -0,0 +1,11 @@ +//@ only-bpf +#![crate_type = "lib"] +#![feature(bpf_target_feature)] +#![no_std] + +#[no_mangle] +#[target_feature(enable = "allows-misaligned-mem-access")] +// CHECK: define noundef zeroext i8 @foo(i8 noundef returned %arg) unnamed_addr #0 { +pub unsafe fn foo(arg: u8) -> u8 { + arg +} diff --git a/tests/ui/check-cfg/target_feature.stderr b/tests/ui/check-cfg/target_feature.stderr index 06a7f477a7fdf..06384c2202f1e 100644 --- a/tests/ui/check-cfg/target_feature.stderr +++ b/tests/ui/check-cfg/target_feature.stderr @@ -17,6 +17,7 @@ LL | cfg!(target_feature = "_UNEXPECTED_VALUE"); `addsubiw` `adx` `aes` +`allows-misaligned-mem-access` `altivec` `alu32` `amx-avx512` From 87880340985aac06989f3acc94365ddd83127f5c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 5 Mar 2026 18:16:07 +0800 Subject: [PATCH 023/101] feat: complete block .let in closure expression Example --- ```rust fn main() { let bar = 2; let f = || bar.$0; } ``` **Before this PR** Cannot complete `.let` **After this PR** ```rust fn main() { let bar = 2; let f = || { let $1 = bar; $0 }; } ``` --- .../ide-completion/src/completions/postfix.rs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index ea53aef40c2e7..4cf5c85d6fa3d 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -16,7 +16,7 @@ use itertools::Itertools; use stdx::never; use syntax::{ SmolStr, - SyntaxKind::{EXPR_STMT, STMT_LIST}, + SyntaxKind::{CLOSURE_EXPR, EXPR_STMT, MATCH_ARM, STMT_LIST}, T, TextRange, TextSize, ToSmolStr, ast::{self, AstNode, AstToken}, format_smolstr, match_ast, @@ -156,7 +156,7 @@ pub(crate) fn complete_postfix( postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};")) .add_to(acc, ctx.db); } - _ if ast::MatchArm::can_cast(parent.kind()) => { + _ if matches!(parent.kind(), MATCH_ARM | CLOSURE_EXPR) => { postfix_snippet( "let", "let", @@ -965,6 +965,28 @@ fn main() { ); } + #[test] + fn closure_let_block() { + check_edit( + "let", + r#" +fn main() { + let bar = 2; + let f = || bar.$0; +} +"#, + r#" +fn main() { + let bar = 2; + let f = || { + let $1 = bar; + $0 +}; +} +"#, + ); + } + #[test] fn option_letelse() { check_edit( From 835c745a9ee60493a23fc596e949b20a604ba0e2 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 6 Mar 2026 19:47:48 +0800 Subject: [PATCH 024/101] internal: shortcuit simple current parameter --- .../crates/ide-completion/src/completions/fn_param.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs index 96dac66b8a199..9a33757d555b4 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs @@ -75,9 +75,6 @@ fn fill_fn_params( let mut file_params = FxHashMap::default(); let mut extract_params = |f: ast::Fn| { - if !is_simple_param(current_param) { - return; - } f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| { if let Some(pat) = param.pat() { let whole_param = param.to_smolstr(); @@ -88,6 +85,9 @@ fn fill_fn_params( }; for node in ctx.token.parent_ancestors() { + if !is_simple_param(current_param) { + break; + } match_ast! { match node { ast::SourceFile(it) => it.items().filter_map(|item| match item { From 923582ca0833798fe6943afc9cde35b49ba16ffd Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 6 Mar 2026 20:20:12 +0800 Subject: [PATCH 025/101] fix: add ident_pat qualifier to fully fn param - Do not show commas on label Example --- ```rust fn f(foo: (), bar: u32) {} fn g(foo: (), mut ba$0) ``` **Before this PR** ```rust fn f(foo: (), bar: u32) {} fn g(foo: (), bar: u32) ``` **After this PR** ```rust fn f(foo: (), bar: u32) {} fn g(foo: (), mut bar: u32) ``` --- .../src/completions/fn_param.rs | 34 ++++++++++++++++--- .../ide-completion/src/render/function.rs | 33 +++++++++++++++--- .../ide-completion/src/tests/fn_param.rs | 2 +- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs index 9a33757d555b4..bd0b69215cf7a 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs @@ -30,14 +30,27 @@ pub(crate) fn complete_fn_param( _ => return None, }; + let qualifier = param_qualifier(param); let comma_wrapper = comma_wrapper(ctx); let mut add_new_item_to_acc = |label: &str| { - let mk_item = |label: &str, range: TextRange| { - CompletionItem::new(CompletionItemKind::Binding, range, label, ctx.edition) + let label = label.strip_prefix(qualifier.as_str()).unwrap_or(label); + let insert = if label.starts_with('#') { + // FIXME: `#[attr] it: i32` -> `#[attr] mut it: i32` + label.to_smolstr() + } else { + format_smolstr!("{qualifier}{label}") + }; + let mk_item = |insert_text: &str, range: TextRange| { + let mut item = + CompletionItem::new(CompletionItemKind::Binding, range, label, ctx.edition); + if insert_text != label { + item.insert_text(insert_text); + } + item }; let item = match &comma_wrapper { - Some((fmt, range)) => mk_item(&fmt(label), *range), - None => mk_item(label, ctx.source_range()), + Some((fmt, range)) => mk_item(&fmt(&insert), *range), + None => mk_item(&insert, ctx.source_range()), }; // Completion lookup is omitted intentionally here. // See the full discussion: https://github.com/rust-lang/rust-analyzer/issues/12073 @@ -214,3 +227,16 @@ fn is_simple_param(param: &ast::Param) -> bool { .pat() .is_none_or(|pat| matches!(pat, ast::Pat::IdentPat(ident_pat) if ident_pat.pat().is_none())) } + +fn param_qualifier(param: &ast::Param) -> SmolStr { + let mut b = syntax::SmolStrBuilder::new(); + if let Some(ast::Pat::IdentPat(pat)) = param.pat() { + if pat.ref_token().is_some() { + b.push_str("ref "); + } + if pat.mut_token().is_some() { + b.push_str("mut "); + } + } + b.finish() +} diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs index 475e00dfcf29b..dfa30841e7db1 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs @@ -678,7 +678,7 @@ fn main() { fn complete_fn_param() { // has mut kw check_edit( - "mut bar: u32", + "bar: u32", r#" fn f(foo: (), mut bar: u32) {} fn g(foo: (), mut ba$0) @@ -689,10 +689,35 @@ fn g(foo: (), mut bar: u32) "#, ); - // has type param + // has unmatched mut kw + check_edit( + "bar: u32", + r#" +fn f(foo: (), bar: u32) {} +fn g(foo: (), mut ba$0) +"#, + r#" +fn f(foo: (), bar: u32) {} +fn g(foo: (), mut bar: u32) +"#, + ); + check_edit( "mut bar: u32", r#" +fn f(foo: (), mut bar: u32) {} +fn g(foo: (), ba$0) +"#, + r#" +fn f(foo: (), mut bar: u32) {} +fn g(foo: (), mut bar: u32) +"#, + ); + + // has type param + check_edit( + "bar: u32", + r#" fn g(foo: (), mut ba$0: u32) fn f(foo: (), mut bar: u32) {} "#, @@ -707,7 +732,7 @@ fn f(foo: (), mut bar: u32) {} fn complete_fn_mut_param_add_comma() { // add leading and trailing comma check_edit( - ", mut bar: u32,", + "bar: u32", r#" fn f(foo: (), mut bar: u32) {} fn g(foo: ()mut ba$0 baz: ()) @@ -746,7 +771,7 @@ fn g(foo: (), #[baz = "qux"] mut bar: u32) ); check_edit( - r#", #[baz = "qux"] mut bar: u32"#, + r#"#[baz = "qux"] mut bar: u32"#, r#" fn f(foo: (), #[baz = "qux"] mut bar: u32) {} fn g(foo: ()#[baz = "qux"] mut ba$0) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs index d6d73da3f140b..aaa225642c6f8 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs @@ -43,7 +43,7 @@ fn bar(file_id: usize) {} fn baz(file$0 id: u32) {} "#, expect![[r#" - bn file_id: usize, + bn file_id: usize kw mut kw ref "#]], From 80fa2b5621fe14d496df35faebba1dcb159ea33e Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 7 Mar 2026 16:31:36 +0800 Subject: [PATCH 026/101] feat: offer on let-expr for inline_local_variable Example --- ```rust fn f() { if true && let xyz = 0 { xyz$0; } } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn f() { if true { 0; } } ``` --- .../src/handlers/inline_local_variable.rs | 146 ++++++++++++++++-- 1 file changed, 133 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs index 5d4bdc6ec76cd..f55ef4229e587 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs @@ -1,3 +1,4 @@ +use either::{Either, for_both}; use hir::{PathResolution, Semantics}; use ide_db::{ EditionedFileId, RootDatabase, @@ -5,8 +6,9 @@ use ide_db::{ search::{FileReference, FileReferenceNode, UsageSearchResult}, }; use syntax::{ - SyntaxElement, TextRange, + Direction, TextRange, ast::{self, AstNode, AstToken, HasName, syntax_factory::SyntaxFactory}, + syntax_editor::{Element, SyntaxEditor}, }; use crate::{ @@ -36,12 +38,15 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>) let InlineData { let_stmt, delete_let, references, target } = if let Some(path_expr) = ctx.find_node_at_offset::() { inline_usage(&ctx.sema, path_expr, range, file_id) - } else if let Some(let_stmt) = ctx.find_node_at_offset::() { + } else if let Some(let_stmt) = ctx.find_node_at_offset() { inline_let(&ctx.sema, let_stmt, range, file_id) } else { None }?; - let initializer_expr = let_stmt.initializer()?; + let initializer_expr = match &let_stmt { + either::Either::Left(it) => it.initializer()?, + either::Either::Right(it) => it.expr()?, + }; let wrap_in_parens = references .into_iter() @@ -81,13 +86,15 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>) let mut editor = builder.make_editor(&target); if delete_let { editor.delete(let_stmt.syntax()); - if let Some(whitespace) = let_stmt - .syntax() - .next_sibling_or_token() - .and_then(SyntaxElement::into_token) - .and_then(ast::Whitespace::cast) + + if let Some(bin_expr) = let_stmt.syntax().parent().and_then(ast::BinExpr::cast) + && let Some(op_token) = bin_expr.op_token() { - editor.delete(whitespace.syntax()); + editor.delete(&op_token); + remove_whitespace(op_token, Direction::Prev, &mut editor); + remove_whitespace(let_stmt.syntax(), Direction::Prev, &mut editor); + } else { + remove_whitespace(let_stmt.syntax(), Direction::Next, &mut editor); } } @@ -116,7 +123,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>) } struct InlineData { - let_stmt: ast::LetStmt, + let_stmt: Either, delete_let: bool, target: ast::NameOrNameRef, references: Vec, @@ -124,11 +131,11 @@ struct InlineData { fn inline_let( sema: &Semantics<'_, RootDatabase>, - let_stmt: ast::LetStmt, + let_stmt: Either, range: TextRange, file_id: EditionedFileId, ) -> Option { - let bind_pat = match let_stmt.pat()? { + let bind_pat = match for_both!(&let_stmt, it => it.pat())? { ast::Pat::IdentPat(pat) => pat, _ => return None, }; @@ -187,7 +194,7 @@ fn inline_usage( let bind_pat = source.as_ident_pat()?; - let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?; + let let_stmt = AstNode::cast(bind_pat.syntax().parent()?)?; let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all(); let mut references = references.remove(&file_id)?; @@ -197,6 +204,23 @@ fn inline_usage( Some(InlineData { let_stmt, delete_let, target: ast::NameOrNameRef::NameRef(name), references }) } +fn remove_whitespace(elem: impl Element, dir: Direction, editor: &mut SyntaxEditor) { + let token = match elem.syntax_element() { + syntax::NodeOrToken::Node(node) => match dir { + Direction::Next => node.last_token(), + Direction::Prev => node.first_token(), + }, + syntax::NodeOrToken::Token(t) => Some(t), + }; + let next_token = match dir { + Direction::Next => token.and_then(|it| it.next_token()), + Direction::Prev => token.and_then(|it| it.prev_token()), + }; + if let Some(whitespace) = next_token.and_then(ast::Whitespace::cast) { + editor.delete(whitespace.syntax()); + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -404,6 +428,38 @@ fn foo() { ); } + #[test] + fn test_inline_let_expr() { + check_assist( + inline_local_variable, + r" +fn bar(a: usize) {} +fn foo() { + if let a$0 = 1 + && true + { + a + 1; + if a > 10 {} + while a > 10 {} + let b = a * 10; + bar(a); + } +}", + r" +fn bar(a: usize) {} +fn foo() { + if true + { + 1 + 1; + if 1 > 10 {} + while 1 > 10 {} + let b = 1 * 10; + bar(1); + } +}", + ); + } + #[test] fn test_not_inline_mut_variable() { cov_mark::check!(test_not_inline_mut_variable); @@ -816,6 +872,70 @@ fn f() { ); } + #[test] + fn let_expr_works_on_local_usage() { + check_assist( + inline_local_variable, + r#" +fn f() { + if let xyz = 0 + && true + { + xyz$0; + } +} +"#, + r#" +fn f() { + if true + { + 0; + } +} +"#, + ); + + check_assist( + inline_local_variable, + r#" +fn f() { + if let xyz = true + && xyz$0 + { + } +} +"#, + r#" +fn f() { + if true + { + } +} +"#, + ); + + check_assist( + inline_local_variable, + r#" +fn f() { + if true + && let xyz = 0 + { + xyz$0; + } +} +"#, + r#" +fn f() { + if true + { + 0; + } +} +"#, + ); + } + #[test] fn does_not_remove_let_when_multiple_usages() { check_assist( From 901cb4cf4006888dc69d95666886960c31e2c75a Mon Sep 17 00:00:00 2001 From: Albab-Hasan Date: Sat, 7 Mar 2026 22:09:48 +0600 Subject: [PATCH 027/101] fix: implement ir_print debug for next-solver predicate types seven IrPrint::print_debug implementations in the next-solver were placeholder stubs that returned "TODO: " instead of meaningful output. these are called via the Display impl (and Debug for PatternKind) of these types, so any solver trace log containing them was unreadable. implemented proper formatting for each: - TraitPredicate -> "T: Trait" / "!T: Trait" - HostEffectPredicate -> "const T: Trait" / "[const] T: Trait" - NormalizesTo -> "AliasTerm(...) -> Term" - SubtypePredicate -> "A <: B" - CoercePredicate -> "A -> B" - FnSig -> "fn([inputs]) -> output" - PatternKind -> "start..=end" / "or([...])" / "!null" also removed the now-unused type_name_of_val import. --- .../crates/hir-ty/src/next_solver/ir_print.rs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ir_print.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ir_print.rs index 998aab5a3fff0..8569b828ae112 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ir_print.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ir_print.rs @@ -1,7 +1,5 @@ //! Things related to IR printing in the next-trait-solver. -use std::any::type_name_of_val; - use rustc_type_ir::{self as ty, ir_print::IrPrint}; use super::SolverDefId; @@ -82,7 +80,10 @@ impl<'db> IrPrint> for DbInterner<'db> { t: &ty::TraitPredicate, fmt: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { - fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t))) + match t.polarity { + ty::PredicatePolarity::Positive => write!(fmt, "{:?}", t.trait_ref), + ty::PredicatePolarity::Negative => write!(fmt, "!{:?}", t.trait_ref), + } } } impl<'db> IrPrint> for DbInterner<'db> { @@ -97,7 +98,11 @@ impl<'db> IrPrint> for DbInterner<'db> t: &rustc_type_ir::HostEffectPredicate, fmt: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { - fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t))) + let prefix = match t.constness { + ty::BoundConstness::Const => "const", + ty::BoundConstness::Maybe => "[const]", + }; + write!(fmt, "{prefix} {:?}", t.trait_ref) } } impl<'db> IrPrint> for DbInterner<'db> { @@ -183,7 +188,7 @@ impl<'db> IrPrint> for DbInterner<'db> { t: &ty::NormalizesTo, fmt: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { - fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t))) + write!(fmt, "{} -> {:?}", t.alias, t.term) } } impl<'db> IrPrint> for DbInterner<'db> { @@ -198,7 +203,7 @@ impl<'db> IrPrint> for DbInterner<'db> { t: &ty::SubtypePredicate, fmt: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { - fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t))) + write!(fmt, "{:?} <: {:?}", t.a, t.b) } } impl<'db> IrPrint> for DbInterner<'db> { @@ -210,7 +215,7 @@ impl<'db> IrPrint> for DbInterner<'db> { t: &ty::CoercePredicate, fmt: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { - fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t))) + write!(fmt, "{:?} -> {:?}", t.a, t.b) } } impl<'db> IrPrint> for DbInterner<'db> { @@ -219,7 +224,9 @@ impl<'db> IrPrint> for DbInterner<'db> { } fn print_debug(t: &ty::FnSig, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t))) + let tys = t.inputs_and_output.as_slice(); + let (output, inputs) = tys.split_last().unwrap(); + write!(fmt, "fn({:?}) -> {:?}", inputs, output) } } @@ -235,6 +242,10 @@ impl<'db> IrPrint>> for DbInterner<'d t: &rustc_type_ir::PatternKind>, fmt: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { - fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t))) + match t { + ty::PatternKind::Range { start, end } => write!(fmt, "{:?}..={:?}", start, end), + ty::PatternKind::Or(list) => write!(fmt, "or({:?})", list), + ty::PatternKind::NotNull => fmt.write_str("!null"), + } } } From e4e8ae7660240d32b79f323066c4e10c9b68aecd Mon Sep 17 00:00:00 2001 From: Albab-Hasan Date: Sun, 8 Mar 2026 11:03:05 +0600 Subject: [PATCH 028/101] fix: qualify NormalizesTo and CoercePredicate in ir_print debug output --- .../rust-analyzer/crates/hir-ty/src/next_solver/ir_print.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ir_print.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ir_print.rs index 8569b828ae112..65931549db02f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ir_print.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ir_print.rs @@ -188,7 +188,7 @@ impl<'db> IrPrint> for DbInterner<'db> { t: &ty::NormalizesTo, fmt: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { - write!(fmt, "{} -> {:?}", t.alias, t.term) + write!(fmt, "NormalizesTo({} -> {:?})", t.alias, t.term) } } impl<'db> IrPrint> for DbInterner<'db> { @@ -215,7 +215,7 @@ impl<'db> IrPrint> for DbInterner<'db> { t: &ty::CoercePredicate, fmt: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { - write!(fmt, "{:?} -> {:?}", t.a, t.b) + write!(fmt, "CoercePredicate({:?} -> {:?})", t.a, t.b) } } impl<'db> IrPrint> for DbInterner<'db> { From 0adea80424ab3af069c9e196da0ae75ae0147f99 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 9 Mar 2026 11:43:54 +0800 Subject: [PATCH 029/101] Fix other predicate for replace_is_method_with_if_let_method - Fix parentheses for replace_is_method_with_if_let_method Example --- ```rust fn main() { let x = Some(1); if x.is_som$0e_and(predicate) {} } ``` **Before this PR** ```rust fn main() { let x = Some(1); if let Some(x1) = x {} } ``` **After this PR** ```rust fn main() { let x = Some(1); if let Some(x1) = x && predicate(x1) {} } ``` --- .../replace_is_method_with_if_let_method.rs | 90 +++++++++++++++---- .../src/handlers/replace_method_eager_lazy.rs | 16 +--- .../crates/ide-assists/src/utils.rs | 15 ++++ 3 files changed, 92 insertions(+), 29 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs index d22e951b5dab0..38d8c38ef23d6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs @@ -1,8 +1,11 @@ use either::Either; use ide_db::syntax_helpers::suggest_name; -use syntax::ast::{self, AstNode, HasArgList, syntax_factory::SyntaxFactory}; +use syntax::ast::{self, AstNode, HasArgList, prec::ExprPrecedence, syntax_factory::SyntaxFactory}; -use crate::{AssistContext, AssistId, Assists, utils::cover_let_chain}; +use crate::{ + AssistContext, AssistId, Assists, + utils::{cover_let_chain, wrap_paren, wrap_paren_in_call}, +}; // Assist: replace_is_some_with_if_let_some // @@ -39,6 +42,7 @@ pub(crate) fn replace_is_method_with_if_let_method( match method_kind { "is_some" | "is_ok" => { let receiver = call_expr.receiver()?; + let make = SyntaxFactory::with_mappings(); let mut name_generator = suggest_name::NameGenerator::new_from_scope_locals( ctx.sema.scope(has_cond.syntax()), @@ -48,7 +52,7 @@ pub(crate) fn replace_is_method_with_if_let_method( } else { name_generator.for_variable(&receiver, &ctx.sema) }; - let (pat, predicate) = method_predicate(&call_expr).unzip(); + let (pat, predicate) = method_predicate(&call_expr, &var_name, &make); let (assist_id, message, text) = if method_kind == "is_some" { ("replace_is_some_with_if_let_some", "Replace `is_some` with `let Some`", "Some") @@ -61,13 +65,9 @@ pub(crate) fn replace_is_method_with_if_let_method( message, call_expr.syntax().text_range(), |edit| { - let make = SyntaxFactory::with_mappings(); let mut editor = edit.make_editor(call_expr.syntax()); - let var_pat = pat.unwrap_or_else(|| { - make.ident_pat(false, false, make.name(&var_name)).into() - }); - let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat]).into(); + let pat = make.tuple_struct_pat(make.ident_path(text), [pat]).into(); let let_expr = make.expr_let(pat, receiver); if let Some(cap) = ctx.config.snippet_cap @@ -81,6 +81,7 @@ pub(crate) fn replace_is_method_with_if_let_method( let new_expr = if let Some(predicate) = predicate { let op = ast::BinaryOp::LogicOp(ast::LogicOp::And); + let predicate = wrap_paren(predicate, &make, ExprPrecedence::LAnd); make.expr_bin(let_expr.into(), op, predicate).into() } else { ast::Expr::from(let_expr) @@ -96,14 +97,23 @@ pub(crate) fn replace_is_method_with_if_let_method( } } -fn method_predicate(call_expr: &ast::MethodCallExpr) -> Option<(ast::Pat, ast::Expr)> { - let argument = call_expr.arg_list()?.args().next()?; - match argument { - ast::Expr::ClosureExpr(it) => { - let pat = it.param_list()?.params().next()?.pat()?; - Some((pat, it.body()?)) - } - _ => None, +fn method_predicate( + call_expr: &ast::MethodCallExpr, + name: &str, + make: &SyntaxFactory, +) -> (ast::Pat, Option) { + let argument = call_expr.arg_list().and_then(|it| it.args().next()); + if let Some(ast::Expr::ClosureExpr(it)) = argument.clone() + && let Some(pat) = it.param_list().and_then(|it| it.params().next()?.pat()) + { + (pat, it.body()) + } else { + let pat = make.ident_pat(false, false, make.name(name)); + let expr = argument.map(|expr| { + let arg_list = make.arg_list([make.expr_path(make.ident_path(name))]); + make.expr_call(wrap_paren_in_call(expr, make), arg_list).into() + }); + (pat.into(), expr) } } @@ -232,6 +242,54 @@ fn main() { let x = Some(1); if let Some(it) = x && it != 3 {} } +"#, + ); + + check_assist( + replace_is_method_with_if_let_method, + r#" +fn main() { + let x = Some(1); + if x.is_som$0e_and(|it| it != 3 || it > 10) {} +} +"#, + r#" +fn main() { + let x = Some(1); + if let Some(it) = x && (it != 3 || it > 10) {} +} +"#, + ); + + check_assist( + replace_is_method_with_if_let_method, + r#" +fn main() { + let x = Some(1); + if x.is_som$0e_and(predicate) {} +} +"#, + r#" +fn main() { + let x = Some(1); + if let Some(x1) = x && predicate(x1) {} +} +"#, + ); + + check_assist( + replace_is_method_with_if_let_method, + r#" +fn main() { + let x = Some(1); + if x.is_som$0e_and(func.f) {} +} +"#, + r#" +fn main() { + let x = Some(1); + if let Some(x1) = x && (func.f)(x1) {} +} "#, ); } 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 6ca3e26ca0187..6e4dd8cb73a6b 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 @@ -2,10 +2,10 @@ use hir::Semantics; use ide_db::{RootDatabase, assists::AssistId, defs::Definition}; use syntax::{ AstNode, - ast::{self, Expr, HasArgList, make}, + ast::{self, Expr, HasArgList, make, syntax_factory::SyntaxFactory}, }; -use crate::{AssistContext, Assists}; +use crate::{AssistContext, Assists, utils::wrap_paren_in_call}; // Assist: replace_with_lazy_method // @@ -177,11 +177,7 @@ fn into_call(param: &Expr, sema: &Semantics<'_, RootDatabase>) -> Expr { } })() .unwrap_or_else(|| { - let callable = if needs_parens_in_call(param) { - make::expr_paren(param.clone()).into() - } else { - param.clone() - }; + let callable = wrap_paren_in_call(param.clone(), &SyntaxFactory::without_mappings()); make::expr_call(callable, make::arg_list(Vec::new())).into() }) } @@ -200,12 +196,6 @@ 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; 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 b4055e77ccf8a..1c1c98cb7a39e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -25,6 +25,7 @@ use syntax::{ edit::{AstNodeEdit, IndentLevel}, edit_in_place::AttrsOwnerEdit, make, + prec::ExprPrecedence, syntax_factory::SyntaxFactory, }, syntax_editor::{Element, Removable, SyntaxEditor}, @@ -97,6 +98,20 @@ pub(crate) fn wrap_block(expr: &ast::Expr) -> ast::BlockExpr { } } +pub(crate) fn wrap_paren(expr: ast::Expr, make: &SyntaxFactory, prec: ExprPrecedence) -> ast::Expr { + if expr.precedence().needs_parentheses_in(prec) { make.expr_paren(expr).into() } else { expr } +} + +pub(crate) fn wrap_paren_in_call(expr: ast::Expr, make: &SyntaxFactory) -> ast::Expr { + if needs_parens_in_call(&expr) { make.expr_paren(expr).into() } else { expr } +} + +fn needs_parens_in_call(param: &ast::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()) +} + /// This is a method with a heuristics to support test methods annotated with custom test annotations, such as /// `#[test_case(...)]`, `#[tokio::test]` and similar. /// Also a regular `#[test]` annotation is supported. From 005a1eddd724bd6d3e01f66497b42164052e17ea Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 10 Mar 2026 17:43:34 +0000 Subject: [PATCH 030/101] internal: Fix test_loading_rust_analyzer when rust-project.json is present load_workspace_at() looks at parent directories. If rust-analyzer is in a directory (e.g. a monorepo) where a parent directory contains a rust-project.json, that configuration wins over the Cargo.toml and the test fails. One easy way of testing this is deliberately writing an invalid JSON file to the parent directory. ``` $ echo '{' > ../rust-project.json $ cargo t -p load-cargo ---- tests::test_loading_rust_analyzer stdout ---- thread 'tests::test_loading_rust_analyzer' (38576150) panicked at crates/load-cargo/src/lib.rs:756:81: called `Result::unwrap()` on an `Err` value: Failed to load the project at /Users/wilfred/src/rust-project.json Caused by: 0: Failed to deserialize json file /Users/wilfred/src/rust-project.json 1: EOF while parsing an object at line 2 column 0 ``` Instead, explicitly load the cargo workspace so the presence of a rust-project.json never changes the result of the test. AI disclosure: Written with help from Claude. --- .../rust-analyzer/crates/load-cargo/src/lib.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index 95fcfce291277..83b1d1cd7a467 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -744,7 +744,15 @@ mod tests { #[test] fn test_loading_rust_analyzer() { - let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); + let cargo_toml_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .join("Cargo.toml"); + let cargo_toml_path = AbsPathBuf::assert_utf8(cargo_toml_path); + let manifest = ProjectManifest::from_manifest_file(cargo_toml_path).unwrap(); + let cargo_config = CargoConfig { set_test: true, ..CargoConfig::default() }; let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: false, @@ -752,8 +760,9 @@ mod tests { prefill_caches: false, proc_macro_processes: 1, }; + let workspace = ProjectWorkspace::load(manifest, &cargo_config, &|_| {}).unwrap(); let (db, _vfs, _proc_macro) = - load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap(); + load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config).unwrap(); let n_crates = db.all_crates().len(); // RA has quite a few crates, but the exact count doesn't matter From e108da9dc263aa28fed4b5a51d5e08311be2355f Mon Sep 17 00:00:00 2001 From: James Kay Date: Wed, 11 Mar 2026 02:25:56 +0000 Subject: [PATCH 031/101] `std`: include `dlmalloc` for all non-wasi Wasm targets Currently, building std for a custom Wasm target with an OS other than `unknown` will fail, because `sys/alloc/mod.rs` will attempt to use `sys/alloc/wasm.rs`, the dlmalloc-based allocator used on `wasm32-unknown-unknown`. However, currently dlmalloc is only pulled in when `target_os = "unknown"`. Instead, we should make `Cargo.toml` and `alloc/mod.rs` match: either - disable `wasm.rs` in `alloc/mod.rs` where `not(target_os = "unknown")`, or - pull in `dlmalloc` for all Wasm targets with `target_family = "wasm32"` that aren't covered by the [upper branches of `alloc/mod.rs`](https://github.com/rust-lang/rust/blob/main/library/std/src/sys/alloc/mod.rs#L72-L100). This PR takes the latter approach, because it allows more code to compile without a custom allocator. --- library/std/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 1b7a41d697367..740d37617547c 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -62,7 +62,7 @@ path = "../windows_link" rand = { version = "0.9.0", default-features = false, features = ["alloc"] } rand_xorshift = "0.4.0" -[target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), target_os = "xous", target_os = "vexos", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] +[target.'cfg(any(all(target_family = "wasm", not(target_os = "wasi")), target_os = "xous", target_os = "vexos", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] dlmalloc = { version = "0.2.10", features = ['rustc-dep-of-std'] } [target.x86_64-fortanix-unknown-sgx.dependencies] From e2923eb86a18b2e288e88605a61c21fcc38f8c82 Mon Sep 17 00:00:00 2001 From: James Kay Date: Wed, 11 Mar 2026 16:49:12 +0000 Subject: [PATCH 032/101] `std`: don't depend on `dlmalloc` when `cfg(unix)` --- library/std/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 740d37617547c..7a8ccdbc5043b 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -62,7 +62,7 @@ path = "../windows_link" rand = { version = "0.9.0", default-features = false, features = ["alloc"] } rand_xorshift = "0.4.0" -[target.'cfg(any(all(target_family = "wasm", not(target_os = "wasi")), target_os = "xous", target_os = "vexos", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] +[target.'cfg(any(all(target_family = "wasm", not(any(unix, target_os = "wasi"))), target_os = "xous", target_os = "vexos", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] dlmalloc = { version = "0.2.10", features = ['rustc-dep-of-std'] } [target.x86_64-fortanix-unknown-sgx.dependencies] From 201bbb30a4faa261f488afae6b6fb66d4b210e0d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 13 Mar 2026 22:34:06 +0800 Subject: [PATCH 033/101] fix: offer on const like path-expr for 'extract_variable' Example --- ```rust struct Foo; fn foo() -> Foo { $0Foo$0 } ``` **Before this PR** Assist not applicable **After this PR** ```rust struct Foo; fn foo() -> Foo { let foo = Foo; foo } ``` --- .../src/handlers/extract_variable.rs | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs index 7c60184142176..6cba52a3b9fd8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs @@ -75,7 +75,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op .next() .and_then(ast::Expr::cast) { - expr.syntax().ancestors().find_map(valid_target_expr)?.syntax().clone() + expr.syntax().ancestors().find_map(valid_target_expr(ctx))?.syntax().clone() } else { return None; } @@ -96,7 +96,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let to_extract = node .descendants() .take_while(|it| range.contains_range(it.text_range())) - .find_map(valid_target_expr)?; + .find_map(valid_target_expr(ctx))?; let ty = ctx.sema.type_of_expr(&to_extract).map(TypeInfo::adjusted); if matches!(&ty, Some(ty_info) if ty_info.is_unit()) { @@ -283,14 +283,19 @@ fn peel_parens(mut expr: ast::Expr) -> ast::Expr { /// Check whether the node is a valid expression which can be extracted to a variable. /// In general that's true for any expression, but in some cases that would produce invalid code. -fn valid_target_expr(node: SyntaxNode) -> Option { - match node.kind() { - SyntaxKind::PATH_EXPR | SyntaxKind::LOOP_EXPR | SyntaxKind::LET_EXPR => None, +fn valid_target_expr(ctx: &AssistContext<'_>) -> impl Fn(SyntaxNode) -> Option { + |node| match node.kind() { + SyntaxKind::LOOP_EXPR | SyntaxKind::LET_EXPR => None, SyntaxKind::BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), SyntaxKind::RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), SyntaxKind::BLOCK_EXPR => { ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from) } + SyntaxKind::PATH_EXPR => { + let path_expr = ast::PathExpr::cast(node)?; + let path_resolution = ctx.sema.resolve_path(&path_expr.path()?)?; + like_const_value(ctx, path_resolution).then_some(path_expr.into()) + } _ => ast::Expr::cast(node), } } @@ -455,6 +460,31 @@ impl Anchor { } } +fn like_const_value(ctx: &AssistContext<'_>, path_resolution: hir::PathResolution) -> bool { + let db = ctx.db(); + let adt_like_const_value = |adt: Option| matches!(adt, Some(hir::Adt::Struct(s)) if s.kind(db) == hir::StructKind::Unit); + match path_resolution { + hir::PathResolution::Def(def) => match def { + hir::ModuleDef::Adt(adt) => adt_like_const_value(Some(adt)), + hir::ModuleDef::Variant(variant) => variant.kind(db) == hir::StructKind::Unit, + hir::ModuleDef::TypeAlias(ty) => adt_like_const_value(ty.ty(db).as_adt()), + hir::ModuleDef::Const(_) | hir::ModuleDef::Static(_) => true, + hir::ModuleDef::Trait(_) + | hir::ModuleDef::BuiltinType(_) + | hir::ModuleDef::Macro(_) + | hir::ModuleDef::Module(_) => false, + hir::ModuleDef::Function(_) => false, // no extract named function + }, + hir::PathResolution::SelfType(ty) => adt_like_const_value(ty.self_ty(db).as_adt()), + hir::PathResolution::ConstParam(_) => true, + hir::PathResolution::Local(_) + | hir::PathResolution::TypeParam(_) + | hir::PathResolution::BuiltinAttr(_) + | hir::PathResolution::ToolModule(_) + | hir::PathResolution::DeriveHelper(_) => false, + } +} + #[cfg(test)] mod tests { // NOTE: We use check_assist_by_label, but not check_assist_not_applicable_by_label @@ -1747,6 +1777,27 @@ fn main() { ); } + #[test] + fn extract_non_local_path_expr() { + check_assist_by_label( + extract_variable, + r#" +struct Foo; +fn foo() -> Foo { + $0Foo$0 +} +"#, + r#" +struct Foo; +fn foo() -> Foo { + let $0foo = Foo; + foo +} +"#, + "Extract into variable", + ); + } + #[test] fn extract_var_for_return_not_applicable() { check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } "); From 5e62d4f1c8fb9409c5163c3f473a01075fa4c348 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 11:10:56 +0000 Subject: [PATCH 034/101] Bump undici from 6.21.3 to 6.24.1 in /editors/code Bumps [undici](https://github.com/nodejs/undici) from 6.21.3 to 6.24.1. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v6.21.3...v6.24.1) --- updated-dependencies: - dependency-name: undici dependency-version: 6.24.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- src/tools/rust-analyzer/editors/code/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index be0bdc8d55bbf..4bddc28c1ef7e 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -6719,9 +6719,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", - "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", "dev": true, "license": "MIT", "engines": { From c34e7ffda7356c37945e5e56e7372c0d5d1bf1bc Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 14 Mar 2026 17:05:44 +0800 Subject: [PATCH 035/101] Migrate 'convert_named_struct_to_tuple_struct' assist --- .../convert_named_struct_to_tuple_struct.rs | 131 +++++++++++------- 1 file changed, 83 insertions(+), 48 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index e518c39dabc27..706cef4120470 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -1,9 +1,12 @@ +use std::ops::RangeInclusive; + use either::Either; use ide_db::{defs::Definition, search::FileReference}; -use itertools::Itertools; use syntax::{ - SyntaxKind, - ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility}, + SyntaxElement, SyntaxKind, SyntaxNode, T, TextRange, + ast::{ + self, AstNode, HasAttrs, HasGenericParams, HasVisibility, syntax_factory::SyntaxFactory, + }, match_ast, syntax_editor::{Position, SyntaxEditor}, }; @@ -81,17 +84,17 @@ pub(crate) fn convert_named_struct_to_tuple_struct( AssistId::refactor_rewrite("convert_named_struct_to_tuple_struct"), "Convert to tuple struct", strukt_or_variant.syntax().text_range(), - |edit| { - edit_field_references(ctx, edit, record_fields.fields()); - edit_struct_references(ctx, edit, strukt_def); - edit_struct_def(ctx, edit, &strukt_or_variant, record_fields); + |builder| { + edit_field_references(ctx, builder, record_fields.fields()); + edit_struct_references(ctx, builder, strukt_def); + edit_struct_def(ctx, builder, &strukt_or_variant, record_fields); }, ) } fn edit_struct_def( ctx: &AssistContext<'_>, - edit: &mut SourceChangeBuilder, + builder: &mut SourceChangeBuilder, strukt: &Either, record_fields: ast::RecordFieldList, ) { @@ -108,24 +111,23 @@ fn edit_struct_def( let field = ast::TupleField::cast(field_syntax)?; Some(field) }); - let tuple_fields = ast::make::tuple_field_list(tuple_fields); - let record_fields_text_range = record_fields.syntax().text_range(); - edit.edit_file(ctx.vfs_file_id()); - edit.replace(record_fields_text_range, tuple_fields.syntax().text()); + let make = SyntaxFactory::without_mappings(); + let mut edit = builder.make_editor(strukt.syntax()); + + let tuple_fields = make.tuple_field_list(tuple_fields); + let mut elements = vec![tuple_fields.syntax().clone().into()]; if let Either::Left(strukt) = strukt { if let Some(w) = strukt.where_clause() { - let mut where_clause = w.to_string(); - if where_clause.ends_with(',') { - where_clause.pop(); - } - where_clause.push(';'); + edit.delete(w.syntax()); - edit.delete(w.syntax().text_range()); - edit.insert(record_fields_text_range.end(), ast::make::tokens::single_newline().text()); - edit.insert(record_fields_text_range.end(), where_clause); - edit.insert(record_fields_text_range.end(), ast::make::tokens::single_newline().text()); + elements.extend([ + make.whitespace("\n").into(), + remove_trailing_comma(w).into(), + make.token(T![;]).into(), + make.whitespace("\n").into(), + ]); if let Some(tok) = strukt .generic_param_list() @@ -133,25 +135,28 @@ fn edit_struct_def( .and_then(|tok| tok.next_token()) .filter(|tok| tok.kind() == SyntaxKind::WHITESPACE) { - edit.delete(tok.text_range()); + edit.delete(tok); } } else { - edit.insert(record_fields_text_range.end(), ";"); + elements.push(make.token(T![;]).into()); } } + edit.replace_with_many(record_fields.syntax(), elements); if let Some(tok) = record_fields .l_curly_token() .and_then(|tok| tok.prev_token()) .filter(|tok| tok.kind() == SyntaxKind::WHITESPACE) { - edit.delete(tok.text_range()) + edit.delete(tok) } + + builder.add_file_edits(ctx.vfs_file_id(), edit); } fn edit_struct_references( ctx: &AssistContext<'_>, - edit: &mut SourceChangeBuilder, + builder: &mut SourceChangeBuilder, strukt: Either, ) { let strukt_def = match strukt { @@ -161,17 +166,20 @@ fn edit_struct_references( let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); for (file_id, refs) in usages { - edit.edit_file(file_id.file_id(ctx.db())); + let source = ctx.sema.parse(file_id); + let mut edit = builder.make_editor(source.syntax()); for r in refs { - process_struct_name_reference(ctx, r, edit); + process_struct_name_reference(ctx, r, &mut edit, &source); } + builder.add_file_edits(file_id.file_id(ctx.db()), edit); } } fn process_struct_name_reference( ctx: &AssistContext<'_>, r: FileReference, - edit: &mut SourceChangeBuilder, + edit: &mut SyntaxEditor, + source: &ast::SourceFile, ) -> Option<()> { // First check if it's the last semgnet of a path that directly belongs to a record // expression/pattern. @@ -186,6 +194,7 @@ fn process_struct_name_reference( // struct we want to edit. return None; } + let make = SyntaxFactory::without_mappings(); // FIXME: Processing RecordPat and RecordExpr for unordered fields, and insert RestPat let parent = full_path.syntax().parent()?; @@ -195,20 +204,17 @@ fn process_struct_name_reference( // When we failed to get the original range for the whole struct expression node, // we can't provide any reasonable edit. Leave it untouched. let file_range = ctx.sema.original_range_opt(record_struct_pat.syntax())?; - edit.replace( - file_range.range, - ast::make::tuple_struct_pat( - record_struct_pat.path()?, - record_struct_pat - .record_pat_field_list()? - .fields() - .filter_map(|pat| pat.pat()) - .chain(record_struct_pat.record_pat_field_list()? - .rest_pat() - .map(Into::into)) - ) - .to_string() + let new = make.tuple_struct_pat( + record_struct_pat.path()?, + record_struct_pat + .record_pat_field_list()? + .fields() + .filter_map(|pat| pat.pat()) + .chain(record_struct_pat.record_pat_field_list()? + .rest_pat() + .map(Into::into)) ); + edit.replace_all(cover_range(source, file_range.range), vec![new.syntax().clone().into()]); }, ast::RecordExpr(record_expr) => { // When we failed to get the original range for the whole struct pattern node, @@ -218,10 +224,9 @@ fn process_struct_name_reference( let args = record_expr .record_expr_field_list()? .fields() - .filter_map(|f| f.expr()) - .join(", "); - - edit.replace(file_range.range, format!("{path}({args})")); + .filter_map(|f| f.expr()); + let new = make.expr_call(make.expr_path(path), make.arg_list(args)); + edit.replace_all(cover_range(source, file_range.range), vec![new.syntax().clone().into()]); }, _ => {} } @@ -232,9 +237,10 @@ fn process_struct_name_reference( fn edit_field_references( ctx: &AssistContext<'_>, - edit: &mut SourceChangeBuilder, + builder: &mut SourceChangeBuilder, fields: impl Iterator, ) { + let make = SyntaxFactory::without_mappings(); for (index, field) in fields.enumerate() { let field = match ctx.sema.to_def(&field) { Some(it) => it, @@ -243,19 +249,48 @@ fn edit_field_references( let def = Definition::Field(field); let usages = def.usages(&ctx.sema).all(); for (file_id, refs) in usages { - edit.edit_file(file_id.file_id(ctx.db())); + let source = ctx.sema.parse(file_id); + let mut edit = builder.make_editor(source.syntax()); + for r in refs { if let Some(name_ref) = r.name.as_name_ref() { // Only edit the field reference if it's part of a `.field` access if name_ref.syntax().parent().and_then(ast::FieldExpr::cast).is_some() { - edit.replace(r.range, index.to_string()); + edit.replace_all( + cover_range(&source, r.range), + vec![make.name_ref(&index.to_string()).syntax().clone().into()], + ); } } } + + builder.add_file_edits(file_id.file_id(ctx.db()), edit); } } } +fn cover_range(source: &ast::SourceFile, range: TextRange) -> RangeInclusive { + let node = match source.syntax().covering_element(range) { + syntax::NodeOrToken::Node(node) => node, + syntax::NodeOrToken::Token(t) => t.parent().unwrap(), + }; + let mut iter = node.children_with_tokens().filter(|it| range.contains_range(it.text_range())); + let first = iter.next().unwrap_or(node.into()); + let last = iter.last().unwrap_or_else(|| first.clone()); + first..=last +} + +fn remove_trailing_comma(w: ast::WhereClause) -> SyntaxNode { + let w = w.syntax().clone_subtree(); + let mut editor = SyntaxEditor::new(w.clone()); + if let Some(last) = w.last_child_or_token() + && last.kind() == T![,] + { + editor.delete(last); + } + editor.finish().new_root().clone() +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; From 31f31ab2fe7e1306a4d52806b654ab3ff8621d25 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 11 Mar 2026 18:33:28 +0800 Subject: [PATCH 036/101] Fix overlap edit on record to tuple assist uses self Example --- ```rust struct $0Foo { field1: u32 } impl Foo { fn clone(&self) -> Self { Self { field1: self.field1 } } } ``` **Before this PR** Panic **After this PR** ```rust struct Foo(u32); impl Foo { fn clone(&self) -> Self { Self(self.0) } } ``` --- .../convert_named_struct_to_tuple_struct.rs | 210 +++++++++++++++--- .../rust-analyzer/crates/syntax/src/algo.rs | 16 ++ 2 files changed, 199 insertions(+), 27 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index 706cef4120470..8dfd4598dfa13 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -3,12 +3,13 @@ use std::ops::RangeInclusive; use either::Either; use ide_db::{defs::Definition, search::FileReference}; use syntax::{ - SyntaxElement, SyntaxKind, SyntaxNode, T, TextRange, + NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, T, TextRange, + algo::next_non_trivia_token, ast::{ self, AstNode, HasAttrs, HasGenericParams, HasVisibility, syntax_factory::SyntaxFactory, }, match_ast, - syntax_editor::{Position, SyntaxEditor}, + syntax_editor::{Element, Position, SyntaxEditor}, }; use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder}; @@ -194,39 +195,32 @@ fn process_struct_name_reference( // struct we want to edit. return None; } - let make = SyntaxFactory::without_mappings(); // FIXME: Processing RecordPat and RecordExpr for unordered fields, and insert RestPat let parent = full_path.syntax().parent()?; match_ast! { match parent { ast::RecordPat(record_struct_pat) => { - // When we failed to get the original range for the whole struct expression node, + // When we failed to get the original range for the whole struct pattern node, // we can't provide any reasonable edit. Leave it untouched. - let file_range = ctx.sema.original_range_opt(record_struct_pat.syntax())?; - let new = make.tuple_struct_pat( - record_struct_pat.path()?, - record_struct_pat - .record_pat_field_list()? - .fields() - .filter_map(|pat| pat.pat()) - .chain(record_struct_pat.record_pat_field_list()? - .rest_pat() - .map(Into::into)) + record_to_tuple_struct_like( + ctx, + source, + edit, + record_struct_pat.record_pat_field_list()?, + |it| it.fields().filter_map(|it| it.name_ref()), ); - edit.replace_all(cover_range(source, file_range.range), vec![new.syntax().clone().into()]); }, ast::RecordExpr(record_expr) => { - // When we failed to get the original range for the whole struct pattern node, + // When we failed to get the original range for the whole struct expression node, // we can't provide any reasonable edit. Leave it untouched. - let file_range = ctx.sema.original_range_opt(record_expr.syntax())?; - let path = record_expr.path()?; - let args = record_expr - .record_expr_field_list()? - .fields() - .filter_map(|f| f.expr()); - let new = make.expr_call(make.expr_path(path), make.arg_list(args)); - edit.replace_all(cover_range(source, file_range.range), vec![new.syntax().clone().into()]); + record_to_tuple_struct_like( + ctx, + source, + edit, + record_expr.record_expr_field_list()?, + |it| it.fields().filter_map(|it| it.name_ref()), + ); }, _ => {} } @@ -235,6 +229,61 @@ fn process_struct_name_reference( Some(()) } +fn record_to_tuple_struct_like( + ctx: &AssistContext<'_>, + source: &ast::SourceFile, + edit: &mut SyntaxEditor, + field_list: T, + fields: impl FnOnce(&T) -> I, +) -> Option<()> +where + T: AstNode, + I: IntoIterator, +{ + let make = SyntaxFactory::without_mappings(); + let orig = ctx.sema.original_range_opt(field_list.syntax())?; + let list_range = cover_range(source, orig.range); + + let l_curly = match list_range.start() { + NodeOrToken::Node(node) => node.first_token()?, + NodeOrToken::Token(t) => t.clone(), + }; + let r_curly = match list_range.end() { + NodeOrToken::Node(node) => node.last_token()?, + NodeOrToken::Token(t) => t.clone(), + }; + + if l_curly.kind() == T!['{'] { + delete_whitespace(edit, l_curly.prev_token()); + delete_whitespace(edit, l_curly.next_token()); + edit.replace(l_curly, make.token(T!['('])); + } + if r_curly.kind() == T!['}'] { + delete_whitespace(edit, r_curly.prev_token()); + edit.replace(r_curly, make.token(T![')'])); + } + + for name_ref in fields(&field_list) { + let Some(orig) = ctx.sema.original_range_opt(name_ref.syntax()) else { continue }; + let name_range = cover_range(source, orig.range); + + if let Some(colon) = next_non_trivia_token(name_range.end().clone()) + && colon.kind() == T![:] + { + edit.delete(&colon); + edit.delete_all(name_range); + + if let Some(next) = next_non_trivia_token(colon.clone()) + && next.kind() != T!['}'] + { + // Avoid overlapping delete whitespace on `{ field: }` + delete_whitespace(edit, colon.next_token()); + } + } + } + Some(()) +} + fn edit_field_references( ctx: &AssistContext<'_>, builder: &mut SourceChangeBuilder, @@ -271,8 +320,8 @@ fn edit_field_references( fn cover_range(source: &ast::SourceFile, range: TextRange) -> RangeInclusive { let node = match source.syntax().covering_element(range) { - syntax::NodeOrToken::Node(node) => node, - syntax::NodeOrToken::Token(t) => t.parent().unwrap(), + NodeOrToken::Node(node) => node, + NodeOrToken::Token(t) => t.parent().unwrap(), }; let mut iter = node.children_with_tokens().filter(|it| range.contains_range(it.text_range())); let first = iter.next().unwrap_or(node.into()); @@ -280,6 +329,15 @@ fn cover_range(source: &ast::SourceFile, range: TextRange) -> RangeInclusive) { + let Some(whitespace) = whitespace else { return }; + let NodeOrToken::Token(token) = whitespace.syntax_element() else { return }; + + if token.kind() == SyntaxKind::WHITESPACE && !token.text().contains('\n') { + edit.delete(token); + } +} + fn remove_trailing_comma(w: ast::WhereClause) -> SyntaxNode { let w = w.syntax().clone_subtree(); let mut editor = SyntaxEditor::new(w.clone()); @@ -712,6 +770,102 @@ where ); } + #[test] + fn convert_constructor_expr_uses_self() { + // regression test for #21595 + check_assist( + convert_named_struct_to_tuple_struct, + r#" +struct $0Foo { field1: u32 } +impl Foo { + fn clone(&self) -> Self { + Self { field1: self.field1 } + } +}"#, + r#" +struct Foo(u32); +impl Foo { + fn clone(&self) -> Self { + Self(self.0) + } +}"#, + ); + + check_assist( + convert_named_struct_to_tuple_struct, + r#" +macro_rules! id { + ($($t:tt)*) => { $($t)* } +} +struct $0Foo { field1: u32 } +impl Foo { + fn clone(&self) -> Self { + id!(Self { field1: self.field1 }) + } +}"#, + r#" +macro_rules! id { + ($($t:tt)*) => { $($t)* } +} +struct Foo(u32); +impl Foo { + fn clone(&self) -> Self { + id!(Self(self.0)) + } +}"#, + ); + } + + #[test] + fn convert_pat_uses_self() { + // regression test for #21595 + check_assist( + convert_named_struct_to_tuple_struct, + r#" +enum Foo { + $0Value { field: &'static Foo }, + Nil, +} +fn foo(foo: &Foo) { + if let Foo::Value { field: Foo::Value { field } } = foo {} +}"#, + r#" +enum Foo { + Value(&'static Foo), + Nil, +} +fn foo(foo: &Foo) { + if let Foo::Value(Foo::Value(field)) = foo {} +}"#, + ); + + check_assist( + convert_named_struct_to_tuple_struct, + r#" +macro_rules! id { + ($($t:tt)*) => { $($t)* } +} +enum Foo { + $0Value { field: &'static Foo }, + Nil, +} +fn foo(foo: &Foo) { + if let id!(Foo::Value { field: Foo::Value { field } }) = foo {} +}"#, + r#" +macro_rules! id { + ($($t:tt)*) => { $($t)* } +} +enum Foo { + Value(&'static Foo), + Nil, +} +fn foo(foo: &Foo) { + if let id!(Foo::Value(Foo::Value(field))) = foo {} +}"#, + ); + } + #[test] fn not_applicable_other_than_record_variant() { check_assist_not_applicable( @@ -1077,7 +1231,9 @@ struct Struct(i32); fn test() { id! { - let s = Struct(42); + let s = Struct( + 42, + ); let Struct(value) = s; let Struct(inner) = s; } diff --git a/src/tools/rust-analyzer/crates/syntax/src/algo.rs b/src/tools/rust-analyzer/crates/syntax/src/algo.rs index 3ab9c902625f5..c679921b3f658 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/algo.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/algo.rs @@ -132,3 +132,19 @@ pub fn previous_non_trivia_token(e: impl Into) -> Option) -> Option { + let mut token = match e.into() { + SyntaxElement::Node(n) => n.last_token()?, + SyntaxElement::Token(t) => t, + } + .next_token(); + while let Some(inner) = token { + if !inner.kind().is_trivia() { + return Some(inner); + } else { + token = inner.next_token(); + } + } + None +} From 4aa2d6fbf48d830dd6f914fa9558c173e40fbbe0 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 14 Mar 2026 22:12:50 +0800 Subject: [PATCH 037/101] Fix field ref in macro for 'convert_tuple_struct_to_named_struct' --- .../handlers/convert_tuple_struct_to_named_struct.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index f8b9bb68db817..ba22224b6be26 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -252,13 +252,15 @@ fn edit_field_references( let usages = def.usages(&ctx.sema).all(); for (file_id, refs) in usages { let source = ctx.sema.parse(file_id); - let source = source.syntax(); - let mut editor = edit.make_editor(source); + let mut editor = edit.make_editor(source.syntax()); for r in refs { if let Some(name_ref) = r.name.as_name_ref() - && let Some(original) = ctx.sema.original_ast_node(name_ref.clone()) + && let Some(original) = ctx.sema.original_range_opt(name_ref.syntax()) { - editor.replace(original.syntax(), name.syntax()); + editor.replace_all( + cover_edit_range(&source, original.range), + vec![name.syntax().clone().into()], + ); } } edit.add_file_edits(file_id.file_id(ctx.db()), editor); From 77a20144b57bb9c5c3cf7379f6235822597c573d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 14 Mar 2026 19:59:40 +0800 Subject: [PATCH 038/101] Fix overlap edit on tuple to record assist expr uses self Example --- ```rust struct T$0(u8); fn test(t: T) { T(t.0); } ``` **Before this PR** Panic **After this PR** ```rust struct T { field1: u8 } fn test(t: T) { T { field1: t.field1 }; } ``` --- .../convert_named_struct_to_tuple_struct.rs | 25 +- .../convert_tuple_struct_to_named_struct.rs | 264 ++++++++++++------ .../crates/ide-assists/src/utils.rs | 14 + 3 files changed, 198 insertions(+), 105 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index 8dfd4598dfa13..42fceb85336b9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -1,9 +1,7 @@ -use std::ops::RangeInclusive; - use either::Either; use ide_db::{defs::Definition, search::FileReference}; use syntax::{ - NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, T, TextRange, + NodeOrToken, SyntaxKind, SyntaxNode, T, algo::next_non_trivia_token, ast::{ self, AstNode, HasAttrs, HasGenericParams, HasVisibility, syntax_factory::SyntaxFactory, @@ -12,7 +10,9 @@ use syntax::{ syntax_editor::{Element, Position, SyntaxEditor}, }; -use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder}; +use crate::{ + AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder, utils::cover_edit_range, +}; // Assist: convert_named_struct_to_tuple_struct // @@ -242,7 +242,7 @@ where { let make = SyntaxFactory::without_mappings(); let orig = ctx.sema.original_range_opt(field_list.syntax())?; - let list_range = cover_range(source, orig.range); + let list_range = cover_edit_range(source, orig.range); let l_curly = match list_range.start() { NodeOrToken::Node(node) => node.first_token()?, @@ -265,7 +265,7 @@ where for name_ref in fields(&field_list) { let Some(orig) = ctx.sema.original_range_opt(name_ref.syntax()) else { continue }; - let name_range = cover_range(source, orig.range); + let name_range = cover_edit_range(source, orig.range); if let Some(colon) = next_non_trivia_token(name_range.end().clone()) && colon.kind() == T![:] @@ -306,7 +306,7 @@ fn edit_field_references( // Only edit the field reference if it's part of a `.field` access if name_ref.syntax().parent().and_then(ast::FieldExpr::cast).is_some() { edit.replace_all( - cover_range(&source, r.range), + cover_edit_range(&source, r.range), vec![make.name_ref(&index.to_string()).syntax().clone().into()], ); } @@ -318,17 +318,6 @@ fn edit_field_references( } } -fn cover_range(source: &ast::SourceFile, range: TextRange) -> RangeInclusive { - let node = match source.syntax().covering_element(range) { - NodeOrToken::Node(node) => node, - NodeOrToken::Token(t) => t.parent().unwrap(), - }; - let mut iter = node.children_with_tokens().filter(|it| range.contains_range(it.text_range())); - let first = iter.next().unwrap_or(node.into()); - let last = iter.last().unwrap_or_else(|| first.clone()); - first..=last -} - fn delete_whitespace(edit: &mut SyntaxEditor, whitespace: Option) { let Some(whitespace) = whitespace else { return }; let NodeOrToken::Token(token) = whitespace.syntax_element() else { return }; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index ba22224b6be26..f1eae83866371 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -1,17 +1,21 @@ use either::Either; -use hir::FileRangeWrapper; -use ide_db::defs::{Definition, NameRefClass}; -use std::ops::RangeInclusive; +use ide_db::{ + defs::{Definition, NameRefClass}, + search::FileReference, +}; use syntax::{ - SyntaxElement, SyntaxKind, SyntaxNode, T, TextSize, + SyntaxKind, T, ast::{ - self, AstNode, HasAttrs, HasGenericParams, HasVisibility, syntax_factory::SyntaxFactory, + self, AstNode, HasArgList, HasAttrs, HasGenericParams, HasVisibility, + syntax_factory::SyntaxFactory, }, match_ast, syntax_editor::{Element, Position, SyntaxEditor}, }; -use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder}; +use crate::{ + AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder, utils::cover_edit_range, +}; // Assist: convert_tuple_struct_to_named_struct // @@ -147,93 +151,121 @@ fn edit_struct_references( }; let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); - let edit_node = |node: SyntaxNode| -> Option { - let make = SyntaxFactory::without_mappings(); - match_ast! { - match node { - ast::TupleStructPat(tuple_struct_pat) => { - Some(make.record_pat_with_fields( - tuple_struct_pat.path()?, - generate_record_pat_list(&tuple_struct_pat, names), - ).syntax().clone()) - }, - // for tuple struct creations like Foo(42) - ast::CallExpr(call_expr) => { - let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?; - - // this also includes method calls like Foo::new(42), we should skip them - if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) { - match NameRefClass::classify(&ctx.sema, &name_ref) { - Some(NameRefClass::Definition(Definition::SelfType(_), _)) => {}, - Some(NameRefClass::Definition(def, _)) if def == strukt_def => {}, - _ => return None, - }; - } + for (file_id, refs) in usages { + let source = ctx.sema.parse(file_id); + let mut editor = edit.make_editor(source.syntax()); - let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?; - Some( - make.record_expr( - path, - ast::make::record_expr_field_list(arg_list.args().zip(names).map( - |(expr, name)| { - ast::make::record_expr_field( - ast::make::name_ref(&name.to_string()), - Some(expr), - ) - }, - )), - ).syntax().clone() - ) - }, - _ => None, - } + for r in refs { + process_struct_name_reference(ctx, r, &mut editor, &source, &strukt_def, names); } - }; - for (file_id, refs) in usages { - let source = ctx.sema.parse(file_id); - let source = source.syntax(); - - let mut editor = edit.make_editor(source); - for r in refs.iter().rev() { - if let Some((old_node, new_node)) = r - .name - .syntax() - .ancestors() - .find_map(|node| Some((node.clone(), edit_node(node.clone())?))) - { - if let Some(old_node) = ctx.sema.original_syntax_node_rooted(&old_node) { - editor.replace(old_node, new_node); - } else { - let FileRangeWrapper { file_id: _, range } = ctx.sema.original_range(&old_node); - let parent = source.covering_element(range); - match parent { - SyntaxElement::Token(token) => { - editor.replace(token, new_node.syntax_element()); - } - SyntaxElement::Node(parent_node) => { - // replace the part of macro - // ``` - // foo!(a, Test::A(0)); - // ^^^^^^^^^^^^^^^ // parent_node - // ^^^^^^^^^^ // replace_range - // ``` - let start = parent_node - .children_with_tokens() - .find(|t| t.text_range().contains(range.start())); - let end = parent_node - .children_with_tokens() - .find(|t| t.text_range().contains(range.end() - TextSize::new(1))); - if let (Some(start), Some(end)) = (start, end) { - let replace_range = RangeInclusive::new(start, end); - editor.replace_all(replace_range, vec![new_node.into()]); - } - } + edit.add_file_edits(file_id.file_id(ctx.db()), editor); + } +} + +fn process_struct_name_reference( + ctx: &AssistContext<'_>, + r: FileReference, + editor: &mut SyntaxEditor, + source: &ast::SourceFile, + strukt_def: &Definition, + names: &[ast::Name], +) -> Option<()> { + let make = SyntaxFactory::without_mappings(); + let name_ref = r.name.as_name_ref()?; + let path_segment = name_ref.syntax().parent().and_then(ast::PathSegment::cast)?; + let full_path = path_segment.syntax().parent().and_then(ast::Path::cast)?.top_path(); + + if full_path.segment()?.name_ref()? != *name_ref { + // `name_ref` isn't the last segment of the path, so `full_path` doesn't point to the + // struct we want to edit. + return None; + } + + let parent = full_path.syntax().parent()?; + match_ast! { + match parent { + ast::TupleStructPat(tuple_struct_pat) => { + let range = ctx.sema.original_range_opt(tuple_struct_pat.syntax())?.range; + let new = make.record_pat_with_fields( + full_path, + generate_record_pat_list(&tuple_struct_pat, names), + ); + editor.replace_all(cover_edit_range(source, range), vec![new.syntax().clone().into()]); + }, + ast::PathExpr(path_expr) => { + let call_expr = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; + + // this also includes method calls like Foo::new(42), we should skip them + match NameRefClass::classify(&ctx.sema, name_ref) { + Some(NameRefClass::Definition(Definition::SelfType(_), _)) => {}, + Some(NameRefClass::Definition(def, _)) if def == *strukt_def => {}, + _ => return None, + } + + let arg_list = call_expr.arg_list()?; + let mut first_insert = vec![]; + for (expr, name) in arg_list.args().zip(names) { + let range = ctx.sema.original_range_opt(expr.syntax())?.range; + let place = cover_edit_range(source, range); + let elements = vec![ + make.name_ref(&name.text()).syntax().clone().into(), + make.token(T![:]).into(), + make.whitespace(" ").into(), + ]; + if first_insert.is_empty() { + // XXX: SyntaxEditor cannot insert after deleted element + first_insert = elements; + } else { + editor.insert_all(Position::before(place.start()), elements); } } - } + process_delimiter(ctx, source, editor, &arg_list, first_insert); + }, + _ => {} } - edit.add_file_edits(file_id.file_id(ctx.db()), editor); + } + Some(()) +} + +fn process_delimiter( + ctx: &AssistContext<'_>, + source: &ast::SourceFile, + editor: &mut SyntaxEditor, + list: &impl AstNode, + first_insert: Vec, +) { + let Some(range) = ctx.sema.original_range_opt(list.syntax()) else { return }; + let place = cover_edit_range(source, range.range); + + let l_paren = match place.start() { + syntax::NodeOrToken::Node(node) => node.first_token(), + syntax::NodeOrToken::Token(t) => Some(t.clone()), + }; + let r_paren = match place.end() { + syntax::NodeOrToken::Node(node) => node.last_token(), + syntax::NodeOrToken::Token(t) => Some(t.clone()), + }; + + let make = SyntaxFactory::without_mappings(); + if let Some(l_paren) = l_paren + && l_paren.kind() == T!['('] + { + let mut open_delim = vec![ + make.whitespace(" ").into(), + make.token(T!['{']).into(), + make.whitespace(" ").into(), + ]; + open_delim.extend(first_insert); + editor.replace_with_many(l_paren, open_delim); + } + if let Some(r_paren) = r_paren + && r_paren.kind() == T![')'] + { + editor.replace_with_many( + r_paren, + vec![make.whitespace(" ").into(), make.token(T!['}']).into()], + ); } } @@ -741,6 +773,64 @@ where "#, ); } + + #[test] + fn convert_expr_uses_self() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +macro_rules! id { + ($($t:tt)*) => { $($t)* } +} +struct T$0(u8); +fn test(t: T) { + T(t.0); + id!(T(t.0)); +}"#, + r#" +macro_rules! id { + ($($t:tt)*) => { $($t)* } +} +struct T { field1: u8 } +fn test(t: T) { + T { field1: t.field1 }; + id!(T { field1: t.field1 }); +}"#, + ); + } + + #[test] + #[ignore = "FIXME overlap edits in nested uses self"] + fn convert_pat_uses_self() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +macro_rules! id { + ($($t:tt)*) => { $($t)* } +} +enum T { + $0Value(&'static T), + Nil, +} +fn test(t: T) { + if let T::Value(T::Value(t)) = t {} + if let id!(T::Value(T::Value(t))) = t {} +}"#, + r#" +macro_rules! id { + ($($t:tt)*) => { $($t)* } +} +enum T { + Value { field1: &'static T }, + Nil, +} +fn test(t: T) { + if let T::Value { field1: T::Value { field1: t } } = t {} + if let id!(T::Value { field1: T::Value { field1: t } }) = t {} +}"#, + ); + } + #[test] fn not_applicable_other_than_tuple_variant() { 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 b4055e77ccf8a..a3178aba1269c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -1254,6 +1254,20 @@ pub(crate) fn cover_let_chain(mut expr: ast::Expr, range: TextRange) -> Option std::ops::RangeInclusive { + let node = match source.syntax().covering_element(range) { + NodeOrToken::Node(node) => node, + NodeOrToken::Token(t) => t.parent().unwrap(), + }; + let mut iter = node.children_with_tokens().filter(|it| range.contains_range(it.text_range())); + let first = iter.next().unwrap_or(node.into()); + let last = iter.last().unwrap_or_else(|| first.clone()); + first..=last +} + pub(crate) fn is_selected( it: &impl AstNode, selection: syntax::TextRange, From ed15a20647524f82c0f75a4666af002dfcc146d2 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 15 Mar 2026 19:45:38 +0800 Subject: [PATCH 039/101] fix: fill match arms on last comma and empty expr Example --- ```rust fn foo() { match Some(2) { Some(_) => ()$0, } } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn foo() { match Some(2) { Some(_) => (), None => ${1:todo!()},$0 } } ``` --- .../src/handlers/add_missing_match_arms.rs | 132 ++++++++++++++++-- 1 file changed, 121 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs index c0ce057d7798d..8124fecff6296 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -245,14 +245,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) .arms() .filter(|arm| { if matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))) { - let is_empty_expr = arm.expr().is_none_or(|e| match e { - ast::Expr::BlockExpr(b) => { - b.statements().next().is_none() && b.tail_expr().is_none() - } - ast::Expr::TupleExpr(t) => t.fields().next().is_none(), - _ => false, - }); - if is_empty_expr { + if arm.expr().is_none_or(is_empty_expr) { false } else { cov_mark::hit!(add_missing_match_arms_empty_expr); @@ -347,12 +340,24 @@ fn cursor_at_trivial_match_arm_list( // $0 // } if let Some(last_arm) = match_arm_list.arms().last() { - let last_arm_range = ctx.sema.original_range_opt(last_arm.syntax())?.range; + let last_node = match last_arm.expr() { + Some(expr) => expr.syntax().clone(), + None => last_arm.syntax().clone(), + }; + let last_node_range = ctx.sema.original_range_opt(&last_node)?.range; let match_expr_range = ctx.sema.original_range_opt(match_expr.syntax())?.range; - if last_arm_range.end() <= ctx.offset() && ctx.offset() < match_expr_range.end() { + if last_node_range.end() <= ctx.offset() && ctx.offset() < match_expr_range.end() { cov_mark::hit!(add_missing_match_arms_end_of_last_arm); return Some(()); } + + if ast::Expr::cast(last_node.clone()).is_some_and(is_empty_expr) + && last_node_range.contains(ctx.offset()) + && !last_node.text().contains_char('\n') + { + cov_mark::hit!(add_missing_match_arms_end_of_last_empty_arm); + return Some(()); + } } // match { _$0 => {...} } @@ -371,6 +376,14 @@ fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool { !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var)) } +fn is_empty_expr(e: ast::Expr) -> bool { + match e { + ast::Expr::BlockExpr(b) => b.statements().next().is_none() && b.tail_expr().is_none(), + ast::Expr::TupleExpr(t) => t.fields().next().is_none(), + _ => false, + } +} + // Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check? fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { match (pat, var) { @@ -1066,7 +1079,7 @@ fn main() { #[test] fn add_missing_match_arms_end_of_last_arm() { - cov_mark::check!(add_missing_match_arms_end_of_last_arm); + cov_mark::check_count!(add_missing_match_arms_end_of_last_arm, 2); check_assist( add_missing_match_arms, r#" @@ -1095,6 +1108,103 @@ fn main() { (A::Two, B::Two) => ${3:todo!()},$0 } } +"#, + ); + + check_assist( + add_missing_match_arms, + r#" +enum A { One, Two } +enum B { One, Two } + +fn main() { + let a = A::One; + let b = B::One; + match (a, b) { + (A::Two, B::One) => 2$0, + } +} +"#, + r#" +enum A { One, Two } +enum B { One, Two } + +fn main() { + let a = A::One; + let b = B::One; + match (a, b) { + (A::Two, B::One) => 2, + (A::One, B::One) => ${1:todo!()}, + (A::One, B::Two) => ${2:todo!()}, + (A::Two, B::Two) => ${3:todo!()},$0 + } +} +"#, + ); + } + + #[test] + fn add_missing_match_arms_end_of_last_empty_arm() { + cov_mark::check_count!(add_missing_match_arms_end_of_last_empty_arm, 2); + check_assist( + add_missing_match_arms, + r#" +enum A { One, Two } +enum B { One, Two } + +fn main() { + let a = A::One; + let b = B::One; + match (a, b) { + (A::Two, B::One) => {$0} + } +} +"#, + r#" +enum A { One, Two } +enum B { One, Two } + +fn main() { + let a = A::One; + let b = B::One; + match (a, b) { + (A::Two, B::One) => {} + (A::One, B::One) => ${1:todo!()}, + (A::One, B::Two) => ${2:todo!()}, + (A::Two, B::Two) => ${3:todo!()},$0 + } +} +"#, + ); + + check_assist( + add_missing_match_arms, + r#" +enum A { One, Two } +enum B { One, Two } + +fn main() { + let a = A::One; + let b = B::One; + match (a, b) { + (A::Two, B::One) => ($0) + } +} +"#, + r#" +enum A { One, Two } +enum B { One, Two } + +fn main() { + let a = A::One; + let b = B::One; + match (a, b) { + (A::Two, B::One) => (), + (A::One, B::One) => ${1:todo!()}, + (A::One, B::Two) => ${2:todo!()}, + (A::Two, B::Two) => ${3:todo!()},$0 + } +} "#, ); } From 1bc2c442870a669669880b7487d46f4ce90e71b0 Mon Sep 17 00:00:00 2001 From: Avinash Thakur Date: Sun, 8 Mar 2026 20:51:00 +0530 Subject: [PATCH 040/101] fix: remove empty angle brackets in inline type alias code assist. fixes https://github.com/rust-lang/rust-analyzer/issues/21769 --- .../src/handlers/inline_type_alias.rs | 110 +++++++++++++++++- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs index c7a48f3261a9f..f3ebe61078192 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs @@ -12,7 +12,7 @@ use itertools::Itertools; use syntax::ast::syntax_factory::SyntaxFactory; use syntax::syntax_editor::SyntaxEditor; use syntax::{ - AstNode, NodeOrToken, SyntaxNode, + AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T, ast::{self, HasGenericParams, HasName}, }; @@ -322,12 +322,42 @@ fn create_replacement( if let Some(old_lifetime) = ast::Lifetime::cast(syntax.clone()) { if let Some(new_lifetime) = lifetime_map.0.get(&old_lifetime.to_string()) { if new_lifetime.text() == "'_" { + // Check if this lifetime is inside a LifetimeArg (in angle brackets) + if let Some(lifetime_arg) = + old_lifetime.syntax().parent().and_then(ast::LifetimeArg::cast) + { + // Remove LifetimeArg and associated comma/whitespace + let lifetime_arg_syntax = lifetime_arg.syntax(); + removals.push(NodeOrToken::Node(lifetime_arg_syntax.clone())); + + // Remove comma and whitespace (look forward then backward) + let comma_and_ws: Vec<_> = lifetime_arg_syntax + .siblings_with_tokens(syntax::Direction::Next) + .skip(1) + .take_while(|it| it.as_token().is_some()) + .take_while_inclusive(|it| it.kind() == T![,]) + .collect(); + + if comma_and_ws.iter().any(|it| it.kind() == T![,]) { + removals.extend(comma_and_ws); + } else { + // No comma after, try before + let comma_and_ws: Vec<_> = lifetime_arg_syntax + .siblings_with_tokens(syntax::Direction::Prev) + .skip(1) + .take_while(|it| it.as_token().is_some()) + .take_while_inclusive(|it| it.kind() == T![,]) + .collect(); + removals.extend(comma_and_ws); + } + continue; + } removals.push(NodeOrToken::Node(syntax.clone())); - - if let Some(ws) = syntax.next_sibling_or_token() { - removals.push(ws.clone()); + if let Some(ws) = syntax.next_sibling_or_token() + && ws.kind() == SyntaxKind::WHITESPACE + { + removals.push(ws); } - continue; } @@ -349,6 +379,34 @@ fn create_replacement( } } + // Deduplicate removals to avoid intersecting changes + removals.sort_by_key(|n| n.text_range().start()); + removals.dedup(); + + // Remove GenericArgList entirely if all its args are being removed (avoids empty angle brackets) + let generic_arg_lists_to_check: Vec<_> = + updated_concrete_type.descendants().filter_map(ast::GenericArgList::cast).collect(); + + for generic_arg_list in generic_arg_lists_to_check { + let will_be_empty = generic_arg_list.generic_args().all(|arg| match arg { + ast::GenericArg::LifetimeArg(lt_arg) => removals.iter().any(|removal| { + if let NodeOrToken::Node(node) = removal { node == lt_arg.syntax() } else { false } + }), + _ => false, + }); + + if will_be_empty && generic_arg_list.generic_args().next().is_some() { + removals.retain(|removal| { + if let NodeOrToken::Node(node) = removal { + !node.ancestors().any(|anc| anc == *generic_arg_list.syntax()) + } else { + true + } + }); + removals.push(NodeOrToken::Node(generic_arg_list.syntax().clone())); + } + } + for (old, new) in replacements { editor.replace(old, new); } @@ -946,6 +1004,48 @@ trait Tr { ); } + #[test] + fn inline_types_with_lifetime() { + check_assist( + inline_type_alias_uses, + r#" +struct A<'a, 'b>(pub &'a mut &'b mut ()); + +type $0T<'a, 'b> = A<'a, 'b>; + +fn foo(_: T) {} +"#, + r#" +struct A<'a, 'b>(pub &'a mut &'b mut ()); + + + +fn foo(_: A) {} +"#, + ); + } + + #[test] + fn mixed_lifetime_and_type_args() { + check_assist( + inline_type_alias, + r#" +type Foo<'a, T> = Bar<'a, T>; +struct Bar<'a, T>(&'a T); +fn main() { + let a: $0Foo; +} +"#, + r#" +type Foo<'a, T> = Bar<'a, T>; +struct Bar<'a, T>(&'a T); +fn main() { + let a: Bar; +} +"#, + ); + } + mod inline_type_alias_uses { use crate::{handlers::inline_type_alias::inline_type_alias_uses, tests::check_assist}; From 8dba85c4873f053914b4d5ea8bfafaf6e4e24561 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Mar 2026 08:22:05 +0530 Subject: [PATCH 041/101] update wrap_expr in utils ref_field_expr and update corresponding handler --- .../src/handlers/destructure_struct_binding.rs | 11 ++++------- .../crates/ide-assists/src/utils/ref_field_expr.rs | 8 ++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_struct_binding.rs index 4c4cee1d7811f..0f5ef0548cf5b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_struct_binding.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_struct_binding.rs @@ -381,23 +381,20 @@ fn build_usage_edit( Some(field_expr) => Some({ let field_name: SmolStr = field_expr.name_ref()?.to_string().into(); let new_field_name = field_names.get(&field_name)?; - let new_expr = ast::make::expr_path(ast::make::ext::ident_path(new_field_name)); + let new_expr = make.expr_path(make.ident_path(new_field_name)); // If struct binding is a reference, we might need to deref field usages if data.is_ref { let (replace_expr, ref_data) = determine_ref_and_parens(ctx, &field_expr); - ( - replace_expr.syntax().clone_for_update(), - ref_data.wrap_expr(new_expr).syntax().clone_for_update(), - ) + (replace_expr.syntax().clone(), ref_data.wrap_expr(new_expr, make).syntax().clone()) } else { - (field_expr.syntax().clone(), new_expr.syntax().clone_for_update()) + (field_expr.syntax().clone(), new_expr.syntax().clone()) } }), None => Some(( usage.name.syntax().as_node().unwrap().clone(), make.expr_macro( - ast::make::ext::ident_path("todo"), + make.ident_path("todo"), make.token_tree(syntax::SyntaxKind::L_PAREN, []), ) .syntax() diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/ref_field_expr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/ref_field_expr.rs index df8ad4111234e..fc9bf210e4872 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/ref_field_expr.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/ref_field_expr.rs @@ -5,7 +5,7 @@ //! based on the parent of the existing expression. use syntax::{ AstNode, T, - ast::{self, FieldExpr, MethodCallExpr, make, syntax_factory::SyntaxFactory}, + ast::{self, FieldExpr, MethodCallExpr, syntax_factory::SyntaxFactory}, }; use crate::AssistContext; @@ -119,13 +119,13 @@ pub(crate) struct RefData { impl RefData { /// Derefs `expr` and wraps it in parens if necessary - pub(crate) fn wrap_expr(&self, mut expr: ast::Expr) -> ast::Expr { + pub(crate) fn wrap_expr(&self, mut expr: ast::Expr, make: &SyntaxFactory) -> ast::Expr { if self.needs_deref { - expr = make::expr_prefix(T![*], expr).into(); + expr = make.expr_prefix(T![*], expr).into(); } if self.needs_parentheses { - expr = make::expr_paren(expr).into(); + expr = make.expr_paren(expr).into(); } expr From 7f3bc0b0a825d6e06f0955436e522c1ecad75f3b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Mar 2026 08:22:50 +0530 Subject: [PATCH 042/101] update wrap_block to accept SyntaxFactory and correspondingly update the handler which depends on it --- .../crates/ide-assists/src/handlers/move_guard.rs | 11 ++++++----- .../rust-analyzer/crates/ide-assists/src/utils.rs | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs index b4c347ff36a7c..80587372e5165 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs @@ -3,7 +3,7 @@ use syntax::{ SyntaxKind::WHITESPACE, TextRange, ast::{ - AstNode, BlockExpr, ElseBranch, Expr, IfExpr, MatchArm, Pat, edit::AstNodeEdit, make, + AstNode, BlockExpr, ElseBranch, Expr, IfExpr, MatchArm, Pat, edit::AstNodeEdit, prec::ExprPrecedence, syntax_factory::SyntaxFactory, }, syntax_editor::Element, @@ -53,14 +53,15 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext<'_>) let space_after_arrow = match_arm.fat_arrow_token()?.next_sibling_or_token(); let arm_expr = match_arm.expr()?; + let make = SyntaxFactory::without_mappings(); let if_branch = chain([&match_arm], &rest_arms) .rfold(None, |else_branch, arm| { if let Some(guard) = arm.guard() { - let then_branch = crate::utils::wrap_block(&arm.expr()?); + let then_branch = crate::utils::wrap_block(&arm.expr()?, &make); let guard_condition = guard.condition()?.reset_indent(); - Some(make::expr_if(guard_condition, then_branch, else_branch).into()) + Some(make.expr_if(guard_condition, then_branch, else_branch).into()) } else { - arm.expr().map(|it| crate::utils::wrap_block(&it).into()) + arm.expr().map(|it| crate::utils::wrap_block(&it, &make).into()) } })? .indent(arm_expr.indent_level()); @@ -84,7 +85,7 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext<'_>) if let Some(element) = space_after_arrow && element.kind() == WHITESPACE { - edit.replace(element, make::tokens::single_space()); + edit.replace(element, make.whitespace(" ")); } edit.delete(guard.syntax()); 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 b4055e77ccf8a..e46cc5cce09c7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -86,14 +86,14 @@ pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option ast::BlockExpr { +pub(crate) fn wrap_block(expr: &ast::Expr, make: &SyntaxFactory) -> ast::BlockExpr { if let ast::Expr::BlockExpr(block) = expr && let Some(first) = block.syntax().first_token() && first.kind() == T!['{'] { block.reset_indent() } else { - make::block_expr(None, Some(expr.reset_indent().indent(1.into()))) + make.block_expr(None, Some(expr.reset_indent().indent(1.into()))) } } From d6b3207f279ef200e81552477348856da79569d2 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Mar 2026 09:06:44 +0530 Subject: [PATCH 043/101] convert_param_list_to_arg_list accept syntaxFactory and update the corresponding handlers --- .../src/handlers/generate_delegate_methods.rs | 69 ++++++++++--------- .../src/handlers/generate_delegate_trait.rs | 6 +- .../crates/ide-assists/src/utils.rs | 7 +- .../src/ast/syntax_factory/constructors.rs | 11 +++ 4 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs index c1eb1a74ecdeb..d57db7b63d2a8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -4,7 +4,7 @@ use syntax::{ ast::{ self, AstNode, HasGenericParams, HasName, HasVisibility as _, edit::{AstNodeEdit, IndentLevel}, - make, + syntax_factory::SyntaxFactory, }, syntax_editor::Position, }; @@ -100,7 +100,6 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let Some(impl_def) = find_struct_impl(ctx, &adt, std::slice::from_ref(&name)) else { continue; }; - let field = make::ext::field_from_idents(["self", &field_name])?; acc.add_group( &GroupLabel("Generate delegate methods…".to_owned()), @@ -108,10 +107,14 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' format!("Generate delegate for `{field_name}.{name}()`",), target, |edit| { + let make = SyntaxFactory::without_mappings(); + let field = make + .field_from_idents(["self", &field_name]) + .expect("always be a valid expression"); // Create the function let method_source = match ctx.sema.source(method) { Some(source) => { - let v = source.value.clone_for_update(); + let v = source.value; let source_scope = ctx.sema.scope(v.syntax()); let target_scope = ctx.sema.scope(strukt.syntax()); if let (Some(s), Some(t)) = (source_scope, target_scope) { @@ -132,42 +135,42 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let is_unsafe = method_source.unsafe_token().is_some(); let is_gen = method_source.gen_token().is_some(); - let fn_name = make::name(&name); + let fn_name = make.name(&name); let type_params = method_source.generic_param_list(); let where_clause = method_source.where_clause(); let params = - method_source.param_list().unwrap_or_else(|| make::param_list(None, [])); + method_source.param_list().unwrap_or_else(|| make.param_list(None, [])); // compute the `body` let arg_list = method_source .param_list() - .map(convert_param_list_to_arg_list) - .unwrap_or_else(|| make::arg_list([])); + .map(|v| convert_param_list_to_arg_list(v, &make)) + .unwrap_or_else(|| make.arg_list([])); - let tail_expr = - make::expr_method_call(field, make::name_ref(&name), arg_list).into(); + let tail_expr = make.expr_method_call(field, make.name_ref(&name), arg_list).into(); let tail_expr_finished = - if is_async { make::expr_await(tail_expr) } else { tail_expr }; - let body = make::block_expr([], Some(tail_expr_finished)); + if is_async { make.expr_await(tail_expr) } else { tail_expr }; + let body = make.block_expr([], Some(tail_expr_finished)); let ret_type = method_source.ret_type(); - let f = make::fn_( - None, - vis, - fn_name, - type_params, - where_clause, - params, - body, - ret_type, - is_async, - is_const, - is_unsafe, - is_gen, - ) - .indent(IndentLevel(1)); + let f = make + .fn_( + None, + vis, + fn_name, + type_params, + where_clause, + params, + body, + ret_type, + is_async, + is_const, + is_unsafe, + is_gen, + ) + .indent(IndentLevel(1)); let item = ast::AssocItem::Fn(f.clone()); let mut editor = edit.make_editor(strukt.syntax()); @@ -179,7 +182,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' Some(item) } None => { - let assoc_item_list = make::assoc_item_list(Some(vec![item])); + let assoc_item_list = make.assoc_item_list(vec![item]); editor.insert( Position::last_child_of(impl_def.syntax()), assoc_item_list.syntax(), @@ -192,17 +195,16 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let ty_params = strukt.generic_param_list(); let ty_args = ty_params.as_ref().map(|it| it.to_generic_args()); let where_clause = strukt.where_clause(); - let assoc_item_list = make::assoc_item_list(Some(vec![item])); + let assoc_item_list = make.assoc_item_list(vec![item]); - let impl_def = make::impl_( + let impl_def = make.impl_( None, ty_params, ty_args, - make::ty_path(make::ext::ident_path(name)), + syntax::ast::Type::PathType(make.ty_path(make.ident_path(name))), where_clause, Some(assoc_item_list), - ) - .clone_for_update(); + ); // Fixup impl_def indentation let indent = strukt.indent_level(); @@ -213,7 +215,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' editor.insert_all( Position::after(strukt.syntax()), vec![ - make::tokens::whitespace(&format!("\n\n{indent}")).into(), + make.whitespace(&format!("\n\n{indent}")).into(), impl_def.syntax().clone().into(), ], ); @@ -227,6 +229,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let tabstop = edit.make_tabstop_before(cap); editor.add_annotation(fn_.syntax(), tabstop); } + editor.add_mappings(make.finish_with_mappings()); edit.add_file_edits(ctx.vfs_file_id(), editor); }, )?; 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 921f04f2a56bc..f703e4dc4ab25 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 @@ -782,7 +782,7 @@ fn func_assoc_item( }; // Build argument list with self expression prepended - let other_args = convert_param_list_to_arg_list(l); + let other_args = convert_param_list_to_arg_list(l, &make); let all_args: Vec = std::iter::once(tail_expr_self).chain(other_args.args()).collect(); let args = make.arg_list(all_args); @@ -790,13 +790,13 @@ fn func_assoc_item( 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)) + .expr_call(make.expr_path(qualified_path), convert_param_list_to_arg_list(l, &make)) .into(), }, None => make .expr_call( make.expr_path(qualified_path), - convert_param_list_to_arg_list(make.param_list(None, Vec::new())), + convert_param_list_to_arg_list(make.param_list(None, Vec::new()), &make), ) .into(), }; 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 e46cc5cce09c7..a3ee0986d574a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -1095,14 +1095,17 @@ pub(crate) fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRa /// Convert a list of function params to a list of arguments that can be passed /// into a function call. -pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgList { +pub(crate) fn convert_param_list_to_arg_list( + list: ast::ParamList, + make: &SyntaxFactory, +) -> ast::ArgList { let mut args = vec![]; for param in list.params() { if let Some(ast::Pat::IdentPat(pat)) = param.pat() && let Some(name) = pat.name() { let name = name.to_string(); - let expr = make::expr_path(make::ext::ident_path(&name)); + let expr = make.expr_path(make.ident_path(&name)); args.push(expr); } } 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 50fe56538080c..3ca163d07f1f1 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 @@ -1765,6 +1765,17 @@ impl SyntaxFactory { } ast } + + pub fn field_from_idents<'a>( + &self, + parts: impl std::iter::IntoIterator, + ) -> Option { + make::ext::field_from_idents(parts) + } + + pub fn expr_await(&self, expr: ast::Expr) -> ast::Expr { + make::expr_await(expr) + } } // `ext` constructors From f2bc6245811c9724dab5a2a644ad5de2247eafa8 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Mar 2026 09:08:33 +0530 Subject: [PATCH 044/101] update convert_param_list_to_arg_list to get ArgList via make --- src/tools/rust-analyzer/crates/ide-assists/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a3ee0986d574a..db9a7a8dfc29a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -1109,7 +1109,7 @@ pub(crate) fn convert_param_list_to_arg_list( args.push(expr); } } - make::arg_list(args) + make.arg_list(args) } /// Calculate the number of hashes required for a raw string containing `s` From cc028380180e6fac63a5bf9befe8daa2b1f76c62 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Mar 2026 09:16:09 +0530 Subject: [PATCH 045/101] add syntaxFactory to tt_from_syntax and update corresponding handlers --- .../extract_expressions_from_format_string.rs | 14 +++++++------- .../rust-analyzer/crates/ide-assists/src/utils.rs | 7 +++++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs index 61af2de6ec683..35e8baa18aca7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs @@ -8,7 +8,7 @@ use syntax::{ AstNode, AstToken, NodeOrToken, SyntaxKind::WHITESPACE, SyntaxToken, T, - ast::{self, TokenTree, make, syntax_factory::SyntaxFactory}, + ast::{self, TokenTree, syntax_factory::SyntaxFactory}, }; // Assist: extract_expressions_from_format_string @@ -57,6 +57,7 @@ pub(crate) fn extract_expressions_from_format_string( "Extract format expressions", tt.syntax().text_range(), |edit| { + let make = SyntaxFactory::without_mappings(); // Extract existing arguments in macro let mut raw_tokens = tt.token_trees_and_tokens().skip(1).collect_vec(); let format_string_index = format_str_index(&raw_tokens, &fmt_string); @@ -94,14 +95,14 @@ pub(crate) fn extract_expressions_from_format_string( let mut new_tt_bits = raw_tokens; let mut placeholder_indexes = vec![]; - new_tt_bits.push(NodeOrToken::Token(make::tokens::literal(&new_fmt))); + new_tt_bits.push(NodeOrToken::Token(make.expr_literal(&new_fmt).token().clone())); for arg in extracted_args { if matches!(arg, Arg::Expr(_) | Arg::Placeholder) { // insert ", " before each arg new_tt_bits.extend_from_slice(&[ - NodeOrToken::Token(make::token(T![,])), - NodeOrToken::Token(make::tokens::single_space()), + NodeOrToken::Token(make.token(T![,])), + NodeOrToken::Token(make.whitespace(" ")), ]); } @@ -109,7 +110,7 @@ pub(crate) fn extract_expressions_from_format_string( Arg::Expr(s) => { // insert arg let expr = ast::Expr::parse(&s, ctx.edition()).syntax_node(); - let mut expr_tt = utils::tt_from_syntax(expr); + let mut expr_tt = utils::tt_from_syntax(expr, &make); new_tt_bits.append(&mut expr_tt); } Arg::Placeholder => { @@ -120,7 +121,7 @@ pub(crate) fn extract_expressions_from_format_string( } None => { placeholder_indexes.push(new_tt_bits.len()); - new_tt_bits.push(NodeOrToken::Token(make::token(T![_]))); + new_tt_bits.push(NodeOrToken::Token(make.token(T![_]))); } } } @@ -129,7 +130,6 @@ pub(crate) fn extract_expressions_from_format_string( } // Insert new args - let make = SyntaxFactory::with_mappings(); let new_tt = make.token_tree(tt_delimiter, new_tt_bits); let mut editor = edit.make_editor(tt.syntax()); editor.replace(tt.syntax(), new_tt.syntax()); 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 db9a7a8dfc29a..85b3e6d317db4 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -1194,7 +1194,10 @@ pub(crate) fn replace_record_field_expr( /// Creates a token tree list from a syntax node, creating the needed delimited sub token trees. /// Assumes that the input syntax node is a valid syntax tree. -pub(crate) fn tt_from_syntax(node: SyntaxNode) -> Vec> { +pub(crate) fn tt_from_syntax( + node: SyntaxNode, + make: &SyntaxFactory, +) -> Vec> { let mut tt_stack = vec![(None, vec![])]; for element in node.descendants_with_tokens() { @@ -1222,7 +1225,7 @@ pub(crate) fn tt_from_syntax(node: SyntaxNode) -> Vec { From 4e50d13d57452b81c10653e529b238c4d9091937 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Mar 2026 10:07:35 +0530 Subject: [PATCH 046/101] remove invert_boolean_expression_legacy --- .../src/handlers/convert_to_guarded_return.rs | 49 +++++++++------- .../src/handlers/convert_while_to_loop.rs | 30 +++++----- .../ide-assists/src/handlers/invert_if.rs | 7 ++- .../crates/ide-assists/src/utils.rs | 57 ------------------- .../src/ast/syntax_factory/constructors.rs | 10 +++- 5 files changed, 56 insertions(+), 97 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index dc51bf4b5b8c1..374c7cd869922 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -10,14 +10,14 @@ use syntax::{ ast::{ self, edit::{AstNodeEdit, IndentLevel}, - make, + syntax_factory::SyntaxFactory, }, }; use crate::{ AssistId, assist_context::{AssistContext, Assists}, - utils::{invert_boolean_expression_legacy, is_never_block}, + utils::{invert_boolean_expression, is_never_block}, }; // Assist: convert_to_guarded_return @@ -69,6 +69,7 @@ fn if_expr_to_guarded_return( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { + let make = SyntaxFactory::without_mappings(); let else_block = match if_expr.else_branch() { Some(ast::ElseBranch::Block(block_expr)) if is_never_block(&ctx.sema, &block_expr) => { Some(block_expr) @@ -88,7 +89,7 @@ fn if_expr_to_guarded_return( return None; } - let let_chains = flat_let_chain(cond); + let let_chains = flat_let_chain(cond, &make); let then_branch = if_expr.then_branch()?; let then_block = then_branch.stmt_list()?; @@ -110,7 +111,8 @@ fn if_expr_to_guarded_return( let early_expression = else_block .or_else(|| { - early_expression(parent_container, &ctx.sema).map(ast::make::tail_only_block_expr) + early_expression(parent_container, &ctx.sema, &make) + .map(ast::make::tail_only_block_expr) })? .reset_indent(); @@ -133,6 +135,7 @@ fn if_expr_to_guarded_return( "Convert to guarded return", target, |edit| { + let make = SyntaxFactory::without_mappings(); let if_indent_level = IndentLevel::from_node(if_expr.syntax()); let replacement = let_chains.into_iter().map(|expr| { if let ast::Expr::LetExpr(let_expr) = &expr @@ -140,15 +143,15 @@ fn if_expr_to_guarded_return( { // If-let. let let_else_stmt = - make::let_else_stmt(pat, None, expr, early_expression.clone()); + make.let_else_stmt(pat, None, expr, early_expression.clone()); let let_else_stmt = let_else_stmt.indent(if_indent_level); let_else_stmt.syntax().clone() } else { // If. let new_expr = { - let then_branch = clean_stmt_block(&early_expression); - let cond = invert_boolean_expression_legacy(expr); - make::expr_if(cond, then_branch, None).indent(if_indent_level) + let then_branch = clean_stmt_block(&early_expression, &make); + let cond = invert_boolean_expression(&make, expr); + make.expr_if(cond, then_branch, None).indent(if_indent_level) }; new_expr.syntax().clone() } @@ -159,7 +162,7 @@ fn if_expr_to_guarded_return( .enumerate() .flat_map(|(i, node)| { (i != 0) - .then(|| make::tokens::whitespace(newline).into()) + .then(|| make.whitespace(newline).into()) .into_iter() .chain(node.children_with_tokens()) }) @@ -201,12 +204,13 @@ fn let_stmt_to_guarded_return( let happy_pattern = try_enum.happy_pattern(pat); let target = let_stmt.syntax().text_range(); + let make = SyntaxFactory::without_mappings(); let early_expression: ast::Expr = { let parent_block = let_stmt.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?; let parent_container = parent_block.syntax().parent()?; - early_expression(parent_container, &ctx.sema)? + early_expression(parent_container, &ctx.sema, &make)? }; acc.add( @@ -215,9 +219,10 @@ fn let_stmt_to_guarded_return( target, |edit| { let let_indent_level = IndentLevel::from_node(let_stmt.syntax()); + let make = SyntaxFactory::without_mappings(); let replacement = { - let let_else_stmt = make::let_else_stmt( + let let_else_stmt = make.let_else_stmt( happy_pattern, let_stmt.ty(), expr.reset_indent(), @@ -228,6 +233,7 @@ fn let_stmt_to_guarded_return( }; let mut editor = edit.make_editor(let_stmt.syntax()); editor.replace(let_stmt.syntax(), replacement); + editor.add_mappings(make.finish_with_mappings()); edit.add_file_edits(ctx.vfs_file_id(), editor); }, ) @@ -236,38 +242,39 @@ fn let_stmt_to_guarded_return( fn early_expression( parent_container: SyntaxNode, sema: &Semantics<'_, RootDatabase>, + make: &SyntaxFactory, ) -> Option { let return_none_expr = || { - let none_expr = make::expr_path(make::ext::ident_path("None")); - make::expr_return(Some(none_expr)) + let none_expr = make.expr_path(make.ident_path("None")); + make.expr_return(Some(none_expr)) }; if let Some(fn_) = ast::Fn::cast(parent_container.clone()) && let Some(fn_def) = sema.to_def(&fn_) && let Some(TryEnum::Option) = TryEnum::from_ty(sema, &fn_def.ret_type(sema.db)) { - return Some(return_none_expr()); + return Some(return_none_expr().into()); } if let Some(body) = ast::ClosureExpr::cast(parent_container.clone()).and_then(|it| it.body()) && let Some(ret_ty) = sema.type_of_expr(&body).map(TypeInfo::original) && let Some(TryEnum::Option) = TryEnum::from_ty(sema, &ret_ty) { - return Some(return_none_expr()); + return Some(return_none_expr().into()); } Some(match parent_container.kind() { - WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make::expr_continue(None), - FN | CLOSURE_EXPR => make::expr_return(None), + WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make.expr_continue(None), + FN | CLOSURE_EXPR => make.expr_return(None).into(), _ => return None, }) } -fn flat_let_chain(mut expr: ast::Expr) -> Vec { +fn flat_let_chain(mut expr: ast::Expr, make: &SyntaxFactory) -> Vec { let mut chains = vec![]; let mut reduce_cond = |rhs| { if !matches!(rhs, ast::Expr::LetExpr(_)) && let Some(last) = chains.pop_if(|last| !matches!(last, ast::Expr::LetExpr(_))) { - chains.push(make::expr_bin_op(rhs, ast::BinaryOp::LogicOp(ast::LogicOp::And), last)); + chains.push(make.expr_bin_op(rhs, ast::BinaryOp::LogicOp(ast::LogicOp::And), last)); } else { chains.push(rhs); } @@ -286,12 +293,12 @@ fn flat_let_chain(mut expr: ast::Expr) -> Vec { chains } -fn clean_stmt_block(block: &ast::BlockExpr) -> ast::BlockExpr { +fn clean_stmt_block(block: &ast::BlockExpr, make: &SyntaxFactory) -> ast::BlockExpr { if block.statements().next().is_none() && let Some(tail_expr) = block.tail_expr() && block.modifier().is_none() { - make::block_expr(once(make::expr_stmt(tail_expr).into()), None) + make.block_expr(once(make.expr_stmt(tail_expr).into()), None) } else { block.clone() } 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 9fd8b4b3159ea..c06e539a4fdbf 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 @@ -6,7 +6,7 @@ use syntax::{ ast::{ self, HasLoopBody, edit::{AstNodeEdit, IndentLevel}, - make, + syntax_factory::SyntaxFactory, }, syntax_editor::{Element, Position}, }; @@ -14,7 +14,7 @@ use syntax::{ use crate::{ AssistId, assist_context::{AssistContext, Assists}, - utils::invert_boolean_expression_legacy, + utils::invert_boolean_expression, }; // Assist: convert_while_to_loop @@ -52,44 +52,44 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) "Convert while to loop", target, |builder| { + let make = SyntaxFactory::without_mappings(); 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(IndentLevel(1)); + let break_block = make + .block_expr(iter::once(make.expr_stmt(make.expr_break(None, None)).into()), None) + .indent(IndentLevel(1)); edit.replace_all( while_kw.syntax_element()..=while_cond.syntax().syntax_element(), - vec![make::token(T![loop]).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()); - let block_expr = make::block_expr(stmts, None); + 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()); + 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).indent(while_indent_level); + let if_cond = invert_boolean_expression(&make, while_cond); + 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}")), + make.whitespace(&format!("\n{while_indent_level}")), ); } edit.insert_all( Position::after(&l_curly), vec![ - make::tokens::whitespace(&format!("\n{}", while_indent_level + 1)).into(), + make.whitespace(&format!("\n{}", while_indent_level + 1)).into(), if_expr.syntax().syntax_element(), ], ); }; + edit.add_mappings(make.finish_with_mappings()); builder.add_file_edits(ctx.vfs_file_id(), edit); }, ) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/invert_if.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/invert_if.rs index bf82d8df9b58f..c8cb7bb60f69c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/invert_if.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/invert_if.rs @@ -1,13 +1,13 @@ use ide_db::syntax_helpers::node_ext::is_pattern_cond; use syntax::{ T, - ast::{self, AstNode}, + ast::{self, AstNode, syntax_factory::SyntaxFactory}, }; use crate::{ AssistId, assist_context::{AssistContext, Assists}, - utils::invert_boolean_expression_legacy, + utils::invert_boolean_expression, }; // Assist: invert_if @@ -50,7 +50,8 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<() }; acc.add(AssistId::refactor_rewrite("invert_if"), "Invert if", if_range, |edit| { - let flip_cond = invert_boolean_expression_legacy(cond.clone()); + let make = SyntaxFactory::without_mappings(); + let flip_cond = invert_boolean_expression(&make, cond.clone()); edit.replace_ast(cond, flip_cond); let else_node = else_block.syntax(); 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 85b3e6d317db4..db59fc29c344f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -275,11 +275,6 @@ pub(crate) fn invert_boolean_expression(make: &SyntaxFactory, expr: ast::Expr) - invert_special_case(make, &expr).unwrap_or_else(|| make.expr_prefix(T![!], expr).into()) } -// FIXME: Migrate usages of this function to the above function and remove this. -pub(crate) fn invert_boolean_expression_legacy(expr: ast::Expr) -> ast::Expr { - invert_special_case_legacy(&expr).unwrap_or_else(|| make::expr_prefix(T![!], expr).into()) -} - fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option { match expr { ast::Expr::BinExpr(bin) => { @@ -343,58 +338,6 @@ fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option Option { - match expr { - ast::Expr::BinExpr(bin) => { - let bin = bin.clone_subtree(); - let op_token = bin.op_token()?; - let rev_token = match op_token.kind() { - T![==] => T![!=], - T![!=] => T![==], - T![<] => T![>=], - T![<=] => T![>], - T![>] => T![<=], - T![>=] => T![<], - // Parenthesize other expressions before prefixing `!` - _ => { - return Some( - make::expr_prefix(T![!], make::expr_paren(expr.clone()).into()).into(), - ); - } - }; - let mut bin_editor = SyntaxEditor::new(bin.syntax().clone()); - bin_editor.replace(op_token, make::token(rev_token)); - ast::Expr::cast(bin_editor.finish().new_root().clone()) - } - ast::Expr::MethodCallExpr(mce) => { - let receiver = mce.receiver()?; - let method = mce.name_ref()?; - let arg_list = mce.arg_list()?; - - let method = match method.text().as_str() { - "is_some" => "is_none", - "is_none" => "is_some", - "is_ok" => "is_err", - "is_err" => "is_ok", - _ => return None, - }; - Some(make::expr_method_call(receiver, make::name_ref(method), arg_list).into()) - } - ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => match pe.expr()? { - ast::Expr::ParenExpr(parexpr) => parexpr.expr(), - _ => pe.expr(), - }, - ast::Expr::Literal(lit) => match lit.kind() { - ast::LiteralKind::Bool(b) => match b { - true => Some(ast::Expr::Literal(make::expr_literal("false"))), - false => Some(ast::Expr::Literal(make::expr_literal("true"))), - }, - _ => None, - }, - _ => None, - } -} - pub(crate) fn insert_attributes( before: impl Element, edit: &mut SyntaxEditor, 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 3ca163d07f1f1..dd8cc8d6f2e2c 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 @@ -5,7 +5,7 @@ use crate::{ AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, ast::{ self, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasName, - HasTypeBounds, HasVisibility, Param, RangeItem, make, + HasTypeBounds, HasVisibility, Lifetime, Param, RangeItem, make, }, syntax_editor::SyntaxMappingBuilder, }; @@ -1806,6 +1806,14 @@ impl SyntaxFactory { self.ty_path(path) } + + pub fn expr_break(&self, label: Option, expr: Option) -> ast::Expr { + make::expr_break(label, expr) + } + + pub fn expr_continue(&self, label: Option) -> ast::Expr { + make::expr_continue(label) + } } // We need to collect `input` here instead of taking `impl IntoIterator + Clone`, From f5836b5ab89fe142a64e9a195adab7c4f81c7af8 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Mar 2026 10:11:35 +0530 Subject: [PATCH 047/101] make insert_attribute accept syntaxFactory --- .../src/handlers/convert_for_to_while_let.rs | 13 +++++++------ .../rust-analyzer/crates/ide-assists/src/utils.rs | 5 ++--- 2 files changed, 9 insertions(+), 9 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 d64e9ceda2ef2..d409374c16f6d 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 @@ -2,7 +2,7 @@ use hir::{Name, sym}; use ide_db::{famous_defs::FamousDefs, syntax_helpers::suggest_name}; use syntax::{ AstNode, - ast::{self, HasAttrs, HasLoopBody, edit::IndentLevel, make, syntax_factory::SyntaxFactory}, + ast::{self, HasAttrs, HasLoopBody, edit::IndentLevel, syntax_factory::SyntaxFactory}, syntax_editor::Position, }; @@ -57,13 +57,13 @@ pub(crate) fn convert_for_loop_to_while_let( { (expr, Some(make.name_ref(method.as_str()))) } else if let ast::Expr::RefExpr(_) = iterable { - (make::expr_paren(iterable).into(), Some(make.name_ref("into_iter"))) + (make.expr_paren(iterable).into(), Some(make.name_ref("into_iter"))) } else { (iterable, Some(make.name_ref("into_iter"))) }; let iterable = if let Some(method) = method { - make::expr_method_call(iterable, method, make::arg_list([])).into() + make.expr_method_call(iterable, method, make.arg_list([])).into() } else { iterable }; @@ -89,17 +89,18 @@ pub(crate) fn convert_for_loop_to_while_let( for_loop.syntax(), &mut editor, for_loop.attrs().map(|it| it.clone_for_update()), + &make, ); editor.insert( Position::before(for_loop.syntax()), - make::tokens::whitespace(format!("\n{indent}").as_str()), + make.whitespace(format!("\n{indent}").as_str()), ); editor.insert(Position::before(for_loop.syntax()), mut_expr.syntax()); - let opt_pat = make.tuple_struct_pat(make::ext::ident_path("Some"), [pat]); + let opt_pat = make.tuple_struct_pat(make.ident_path("Some"), [pat]); let iter_next_expr = make.expr_method_call( - make.expr_path(make::ext::ident_path(&tmp_var)), + make.expr_path(make.ident_path(&tmp_var)), make.name_ref("next"), make.arg_list([]), ); 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 db59fc29c344f..27788a70ebcfc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -342,6 +342,7 @@ pub(crate) fn insert_attributes( before: impl Element, edit: &mut SyntaxEditor, attrs: impl IntoIterator, + make: &SyntaxFactory, ) { let mut attrs = attrs.into_iter().peekable(); if attrs.peek().is_none() { @@ -353,9 +354,7 @@ pub(crate) fn insert_attributes( edit.insert_all( syntax::syntax_editor::Position::before(elem), attrs - .flat_map(|attr| { - [attr.syntax().clone().into(), make::tokens::whitespace(&whitespace).into()] - }) + .flat_map(|attr| [attr.syntax().clone().into(), make.whitespace(&whitespace).into()]) .collect(), ); } From 637cc71c3b059d2d624877b022d9ddb2432d9b1f Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 16 Mar 2026 11:58:13 +0200 Subject: [PATCH 048/101] Don't trigger GC on slow tests It can lead to UB. --- .../crates/rust-analyzer/src/main_loop.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 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 64decc9e0db60..eb272b5e0b372 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 @@ -303,6 +303,15 @@ impl GlobalState { .map(Some) } + fn trigger_garbage_collection(&mut self) { + if cfg!(test) { + // Slow tests run the main loop in multiple threads, but GC isn't thread safe. + return; + } + + self.analysis_host.trigger_garbage_collection(); + } + fn handle_event(&mut self, event: Event) { let loop_start = Instant::now(); let _p = tracing::info_span!("GlobalState::handle_event", event = %event).entered(); @@ -383,7 +392,7 @@ impl GlobalState { )); } PrimeCachesProgress::End { cancelled } => { - self.analysis_host.trigger_garbage_collection(); + self.trigger_garbage_collection(); self.prime_caches_queue.op_completed(()); if cancelled { self.prime_caches_queue @@ -542,7 +551,7 @@ impl GlobalState { && self.fmt_pool.handle.is_empty() && current_revision != self.last_gc_revision { - self.analysis_host.trigger_garbage_collection(); + self.trigger_garbage_collection(); self.last_gc_revision = current_revision; } } From b7bc96291e12bfe60a1fc6dae9db5af3b62f7431 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Fri, 13 Mar 2026 18:34:01 +0000 Subject: [PATCH 049/101] fix: SCIP generation should prime caches in parallel Previously load_workspace_into_db() called parallel_prime_caches() with a worker count of 1 unconditionally. This made SCIP generation slower than it needed to be. Instead, pass the number of workers explicitly, and allow the SCIP subcommand to specify the number of workers from the CLI, defaulting to the number of physical CPUs. On my M4 Macbook Pro this reduces the time taken to generate SCIP for rust-analyzer from ~39 seconds to ~35 seconds. AI disclosure: Partially written with Claude Opus 4.6. --- src/tools/rust-analyzer/crates/load-cargo/src/lib.rs | 4 +++- .../crates/rust-analyzer/src/cli/analysis_stats.rs | 1 + .../rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs | 1 + src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs | 4 ++++ src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs | 1 + .../crates/rust-analyzer/src/cli/prime_caches.rs | 1 + .../rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs | 1 + .../rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs | 1 + src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs | 1 + src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs | 2 ++ .../crates/rust-analyzer/src/cli/unresolved_references.rs | 1 + .../crates/rust-analyzer/src/integrated_benchmarks.rs | 3 +++ 12 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index 95fcfce291277..30cd877092201 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -42,6 +42,7 @@ pub struct LoadCargoConfig { pub load_out_dirs_from_check: bool, pub with_proc_macro_server: ProcMacroServerChoice, pub prefill_caches: bool, + pub num_worker_threads: usize, pub proc_macro_processes: usize, } @@ -197,7 +198,7 @@ pub fn load_workspace_into_db( ); if load_config.prefill_caches { - prime_caches::parallel_prime_caches(db, 1, &|_| ()); + prime_caches::parallel_prime_caches(db, load_config.num_worker_threads, &|_| ()); } Ok((vfs, proc_macro_server.and_then(Result::ok))) @@ -750,6 +751,7 @@ mod tests { load_out_dirs_from_check: false, with_proc_macro_server: ProcMacroServerChoice::None, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: 1, }; let (db, _vfs, _proc_macro) = diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 82f04aa78ebb7..ad1cca08cb579 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -91,6 +91,7 @@ impl flags::AnalysisStats { } }, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: 1, }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs index 575c77f8428ca..efbaad3c4936e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs @@ -41,6 +41,7 @@ impl flags::Diagnostics { load_out_dirs_from_check: !self.disable_build_scripts, with_proc_macro_server, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: 1, }; let (db, _vfs, _proc_macro) = diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs index d68f7ab5b7b24..03849938f5984 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs @@ -191,6 +191,9 @@ xflags::xflags! { /// Exclude code from vendored libraries from the resulting index. optional --exclude-vendored-libraries + + /// The number of worker threads for cache priming. Defaults to the number of physical cores. + optional --num-threads num_threads: usize } } } @@ -338,6 +341,7 @@ pub struct Scip { pub output: Option, pub config_path: Option, pub exclude_vendored_libraries: bool, + pub num_threads: Option, } impl RustAnalyzer { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs index e5e238db63618..3950a581fd776 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs @@ -293,6 +293,7 @@ impl flags::Lsif { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: 1, }; let path = AbsPathBuf::assert_utf8(env::current_dir()?.join(self.path)); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/prime_caches.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/prime_caches.rs index d5da6791797b8..beedcfae4ed85 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/prime_caches.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/prime_caches.rs @@ -38,6 +38,7 @@ impl flags::PrimeCaches { // we want to ensure that this command, not `load_workspace_at`, // is responsible for that work. prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: config.proc_macro_num_processes(), }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs index d4a56d773e7de..e8c88cadf6f0a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs @@ -23,6 +23,7 @@ impl flags::RunTests { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: 1, }; let (ref db, _vfs, _proc_macro) = diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs index e8c6c5f4d4f70..49f28352b6cf6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -103,6 +103,7 @@ impl Tester { load_out_dirs_from_check: false, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: 1, }; let (db, _vfs, _proc_macro) = 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 ed0476697c9cc..ef6d4399e663c 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 @@ -52,6 +52,7 @@ impl flags::Scip { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, + num_worker_threads: self.num_threads.unwrap_or_else(num_cpus::get_physical), proc_macro_processes: config.proc_macro_num_processes(), }; let cargo_config = config.cargo(None); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs index 6bc0792daabbd..7b00aebbfc4a5 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs @@ -20,6 +20,7 @@ impl flags::Ssr { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: 1, }; let (ref db, vfs, _proc_macro) = load_workspace_at( @@ -57,6 +58,7 @@ impl flags::Search { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: 1, }; let (ref db, _vfs, _proc_macro) = load_workspace_at( diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs index 49c6fcb91ebfc..2d9b870f4de82 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs @@ -44,6 +44,7 @@ impl flags::UnresolvedReferences { load_out_dirs_from_check: !self.disable_build_scripts, with_proc_macro_server, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: config.proc_macro_num_processes(), }; let (db, vfs, _proc_macro) = diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs index d16ca2fb48ac0..6a74b8a54deb6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -53,6 +53,7 @@ fn integrated_highlighting_benchmark() { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + num_worker_threads: 1, proc_macro_processes: 1, }; @@ -122,6 +123,7 @@ fn integrated_completion_benchmark() { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, + num_worker_threads: 1, proc_macro_processes: 1, }; @@ -324,6 +326,7 @@ fn integrated_diagnostics_benchmark() { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, + num_worker_threads: 1, proc_macro_processes: 1, }; From 5fe6600b00fbbf70b7749113fd29082020b7479f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 16 Mar 2026 19:32:27 +0530 Subject: [PATCH 050/101] add mappings for constructors --- .../src/handlers/convert_to_guarded_return.rs | 2 +- .../src/handlers/convert_while_to_loop.rs | 5 +- .../src/handlers/generate_delegate_methods.rs | 2 +- .../src/ast/syntax_factory/constructors.rs | 60 +++++++++++++++---- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index 374c7cd869922..db45916792206 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -262,7 +262,7 @@ fn early_expression( } Some(match parent_container.kind() { - WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make.expr_continue(None), + WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make.expr_continue(None).into(), FN | CLOSURE_EXPR => make.expr_return(None).into(), _ => return None, }) 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 c06e539a4fdbf..f8215d6723d3f 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 @@ -57,7 +57,10 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) 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) + .block_expr( + iter::once(make.expr_stmt(make.expr_break(None, None).into()).into()), + None, + ) .indent(IndentLevel(1)); edit.replace_all( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs index d57db7b63d2a8..63033c7d5e398 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -150,7 +150,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let tail_expr = make.expr_method_call(field, make.name_ref(&name), arg_list).into(); let tail_expr_finished = - if is_async { make.expr_await(tail_expr) } else { tail_expr }; + if is_async { make.expr_await(tail_expr).into() } else { tail_expr }; let body = make.block_expr([], Some(tail_expr_finished)); let ret_type = method_source.ret_type(); 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 dd8cc8d6f2e2c..d0f14dafe3f09 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 @@ -1773,8 +1773,56 @@ impl SyntaxFactory { make::ext::field_from_idents(parts) } - pub fn expr_await(&self, expr: ast::Expr) -> ast::Expr { - make::expr_await(expr) + pub fn expr_await(&self, expr: ast::Expr) -> ast::AwaitExpr { + let ast::Expr::AwaitExpr(ast) = make::expr_await(expr.clone()).clone_for_update() else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + + pub fn expr_break(&self, label: Option, expr: Option) -> ast::BreakExpr { + let ast::Expr::BreakExpr(ast) = + make::expr_break(label.clone(), expr.clone()).clone_for_update() + else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(label) = label { + builder.map_node(label.syntax().clone(), ast.lifetime().unwrap().syntax().clone()); + } + if let Some(expr) = expr { + builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone()); + } + builder.finish(&mut mapping); + } + + ast + } + + pub fn expr_continue(&self, label: Option) -> ast::ContinueExpr { + let ast::Expr::ContinueExpr(ast) = make::expr_continue(label.clone()).clone_for_update() + else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(label) = label { + builder.map_node(label.syntax().clone(), ast.lifetime().unwrap().syntax().clone()); + } + builder.finish(&mut mapping); + } + + ast } } @@ -1806,14 +1854,6 @@ impl SyntaxFactory { self.ty_path(path) } - - pub fn expr_break(&self, label: Option, expr: Option) -> ast::Expr { - make::expr_break(label, expr) - } - - pub fn expr_continue(&self, label: Option) -> ast::Expr { - make::expr_continue(label) - } } // We need to collect `input` here instead of taking `impl IntoIterator + Clone`, From 60207debab17c11d6bd74f170230f2521f52b1bc Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 12 Mar 2026 22:10:01 +0100 Subject: [PATCH 051/101] small clean-up - extract unrelated `if` out of `matches!` - in `include_references`: - add docs - use a while-let chain - add a more complex test --- .../crates/ide-completion/src/completions/postfix.rs | 10 ++++++---- .../crates/ide-completion/src/context/analysis.rs | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index ea53aef40c2e7..5ff0cdc7952b5 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -402,6 +402,10 @@ fn receiver_accessor(receiver: &ast::Expr) -> ast::Expr { .unwrap_or_else(|| receiver.clone()) } +/// Given an `initial_element`, tries to expand it to include deref(s), and then references. +/// Returns the expanded expressions, and the added prefix as a string +/// +/// For example, if called with the `42` in `&&mut *42`, would return `(&&mut *42, "&&mut *")`. fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) { let mut resulting_element = initial_element.clone(); let mut prefix = String::new(); @@ -410,11 +414,8 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) { while let Some(parent_deref_element) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast) + && parent_deref_element.op_kind() == Some(ast::UnaryOp::Deref) { - if parent_deref_element.op_kind() != Some(ast::UnaryOp::Deref) { - break; - } - found_ref_or_deref = true; resulting_element = ast::Expr::from(parent_deref_element); @@ -1040,6 +1041,7 @@ fn main() { #[test] fn postfix_completion_for_references() { check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#); + check_edit("dbg", r#"fn main() { &&*"hello".$0 }"#, r#"fn main() { dbg!(&&*"hello") }"#); check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#); check_edit( "ifl", diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index 4b0cc0c7cd98e..4ab2850ece6f0 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -944,10 +944,10 @@ fn classify_name_ref<'db>( let field_expr_handle = |receiver, node| { let receiver = find_opt_node_in_file(original_file, receiver); let receiver_is_ambiguous_float_literal = match &receiver { - Some(ast::Expr::Literal(l)) => matches! { - l.kind(), - ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().is_some_and(|it| it.text().ends_with('.')) - }, + Some(ast::Expr::Literal(l)) => { + matches!(l.kind(), ast::LiteralKind::FloatNumber { .. }) + && l.syntax().last_token().is_some_and(|it| it.text().ends_with('.')) + } _ => false, }; From 0d35d9cf0abdc3d9212d44b42240d6520a905d90 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:46:33 +0530 Subject: [PATCH 052/101] add mapping to struct_ syntax_factory constructor method --- .../src/ast/syntax_factory/constructors.rs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) 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 d0f14dafe3f09..0eadf6a5116f5 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 @@ -96,7 +96,35 @@ impl SyntaxFactory { generic_param_list: Option, field_list: ast::FieldList, ) -> ast::Struct { - make::struct_(visibility, strukt_name, generic_param_list, field_list).clone_for_update() + let ast = make::struct_( + visibility.clone(), + strukt_name.clone(), + generic_param_list.clone(), + field_list.clone(), + ) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + builder.map_node(strukt_name.syntax().clone(), ast.name().unwrap().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(), + ); + } + builder + .map_node(field_list.syntax().clone(), ast.field_list().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast } pub fn enum_( From 7c21a3815f96f6ff06f46c3c786d9f0d92d90162 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:48:08 +0530 Subject: [PATCH 053/101] add mapping to enum_ syntax_factory constructor method --- .../src/ast/syntax_factory/constructors.rs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 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 0eadf6a5116f5..29b3894ef3e27 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 @@ -136,8 +136,47 @@ impl SyntaxFactory { where_clause: Option, variant_list: ast::VariantList, ) -> ast::Enum { - make::enum_(attrs, visibility, enum_name, generic_param_list, where_clause, variant_list) - .clone_for_update() + let (attrs, attrs_input) = iterator_input(attrs); + let ast = make::enum_( + attrs, + visibility.clone(), + enum_name.clone(), + generic_param_list.clone(), + where_clause.clone(), + variant_list.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(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + builder.map_node(enum_name.syntax().clone(), ast.name().unwrap().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(where_clause) = where_clause { + builder.map_node( + where_clause.syntax().clone(), + ast.where_clause().unwrap().syntax().clone(), + ); + } + builder.map_node( + variant_list.syntax().clone(), + ast.variant_list().unwrap().syntax().clone(), + ); + builder.finish(&mut mapping); + } + + ast } pub fn unnamed_param(&self, ty: ast::Type) -> ast::Param { From ea2bebd36006ed1f913f8d076170c002729ae735 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:48:38 +0530 Subject: [PATCH 054/101] add mapping to unnamed_param syntax_factory constructor method --- .../syntax/src/ast/syntax_factory/constructors.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 29b3894ef3e27..ae9d7e617493d 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 @@ -180,7 +180,15 @@ impl SyntaxFactory { } pub fn unnamed_param(&self, ty: ast::Type) -> ast::Param { - make::unnamed_param(ty).clone_for_update() + let ast = make::unnamed_param(ty.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast } pub fn ty_fn_ptr>( From 9c6a66d2beda94bee361edc3eb0b660234df7f4b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:49:36 +0530 Subject: [PATCH 055/101] add mapping to ty_fn_ptr syntax_factory constructor method --- .../src/ast/syntax_factory/constructors.rs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) 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 ae9d7e617493d..4570502b1bec6 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 @@ -198,7 +198,27 @@ impl SyntaxFactory { params: I, ret_type: Option, ) -> ast::FnPtrType { - make::ty_fn_ptr(is_unsafe, abi, params, ret_type).clone_for_update() + let (params, params_input) = iterator_input(params); + let ast = make::ty_fn_ptr(is_unsafe, abi.clone(), params.into_iter(), ret_type.clone()) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(abi) = abi { + builder.map_node(abi.syntax().clone(), ast.abi().unwrap().syntax().clone()); + } + builder.map_children( + params_input, + ast.param_list().unwrap().params().map(|p| p.syntax().clone()), + ); + if let Some(ret_type) = ret_type { + builder + .map_node(ret_type.syntax().clone(), ast.ret_type().unwrap().syntax().clone()); + } + builder.finish(&mut mapping); + } + + ast } pub fn where_pred( From 597f11b13d0da9601328f80fec3b8c10975b3c7d Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:50:21 +0530 Subject: [PATCH 056/101] add mapping to where_pred syntax_factory constructor method --- .../src/ast/syntax_factory/constructors.rs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) 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 4570502b1bec6..fdf5eb5f51638 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 @@ -226,7 +226,32 @@ impl SyntaxFactory { path: Either, bounds: impl IntoIterator, ) -> ast::WherePred { - make::where_pred(path, bounds).clone_for_update() + let (bounds, bounds_input) = iterator_input(bounds); + let ast = make::where_pred(path.clone(), bounds).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + match &path { + Either::Left(lifetime) => { + builder.map_node( + lifetime.syntax().clone(), + ast.lifetime().unwrap().syntax().clone(), + ); + } + Either::Right(ty) => { + builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone()); + } + } + if let Some(type_bound_list) = ast.type_bound_list() { + builder.map_children( + bounds_input, + type_bound_list.bounds().map(|b| b.syntax().clone()), + ); + } + builder.finish(&mut mapping); + } + + ast } pub fn where_clause( From 16949c559213122f3a713a7783486275f7485d36 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:50:52 +0530 Subject: [PATCH 057/101] add mapping to where_clause syntax_factory constructor method --- .../syntax/src/ast/syntax_factory/constructors.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 fdf5eb5f51638..241148d90066c 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 @@ -258,7 +258,16 @@ impl SyntaxFactory { &self, predicates: impl IntoIterator, ) -> ast::WhereClause { - make::where_clause(predicates).clone_for_update() + let (predicates, input) = iterator_input(predicates); + let ast = make::where_clause(predicates).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(input, ast.predicates().map(|p| p.syntax().clone())); + builder.finish(&mut mapping); + } + + ast } pub fn impl_trait_type(&self, bounds: ast::TypeBoundList) -> ast::ImplTraitType { From 3e9dc4e445c4894baa5a30ee641bc4ea46c8b3ea Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:51:23 +0530 Subject: [PATCH 058/101] add mapping to impl_trait_type syntax_factory constructor method --- .../syntax/src/ast/syntax_factory/constructors.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 241148d90066c..86fc7f0fdcd90 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 @@ -271,7 +271,16 @@ impl SyntaxFactory { } pub fn impl_trait_type(&self, bounds: ast::TypeBoundList) -> ast::ImplTraitType { - make::impl_trait_type(bounds).clone_for_update() + let ast = make::impl_trait_type(bounds.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder + .map_node(bounds.syntax().clone(), ast.type_bound_list().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast } pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr { From f3f1448031a8e6d536579c15b4b2360980d69694 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:52:10 +0530 Subject: [PATCH 059/101] add mapping to generic_ty_path_segment syntax_factory constructor method --- .../syntax/src/ast/syntax_factory/constructors.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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 86fc7f0fdcd90..a212dd3c34727 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 @@ -478,7 +478,20 @@ impl SyntaxFactory { name_ref: ast::NameRef, generic_args: impl IntoIterator, ) -> ast::PathSegment { - make::generic_ty_path_segment(name_ref, generic_args).clone_for_update() + let (generic_args, input) = iterator_input(generic_args); + let ast = make::generic_ty_path_segment(name_ref.clone(), generic_args).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(name_ref.syntax().clone(), ast.name_ref().unwrap().syntax().clone()); + builder.map_children( + input, + ast.generic_arg_list().unwrap().generic_args().map(|a| a.syntax().clone()), + ); + builder.finish(&mut mapping); + } + + ast } pub fn tail_only_block_expr(&self, tail_expr: ast::Expr) -> ast::BlockExpr { From 4ed8442341464702a5bf30e97ea085734cccbb3b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:53:05 +0530 Subject: [PATCH 060/101] add mapping to tail_only_block_expr syntax_factory constructor method --- .../syntax/src/ast/syntax_factory/constructors.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 a212dd3c34727..eb022d88e26a3 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 @@ -495,7 +495,19 @@ impl SyntaxFactory { } pub fn tail_only_block_expr(&self, tail_expr: ast::Expr) -> ast::BlockExpr { - make::tail_only_block_expr(tail_expr) + let ast = make::tail_only_block_expr(tail_expr.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let stmt_list = ast.stmt_list().unwrap(); + let mut builder = SyntaxMappingBuilder::new(stmt_list.syntax().clone()); + builder.map_node( + tail_expr.syntax().clone(), + stmt_list.tail_expr().unwrap().syntax().clone(), + ); + builder.finish(&mut mapping); + } + + ast } pub fn expr_bin_op(&self, lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr { From 7a5d26d077c9672842857f6bbe6d952f8b2f3955 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:53:32 +0530 Subject: [PATCH 061/101] add mapping to expr_bin_op syntax_factory constructor method --- .../syntax/src/ast/syntax_factory/constructors.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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 eb022d88e26a3..eec8ea3f3f28f 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 @@ -511,7 +511,20 @@ impl SyntaxFactory { } pub fn expr_bin_op(&self, lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr { - make::expr_bin_op(lhs, op, rhs) + let ast::Expr::BinExpr(ast) = + make::expr_bin_op(lhs.clone(), op, rhs.clone()).clone_for_update() + else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(lhs.syntax().clone(), ast.lhs().unwrap().syntax().clone()); + builder.map_node(rhs.syntax().clone(), ast.rhs().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast.into() } pub fn ty_placeholder(&self) -> ast::Type { From 01716dde697304052af90917013b8826e31706f1 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:53:54 +0530 Subject: [PATCH 062/101] add mapping to use_ syntax_factory constructor method --- .../src/ast/syntax_factory/constructors.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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 eec8ea3f3f28f..e62cf10a45cdc 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 @@ -561,7 +561,23 @@ impl SyntaxFactory { visibility: Option, use_tree: ast::UseTree, ) -> ast::Use { - make::use_(attrs, visibility, use_tree).clone_for_update() + let (attrs, attrs_input) = iterator_input(attrs); + let ast = make::use_(attrs, visibility.clone(), use_tree.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(visibility) = visibility { + builder.map_node( + visibility.syntax().clone(), + ast.visibility().unwrap().syntax().clone(), + ); + } + builder.map_node(use_tree.syntax().clone(), ast.use_tree().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast } pub fn use_tree( From ec8ab854c656e0287b1215ac8e55546bc66d220a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 17:54:15 +0530 Subject: [PATCH 063/101] add mapping to use_tree syntax_factory constructor method --- .../src/ast/syntax_factory/constructors.rs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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 e62cf10a45cdc..a9cdcbb041a4b 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 @@ -587,7 +587,25 @@ impl SyntaxFactory { alias: Option, add_star: bool, ) -> ast::UseTree { - make::use_tree(path, use_tree_list, alias, add_star).clone_for_update() + let ast = make::use_tree(path.clone(), use_tree_list.clone(), alias.clone(), add_star) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone()); + if let Some(use_tree_list) = use_tree_list { + builder.map_node( + use_tree_list.syntax().clone(), + ast.use_tree_list().unwrap().syntax().clone(), + ); + } + if let Some(alias) = alias { + builder.map_node(alias.syntax().clone(), ast.rename().unwrap().syntax().clone()); + } + builder.finish(&mut mapping); + } + + ast } pub fn path_unqualified(&self, segment: ast::PathSegment) -> ast::Path { From 257bcc4afb0720e00bfb12e24b53717a12ec0c9b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 17 Mar 2026 18:18:42 +0530 Subject: [PATCH 064/101] remove redundant enum_ constructor --- .../src/handlers/convert_bool_to_enum.rs | 2 +- .../src/ast/syntax_factory/constructors.rs | 52 ------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs index 934779810054f..e88778a62e193 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs @@ -533,7 +533,7 @@ fn make_bool_enum(make_pub: bool, make: &SyntaxFactory) -> ast::Enum { ], ), )); - make.enum_( + make.item_enum( [derive_eq], if make_pub { Some(make.visibility_pub()) } else { None }, make.name("Bool"), 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 a9cdcbb041a4b..e8339eaeac63b 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 @@ -127,58 +127,6 @@ impl SyntaxFactory { ast } - pub fn enum_( - &self, - attrs: impl IntoIterator, - visibility: Option, - enum_name: ast::Name, - generic_param_list: Option, - where_clause: Option, - variant_list: ast::VariantList, - ) -> ast::Enum { - let (attrs, attrs_input) = iterator_input(attrs); - let ast = make::enum_( - attrs, - visibility.clone(), - enum_name.clone(), - generic_param_list.clone(), - where_clause.clone(), - variant_list.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(visibility) = visibility { - builder.map_node( - visibility.syntax().clone(), - ast.visibility().unwrap().syntax().clone(), - ); - } - builder.map_node(enum_name.syntax().clone(), ast.name().unwrap().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(where_clause) = where_clause { - builder.map_node( - where_clause.syntax().clone(), - ast.where_clause().unwrap().syntax().clone(), - ); - } - builder.map_node( - variant_list.syntax().clone(), - ast.variant_list().unwrap().syntax().clone(), - ); - builder.finish(&mut mapping); - } - - ast - } - pub fn unnamed_param(&self, ty: ast::Type) -> ast::Param { let ast = make::unnamed_param(ty.clone()).clone_for_update(); From 7c93774f8d5fc2aaa1ffdeff85f72f0c85d8dc25 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 10 Mar 2026 17:55:23 +0000 Subject: [PATCH 065/101] internal: Add codecov badge It's tricky to find the relevant codecov page, so add a badge to the readme showing coverage and linking to the codecov dashboard. --- src/tools/rust-analyzer/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/rust-analyzer/README.md b/src/tools/rust-analyzer/README.md index 6d2a673286853..06d63a2d23a73 100644 --- a/src/tools/rust-analyzer/README.md +++ b/src/tools/rust-analyzer/README.md @@ -20,6 +20,8 @@ analyzing Rust code. See [Architecture](https://rust-analyzer.github.io/book/contributing/architecture.html) in the manual. +[![codecov](https://codecov.io/github/rust-lang/rust-analyzer/graph/badge.svg)](https://app.codecov.io/github/rust-lang/rust-analyzer/tree/master) + ## Quick Start https://rust-analyzer.github.io/book/installation.html From 9d9f68f52769edef5710ded13d80b3e2fe7a0dfe Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 18 Mar 2026 19:33:05 +0800 Subject: [PATCH 066/101] fix: .let no complete semicolon before semicolon Make `.let` `.letm` `.return` `.break` not duplicate semicolons Example --- ```rust fn main() { baz.l$0; } ``` **Before this PR** ```rust fn main() { let $0 = baz;; } ``` **After this PR** ```rust fn main() { let $0 = baz; } ``` --- .../ide-completion/src/completions/postfix.rs | 79 ++++++++++++++----- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index 5ff0cdc7952b5..d85f23e0848d3 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -66,6 +66,12 @@ pub(crate) fn complete_postfix( Some(it) => it, None => return, }; + let semi = + if expr_ctx.in_block_expr && ctx.token.next_token().is_none_or(|it| it.kind() != T![;]) { + ";" + } else { + "" + }; let cfg = ctx.config.find_path_config(ctx.is_nightly); @@ -151,9 +157,9 @@ pub(crate) fn complete_postfix( .add_to(acc, ctx.db); } _ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) => { - postfix_snippet("let", "let", &format!("let $0 = {receiver_text};")) + postfix_snippet("let", "let", &format!("let $0 = {receiver_text}{semi}")) .add_to(acc, ctx.db); - postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};")) + postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text}{semi}")) .add_to(acc, ctx.db); } _ if ast::MatchArm::can_cast(parent.kind()) => { @@ -307,26 +313,12 @@ pub(crate) fn complete_postfix( add_format_like_completions(acc, ctx, &dot_receiver_including_refs, cap, &literal_text); } - postfix_snippet( - "return", - "return expr", - &format!( - "return {receiver_text}{semi}", - semi = if expr_ctx.in_block_expr { ";" } else { "" } - ), - ) - .add_to(acc, ctx.db); + postfix_snippet("return", "return expr", &format!("return {receiver_text}{semi}")) + .add_to(acc, ctx.db); if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable { - postfix_snippet( - "break", - "break expr", - &format!( - "break {receiver_text}{semi}", - semi = if expr_ctx.in_block_expr { ";" } else { "" } - ), - ) - .add_to(acc, ctx.db); + postfix_snippet("break", "break expr", &format!("break {receiver_text}{semi}")) + .add_to(acc, ctx.db); } } @@ -664,6 +656,22 @@ fn main() { #[test] fn let_middle_block() { + check_edit( + "let", + r#" +fn main() { + baz.l$0 + res +} +"#, + r#" +fn main() { + let $0 = baz; + res +} +"#, + ); + check( r#" fn main() { @@ -720,6 +728,20 @@ fn main() { #[test] fn let_tail_block() { + check_edit( + "let", + r#" +fn main() { + baz.l$0 +} +"#, + r#" +fn main() { + let $0 = baz; +} +"#, + ); + check( r#" fn main() { @@ -773,6 +795,23 @@ fn main() { ); } + #[test] + fn let_before_semicolon() { + check_edit( + "let", + r#" +fn main() { + baz.l$0; +} +"#, + r#" +fn main() { + let $0 = baz; +} +"#, + ); + } + #[test] fn option_iflet() { check_edit( From b0d4f7a9ae8d8e0c43717c2fc376b27e1adab7d0 Mon Sep 17 00:00:00 2001 From: kawkoi Date: Wed, 18 Mar 2026 22:42:24 +0530 Subject: [PATCH 067/101] Migrate convert_from_to_tryfrom assist to SyntaxEditor API Replace mutable syntax tree editing (clone_for_update + make::) with the SyntaxFactory API, which is the preferred approach for avoiding mutable tree operations. Changes: - Use SyntaxFactory::with_mappings() instead of bare make:: constructors - Replace make::ty() + .clone_for_update() with make.ty() - Replace make::name() + .clone_for_update() with make.name() - Replace make::ty_alias() + .clone_for_update() with make.ty_alias() - Update wrap_ok() helper to accept &SyntaxFactory and use make.expr_call/arg_list/expr_path - Add editor.add_mappings(make.finish_with_mappings()) to register mappings --- .../src/handlers/convert_from_to_tryfrom.rs | 48 +++++++------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs index 6a74d214512fb..66ccd2a4e4093 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs @@ -1,6 +1,6 @@ use ide_db::{famous_defs::FamousDefs, traits::resolve_target_trait}; use syntax::ast::edit::IndentLevel; -use syntax::ast::{self, AstNode, HasGenericArgs, HasName, make}; +use syntax::ast::{self, AstNode, HasGenericArgs, HasName, syntax_factory::SyntaxFactory}; use syntax::syntax_editor::{Element, Position}; use crate::{AssistContext, AssistId, Assists}; @@ -74,36 +74,25 @@ pub(crate) fn convert_from_to_tryfrom(acc: &mut Assists, ctx: &AssistContext<'_> "Convert From to TryFrom", impl_.syntax().text_range(), |builder| { + let make = SyntaxFactory::with_mappings(); let mut editor = builder.make_editor(impl_.syntax()); - editor.replace( - trait_ty.syntax(), - make::ty(&format!("TryFrom<{from_type}>")).syntax().clone_for_update(), - ); + + editor.replace(trait_ty.syntax(), make.ty(&format!("TryFrom<{from_type}>")).syntax()); editor.replace( from_fn_return_type.syntax(), - make::ty("Result").syntax().clone_for_update(), - ); - editor - .replace(from_fn_name.syntax(), make::name("try_from").syntax().clone_for_update()); - editor.replace( - tail_expr.syntax(), - wrap_ok(tail_expr.clone()).syntax().clone_for_update(), + make.ty("Result").syntax(), ); + editor.replace(from_fn_name.syntax(), make.name("try_from").syntax()); + editor.replace(tail_expr.syntax(), wrap_ok(&make, tail_expr.clone()).syntax()); for r in return_exprs { - let t = r.expr().unwrap_or_else(make::ext::expr_unit); - editor.replace(t.syntax(), wrap_ok(t.clone()).syntax().clone_for_update()); + let t = r.expr().unwrap_or_else(|| make.expr_unit()); + editor.replace(t.syntax(), wrap_ok(&make, t.clone()).syntax()); } - let error_type = ast::AssocItem::TypeAlias(make::ty_alias( - None, - "Error", - None, - None, - None, - Some((make::ty_unit(), None)), - )) - .clone_for_update(); + let error_type_alias = + make.ty_alias(None, "Error", None, None, None, Some((make.ty("()"), None))); + let error_type = ast::AssocItem::TypeAlias(error_type_alias); if let Some(cap) = ctx.config.snippet_cap && let ast::AssocItem::TypeAlias(type_alias) = &error_type @@ -117,22 +106,19 @@ pub(crate) fn convert_from_to_tryfrom(acc: &mut Assists, ctx: &AssistContext<'_> editor.insert_all( Position::after(associated_l_curly), vec![ - make::tokens::whitespace(&format!("\n{indent}")).syntax_element(), + make.whitespace(&format!("\n{indent}")).syntax_element(), error_type.syntax().syntax_element(), - make::tokens::whitespace("\n").syntax_element(), + make.whitespace("\n").syntax_element(), ], ); + editor.add_mappings(make.finish_with_mappings()); builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } -fn wrap_ok(expr: ast::Expr) -> ast::Expr { - make::expr_call( - make::expr_path(make::ext::ident_path("Ok")), - make::arg_list(std::iter::once(expr)), - ) - .into() +fn wrap_ok(make: &SyntaxFactory, expr: ast::Expr) -> ast::Expr { + make.expr_call(make.expr_path(make.path_from_text("Ok")), make.arg_list([expr])).into() } #[cfg(test)] From e15897f6c4bd948526b8f43d3ea8874075eccab9 Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Thu, 19 Mar 2026 10:27:40 +0000 Subject: [PATCH 068/101] Parenthesize or-patterns in prefix pattern positions in pretty printer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AST pretty printer was dropping parentheses around or-patterns when they appeared inside `@` bindings, `&` references, or `box` patterns. For example: - `v @ (1 | 2 | 3)` was printed as `v @ 1 | 2 | 3` - `&(1 | 2 | 3)` was printed as `&1 | 2 | 3` - `box (1 | 2 | 3)` was printed as `box 1 | 2 | 3` Since `|` has the lowest precedence among pattern operators, all of these are parsed incorrectly without parentheses — e.g. `v @ 1 | 2 | 3` becomes `(v @ 1) | 2 | 3`, binding `v` only to the first alternative. This caused E0408 ("variable not bound in all patterns") when the expanded output was fed back to the compiler, affecting crates like html5ever and wgpu-core that use macros expanding to or-patterns after `@`. The fix adds a `print_pat_paren_if_or` helper that wraps `PatKind::Or` subpatterns in parentheses, and uses it in the `@`, `&`, and `box` printing arms. This is similar in spirit to the existing `FixupContext` parenthesization approach used for expression printing. Signed-off-by: Andrew V. Teylu --- compiler/rustc_ast_pretty/src/pprust/state.rs | 23 ++++++++++-- tests/pretty/or-pattern-paren.pp | 27 ++++++++++++++ tests/pretty/or-pattern-paren.rs | 36 +++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 tests/pretty/or-pattern-paren.pp create mode 100644 tests/pretty/or-pattern-paren.rs diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 4ba5dc541342a..74f0f970f76d3 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -1749,6 +1749,23 @@ impl<'a> State<'a> { } } + /// Print a pattern, parenthesizing it if it is an or-pattern (`A | B`). + /// + /// Or-patterns have the lowest precedence among patterns, so they need + /// parentheses when nested inside `@` bindings, `&` references, or `box` + /// patterns — otherwise `x @ A | B` parses as `(x @ A) | B`, `&A | B` + /// parses as `(&A) | B`, etc. + fn print_pat_paren_if_or(&mut self, pat: &ast::Pat) { + let needs_paren = matches!(pat.kind, PatKind::Or(..)); + if needs_paren { + self.popen(); + } + self.print_pat(pat); + if needs_paren { + self.pclose(); + } + } + fn print_pat(&mut self, pat: &ast::Pat) { self.maybe_print_comment(pat.span.lo()); self.ann.pre(self, AnnNode::Pat(pat)); @@ -1776,7 +1793,7 @@ impl<'a> State<'a> { if let Some(p) = sub { self.space(); self.word_space("@"); - self.print_pat(p); + self.print_pat_paren_if_or(p); } } PatKind::TupleStruct(qself, path, elts) => { @@ -1848,7 +1865,7 @@ impl<'a> State<'a> { } PatKind::Box(inner) => { self.word("box "); - self.print_pat(inner); + self.print_pat_paren_if_or(inner); } PatKind::Deref(inner) => { self.word("deref!"); @@ -1872,7 +1889,7 @@ impl<'a> State<'a> { self.print_pat(inner); self.pclose(); } else { - self.print_pat(inner); + self.print_pat_paren_if_or(inner); } } PatKind::Expr(e) => self.print_expr(e, FixupContext::default()), diff --git a/tests/pretty/or-pattern-paren.pp b/tests/pretty/or-pattern-paren.pp new file mode 100644 index 0000000000000..6ea94eb7b91f8 --- /dev/null +++ b/tests/pretty/or-pattern-paren.pp @@ -0,0 +1,27 @@ +#![feature(prelude_import)] +#![no_std] +#![feature(box_patterns)] +extern crate std; +#[prelude_import] +use ::std::prelude::rust_2015::*; + +//@ pretty-compare-only +//@ pretty-mode:expanded +//@ pp-exact:or-pattern-paren.pp + +macro_rules! or_pat { ($($name:pat),+) => { $($name)|+ } } + +fn check_at(x: Option) { + match x { + Some(v @ (1 | 2 | 3)) => + + + { + ::std::io::_print(format_args!("{0}\n", v)); + } + _ => {} + } +} +fn check_ref(x: &i32) { match x { &(1 | 2 | 3) => {} _ => {} } } +fn check_box(x: Box) { match x { box (1 | 2 | 3) => {} _ => {} } } +fn main() { check_at(Some(2)); check_ref(&1); check_box(Box::new(1)); } diff --git a/tests/pretty/or-pattern-paren.rs b/tests/pretty/or-pattern-paren.rs new file mode 100644 index 0000000000000..ea6a8f3de9e49 --- /dev/null +++ b/tests/pretty/or-pattern-paren.rs @@ -0,0 +1,36 @@ +#![feature(box_patterns)] + +//@ pretty-compare-only +//@ pretty-mode:expanded +//@ pp-exact:or-pattern-paren.pp + +macro_rules! or_pat { + ($($name:pat),+) => { $($name)|+ } +} + +fn check_at(x: Option) { + match x { + Some(v @ or_pat!(1, 2, 3)) => println!("{v}"), + _ => {} + } +} + +fn check_ref(x: &i32) { + match x { + &or_pat!(1, 2, 3) => {} + _ => {} + } +} + +fn check_box(x: Box) { + match x { + box or_pat!(1, 2, 3) => {} + _ => {} + } +} + +fn main() { + check_at(Some(2)); + check_ref(&1); + check_box(Box::new(1)); +} From 1a1a0d2bb57cf7f41a02d6e5e214bad7a9080324 Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Thu, 19 Mar 2026 13:38:26 +0000 Subject: [PATCH 069/101] Update Clippy test expectations for or-pattern parenthesization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pretty printer now correctly parenthesizes or-patterns inside `box` patterns, which changes the Clippy `unnested_or_patterns` lint suggestion from `box box (0 | 2 | 4)` to `box (box (0 | 2 | 4))`. The new output is more correct — the old suggestion was itself missing parens and would have been parsed as `box (box 0) | 2 | 4`. Signed-off-by: Andrew V. Teylu --- src/tools/clippy/tests/ui/unnested_or_patterns2.fixed | 2 +- src/tools/clippy/tests/ui/unnested_or_patterns2.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns2.fixed b/src/tools/clippy/tests/ui/unnested_or_patterns2.fixed index 2c794463457fb..852c4ddcfd712 100644 --- a/src/tools/clippy/tests/ui/unnested_or_patterns2.fixed +++ b/src/tools/clippy/tests/ui/unnested_or_patterns2.fixed @@ -23,6 +23,6 @@ fn main() { //~^ unnested_or_patterns if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {} //~^ unnested_or_patterns - if let box box (0 | 2 | 4) = Box::new(Box::new(0)) {} + if let box (box (0 | 2 | 4)) = Box::new(Box::new(0)) {} //~^ unnested_or_patterns } diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns2.stderr b/src/tools/clippy/tests/ui/unnested_or_patterns2.stderr index 3fc8fa174c939..3deef33cf258e 100644 --- a/src/tools/clippy/tests/ui/unnested_or_patterns2.stderr +++ b/src/tools/clippy/tests/ui/unnested_or_patterns2.stderr @@ -93,7 +93,7 @@ LL | if let box box 0 | box (box 2 | box 4) = Box::new(Box::new(0)) {} help: nest the patterns | LL - if let box box 0 | box (box 2 | box 4) = Box::new(Box::new(0)) {} -LL + if let box box (0 | 2 | 4) = Box::new(Box::new(0)) {} +LL + if let box (box (0 | 2 | 4)) = Box::new(Box::new(0)) {} | error: aborting due to 8 previous errors From f9752e2074ae6cf1c1ad8bb5694fdecd7aedee1c Mon Sep 17 00:00:00 2001 From: Jynn Nelson Date: Thu, 19 Mar 2026 17:58:45 +0100 Subject: [PATCH 070/101] bootstrap: Pass `--features=rustc` to rustc_transmute This matters for `x test rustc_transmute`, where the feature won't automatically be enabled and as a result spew a bunch of warnings. --- src/bootstrap/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index f1303814f6d72..6af11d9ae0a81 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -883,6 +883,12 @@ impl Build { features.push("check_only"); } + if crates.iter().any(|c| c == "rustc_transmute") { + // for `x test rustc_transmute`, this feature isn't enabled automatically by a + // dependent crate. + features.push("rustc"); + } + // If debug logging is on, then we want the default for tracing: // https://github.com/tokio-rs/tracing/blob/3dd5c03d907afdf2c39444a29931833335171554/tracing/src/level_filters.rs#L26 // which is everything (including debug/trace/etc.) From 191969d3eaa2a45ccc645fee8e62af3b6fb02ad1 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 19 Mar 2026 19:38:25 +0100 Subject: [PATCH 071/101] remove outdated TODO we decided not to implement this after all --- .../rust-analyzer/crates/hir/src/diagnostics.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index 050777a4806da..7f672a697c41e 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -51,20 +51,6 @@ macro_rules! diagnostics { )* }; } -// FIXME Accept something like the following in the macro call instead -// diagnostics![ -// pub struct BreakOutsideOfLoop { -// pub expr: InFile>, -// pub is_break: bool, -// pub bad_value_break: bool, -// }, ... -// or more concisely -// BreakOutsideOfLoop { -// expr: InFile>, -// is_break: bool, -// bad_value_break: bool, -// }, ... -// ] diagnostics![AnyDiagnostic<'db> -> AwaitOutsideOfAsync, From 54819a132525014ac021e90729728c1e6fdd4160 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 20 Mar 2026 10:11:50 +0530 Subject: [PATCH 072/101] migrate qualify_path to use SyntaxEditor and SyntaxFactory --- .../ide-assists/src/handlers/qualify_path.rs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs index b3cf2969650a8..9ebeef90bae9e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs @@ -11,7 +11,8 @@ use syntax::Edition; use syntax::ast::HasGenericArgs; use syntax::{ AstNode, ast, - ast::{HasArgList, make}, + ast::{HasArgList, syntax_factory::SyntaxFactory}, + syntax_editor::SyntaxEditor, }; use crate::{ @@ -54,25 +55,25 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option let qualify_candidate = match candidate { ImportCandidate::Path(candidate) if !candidate.qualifier.is_empty() => { cov_mark::hit!(qualify_path_qualifier_start); - let path = ast::Path::cast(syntax_under_caret)?; + let path = ast::Path::cast(syntax_under_caret.clone())?; let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list()) } ImportCandidate::Path(_) => { cov_mark::hit!(qualify_path_unqualified_name); - let path = ast::Path::cast(syntax_under_caret)?; + let path = ast::Path::cast(syntax_under_caret.clone())?; let generics = path.segment()?.generic_arg_list(); QualifyCandidate::UnqualifiedName(generics) } ImportCandidate::TraitAssocItem(_) => { cov_mark::hit!(qualify_path_trait_assoc_item); - let path = ast::Path::cast(syntax_under_caret)?; + let path = ast::Path::cast(syntax_under_caret.clone())?; let (qualifier, segment) = (path.qualifier()?, path.segment()?); QualifyCandidate::TraitAssocItem(qualifier, segment) } ImportCandidate::TraitMethod(_) => { cov_mark::hit!(qualify_path_trait_method); - let mcall_expr = ast::MethodCallExpr::cast(syntax_under_caret)?; + let mcall_expr = ast::MethodCallExpr::cast(syntax_under_caret.clone())?; QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr) } }; @@ -101,12 +102,18 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option label(ctx.db(), candidate, &import, current_edition), range, |builder| { + let make = SyntaxFactory::with_mappings(); + let mut editor = builder.make_editor(&syntax_under_caret); qualify_candidate.qualify( |replace_with: String| builder.replace(range, replace_with), + &mut editor, + &make, &import.import_path, import.item_to_import, current_edition, - ) + ); + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ); } @@ -124,6 +131,8 @@ impl QualifyCandidate<'_> { pub(crate) fn qualify( &self, mut replacer: impl FnMut(String), + editor: &mut SyntaxEditor, + make: &SyntaxFactory, import: &hir::ModPath, item: hir::ItemInNs, edition: Edition, @@ -142,10 +151,10 @@ impl QualifyCandidate<'_> { replacer(format!("<{qualifier} as {import}>::{segment}")); } QualifyCandidate::TraitMethod(db, mcall_expr) => { - Self::qualify_trait_method(db, mcall_expr, replacer, import, item); + Self::qualify_trait_method(db, mcall_expr, editor, make, import, item); } QualifyCandidate::ImplMethod(db, mcall_expr, hir_fn) => { - Self::qualify_fn_call(db, mcall_expr, replacer, import, hir_fn); + Self::qualify_fn_call(db, mcall_expr, editor, make, import, hir_fn); } } } @@ -153,7 +162,8 @@ impl QualifyCandidate<'_> { fn qualify_fn_call( db: &RootDatabase, mcall_expr: &ast::MethodCallExpr, - mut replacer: impl FnMut(String), + editor: &mut SyntaxEditor, + make: &SyntaxFactory, import: ast::Path, hir_fn: &hir::Function, ) -> Option<()> { @@ -165,15 +175,18 @@ impl QualifyCandidate<'_> { if let Some(self_access) = hir_fn.self_param(db).map(|sp| sp.access(db)) { let receiver = match self_access { - hir::Access::Shared => make::expr_ref(receiver, false), - hir::Access::Exclusive => make::expr_ref(receiver, true), + hir::Access::Shared => make.expr_ref(receiver, false), + hir::Access::Exclusive => make.expr_ref(receiver, true), hir::Access::Owned => receiver, }; let arg_list = match arg_list { - Some(args) => make::arg_list(iter::once(receiver).chain(args)), - None => make::arg_list(iter::once(receiver)), + Some(args) => make.arg_list(iter::once(receiver).chain(args)), + None => make.arg_list(iter::once(receiver)), }; - replacer(format!("{import}::{method_name}{generics}{arg_list}")); + let call_path = + make.path_from_text(&format!("{import}::{method_name}{generics}")); + let call_expr = make.expr_call(make.expr_path(call_path), arg_list); + editor.replace(mcall_expr.syntax(), call_expr.syntax()); } Some(()) } @@ -181,14 +194,15 @@ impl QualifyCandidate<'_> { fn qualify_trait_method( db: &RootDatabase, mcall_expr: &ast::MethodCallExpr, - replacer: impl FnMut(String), + editor: &mut SyntaxEditor, + make: &SyntaxFactory, import: ast::Path, item: hir::ItemInNs, ) -> Option<()> { let trait_method_name = mcall_expr.name_ref()?; let trait_ = item_as_trait(db, item)?; let method = find_trait_method(db, trait_, &trait_method_name)?; - Self::qualify_fn_call(db, mcall_expr, replacer, import, &method) + Self::qualify_fn_call(db, mcall_expr, editor, make, import, &method) } } From 43078250c25e7ba08037ead98fa4bd51a3b76a1c Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 20 Mar 2026 10:12:31 +0530 Subject: [PATCH 073/101] remove direct make usage in add_label_to_loop via syntaxFactory --- .../ide-assists/src/handlers/add_label_to_loop.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs index b84ad24cfcef3..7e9552e1fd1d6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs @@ -3,11 +3,7 @@ use ide_db::{ }; use syntax::{ SyntaxToken, T, - ast::{ - self, AstNode, HasLoopBody, - make::{self, tokens}, - syntax_factory::SyntaxFactory, - }, + ast::{self, AstNode, HasLoopBody, syntax_factory::SyntaxFactory}, syntax_editor::{Position, SyntaxEditor}, }; @@ -52,8 +48,8 @@ pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> O let label = make.lifetime("'l"); let elements = vec![ label.syntax().clone().into(), - make::token(T![:]).into(), - tokens::single_space().into(), + make.token(T![:]).into(), + make.whitespace(" ").into(), ]; editor.insert_all(Position::before(&loop_kw), elements); @@ -88,7 +84,7 @@ fn insert_label_after_token( builder: &mut SourceChangeBuilder, ) { let label = make.lifetime("'l"); - let elements = vec![tokens::single_space().into(), label.syntax().clone().into()]; + let elements = vec![make.whitespace(" ").into(), label.syntax().clone().into()]; editor.insert_all(Position::after(token), elements); if let Some(cap) = ctx.config.snippet_cap { From 61be42c90c744ffb1fee8d7d330a3013f55b1eb8 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 20 Mar 2026 10:15:20 +0530 Subject: [PATCH 074/101] migrate desugar_try_expr to use syntax editor and syntax Factory --- .../src/handlers/desugar_try_expr.rs | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs index 9976e34e730cc..4022030159324 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_try_expr.rs @@ -9,7 +9,6 @@ use syntax::{ ast::{ self, edit::{AstNodeEdit, IndentLevel}, - make, syntax_factory::SyntaxFactory, }, }; @@ -68,41 +67,46 @@ pub(crate) fn desugar_try_expr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op AssistId::refactor_rewrite("desugar_try_expr_match"), "Replace try expression with match", target, - |edit| { + |builder| { + let make = SyntaxFactory::with_mappings(); + let mut editor = builder.make_editor(try_expr.syntax()); + let sad_pat = match try_enum { - TryEnum::Option => make::path_pat(make::ext::ident_path("None")), - TryEnum::Result => make::tuple_struct_pat( - make::ext::ident_path("Err"), - iter::once(make::path_pat(make::ext::ident_path("err"))), - ) - .into(), + TryEnum::Option => make.path_pat(make.ident_path("None")), + TryEnum::Result => make + .tuple_struct_pat( + make.ident_path("Err"), + iter::once(make.path_pat(make.ident_path("err"))), + ) + .into(), }; let sad_expr = match try_enum { - TryEnum::Option => { - make::expr_return(Some(make::expr_path(make::ext::ident_path("None")))) - } - TryEnum::Result => make::expr_return(Some( - make::expr_call( - make::expr_path(make::ext::ident_path("Err")), - make::arg_list(iter::once(make::expr_path(make::ext::ident_path("err")))), + TryEnum::Option => make.expr_return(Some(make.expr_path(make.ident_path("None")))), + TryEnum::Result => make.expr_return(Some( + make.expr_call( + make.expr_path(make.ident_path("Err")), + make.arg_list(iter::once(make.expr_path(make.ident_path("err")))), ) .into(), )), }; - let happy_arm = make::match_arm( - try_enum.happy_pattern(make::ident_pat(false, false, make::name("it")).into()), + let happy_arm = make.match_arm( + try_enum.happy_pattern(make.ident_pat(false, false, make.name("it")).into()), None, - make::expr_path(make::ext::ident_path("it")), + make.expr_path(make.ident_path("it")), ); - let sad_arm = make::match_arm(sad_pat, None, sad_expr); + let sad_arm = make.match_arm(sad_pat, None, sad_expr.into()); - let match_arm_list = make::match_arm_list([happy_arm, sad_arm]); + let match_arm_list = make.match_arm_list([happy_arm, sad_arm]); - let expr_match = make::expr_match(expr.clone(), match_arm_list) + let expr_match = make + .expr_match(expr.clone(), match_arm_list) .indent(IndentLevel::from_node(try_expr.syntax())); - edit.replace_ast::(try_expr.clone().into(), expr_match.into()); + editor.replace(try_expr.syntax(), expr_match.syntax()); + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ); From 345a80f03cf09ca8100699523e5ae92a0f0f2c23 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 20 Mar 2026 10:16:03 +0530 Subject: [PATCH 075/101] remove use make directly from extract_variable assist replace use of make directly via syntaxFactory in replace_let_with_if_let assist --- .../ide-assists/src/handlers/extract_variable.rs | 5 ++--- .../src/handlers/replace_let_with_if_let.rs | 12 +++++++----- .../syntax/src/ast/syntax_factory/constructors.rs | 8 ++++++++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs index 7c60184142176..092775596239b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs @@ -9,7 +9,6 @@ use syntax::{ ast::{ self, AstNode, edit::{AstNodeEdit, IndentLevel}, - make, syntax_factory::SyntaxFactory, }, syntax_editor::Position, @@ -176,7 +175,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let mut editor = edit.make_editor(&expr_replace); let pat_name = make.name(&var_name); - let name_expr = make.expr_path(make::ext::ident_path(&var_name)); + let name_expr = make.expr_path(make.ident_path(&var_name)); if let Some(cap) = ctx.config.snippet_cap { let tabstop = edit.make_tabstop_before(cap); @@ -233,7 +232,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op Position::before(place), vec![ new_stmt.syntax().clone().into(), - make::tokens::whitespace(&trailing_ws).into(), + make.whitespace(&trailing_ws).into(), ], ); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs index 5587f1b59c542..55e40d0fe7209 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs @@ -64,8 +64,11 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_> } } }; - let init_expr = - if let_expr_needs_paren(&init) { make.expr_paren(init).into() } else { init }; + let init_expr = if let_expr_needs_paren(&init, &make) { + make.expr_paren(init).into() + } else { + init + }; let block = make.block_expr([], None); let block = block.indent(IndentLevel::from_node(let_stmt.syntax())); @@ -85,9 +88,8 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_> ) } -fn let_expr_needs_paren(expr: &ast::Expr) -> bool { - let fake_expr_let = - ast::make::expr_let(ast::make::tuple_pat(None).into(), ast::make::ext::expr_unit()); +fn let_expr_needs_paren(expr: &ast::Expr, make: &SyntaxFactory) -> bool { + let fake_expr_let = make.expr_let(make.tuple_pat(None).into(), make.expr_unit()); let Some(fake_expr) = fake_expr_let.expr() else { stdx::never!(); return false; 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 d0f14dafe3f09..d496f131652bc 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 @@ -21,6 +21,14 @@ impl SyntaxFactory { make::name_ref(name).clone_for_update() } + pub fn name_ref_self_ty(&self) -> ast::NameRef { + make::name_ref_self_ty().clone_for_update() + } + + pub fn expr_todo(&self) -> ast::Expr { + make::ext::expr_todo().clone_for_update() + } + pub fn lifetime(&self, text: &str) -> ast::Lifetime { make::lifetime(text).clone_for_update() } From 6c8246976c57ddaaf53cbde09af4da2d8cb08131 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 20 Mar 2026 10:21:25 +0530 Subject: [PATCH 076/101] migrate qualify_method_call to use syntaxEditor and SyntaxFactory --- .../src/handlers/qualify_method_call.rs | 14 ++++++++++---- .../ide-assists/src/handlers/qualify_path.rs | 3 +-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs index 495a84d62b07d..8b9e6570e917b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs @@ -1,6 +1,6 @@ use hir::{AsAssocItem, AssocItem, AssocItemContainer, ItemInNs, ModuleDef, db::HirDatabase}; use ide_db::assists::AssistId; -use syntax::{AstNode, ast}; +use syntax::{AstNode, ast, ast::syntax_factory::SyntaxFactory}; use crate::{ assist_context::{AssistContext, Assists}, @@ -52,19 +52,25 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> cfg, )?; - let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call, resolved_call); + let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call.clone(), resolved_call); acc.add( AssistId::refactor_rewrite("qualify_method_call"), format!("Qualify `{ident}` method call"), range, |builder| { + let make = SyntaxFactory::with_mappings(); + let mut editor = builder.make_editor(call.syntax()); qualify_candidate.qualify( - |replace_with: String| builder.replace(range, replace_with), + |_| {}, + &mut editor, + &make, &receiver_path, item_in_ns, current_edition, - ) + ); + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ); Some(()) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs index 9ebeef90bae9e..c059f758c47e2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs @@ -183,8 +183,7 @@ impl QualifyCandidate<'_> { Some(args) => make.arg_list(iter::once(receiver).chain(args)), None => make.arg_list(iter::once(receiver)), }; - let call_path = - make.path_from_text(&format!("{import}::{method_name}{generics}")); + let call_path = make.path_from_text(&format!("{import}::{method_name}{generics}")); let call_expr = make.expr_call(make.expr_path(call_path), arg_list); editor.replace(mcall_expr.syntax(), call_expr.syntax()); } From eabc0199bebd995f11482db7309afa3b771cdff9 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 20 Mar 2026 10:47:54 +0530 Subject: [PATCH 077/101] remove mapping for expr underscore in syntax factory constructor --- .../crates/syntax/src/ast/syntax_factory/constructors.rs | 4 ---- 1 file changed, 4 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 d0f14dafe3f09..c94277f8cf05f 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 @@ -896,10 +896,6 @@ impl SyntaxFactory { unreachable!() }; - if let Some(mut mapping) = self.mappings() { - SyntaxMappingBuilder::new(ast.syntax().clone()).finish(&mut mapping); - } - ast } From ca1fc35595fa6db04fe84e7c1b44bc0c83465254 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 8 Aug 2025 19:15:14 +0800 Subject: [PATCH 078/101] Remove doc comments for generate_trait_from_impl **Input**: ```rust struct Foo(f64); impl F$0oo { /// Add `x` /// /// # Examples fn add(&mut self, x: f64) { self.0 += x; } } ``` **This PR**: ```rust struct Foo(f64); trait NewTrait { /// Add `x` /// /// # Examples fn add(&mut self, x: f64); } impl NewTrait for Foo { fn add(&mut self, x: f64) { self.0 += x; } } ``` **Old**: ```rust struct Foo(f64); trait NewTrait { /// Add `x` /// /// # Examples fn add(&mut self, x: f64); } impl NewTrait for Foo { /// Add `x` /// /// # Examples fn add(&mut self, x: f64) { self.0 += x; } } ``` --- .../src/handlers/generate_trait_from_impl.rs | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index 8bc4d50cf6af2..7e858cdc8725e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -1,8 +1,10 @@ use crate::assist_context::{AssistContext, Assists}; use ide_db::assists::AssistId; use syntax::{ - AstNode, SyntaxKind, T, - ast::{self, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, make}, + AstNode, AstToken, SyntaxKind, T, + ast::{ + self, HasDocComments, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, make, + }, syntax_editor::{Position, SyntaxEditor}, }; @@ -133,6 +135,7 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ let mut editor = builder.make_editor(impl_ast.syntax()); impl_assoc_items.assoc_items().for_each(|item| { remove_items_visibility(&mut editor, &item); + remove_doc_comments(&mut editor, &item); }); editor.insert_all(Position::before(impl_name.syntax()), elements); @@ -175,6 +178,17 @@ fn remove_items_visibility(editor: &mut SyntaxEditor, item: &ast::AssocItem) { } } +fn remove_doc_comments(editor: &mut SyntaxEditor, item: &ast::AssocItem) { + for doc in item.doc_comments() { + if let Some(next) = doc.syntax().next_token() + && next.kind() == SyntaxKind::WHITESPACE + { + editor.delete(next); + } + editor.delete(doc.syntax()); + } +} + fn strip_body(editor: &mut SyntaxEditor, item: &ast::AssocItem) { if let ast::AssocItem::Fn(f) = item && let Some(body) = f.body() @@ -238,6 +252,42 @@ impl NewTrait for Foo { ) } + #[test] + fn test_remove_doc_comments() { + check_assist_no_snippet_cap( + generate_trait_from_impl, + r#" +struct Foo(f64); + +impl F$0oo { + /// Add `x` + /// + /// # Examples + #[cfg(true)] + fn add(&mut self, x: f64) { + self.0 += x; + } +}"#, + r#" +struct Foo(f64); + +trait NewTrait { + /// Add `x` + /// + /// # Examples + #[cfg(true)] + fn add(&mut self, x: f64); +} + +impl NewTrait for Foo { + #[cfg(true)] + fn add(&mut self, x: f64) { + self.0 += x; + } +}"#, + ) + } + #[test] fn test_assoc_item_macro() { check_assist_no_snippet_cap( From c01af5a28553dac98e31104567e7852fe2de1a33 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 3 Sep 2025 22:27:56 +0800 Subject: [PATCH 079/101] Add `ops::AddAssign` implement for IndentLevel --- .../crates/ide-assists/src/handlers/unwrap_tuple.rs | 2 +- .../ide-diagnostics/src/handlers/remove_unnecessary_else.rs | 2 +- src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs index 2345075a52be6..e03274bbb3c9e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_tuple.rs @@ -33,7 +33,7 @@ pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option let (ty, init, prefix, suffix) = match &let_stmt { Either::Left(let_stmt) => (let_stmt.ty(), let_stmt.initializer()?, "", ";"), Either::Right(let_expr) => { - indent_level = indent_level + 1; + indent_level += 1; (None, let_expr.expr()?, "&& ", "") } }; diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs index 7dc5b5b45e5f4..04f48ae3db170 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs @@ -48,7 +48,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &RemoveUnnecessaryElse) -> Option block diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs index 9b30642fe4b08..194d06900a489 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs @@ -43,6 +43,12 @@ impl ops::Add for IndentLevel { } } +impl ops::AddAssign for IndentLevel { + fn add_assign(&mut self, rhs: u8) { + self.0 += rhs; + } +} + impl IndentLevel { pub fn single() -> IndentLevel { IndentLevel(0) From eee8bddebebf8058557b4417e065220fcc5c651b Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 24 Jul 2025 15:36:53 +0800 Subject: [PATCH 080/101] Add auto trait name for generate_trait_from_impl Auto generate trait name from first method. Input: ```rust impl S$0 { fn foo() {} } ``` Old output: ```rust trait NewTrait { fn foo(); } impl NewTrait for S { fn foo() {} } ``` This PR output: ```rust trait Foo { fn foo(); } impl Foo for S { fn foo() {} } ``` --- .../src/handlers/generate_trait_from_impl.rs | 30 ++++++++++++------- .../crates/ide-assists/src/tests/generated.rs | 4 +-- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index 7e858cdc8725e..9a8f3c6d8879c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -47,7 +47,7 @@ use syntax::{ // }; // } // -// trait ${0:NewTrait} { +// trait ${0:Create} { // // Used as an associated constant. // const CONST_ASSOC: usize = N * 4; // @@ -56,7 +56,7 @@ use syntax::{ // const_maker! {i32, 7} // } // -// impl ${0:NewTrait} for Foo { +// impl ${0:Create} for Foo { // // Used as an associated constant. // const CONST_ASSOC: usize = N * 4; // @@ -109,7 +109,7 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ }; let trait_ast = make::trait_( false, - "NewTrait", + &trait_name(&impl_assoc_items).text(), impl_ast.generic_param_list(), impl_ast.where_clause(), trait_items, @@ -163,6 +163,14 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ Some(()) } +fn trait_name(items: &ast::AssocItemList) -> ast::Name { + items + .assoc_items() + .find_map(|x| if let ast::AssocItem::Fn(f) = x { f.name() } else { None }) + .map(|name| make::name(&stdx::to_camel_case(&name.text()))) + .unwrap_or_else(|| make::name("NewTrait")) +} + /// `E0449` Trait items always share the visibility of their trait fn remove_items_visibility(editor: &mut SyntaxEditor, item: &ast::AssocItem) { if let Some(has_vis) = ast::AnyHasVisibility::cast(item.syntax().clone()) { @@ -240,11 +248,11 @@ impl F$0oo { r#" struct Foo(f64); -trait NewTrait { +trait Add { fn add(&mut self, x: f64); } -impl NewTrait for Foo { +impl Add for Foo { fn add(&mut self, x: f64) { self.0 += x; } @@ -271,7 +279,7 @@ impl F$0oo { r#" struct Foo(f64); -trait NewTrait { +trait Add { /// Add `x` /// /// # Examples @@ -279,7 +287,7 @@ trait NewTrait { fn add(&mut self, x: f64); } -impl NewTrait for Foo { +impl Add for Foo { #[cfg(true)] fn add(&mut self, x: f64) { self.0 += x; @@ -389,11 +397,11 @@ impl F$0oo { r#" struct Foo; -trait NewTrait { +trait AFunc { fn a_func() -> Option<()>; } -impl NewTrait for Foo { +impl AFunc for Foo { fn a_func() -> Option<()> { Some(()) } @@ -423,11 +431,11 @@ mod a { }"#, r#" mod a { - trait NewTrait { + trait Foo { fn foo(); } - impl NewTrait for S { + impl Foo for S { fn foo() {} } }"#, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs index 30405090002ab..8ba46799d5abd 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs @@ -2282,7 +2282,7 @@ macro_rules! const_maker { }; } -trait ${0:NewTrait} { +trait ${0:Create} { // Used as an associated constant. const CONST_ASSOC: usize = N * 4; @@ -2291,7 +2291,7 @@ trait ${0:NewTrait} { const_maker! {i32, 7} } -impl ${0:NewTrait} for Foo { +impl ${0:Create} for Foo { // Used as an associated constant. const CONST_ASSOC: usize = N * 4; From a307600f53ce040052b6c1162092be8c98c21798 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 20 Mar 2026 21:15:05 +0800 Subject: [PATCH 081/101] Do not suggest trait name on multi fn --- .../src/handlers/generate_trait_from_impl.rs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index 9a8f3c6d8879c..1286abe3565e2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -164,10 +164,14 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ } fn trait_name(items: &ast::AssocItemList) -> ast::Name { - items + let mut fn_names = items .assoc_items() - .find_map(|x| if let ast::AssocItem::Fn(f) = x { f.name() } else { None }) - .map(|name| make::name(&stdx::to_camel_case(&name.text()))) + .filter_map(|x| if let ast::AssocItem::Fn(f) = x { f.name() } else { None }); + fn_names + .next() + .and_then(|name| { + fn_names.next().is_none().then(|| make::name(&stdx::to_camel_case(&name.text()))) + }) .unwrap_or_else(|| make::name("NewTrait")) } @@ -442,6 +446,28 @@ mod a { ) } + #[test] + fn test_multi_fn_impl_not_suggest_trait_name() { + check_assist_no_snippet_cap( + generate_trait_from_impl, + r#" +impl S$0 { + fn foo() {} + fn bar() {} +}"#, + r#" +trait NewTrait { + fn foo(); + fn bar(); +} + +impl NewTrait for S { + fn foo() {} + fn bar() {} +}"#, + ) + } + #[test] fn test_snippet_cap_is_some() { check_assist( From ee041a376851cf346a53a03c6cee2ceb870bd43b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 21 Mar 2026 01:16:49 +0530 Subject: [PATCH 082/101] add without_mappings variant in let_expr_needs_paren --- .../src/handlers/replace_let_with_if_let.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs index 55e40d0fe7209..6ff5f0bbd30cf 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs @@ -64,11 +64,8 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_> } } }; - let init_expr = if let_expr_needs_paren(&init, &make) { - make.expr_paren(init).into() - } else { - init - }; + let init_expr = + if let_expr_needs_paren(&init) { make.expr_paren(init).into() } else { init }; let block = make.block_expr([], None); let block = block.indent(IndentLevel::from_node(let_stmt.syntax())); @@ -88,7 +85,8 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_> ) } -fn let_expr_needs_paren(expr: &ast::Expr, make: &SyntaxFactory) -> bool { +fn let_expr_needs_paren(expr: &ast::Expr) -> bool { + let make = SyntaxFactory::without_mappings(); let fake_expr_let = make.expr_let(make.tuple_pat(None).into(), make.expr_unit()); let Some(fake_expr) = fake_expr_let.expr() else { stdx::never!(); From 3ad92c9f23253aaae65174c84b2edae341762e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sat, 21 Mar 2026 02:24:36 +0000 Subject: [PATCH 083/101] On E0277 tweak help when single type impls traits When encountering an unmet predicate, when we point at the trait impls that do exist, if they are all for the same self type, tweak the wording to make it less verbose. --- .../traits/fulfillment_errors.rs | 48 ++++++++++++++++--- .../multiple-dep-versions.stderr | 6 +-- .../ui/const-generics/exhaustive-value.stderr | 18 +++---- ...e-21659-show-relevant-trait-impls-1.stderr | 6 +-- ...e-21659-show-relevant-trait-impls-2.stderr | 14 +++--- .../issue-39802-show-5-trait-impls.stderr | 36 +++++++------- tests/ui/indexing/index-help.stderr | 6 +-- .../indexing/indexing-integral-types.stderr | 48 +++++++++---------- .../indexing/indexing-requires-a-uint.stderr | 6 +-- .../invalid-iterator-chain-fixable.stderr | 18 +++---- ...valid-iterator-chain-with-int-infer.stderr | 6 +-- .../iterators/invalid-iterator-chain.stderr | 30 ++++++------ .../trailing-where-clause.stderr | 14 +++--- tests/ui/on-unimplemented/slice-index.stderr | 14 +++--- .../suggest_tuple_wrap_root_obligation.rs | 2 +- .../suggest_tuple_wrap_root_obligation.stderr | 8 ++-- tests/ui/on-unimplemented/sum.stderr | 12 ++--- tests/ui/str/str-idx.stderr | 18 +++---- tests/ui/str/str-mut-idx.stderr | 18 +++---- ...t-suggest-borrowing-existing-borrow.stderr | 14 +++--- tests/ui/suggestions/into-str.stderr | 14 +++--- .../partialeq_suggest_swap_on_e0277.stderr | 18 +++---- ...stion-when-stmt-and-expr-span-equal.stderr | 18 +++---- .../suggest-dereferencing-index.stderr | 6 +-- tests/ui/traits/explicit-reference-cast.rs | 2 +- .../ui/traits/explicit-reference-cast.stderr | 6 +-- .../repeated-supertrait-ambig.stderr | 18 +++---- ...rced_ambiguity-use-head-maybe-cause.stderr | 2 +- .../cycles/inductive-cycle-but-err.stderr | 2 +- ...valuated-const-impl-trait-ref.fails.stderr | 6 +-- .../question-mark-result-err-mismatch.rs | 2 +- .../question-mark-result-err-mismatch.stderr | 14 +++--- .../traits/question-mark-span-144304.stderr | 24 +++++----- tests/ui/try-trait/bad-interconversion.stderr | 6 +-- .../constrain_in_projection2.current.stderr | 12 ++--- .../nested-tait-inference2.current.stderr | 6 +-- tests/ui/typeck/issue-90101.stderr | 12 ++--- 37 files changed, 272 insertions(+), 238 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 6bf08170bcf7e..eeccf1eb0ecfa 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -2299,6 +2299,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // FIXME: this could use a better heuristic, like just checking // that args[1..] is the same. let all_traits_equal = traits.len() == 1; + let mut types: Vec<_> = + candidates.iter().map(|(c, _)| c.self_ty().to_string()).collect(); + types.sort(); + types.dedup(); + let all_types_equal = types.len() == 1; let end = if candidates.len() <= 9 || self.tcx.sess.opts.verbose { candidates.len() @@ -2312,6 +2317,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { for (c, def_id) in &candidates { let msg = if all_traits_equal { format!("`{}`", self.tcx.short_string(c.self_ty(), err.long_ty_path())) + } else if all_types_equal { + format!( + "`{}`", + self.tcx.short_string(c.print_only_trait_path(), err.long_ty_path()) + ) } else { format!( "`{}` implements `{}`", @@ -2321,13 +2331,19 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }; span.push_span_label(self.tcx.def_span(*def_id), msg); } - err.span_help( - span, + let msg = if all_types_equal { + format!( + "`{}` implements trait `{}`", + self.tcx.short_string(candidates[0].0.self_ty(), err.long_ty_path()), + self.tcx.short_string(trait_ref.print_trait_sugared(), err.long_ty_path()), + ) + } else { format!( "the following {other}types implement trait `{}`", - trait_ref.print_trait_sugared(), - ), - ); + self.tcx.short_string(trait_ref.print_trait_sugared(), err.long_ty_path()), + ) + }; + err.span_help(span, msg); } else { let candidate_names: Vec = candidates .iter() @@ -2337,6 +2353,12 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { "\n {}", self.tcx.short_string(c.self_ty(), err.long_ty_path()) ) + } else if all_types_equal { + format!( + "\n {}", + self.tcx + .short_string(c.print_only_trait_path(), err.long_ty_path()) + ) } else { format!( "\n `{}` implements `{}`", @@ -2347,9 +2369,21 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } }) .collect(); + let msg = if all_types_equal { + format!( + "`{}` implements trait `{}`", + self.tcx.short_string(candidates[0].0.self_ty(), err.long_ty_path()), + self.tcx.short_string(trait_ref.print_trait_sugared(), err.long_ty_path()), + ) + } else { + format!( + "the following {other}types implement trait `{}`", + self.tcx.short_string(trait_ref.print_trait_sugared(), err.long_ty_path()), + ) + }; + err.help(format!( - "the following {other}types implement trait `{}`:{}{}", - trait_ref.print_trait_sugared(), + "{msg}:{}{}", candidate_names[..end].join(""), if candidates.len() > 9 && !self.tcx.sess.opts.verbose { format!("\nand {} others", candidates.len() - 8) diff --git a/tests/run-make/crate-loading/multiple-dep-versions.stderr b/tests/run-make/crate-loading/multiple-dep-versions.stderr index ef7fb70822665..a683fdd389c6b 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions.stderr +++ b/tests/run-make/crate-loading/multiple-dep-versions.stderr @@ -177,14 +177,14 @@ LL | Err(Error2)?; | this can't be annotated with `?` because it has type `Result<_, Error2>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait -help: the following other types implement trait `From` +help: `dependency::OtherError` implements trait `From` --> replaced | LL | impl From<()> for OtherError { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `dependency::OtherError` implements `From<()>` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `From<()>` ... LL | impl From for OtherError { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `dependency::OtherError` implements `From` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `From` = note: there are multiple different versions of crate `dependency` in the dependency graph = help: you can use `cargo tree` to explore your dependency tree diff --git a/tests/ui/const-generics/exhaustive-value.stderr b/tests/ui/const-generics/exhaustive-value.stderr index 791bb19ffe866..3377f4cc4e11c 100644 --- a/tests/ui/const-generics/exhaustive-value.stderr +++ b/tests/ui/const-generics/exhaustive-value.stderr @@ -4,15 +4,15 @@ error[E0277]: the trait bound `(): Foo` is not satisfied LL | <() as Foo>::test() | ^^ the trait `Foo` is not implemented for `()` | - = help: the following other types implement trait `Foo`: - `()` implements `Foo<0>` - `()` implements `Foo<100>` - `()` implements `Foo<101>` - `()` implements `Foo<102>` - `()` implements `Foo<103>` - `()` implements `Foo<104>` - `()` implements `Foo<105>` - `()` implements `Foo<106>` + = help: `()` implements trait `Foo`: + Foo<0> + Foo<100> + Foo<101> + Foo<102> + Foo<103> + Foo<104> + Foo<105> + Foo<106> and 248 others error: aborting due to 1 previous error diff --git a/tests/ui/did_you_mean/issue-21659-show-relevant-trait-impls-1.stderr b/tests/ui/did_you_mean/issue-21659-show-relevant-trait-impls-1.stderr index 4a38546e2a88f..0c07e5afb6712 100644 --- a/tests/ui/did_you_mean/issue-21659-show-relevant-trait-impls-1.stderr +++ b/tests/ui/did_you_mean/issue-21659-show-relevant-trait-impls-1.stderr @@ -11,14 +11,14 @@ help: the trait `Foo` is not implemented for `Bar` | LL | struct Bar; | ^^^^^^^^^^ -help: the following other types implement trait `Foo` +help: `Bar` implements trait `Foo` --> $DIR/issue-21659-show-relevant-trait-impls-1.rs:15:1 | LL | impl Foo for Bar {} - | ^^^^^^^^^^^^^^^^^^^^^ `Bar` implements `Foo` + | ^^^^^^^^^^^^^^^^^^^^^ `Foo` LL | LL | impl Foo for Bar {} - | ^^^^^^^^^^^^^^^^^^^^ `Bar` implements `Foo` + | ^^^^^^^^^^^^^^^^^^^^ `Foo` error: aborting due to 1 previous error diff --git a/tests/ui/did_you_mean/issue-21659-show-relevant-trait-impls-2.stderr b/tests/ui/did_you_mean/issue-21659-show-relevant-trait-impls-2.stderr index 8d1c05e7b544c..cb4ca9082b44c 100644 --- a/tests/ui/did_you_mean/issue-21659-show-relevant-trait-impls-2.stderr +++ b/tests/ui/did_you_mean/issue-21659-show-relevant-trait-impls-2.stderr @@ -11,13 +11,13 @@ help: the trait `Foo` is not implemented for `Bar` | LL | struct Bar; | ^^^^^^^^^^ - = help: the following other types implement trait `Foo`: - `Bar` implements `Foo` - `Bar` implements `Foo` - `Bar` implements `Foo` - `Bar` implements `Foo` - `Bar` implements `Foo` - `Bar` implements `Foo` + = help: `Bar` implements trait `Foo`: + Foo + Foo + Foo + Foo + Foo + Foo error: aborting due to 1 previous error diff --git a/tests/ui/did_you_mean/issue-39802-show-5-trait-impls.stderr b/tests/ui/did_you_mean/issue-39802-show-5-trait-impls.stderr index 8bacae9e96a70..4bb816bdb80ed 100644 --- a/tests/ui/did_you_mean/issue-39802-show-5-trait-impls.stderr +++ b/tests/ui/did_you_mean/issue-39802-show-5-trait-impls.stderr @@ -6,12 +6,12 @@ LL | Foo::::bar(&1i8); | | | required by a bound introduced by this call | - = help: the following other types implement trait `Foo`: - `i8` implements `Foo` - `i8` implements `Foo` - `i8` implements `Foo` - `i8` implements `Foo` - `i8` implements `Foo` + = help: `i8` implements trait `Foo`: + Foo + Foo + Foo + Foo + Foo error[E0277]: the trait bound `u8: Foo` is not satisfied --> $DIR/issue-39802-show-5-trait-impls.rs:25:21 @@ -21,17 +21,17 @@ LL | Foo::::bar(&1u8); | | | required by a bound introduced by this call | -help: the following other types implement trait `Foo` +help: `u8` implements trait `Foo` --> $DIR/issue-39802-show-5-trait-impls.rs:11:1 | LL | impl Foo for u8 {} - | ^^^^^^^^^^^^^^^^^^^^ `u8` implements `Foo` + | ^^^^^^^^^^^^^^^^^^^^ `Foo` LL | impl Foo for u8 {} - | ^^^^^^^^^^^^^^^^^^^^ `u8` implements `Foo` + | ^^^^^^^^^^^^^^^^^^^^ `Foo` LL | impl Foo for u8 {} - | ^^^^^^^^^^^^^^^^^^^^ `u8` implements `Foo` + | ^^^^^^^^^^^^^^^^^^^^ `Foo` LL | impl Foo for u8 {} - | ^^^^^^^^^^^^^^^^^^^^^ `u8` implements `Foo` + | ^^^^^^^^^^^^^^^^^^^^^ `Foo` error[E0277]: the trait bound `bool: Foo` is not satisfied --> $DIR/issue-39802-show-5-trait-impls.rs:26:21 @@ -41,13 +41,13 @@ LL | Foo::::bar(&true); | | | required by a bound introduced by this call | - = help: the following other types implement trait `Foo`: - `bool` implements `Foo` - `bool` implements `Foo` - `bool` implements `Foo` - `bool` implements `Foo` - `bool` implements `Foo` - `bool` implements `Foo` + = help: `bool` implements trait `Foo`: + Foo + Foo + Foo + Foo + Foo + Foo error: aborting due to 3 previous errors diff --git a/tests/ui/indexing/index-help.stderr b/tests/ui/indexing/index-help.stderr index 8fc91c1bf3952..65faaec41258e 100644 --- a/tests/ui/indexing/index-help.stderr +++ b/tests/ui/indexing/index-help.stderr @@ -5,13 +5,13 @@ LL | x[0i32]; | ^^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[{integer}]>` is not implemented for `i32` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `Vec<{integer}>` to implement `Index` error: aborting due to 1 previous error diff --git a/tests/ui/indexing/indexing-integral-types.stderr b/tests/ui/indexing/indexing-integral-types.stderr index 3dfc5f5e26e07..604161325afcb 100644 --- a/tests/ui/indexing/indexing-integral-types.stderr +++ b/tests/ui/indexing/indexing-integral-types.stderr @@ -5,13 +5,13 @@ LL | v[3u8]; | ^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[isize]>` is not implemented for `u8` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `Vec` to implement `Index` error[E0277]: the type `[isize]` cannot be indexed by `i8` @@ -21,13 +21,13 @@ LL | v[3i8]; | ^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[isize]>` is not implemented for `i8` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `Vec` to implement `Index` error[E0277]: the type `[isize]` cannot be indexed by `u32` @@ -37,13 +37,13 @@ LL | v[3u32]; | ^^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[isize]>` is not implemented for `u32` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `Vec` to implement `Index` error[E0277]: the type `[isize]` cannot be indexed by `i32` @@ -53,13 +53,13 @@ LL | v[3i32]; | ^^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[isize]>` is not implemented for `i32` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `Vec` to implement `Index` error[E0277]: the type `[u8]` cannot be indexed by `u8` @@ -69,13 +69,13 @@ LL | s.as_bytes()[3u8]; | ^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[u8]>` is not implemented for `u8` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `[u8]` to implement `Index` error[E0277]: the type `[u8]` cannot be indexed by `i8` @@ -85,13 +85,13 @@ LL | s.as_bytes()[3i8]; | ^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[u8]>` is not implemented for `i8` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `[u8]` to implement `Index` error[E0277]: the type `[u8]` cannot be indexed by `u32` @@ -101,13 +101,13 @@ LL | s.as_bytes()[3u32]; | ^^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[u8]>` is not implemented for `u32` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `[u8]` to implement `Index` error[E0277]: the type `[u8]` cannot be indexed by `i32` @@ -117,13 +117,13 @@ LL | s.as_bytes()[3i32]; | ^^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[u8]>` is not implemented for `i32` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `[u8]` to implement `Index` error: aborting due to 8 previous errors diff --git a/tests/ui/indexing/indexing-requires-a-uint.stderr b/tests/ui/indexing/indexing-requires-a-uint.stderr index e5f9f7854f7b4..9e974c8c9bbcc 100644 --- a/tests/ui/indexing/indexing-requires-a-uint.stderr +++ b/tests/ui/indexing/indexing-requires-a-uint.stderr @@ -5,13 +5,13 @@ LL | [0][0u8]; | ^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[{integer}]>` is not implemented for `u8` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `[{integer}]` to implement `Index` error[E0308]: mismatched types diff --git a/tests/ui/iterators/invalid-iterator-chain-fixable.stderr b/tests/ui/iterators/invalid-iterator-chain-fixable.stderr index 99bff6b450b56..f22fc2143edf2 100644 --- a/tests/ui/iterators/invalid-iterator-chain-fixable.stderr +++ b/tests/ui/iterators/invalid-iterator-chain-fixable.stderr @@ -33,13 +33,13 @@ LL | println!("{}", scores.sum::()); | required by a bound introduced by this call | = help: the trait `Sum<()>` is not implemented for `i32` -help: the following other types implement trait `Sum` +help: `i32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum<&i32>` + = note: `Sum<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation @@ -74,13 +74,13 @@ LL | .sum::(), | required by a bound introduced by this call | = help: the trait `Sum<()>` is not implemented for `i32` -help: the following other types implement trait `Sum` +help: `i32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum<&i32>` + = note: `Sum<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation @@ -115,13 +115,13 @@ LL | println!("{}", vec![0, 1].iter().map(|x| { x; }).sum::()); | required by a bound introduced by this call | = help: the trait `Sum<()>` is not implemented for `i32` -help: the following other types implement trait `Sum` +help: `i32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum<&i32>` + = note: `Sum<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation diff --git a/tests/ui/iterators/invalid-iterator-chain-with-int-infer.stderr b/tests/ui/iterators/invalid-iterator-chain-with-int-infer.stderr index c94b716e3131f..82269f6253a36 100644 --- a/tests/ui/iterators/invalid-iterator-chain-with-int-infer.stderr +++ b/tests/ui/iterators/invalid-iterator-chain-with-int-infer.stderr @@ -7,13 +7,13 @@ LL | let x = Some(()).iter().map(|()| 1).sum::(); | required by a bound introduced by this call | = help: the trait `Sum<{integer}>` is not implemented for `f32` -help: the following other types implement trait `Sum` +help: `f32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `f32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `f32` implements `Sum<&f32>` + = note: `Sum<&f32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation diff --git a/tests/ui/iterators/invalid-iterator-chain.stderr b/tests/ui/iterators/invalid-iterator-chain.stderr index 7f0c154e255ad..d58fe438a6271 100644 --- a/tests/ui/iterators/invalid-iterator-chain.stderr +++ b/tests/ui/iterators/invalid-iterator-chain.stderr @@ -33,13 +33,13 @@ LL | println!("{}", scores.sum::()); | required by a bound introduced by this call | = help: the trait `Sum<()>` is not implemented for `i32` -help: the following other types implement trait `Sum` +help: `i32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum<&i32>` + = note: `Sum<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation @@ -73,13 +73,13 @@ LL | .sum::(), | required by a bound introduced by this call | = help: the trait `Sum<()>` is not implemented for `i32` -help: the following other types implement trait `Sum` +help: `i32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum<&i32>` + = note: `Sum<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation @@ -120,13 +120,13 @@ LL | .sum::(), | required by a bound introduced by this call | = help: the trait `Sum` is not implemented for `i32` -help: the following other types implement trait `Sum` +help: `i32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum<&i32>` + = note: `Sum<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation @@ -158,13 +158,13 @@ LL | println!("{}", vec![0, 1].iter().map(|x| { x; }).sum::()); | required by a bound introduced by this call | = help: the trait `Sum<()>` is not implemented for `i32` -help: the following other types implement trait `Sum` +help: `i32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum<&i32>` + = note: `Sum<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation @@ -194,13 +194,13 @@ LL | println!("{}", vec![(), ()].iter().sum::()); | required by a bound introduced by this call | = help: the trait `Sum<&()>` is not implemented for `i32` -help: the following other types implement trait `Sum` +help: `i32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum<&i32>` + = note: `Sum<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation diff --git a/tests/ui/lazy-type-alias/trailing-where-clause.stderr b/tests/ui/lazy-type-alias/trailing-where-clause.stderr index 93cd3145928b7..170bc3bca78d3 100644 --- a/tests/ui/lazy-type-alias/trailing-where-clause.stderr +++ b/tests/ui/lazy-type-alias/trailing-where-clause.stderr @@ -4,13 +4,13 @@ error[E0277]: the trait bound `String: From<()>` is not satisfied LL | let _: Alias<()>; | ^^ the trait `From<()>` is not implemented for `String` | - = help: the following other types implement trait `From`: - `String` implements `From<&String>` - `String` implements `From<&mut str>` - `String` implements `From<&str>` - `String` implements `From>` - `String` implements `From>` - `String` implements `From` + = help: `String` implements trait `From`: + From<&String> + From<&mut str> + From<&str> + From> + From> + From note: required by a bound in `Alias` --> $DIR/trailing-where-clause.rs:8:13 | diff --git a/tests/ui/on-unimplemented/slice-index.stderr b/tests/ui/on-unimplemented/slice-index.stderr index 67b72bd038d80..61d4866f5618b 100644 --- a/tests/ui/on-unimplemented/slice-index.stderr +++ b/tests/ui/on-unimplemented/slice-index.stderr @@ -5,13 +5,13 @@ LL | x[1i32]; | ^^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[i32]>` is not implemented for `i32` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `[i32]` to implement `Index` error[E0277]: the type `[i32]` cannot be indexed by `RangeTo` @@ -21,19 +21,19 @@ LL | x[..1i32]; | ^^^^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[i32]>` is not implemented for `RangeTo` -help: the following other types implement trait `SliceIndex` +help: `RangeTo` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `RangeTo` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `RangeTo` implements `SliceIndex` + = note: `SliceIndex` ::: $SRC_DIR/core/src/bstr/traits.rs:LL:COL | = note: in this macro invocation --> $SRC_DIR/core/src/str/traits.rs:LL:COL | - = note: `RangeTo` implements `SliceIndex` + = note: `SliceIndex` = note: required for `[i32]` to implement `Index>` = note: this error originates in the macro `impl_slice_index` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.rs b/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.rs index aaaf4d3b2e112..2c898633b5e41 100644 --- a/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.rs +++ b/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.rs @@ -5,7 +5,7 @@ impl From<(u8,)> for Tuple { todo!() } } -impl From<(u8, u8)> for Tuple { //~ HELP the following other types implement trait `From` +impl From<(u8, u8)> for Tuple { //~ HELP `Tuple` implements trait `From` fn from(_: (u8, u8)) -> Self { todo!() } diff --git a/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.stderr b/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.stderr index cf15ac1f279b1..f07db05abad6b 100644 --- a/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.stderr +++ b/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.stderr @@ -11,17 +11,17 @@ help: the trait `From` is not implemented for `Tuple` | LL | struct Tuple; | ^^^^^^^^^^^^ -help: the following other types implement trait `From` +help: `Tuple` implements trait `From` --> $DIR/suggest_tuple_wrap_root_obligation.rs:3:1 | LL | impl From<(u8,)> for Tuple { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `Tuple` implements `From<(u8,)>` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `From<(u8,)>` ... LL | impl From<(u8, u8)> for Tuple { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Tuple` implements `From<(u8, u8)>` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `From<(u8, u8)>` ... LL | impl From<(u8, u8, u8)> for Tuple { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Tuple` implements `From<(u8, u8, u8)>` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `From<(u8, u8, u8)>` = note: required for `u8` to implement `Into` note: required by a bound in `convert_into_tuple` --> $DIR/suggest_tuple_wrap_root_obligation.rs:19:32 diff --git a/tests/ui/on-unimplemented/sum.stderr b/tests/ui/on-unimplemented/sum.stderr index 5e82948352f75..56257079bd314 100644 --- a/tests/ui/on-unimplemented/sum.stderr +++ b/tests/ui/on-unimplemented/sum.stderr @@ -7,13 +7,13 @@ LL | vec![(), ()].iter().sum::(); | required by a bound introduced by this call | = help: the trait `Sum<&()>` is not implemented for `i32` -help: the following other types implement trait `Sum` +help: `i32` implements trait `Sum` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum` + = note: `Sum` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Sum<&i32>` + = note: `Sum<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation @@ -37,13 +37,13 @@ LL | vec![(), ()].iter().product::(); | required by a bound introduced by this call | = help: the trait `Product<&()>` is not implemented for `i32` -help: the following other types implement trait `Product` +help: `i32` implements trait `Product` --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Product` + = note: `Product` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | - = note: `i32` implements `Product<&i32>` + = note: `Product<&i32>` ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL | = note: in this macro invocation diff --git a/tests/ui/str/str-idx.stderr b/tests/ui/str/str-idx.stderr index 97a083ba8ba85..84f698a6e6625 100644 --- a/tests/ui/str/str-idx.stderr +++ b/tests/ui/str/str-idx.stderr @@ -7,13 +7,13 @@ LL | let _: u8 = s[4]; = help: the trait `SliceIndex` is not implemented for `{integer}` = note: you can use `.chars().nth()` or `.bytes().nth()` for more information, see chapter 8 in The Book: -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `str` to implement `Index<{integer}>` error[E0277]: the type `str` cannot be indexed by `{integer}` @@ -27,13 +27,13 @@ LL | let _ = s.get(4); = help: the trait `SliceIndex` is not implemented for `{integer}` = note: you can use `.chars().nth()` or `.bytes().nth()` for more information, see chapter 8 in The Book: -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` note: required by a bound in `core::str::::get` --> $SRC_DIR/core/src/str/mod.rs:LL:COL @@ -48,13 +48,13 @@ LL | let _ = s.get_unchecked(4); = help: the trait `SliceIndex` is not implemented for `{integer}` = note: you can use `.chars().nth()` or `.bytes().nth()` for more information, see chapter 8 in The Book: -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` note: required by a bound in `core::str::::get_unchecked` --> $SRC_DIR/core/src/str/mod.rs:LL:COL diff --git a/tests/ui/str/str-mut-idx.stderr b/tests/ui/str/str-mut-idx.stderr index c9bd66dfbc8aa..87b78915075d2 100644 --- a/tests/ui/str/str-mut-idx.stderr +++ b/tests/ui/str/str-mut-idx.stderr @@ -31,13 +31,13 @@ LL | s[1usize] = bot(); | ^^^^^^ string indices are ranges of `usize` | = help: the trait `SliceIndex` is not implemented for `usize` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `str` to implement `Index` error[E0277]: the type `str` cannot be indexed by `{integer}` @@ -51,13 +51,13 @@ LL | s.get_mut(1); = help: the trait `SliceIndex` is not implemented for `{integer}` = note: you can use `.chars().nth()` or `.bytes().nth()` for more information, see chapter 8 in The Book: -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` note: required by a bound in `core::str::::get_mut` --> $SRC_DIR/core/src/str/mod.rs:LL:COL @@ -72,13 +72,13 @@ LL | s.get_unchecked_mut(1); = help: the trait `SliceIndex` is not implemented for `{integer}` = note: you can use `.chars().nth()` or `.bytes().nth()` for more information, see chapter 8 in The Book: -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` note: required by a bound in `core::str::::get_unchecked_mut` --> $SRC_DIR/core/src/str/mod.rs:LL:COL diff --git a/tests/ui/suggestions/dont-suggest-borrowing-existing-borrow.stderr b/tests/ui/suggestions/dont-suggest-borrowing-existing-borrow.stderr index 5eb64c45f9d7f..836d31f81541c 100644 --- a/tests/ui/suggestions/dont-suggest-borrowing-existing-borrow.stderr +++ b/tests/ui/suggestions/dont-suggest-borrowing-existing-borrow.stderr @@ -4,13 +4,13 @@ error[E0277]: the trait bound `str: From<_>` is not satisfied LL | let _ = &str::from("value"); | ^^^ the trait `From<_>` is not implemented for `str` | - = help: the following other types implement trait `From`: - `String` implements `From<&String>` - `String` implements `From<&mut str>` - `String` implements `From<&str>` - `String` implements `From>` - `String` implements `From>` - `String` implements `From` + = help: `String` implements trait `From`: + From<&String> + From<&mut str> + From<&str> + From> + From> + From help: you likely meant to call the associated function `from` for type `&str`, but the code as written calls associated function `from` on type `str` | LL | let _ = <&str>::from("value"); diff --git a/tests/ui/suggestions/into-str.stderr b/tests/ui/suggestions/into-str.stderr index d02d318608297..cfb8f33bfcef3 100644 --- a/tests/ui/suggestions/into-str.stderr +++ b/tests/ui/suggestions/into-str.stderr @@ -7,13 +7,13 @@ LL | foo(String::new()); | required by a bound introduced by this call | = note: to coerce a `String` into a `&str`, use `&*` as a prefix - = help: the following other types implement trait `From`: - `String` implements `From<&String>` - `String` implements `From<&mut str>` - `String` implements `From<&str>` - `String` implements `From>` - `String` implements `From>` - `String` implements `From` + = help: `String` implements trait `From`: + From<&String> + From<&mut str> + From<&str> + From> + From> + From = note: required for `String` to implement `Into<&str>` note: required by a bound in `foo` --> $DIR/into-str.rs:1:31 diff --git a/tests/ui/suggestions/partialeq_suggest_swap_on_e0277.stderr b/tests/ui/suggestions/partialeq_suggest_swap_on_e0277.stderr index c5984f53f68b5..29e1c6b8a0a32 100644 --- a/tests/ui/suggestions/partialeq_suggest_swap_on_e0277.stderr +++ b/tests/ui/suggestions/partialeq_suggest_swap_on_e0277.stderr @@ -5,15 +5,15 @@ LL | String::from("Girls Band Cry") == T(String::from("Girls Band Cry")); | ^^ no implementation for `String == T` | = help: the trait `PartialEq` is not implemented for `String` - = help: the following other types implement trait `PartialEq`: - `String` implements `PartialEq<&str>` - `String` implements `PartialEq` - `String` implements `PartialEq` - `String` implements `PartialEq>` - `String` implements `PartialEq` - `String` implements `PartialEq` - `String` implements `PartialEq` - `String` implements `PartialEq` + = help: `String` implements trait `PartialEq`: + PartialEq<&str> + PartialEq + PartialEq + PartialEq> + PartialEq + PartialEq + PartialEq + PartialEq = note: `T` implements `PartialEq` help: consider swapping the equality | diff --git a/tests/ui/suggestions/semi-suggestion-when-stmt-and-expr-span-equal.stderr b/tests/ui/suggestions/semi-suggestion-when-stmt-and-expr-span-equal.stderr index 9f34d27478814..6dd4b1e5d5110 100644 --- a/tests/ui/suggestions/semi-suggestion-when-stmt-and-expr-span-equal.stderr +++ b/tests/ui/suggestions/semi-suggestion-when-stmt-and-expr-span-equal.stderr @@ -20,15 +20,15 @@ LL | .collect::(); | required by a bound introduced by this call | = help: the trait `FromIterator<()>` is not implemented for `String` - = help: the following other types implement trait `FromIterator`: - `String` implements `FromIterator<&char>` - `String` implements `FromIterator<&std::ascii::Char>` - `String` implements `FromIterator<&str>` - `String` implements `FromIterator>` - `String` implements `FromIterator>` - `String` implements `FromIterator` - `String` implements `FromIterator` - `String` implements `FromIterator` + = help: `String` implements trait `FromIterator`: + FromIterator<&char> + FromIterator<&std::ascii::Char> + FromIterator<&str> + FromIterator> + FromIterator> + FromIterator + FromIterator + FromIterator note: the method call chain might not have had the expected associated types --> $DIR/semi-suggestion-when-stmt-and-expr-span-equal.rs:20:10 | diff --git a/tests/ui/suggestions/suggest-dereferencing-index.stderr b/tests/ui/suggestions/suggest-dereferencing-index.stderr index 541c625ebb10b..cee5ffcb2ae8c 100644 --- a/tests/ui/suggestions/suggest-dereferencing-index.stderr +++ b/tests/ui/suggestions/suggest-dereferencing-index.stderr @@ -5,13 +5,13 @@ LL | let one_item_please: i32 = [1, 2, 3][i]; | ^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `SliceIndex<[{integer}]>` is not implemented for `&usize` -help: the following other types implement trait `SliceIndex` +help: `usize` implements trait `SliceIndex` --> $SRC_DIR/core/src/slice/index.rs:LL:COL | - = note: `usize` implements `SliceIndex<[T]>` + = note: `SliceIndex<[T]>` --> $SRC_DIR/core/src/bstr/traits.rs:LL:COL | - = note: `usize` implements `SliceIndex` + = note: `SliceIndex` = note: required for `[{integer}]` to implement `Index<&usize>` help: dereference this index | diff --git a/tests/ui/traits/explicit-reference-cast.rs b/tests/ui/traits/explicit-reference-cast.rs index efb4f10bea67e..b9ccc1827a555 100644 --- a/tests/ui/traits/explicit-reference-cast.rs +++ b/tests/ui/traits/explicit-reference-cast.rs @@ -7,7 +7,7 @@ pub struct ToolA(PathBuf); //~^ HELP the trait `From<&PathBuf>` is not implemented for `ToolA` impl From<&Path> for ToolA { - //~^ HELP the following other types implement trait `From` + //~^ HELP `ToolA` implements trait `From` fn from(p: &Path) -> ToolA { ToolA(p.to_path_buf()) } diff --git a/tests/ui/traits/explicit-reference-cast.stderr b/tests/ui/traits/explicit-reference-cast.stderr index 924de3d5bbe3b..78eb25b0243de 100644 --- a/tests/ui/traits/explicit-reference-cast.stderr +++ b/tests/ui/traits/explicit-reference-cast.stderr @@ -10,14 +10,14 @@ help: the trait `From<&PathBuf>` is not implemented for `ToolA` | LL | pub struct ToolA(PathBuf); | ^^^^^^^^^^^^^^^^ -help: the following other types implement trait `From` +help: `ToolA` implements trait `From` --> $DIR/explicit-reference-cast.rs:9:1 | LL | impl From<&Path> for ToolA { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `ToolA` implements `From<&Path>` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `From<&Path>` ... LL | impl From<&str> for ToolA { - | ^^^^^^^^^^^^^^^^^^^^^^^^^ `ToolA` implements `From<&str>` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ `From<&str>` error[E0277]: the trait bound `ToolB: TryFrom<&PathBuf>` is not satisfied --> $DIR/explicit-reference-cast.rs:43:13 diff --git a/tests/ui/traits/inheritance/repeated-supertrait-ambig.stderr b/tests/ui/traits/inheritance/repeated-supertrait-ambig.stderr index e58f5c3fe90f2..9b27491f5046c 100644 --- a/tests/ui/traits/inheritance/repeated-supertrait-ambig.stderr +++ b/tests/ui/traits/inheritance/repeated-supertrait-ambig.stderr @@ -6,14 +6,14 @@ LL | c.same_as(22) | | | required by a bound introduced by this call | -help: the following other types implement trait `CompareTo` +help: `i64` implements trait `CompareTo` --> $DIR/repeated-supertrait-ambig.rs:15:1 | LL | impl CompareTo for i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `i64` implements `CompareTo` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `CompareTo` ... LL | impl CompareTo for i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `i64` implements `CompareTo` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `CompareTo` error[E0277]: the trait bound `C: CompareTo` is not satisfied --> $DIR/repeated-supertrait-ambig.rs:30:15 @@ -34,14 +34,14 @@ error[E0277]: the trait bound `dyn CompareToInts: CompareTo` is not satisfi LL | ::same_as(c, 22) | ^^^^^^^^^^^^^^^^^ the trait `CompareTo` is not implemented for `dyn CompareToInts` | -help: the following other types implement trait `CompareTo` +help: `i64` implements trait `CompareTo` --> $DIR/repeated-supertrait-ambig.rs:15:1 | LL | impl CompareTo for i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `i64` implements `CompareTo` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `CompareTo` ... LL | impl CompareTo for i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `i64` implements `CompareTo` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `CompareTo` error[E0277]: the trait bound `C: CompareTo` is not satisfied --> $DIR/repeated-supertrait-ambig.rs:38:24 @@ -64,14 +64,14 @@ LL | assert_eq!(22_i64.same_as(22), true); | | | required by a bound introduced by this call | -help: the following other types implement trait `CompareTo` +help: `i64` implements trait `CompareTo` --> $DIR/repeated-supertrait-ambig.rs:15:1 | LL | impl CompareTo for i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `i64` implements `CompareTo` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `CompareTo` ... LL | impl CompareTo for i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `i64` implements `CompareTo` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `CompareTo` error: aborting due to 5 previous errors diff --git a/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.stderr b/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.stderr index 42b76a8f7ff67..04a4c70c6d7bf 100644 --- a/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.stderr +++ b/tests/ui/traits/next-solver/cycles/forced_ambiguity-use-head-maybe-cause.stderr @@ -5,7 +5,7 @@ LL | impls_trait::>() | ^^^^^^^ cannot infer type for struct `Head<_>` | = note: cannot satisfy `Head<_>: Trait` -help: the following types implement trait `Trait` +help: `Head` implements trait `Trait` --> $DIR/forced_ambiguity-use-head-maybe-cause.rs:23:1 | LL | / impl Trait for Head diff --git a/tests/ui/traits/next-solver/cycles/inductive-cycle-but-err.stderr b/tests/ui/traits/next-solver/cycles/inductive-cycle-but-err.stderr index 8b24e682c769f..cea95eb365e6a 100644 --- a/tests/ui/traits/next-solver/cycles/inductive-cycle-but-err.stderr +++ b/tests/ui/traits/next-solver/cycles/inductive-cycle-but-err.stderr @@ -21,7 +21,7 @@ help: the trait `Trait` is not implemented for `MultipleCandidates` | LL | struct MultipleCandidates; | ^^^^^^^^^^^^^^^^^^^^^^^^^ -help: the following other types implement trait `Trait` +help: `MultipleCandidates` implements trait `Trait` --> $DIR/inductive-cycle-but-err.rs:26:1 | LL | / impl Trait for MultipleCandidates diff --git a/tests/ui/traits/next-solver/unevaluated-const-impl-trait-ref.fails.stderr b/tests/ui/traits/next-solver/unevaluated-const-impl-trait-ref.fails.stderr index 8202b6ecb5d60..ce68c2f435fa3 100644 --- a/tests/ui/traits/next-solver/unevaluated-const-impl-trait-ref.fails.stderr +++ b/tests/ui/traits/next-solver/unevaluated-const-impl-trait-ref.fails.stderr @@ -4,13 +4,13 @@ error[E0277]: the trait bound `(): Trait<1>` is not satisfied LL | needs::<1>(); | ^ the trait `Trait<1>` is not implemented for `()` | -help: the following other types implement trait `Trait` +help: `()` implements trait `Trait` --> $DIR/unevaluated-const-impl-trait-ref.rs:7:1 | LL | impl Trait<{ 1 - 1 }> for () {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `()` implements `Trait<0>` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Trait<0>` LL | impl Trait<{ 1 + 1 }> for () {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `()` implements `Trait<2>` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Trait<2>` note: required by a bound in `needs` --> $DIR/unevaluated-const-impl-trait-ref.rs:10:38 | diff --git a/tests/ui/traits/question-mark-result-err-mismatch.rs b/tests/ui/traits/question-mark-result-err-mismatch.rs index dfea4b93f46da..f9ca6e0ab4483 100644 --- a/tests/ui/traits/question-mark-result-err-mismatch.rs +++ b/tests/ui/traits/question-mark-result-err-mismatch.rs @@ -35,7 +35,7 @@ fn bar() -> Result<(), String> { //~ NOTE expected `String` because of this //~| NOTE the trait `From<()>` is not implemented for `String` //~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait //~| NOTE required for `Result<(), String>` to implement `FromResidual>` - //~| HELP the following other types implement trait `From`: + //~| HELP `String` implements trait `From`: Ok(one) } diff --git a/tests/ui/traits/question-mark-result-err-mismatch.stderr b/tests/ui/traits/question-mark-result-err-mismatch.stderr index be3f17cfc5274..3739b508a8689 100644 --- a/tests/ui/traits/question-mark-result-err-mismatch.stderr +++ b/tests/ui/traits/question-mark-result-err-mismatch.stderr @@ -30,13 +30,13 @@ LL | .map_err(|_| ())?; | this can't be annotated with `?` because it has type `Result<_, ()>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait - = help: the following other types implement trait `From`: - `String` implements `From<&String>` - `String` implements `From<&mut str>` - `String` implements `From<&str>` - `String` implements `From>` - `String` implements `From>` - `String` implements `From` + = help: `String` implements trait `From`: + From<&String> + From<&mut str> + From<&str> + From> + From> + From = note: required for `Result<(), String>` to implement `FromResidual>` error[E0277]: `?` couldn't convert the error to `String` diff --git a/tests/ui/traits/question-mark-span-144304.stderr b/tests/ui/traits/question-mark-span-144304.stderr index a412da0d235db..37452cda8a6db 100644 --- a/tests/ui/traits/question-mark-span-144304.stderr +++ b/tests/ui/traits/question-mark-span-144304.stderr @@ -9,12 +9,12 @@ LL | Err("str").map_err(|e| e)?; | this has type `Result<_, &str>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait - = help: the following other types implement trait `From`: - `i32` implements `From` - `i32` implements `From` - `i32` implements `From` - `i32` implements `From` - `i32` implements `From` + = help: `i32` implements trait `From`: + From + From + From + From + From error[E0277]: `?` couldn't convert the error to `i32` --> $DIR/question-mark-span-144304.rs:4:42 @@ -29,12 +29,12 @@ LL | Err("str").map_err(|e| e.to_string())?; | this has type `Result<_, &str>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait - = help: the following other types implement trait `From`: - `i32` implements `From` - `i32` implements `From` - `i32` implements `From` - `i32` implements `From` - `i32` implements `From` + = help: `i32` implements trait `From`: + From + From + From + From + From error: aborting due to 2 previous errors diff --git a/tests/ui/try-trait/bad-interconversion.stderr b/tests/ui/try-trait/bad-interconversion.stderr index a566800da53e4..e1cc7a45e5de9 100644 --- a/tests/ui/try-trait/bad-interconversion.stderr +++ b/tests/ui/try-trait/bad-interconversion.stderr @@ -9,16 +9,16 @@ LL | Ok(Err(123_i32)?) | this can't be annotated with `?` because it has type `Result<_, i32>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait -help: the following other types implement trait `From` +help: `u8` implements trait `From` --> $SRC_DIR/core/src/convert/num.rs:LL:COL | - = note: `u8` implements `From` + = note: `From` ::: $SRC_DIR/core/src/convert/num.rs:LL:COL | = note: in this macro invocation --> $SRC_DIR/core/src/ascii/ascii_char.rs:LL:COL | - = note: `u8` implements `From` + = note: `From` ::: $SRC_DIR/core/src/ascii/ascii_char.rs:LL:COL | = note: in this macro invocation diff --git a/tests/ui/type-alias-impl-trait/constrain_in_projection2.current.stderr b/tests/ui/type-alias-impl-trait/constrain_in_projection2.current.stderr index d17a821943532..fc6bf3f189026 100644 --- a/tests/ui/type-alias-impl-trait/constrain_in_projection2.current.stderr +++ b/tests/ui/type-alias-impl-trait/constrain_in_projection2.current.stderr @@ -9,14 +9,14 @@ help: the trait `Trait` is not implemented for `Foo` | LL | struct Foo; | ^^^^^^^^^^ -help: the following other types implement trait `Trait` +help: `Foo` implements trait `Trait` --> $DIR/constrain_in_projection2.rs:18:1 | LL | impl Trait<()> for Foo { - | ^^^^^^^^^^^^^^^^^^^^^^ `Foo` implements `Trait<()>` + | ^^^^^^^^^^^^^^^^^^^^^^ `Trait<()>` ... LL | impl Trait for Foo { - | ^^^^^^^^^^^^^^^^^^^^^^^ `Foo` implements `Trait` + | ^^^^^^^^^^^^^^^^^^^^^^^ `Trait` error[E0277]: the trait bound `Foo: Trait` is not satisfied --> $DIR/constrain_in_projection2.rs:28:13 @@ -29,14 +29,14 @@ help: the trait `Trait` is not implemented for `Foo` | LL | struct Foo; | ^^^^^^^^^^ -help: the following other types implement trait `Trait` +help: `Foo` implements trait `Trait` --> $DIR/constrain_in_projection2.rs:18:1 | LL | impl Trait<()> for Foo { - | ^^^^^^^^^^^^^^^^^^^^^^ `Foo` implements `Trait<()>` + | ^^^^^^^^^^^^^^^^^^^^^^ `Trait<()>` ... LL | impl Trait for Foo { - | ^^^^^^^^^^^^^^^^^^^^^^^ `Foo` implements `Trait` + | ^^^^^^^^^^^^^^^^^^^^^^^ `Trait` error: aborting due to 2 previous errors diff --git a/tests/ui/type-alias-impl-trait/nested-tait-inference2.current.stderr b/tests/ui/type-alias-impl-trait/nested-tait-inference2.current.stderr index 4cc69daffe622..674442784ae7f 100644 --- a/tests/ui/type-alias-impl-trait/nested-tait-inference2.current.stderr +++ b/tests/ui/type-alias-impl-trait/nested-tait-inference2.current.stderr @@ -7,13 +7,13 @@ LL | LL | () | -- return type was inferred to be `()` here | -help: the following other types implement trait `Foo` +help: `()` implements trait `Foo` --> $DIR/nested-tait-inference2.rs:14:1 | LL | impl Foo<()> for () {} - | ^^^^^^^^^^^^^^^^^^^ `()` implements `Foo<()>` + | ^^^^^^^^^^^^^^^^^^^ `Foo<()>` LL | impl Foo for () {} - | ^^^^^^^^^^^^^^^^^^^^ `()` implements `Foo` + | ^^^^^^^^^^^^^^^^^^^^ `Foo` error: aborting due to 1 previous error diff --git a/tests/ui/typeck/issue-90101.stderr b/tests/ui/typeck/issue-90101.stderr index 2e140461c1d96..fe9af5a5f4f56 100644 --- a/tests/ui/typeck/issue-90101.stderr +++ b/tests/ui/typeck/issue-90101.stderr @@ -6,12 +6,12 @@ LL | func(Path::new("hello").to_path_buf().to_string_lossy(), "world") | | | required by a bound introduced by this call | - = help: the following other types implement trait `From`: - `PathBuf` implements `From<&T>` - `PathBuf` implements `From>` - `PathBuf` implements `From>` - `PathBuf` implements `From` - `PathBuf` implements `From` + = help: `PathBuf` implements trait `From`: + From<&T> + From> + From> + From + From = note: required for `Cow<'_, str>` to implement `Into` note: required by a bound in `func` --> $DIR/issue-90101.rs:3:20 From c1a24b4a9cccde386bad58b88aa7d8449cf6a5d7 Mon Sep 17 00:00:00 2001 From: Amit Singhmar Date: Sat, 21 Mar 2026 16:24:18 +0800 Subject: [PATCH 084/101] ide-assists: add tests for const, static in add_braces --- .../ide-assists/src/handlers/add_braces.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs index 99ee50fa5848f..7c340d91027f1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs @@ -178,6 +178,36 @@ fn foo() { ); } + #[test] + fn suggest_add_braces_for_const_initializer() { + check_assist( + add_braces, + r#" +const X: i32 =$0 1 + 2; +"#, + r#" +const X: i32 = { + 1 + 2 +}; +"#, + ); + } + + #[test] + fn suggest_add_braces_for_static_initializer() { + check_assist( + add_braces, + r#" +static X: i32 $0= 1 + 2; +"#, + r#" +static X: i32 = { + 1 + 2 +}; +"#, + ); + } + #[test] fn no_assist_for_closures_with_braces() { check_assist_not_applicable( From 9bff35bd8015f31bef46aec598d12ce6b9a21444 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 21 Mar 2026 16:16:03 +0800 Subject: [PATCH 085/101] add tests for let-expr in add_braces --- .../ide-assists/src/handlers/add_braces.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs index 7c340d91027f1..35ee1e5a7816e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs @@ -174,6 +174,24 @@ fn foo() { n + 100 }; } +"#, + ); + + check_assist( + add_braces, + r#" +fn foo() { + let x; + x =$0 n + 100; +} +"#, + r#" +fn foo() { + let x; + x = { + n + 100 + }; +} "#, ); } From dc8d64dbd4541fa1886aa54537baa1bb98722674 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 21 Mar 2026 16:16:03 +0800 Subject: [PATCH 086/101] feat: offer 'add_braces' on bin-expr assignment Example --- ```rust fn foo() { let x; x =$0 n + 100; } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn foo() { let x; x = { n + 100 }; } ``` --- .../ide-assists/src/handlers/add_braces.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs index 35ee1e5a7816e..da1322de4b641 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs @@ -84,6 +84,7 @@ fn get_replacement_node(ctx: &AssistContext<'_>) -> Option<(ParentType, ast::Exp match parent { ast::LetStmt(it) => it.initializer()?, ast::LetExpr(it) => it.expr()?, + ast::BinExpr(it) => it.rhs()?, ast::Static(it) => it.body()?, ast::Const(it) => it.body()?, _ => return None, @@ -192,6 +193,22 @@ fn foo() { n + 100 }; } +"#, + ); + + check_assist( + add_braces, + r#" +fn foo() { + if let x =$0 n + 100 {} +} +"#, + r#" +fn foo() { + if let x = { + n + 100 + } {} +} "#, ); } From 1a05c119c2407be1364e5266e5add44cda009e0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:29:56 +0000 Subject: [PATCH 087/101] Bump flatted from 3.3.3 to 3.4.2 in /editors/code Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.2. - [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2) --- updated-dependencies: - dependency-name: flatted dependency-version: 3.4.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- src/tools/rust-analyzer/editors/code/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index 4bddc28c1ef7e..047dbba11fcfc 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -3697,9 +3697,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, From 5841fa86500defa98a92c99d3998989da89ff1a7 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 22 Mar 2026 14:32:43 +1100 Subject: [PATCH 088/101] Inline and remove `QueryVTable::is_loadable_from_disk_fn`. It's very small and only has two call sites. The tiny loss of DRY is worth it to shrink `QueryVTable`. --- compiler/rustc_middle/src/query/plumbing.rs | 3 --- compiler/rustc_query_impl/src/execution.rs | 7 ++++--- compiler/rustc_query_impl/src/query_impl.rs | 8 -------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index dcf7a952fb970..7d94d60523fb1 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -104,9 +104,6 @@ pub struct QueryVTable<'tcx, C: QueryCache> { index: DepNodeIndex, ) -> Option, - pub is_loadable_from_disk_fn: - fn(tcx: TyCtxt<'tcx>, key: C::Key, index: SerializedDepNodeIndex) -> bool, - /// Function pointer that hashes this query's result values. /// /// For `no_hash` queries, this function pointer is None. diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index bbd55e9ead119..8fc6c851c3da9 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -18,7 +18,7 @@ use tracing::warn; use crate::dep_graph::{DepNode, DepNodeIndex}; use crate::job::{QueryJobInfo, QueryJobMap, find_cycle_in_stack, report_cycle}; -use crate::plumbing::{current_query_job, next_job_id, start_query}; +use crate::plumbing::{current_query_job, loadable_from_disk, next_job_id, start_query}; use crate::query_impl::for_each_query_vtable; #[inline] @@ -531,7 +531,7 @@ fn load_from_disk_or_invoke_provider_green<'tcx, C: QueryCache>( // Sanity check for the logic in `ensure`: if the node is green and the result loadable, // we should actually be able to load it. debug_assert!( - !(query.is_loadable_from_disk_fn)(tcx, key, prev_index), + !((query.will_cache_on_disk_for_key_fn)(tcx, key) && loadable_from_disk(tcx, prev_index)), "missing on-disk cache entry for loadable {dep_node:?}" ); @@ -624,7 +624,8 @@ fn check_if_ensure_can_skip_execution<'tcx, C: QueryCache>( // In ensure-done mode, we can only skip execution for this key if // there's a disk-cached value available to load later if needed, // which guarantees the query provider will never run for this key. - let is_loadable = (query.is_loadable_from_disk_fn)(tcx, key, serialized_dep_node_index); + let is_loadable = (query.will_cache_on_disk_for_key_fn)(tcx, key) + && loadable_from_disk(tcx, serialized_dep_node_index); EnsureCanSkip { skip_execution: is_loadable, dep_node: Some(dep_node) } } } diff --git a/compiler/rustc_query_impl/src/query_impl.rs b/compiler/rustc_query_impl/src/query_impl.rs index 56b7534da9edf..1a8ed053e347a 100644 --- a/compiler/rustc_query_impl/src/query_impl.rs +++ b/compiler/rustc_query_impl/src/query_impl.rs @@ -165,14 +165,6 @@ macro_rules! define_queries { #[cfg(not($cache_on_disk))] try_load_from_disk_fn: |_tcx, _key, _prev_index, _index| None, - #[cfg($cache_on_disk)] - is_loadable_from_disk_fn: |tcx, key, index| -> bool { - rustc_middle::queries::_cache_on_disk_if_fns::$name(tcx, key) && - $crate::plumbing::loadable_from_disk(tcx, index) - }, - #[cfg(not($cache_on_disk))] - is_loadable_from_disk_fn: |_tcx, _key, _index| false, - // The default just emits `err` and then aborts. // `from_cycle_error::specialize_query_vtables` overwrites this default for // certain queries. From 5491c3529b59fabce7df1e0581b63bc5548c3e79 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Fri, 27 Feb 2026 15:32:44 +0000 Subject: [PATCH 089/101] check earlier for misused crate-level attributes --- compiler/rustc_attr_parsing/src/errors.rs | 26 +++++ compiler/rustc_attr_parsing/src/interface.rs | 16 ++- compiler/rustc_attr_parsing/src/lib.rs | 2 + .../rustc_attr_parsing/src/target_checking.rs | 107 +++++++++++++++++- compiler/rustc_passes/src/check_attr.rs | 63 +---------- compiler/rustc_passes/src/errors.rs | 41 +------ tests/ui/attributes/malformed-reprs.stderr | 14 +-- tests/ui/derives/issue-36617.stderr | 10 +- .../issue-43106-gating-of-bench.stderr | 2 +- ...sue-43106-gating-of-builtin-attrs-error.rs | 2 +- ...43106-gating-of-builtin-attrs-error.stderr | 30 ++--- .../issue-43106-gating-of-test.stderr | 2 +- tests/ui/lowering/issue-121108.stderr | 2 +- ...st-fix-invalid-top-level-macro-attr.stderr | 2 +- .../ui/span/issue-43927-non-ADT-derive.stderr | 2 +- 15 files changed, 180 insertions(+), 141 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/errors.rs diff --git a/compiler/rustc_attr_parsing/src/errors.rs b/compiler/rustc_attr_parsing/src/errors.rs new file mode 100644 index 0000000000000..f45b613c06e82 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/errors.rs @@ -0,0 +1,26 @@ +use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_span::{Span, Symbol}; + +#[derive(Diagnostic)] +#[diag("`{$name}` attribute cannot be used at crate level")] +pub(crate) struct InvalidAttrAtCrateLevel { + #[primary_span] + pub span: Span, + #[suggestion( + "perhaps you meant to use an outer attribute", + code = "#[", + applicability = "machine-applicable", + style = "verbose" + )] + pub pound_to_opening_bracket: Span, + pub name: Symbol, + #[subdiagnostic] + pub item: Option, +} + +#[derive(Clone, Copy, Subdiagnostic)] +#[label("the inner attribute doesn't annotate this item")] +pub(crate) struct ItemFollowingInnerAttr { + #[primary_span] + pub span: Span, +} diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index fa66dec6a1568..ddfe5953b8523 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -304,7 +304,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { kind: DocFragmentKind::Sugared(*comment_kind), span: attr_span, comment: *symbol, - })) + })); } ast::AttrKind::Normal(n) => { attr_paths.push(PathParser(&n.item.path)); @@ -408,15 +408,23 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { // "attribute {path} wasn't parsed and isn't a know tool attribute", // ); - attributes.push(Attribute::Unparsed(Box::new(AttrItem { + let attr = AttrItem { path: attr_path.clone(), args: self .lower_attr_args(n.item.args.unparsed_ref().unwrap(), lower_span), id: HashIgnoredAttrId { attr_id: attr.id }, style: attr.style, span: attr_span, - }))); - } + }; + + if !matches!(self.stage.should_emit(), ShouldEmit::Nothing) + && target == Target::Crate + { + self.check_invalid_crate_level_attr_item(&attr, n.item.span()); + } + + attributes.push(Attribute::Unparsed(Box::new(attr))); + }; } } } diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index db09572cc56b3..93eb5a0c3ab73 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -79,6 +79,7 @@ // tidy-alphabetical-start #![feature(decl_macro)] #![feature(iter_intersperse)] +#![feature(try_blocks)] #![recursion_limit = "256"] // tidy-alphabetical-end @@ -99,6 +100,7 @@ mod interface; pub mod parser; mod early_parsed; +mod errors; mod safety; mod session_diagnostics; mod target_checking; diff --git a/compiler/rustc_attr_parsing/src/target_checking.rs b/compiler/rustc_attr_parsing/src/target_checking.rs index fb005477f0fa1..a874278ed1c56 100644 --- a/compiler/rustc_attr_parsing/src/target_checking.rs +++ b/compiler/rustc_attr_parsing/src/target_checking.rs @@ -1,14 +1,15 @@ use std::borrow::Cow; use rustc_ast::AttrStyle; -use rustc_errors::DiagArgValue; +use rustc_errors::{DiagArgValue, StashKey}; use rustc_feature::Features; use rustc_hir::lints::AttributeLintKind; -use rustc_hir::{MethodKind, Target}; -use rustc_span::sym; +use rustc_hir::{AttrItem, MethodKind, Target}; +use rustc_span::{BytePos, Span, Symbol, sym}; use crate::AttributeParser; use crate::context::{AcceptContext, Stage}; +use crate::errors::{InvalidAttrAtCrateLevel, ItemFollowingInnerAttr}; use crate::session_diagnostics::InvalidTarget; use crate::target_checking::Policy::Allow; @@ -96,6 +97,25 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { return; } + if matches!(cx.attr_path.segments.as_ref(), [sym::repr]) && target == Target::Crate { + // The allowed targets of `repr` depend on its arguments. They can't be checked using + // the `AttributeParser` code. + let span = cx.attr_span; + let item = + cx.cx.first_line_of_next_item(span).map(|span| ItemFollowingInnerAttr { span }); + + let pound_to_opening_bracket = cx.attr_span.until(cx.inner_span); + + cx.dcx() + .create_err(InvalidAttrAtCrateLevel { + span, + pound_to_opening_bracket, + name: sym::repr, + item, + }) + .emit(); + } + match allowed_targets.is_allowed(target) { AllowedResult::Allowed => {} AllowedResult::Warn => { @@ -163,6 +183,87 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { cx.emit_lint(rustc_session::lint::builtin::UNUSED_ATTRIBUTES, kind, attr_span); } + + // FIXME: Fix "Cannot determine resolution" error and remove built-in macros + // from this check. + pub(crate) fn check_invalid_crate_level_attr_item(&self, attr: &AttrItem, inner_span: Span) { + // Check for builtin attributes at the crate level + // which were unsuccessfully resolved due to cannot determine + // resolution for the attribute macro error. + const ATTRS_TO_CHECK: &[Symbol] = + &[sym::derive, sym::test, sym::test_case, sym::global_allocator, sym::bench]; + + // FIXME(jdonszelmann): all attrs should be combined here cleaning this up some day. + if let Some(name) = ATTRS_TO_CHECK.iter().find(|attr_to_check| matches!(attr.path.segments.as_ref(), [segment] if segment == *attr_to_check)) { + let span = attr.span; + let name = *name; + + let item = self.first_line_of_next_item(span).map(|span| ItemFollowingInnerAttr { span }); + + let err = self.dcx().create_err(InvalidAttrAtCrateLevel { + span, + pound_to_opening_bracket: span.until(inner_span), + name, + item, + }); + + self.dcx().try_steal_replace_and_emit_err( + attr.path.span, + StashKey::UndeterminedMacroResolution, + err, + ); + } + } + + fn first_line_of_next_item(&self, span: Span) -> Option { + // We can't exactly call `tcx.hir_free_items()` here because it's too early and querying + // this would create a circular dependency. Instead, we resort to getting the original + // source code that follows `span` and find the next item from here. + + self.sess() + .source_map() + .span_to_source(span, |content, _, span_end| { + let mut source = &content[span_end..]; + let initial_source_len = source.len(); + let span = try { + loop { + let first = source.chars().next()?; + + if first.is_whitespace() { + let split_idx = source.find(|c: char| !c.is_whitespace())?; + source = &source[split_idx..]; + } else if source.starts_with("//") { + let line_idx = source.find('\n')?; + source = &source[line_idx + '\n'.len_utf8()..]; + } else if source.starts_with("/*") { + // FIXME: support nested comments. + let close_idx = source.find("*/")?; + source = &source[close_idx + "*/".len()..]; + } else if first == '#' { + // FIXME: properly find the end of the attributes in order to accurately + // skip them. This version just consumes the source code until the next + // `]`. + let close_idx = source.find(']')?; + source = &source[close_idx + ']'.len_utf8()..]; + } else { + let lo = span_end + initial_source_len - source.len(); + let last_line = source.split('\n').next().map(|s| s.trim_end())?; + + let hi = lo + last_line.len(); + let lo = BytePos(lo as u32); + let hi = BytePos(hi as u32); + let next_item_span = Span::new(lo, hi, span.ctxt(), None); + + break next_item_span; + } + } + }; + + Ok(span) + }) + .ok() + .flatten() + } } /// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to. diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index a8ca8011b5710..508bcb03731df 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -15,7 +15,7 @@ use rustc_attr_parsing::{AttributeParser, Late}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::thin_vec::ThinVec; use rustc_data_structures::unord::UnordMap; -use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey, msg}; +use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, msg}; use rustc_feature::{AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute}; use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::{ @@ -45,7 +45,7 @@ use rustc_session::lint::builtin::{ }; use rustc_session::parse::feature_err; use rustc_span::edition::Edition; -use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, sym}; +use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs}; use rustc_trait_selection::traits::ObligationCtxt; @@ -2025,64 +2025,6 @@ fn is_c_like_enum(item: &Item<'_>) -> bool { } } -// FIXME: Fix "Cannot determine resolution" error and remove built-in macros -// from this check. -fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) { - // Check for builtin attributes at the crate level - // which were unsuccessfully resolved due to cannot determine - // resolution for the attribute macro error. - const ATTRS_TO_CHECK: &[Symbol] = - &[sym::derive, sym::test, sym::test_case, sym::global_allocator, sym::bench]; - - for attr in attrs { - // FIXME(jdonszelmann): all attrs should be combined here cleaning this up some day. - let (span, name) = if let Some(a) = - ATTRS_TO_CHECK.iter().find(|attr_to_check| attr.has_name(**attr_to_check)) - { - (attr.span(), *a) - } else if let Attribute::Parsed(AttributeKind::Repr { - reprs: _, - first_span: first_attr_span, - }) = attr - { - (*first_attr_span, sym::repr) - } else { - continue; - }; - - let item = tcx - .hir_free_items() - .map(|id| tcx.hir_item(id)) - .find(|item| !item.span.is_dummy()) // Skip prelude `use`s - .map(|item| errors::ItemFollowingInnerAttr { - span: if let Some(ident) = item.kind.ident() { ident.span } else { item.span }, - kind: tcx.def_descr(item.owner_id.to_def_id()), - }); - let err = tcx.dcx().create_err(errors::InvalidAttrAtCrateLevel { - span, - sugg_span: tcx - .sess - .source_map() - .span_to_snippet(span) - .ok() - .filter(|src| src.starts_with("#![")) - .map(|_| span.with_lo(span.lo() + BytePos(1)).with_hi(span.lo() + BytePos(2))), - name, - item, - }); - - if let Attribute::Unparsed(p) = attr { - tcx.dcx().try_steal_replace_and_emit_err( - p.path.span, - StashKey::UndeterminedMacroResolution, - err, - ); - } else { - err.emit(); - } - } -} - fn check_non_exported_macro_for_invalid_attrs(tcx: TyCtxt<'_>, item: &Item<'_>) { let attrs = tcx.hir_attrs(item.hir_id()); @@ -2098,7 +2040,6 @@ fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { tcx.hir_visit_item_likes_in_module(module_def_id, check_attr_visitor); if module_def_id.to_local_def_id().is_top_level_module() { check_attr_visitor.check_attributes(CRATE_HIR_ID, DUMMY_SP, Target::Mod, None); - check_invalid_crate_level_attr(tcx, tcx.hir_krate_attrs()); } if check_attr_visitor.abort.get() { tcx.dcx().abort_if_errors() diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 5e84122fcb10e..7560ddede895e 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -3,8 +3,7 @@ use std::path::{Path, PathBuf}; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee, Level, - MultiSpan, msg, + Diag, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee, Level, MultiSpan, msg, }; use rustc_hir::Target; use rustc_macros::{Diagnostic, Subdiagnostic}; @@ -447,44 +446,6 @@ pub(crate) struct LangItemOnIncorrectTarget { pub actual_target: Target, } -pub(crate) struct InvalidAttrAtCrateLevel { - pub span: Span, - pub sugg_span: Option, - pub name: Symbol, - pub item: Option, -} - -#[derive(Clone, Copy)] -pub(crate) struct ItemFollowingInnerAttr { - pub span: Span, - pub kind: &'static str, -} - -impl Diagnostic<'_, G> for InvalidAttrAtCrateLevel { - #[track_caller] - fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> { - let mut diag = - Diag::new(dcx, level, msg!("`{$name}` attribute cannot be used at crate level")); - diag.span(self.span); - diag.arg("name", self.name); - // Only emit an error with a suggestion if we can create a string out - // of the attribute span - if let Some(span) = self.sugg_span { - diag.span_suggestion_verbose( - span, - msg!("perhaps you meant to use an outer attribute"), - String::new(), - Applicability::MachineApplicable, - ); - } - if let Some(item) = self.item { - diag.arg("kind", item.kind); - diag.span_label(item.span, msg!("the inner attribute doesn't annotate this {$kind}")); - } - diag - } -} - #[derive(Diagnostic)] #[diag("duplicate diagnostic item in crate `{$crate_name}`: `{$name}`")] pub(crate) struct DuplicateDiagnosticItemInCrate { diff --git a/tests/ui/attributes/malformed-reprs.stderr b/tests/ui/attributes/malformed-reprs.stderr index 504ba91aac5f5..6e77c6e4b9a97 100644 --- a/tests/ui/attributes/malformed-reprs.stderr +++ b/tests/ui/attributes/malformed-reprs.stderr @@ -6,12 +6,6 @@ LL | #![repr] | = note: for more information, visit -error[E0589]: invalid `repr(align)` attribute: not a power of two - --> $DIR/malformed-reprs.rs:9:14 - | -LL | #[repr(align(0))] - | ^ - error: `repr` attribute cannot be used at crate level --> $DIR/malformed-reprs.rs:4:1 | @@ -19,7 +13,7 @@ LL | #![repr] | ^^^^^^^^ ... LL | enum Foo {} - | --- the inner attribute doesn't annotate this enum + | ----------- the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | @@ -27,6 +21,12 @@ LL - #![repr] LL + #[repr] | +error[E0589]: invalid `repr(align)` attribute: not a power of two + --> $DIR/malformed-reprs.rs:9:14 + | +LL | #[repr(align(0))] + | ^ + error[E0084]: unsupported representation for zero-variant enum --> $DIR/malformed-reprs.rs:9:1 | diff --git a/tests/ui/derives/issue-36617.stderr b/tests/ui/derives/issue-36617.stderr index 3de1d87c5046c..f76c5dfc7e067 100644 --- a/tests/ui/derives/issue-36617.stderr +++ b/tests/ui/derives/issue-36617.stderr @@ -5,7 +5,7 @@ LL | #![derive(Copy)] | ^^^^^^^^^^^^^^^^ ... LL | fn main() {} - | ---- the inner attribute doesn't annotate this function + | ------------ the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | @@ -20,7 +20,7 @@ LL | #![test] | ^^^^^^^^ ... LL | fn main() {} - | ---- the inner attribute doesn't annotate this function + | ------------ the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | @@ -35,7 +35,7 @@ LL | #![test_case] | ^^^^^^^^^^^^^ ... LL | fn main() {} - | ---- the inner attribute doesn't annotate this function + | ------------ the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | @@ -50,7 +50,7 @@ LL | #![bench] | ^^^^^^^^^ ... LL | fn main() {} - | ---- the inner attribute doesn't annotate this function + | ------------ the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | @@ -65,7 +65,7 @@ LL | #![global_allocator] | ^^^^^^^^^^^^^^^^^^^^ ... LL | fn main() {} - | ---- the inner attribute doesn't annotate this function + | ------------ the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | diff --git a/tests/ui/feature-gates/issue-43106-gating-of-bench.stderr b/tests/ui/feature-gates/issue-43106-gating-of-bench.stderr index 912c2746f3835..c51d615846f5a 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-bench.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-bench.stderr @@ -5,7 +5,7 @@ LL | #![bench = "4100"] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LL | LL | fn main() {} - | ---- the inner attribute doesn't annotate this function + | ------------ the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.rs b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.rs index 1560f2b5f4a77..66d36d9a5d6de 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.rs +++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.rs @@ -37,7 +37,7 @@ #[inline] //~^ ERROR attribute cannot be used on mod inline { - //~^ NOTE the inner attribute doesn't annotate this module + //~^ NOTE the inner attribute doesn't annotate this item mod inner { #![inline] } //~^ ERROR attribute cannot be used on diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr index 662776e580267..db56f22acfba6 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr @@ -24,6 +24,21 @@ LL | #![rustc_main] | = help: `#[rustc_main]` can only be applied to functions +error: `repr` attribute cannot be used at crate level + --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:16:1 + | +LL | #![repr()] + | ^^^^^^^^^^ +... +LL | mod inline { + | ------------ the inner attribute doesn't annotate this item + | +help: perhaps you meant to use an outer attribute + | +LL - #![repr()] +LL + #[repr()] + | + error: `#[path]` attribute cannot be used on crates --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:20:1 | @@ -243,21 +258,6 @@ help: remove the `#[no_mangle]` attribute LL - #![no_mangle] | -error: `repr` attribute cannot be used at crate level - --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:16:1 - | -LL | #![repr()] - | ^^^^^^^^^^ -... -LL | mod inline { - | ------ the inner attribute doesn't annotate this module - | -help: perhaps you meant to use an outer attribute - | -LL - #![repr()] -LL + #[repr()] - | - error[E0517]: attribute should be applied to a struct, enum, or union --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:127:25 | diff --git a/tests/ui/feature-gates/issue-43106-gating-of-test.stderr b/tests/ui/feature-gates/issue-43106-gating-of-test.stderr index 6e706b151b72f..8af140bc93d9b 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-test.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-test.stderr @@ -5,7 +5,7 @@ LL | #![test = "4200"] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LL | LL | fn main() {} - | ---- the inner attribute doesn't annotate this function + | ------------ the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | diff --git a/tests/ui/lowering/issue-121108.stderr b/tests/ui/lowering/issue-121108.stderr index f68655a70021f..e1aa153eede64 100644 --- a/tests/ui/lowering/issue-121108.stderr +++ b/tests/ui/lowering/issue-121108.stderr @@ -5,7 +5,7 @@ LL | #![derive(Clone, Copy)] | ^^^^^^^^^^^^^^^^^^^^^^^ LL | LL | use std::ptr::addr_of; - | ------- the inner attribute doesn't annotate this import + | ---------------------- the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | diff --git a/tests/ui/proc-macro/issue-89566-suggest-fix-invalid-top-level-macro-attr.stderr b/tests/ui/proc-macro/issue-89566-suggest-fix-invalid-top-level-macro-attr.stderr index 09908160542d4..1d7b5ab8c6af1 100644 --- a/tests/ui/proc-macro/issue-89566-suggest-fix-invalid-top-level-macro-attr.stderr +++ b/tests/ui/proc-macro/issue-89566-suggest-fix-invalid-top-level-macro-attr.stderr @@ -5,7 +5,7 @@ LL | #![derive(Debug)] | ^^^^^^^^^^^^^^^^^ LL | #[allow(dead_code)] LL | struct Test {} - | ---- the inner attribute doesn't annotate this struct + | -------------- the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | diff --git a/tests/ui/span/issue-43927-non-ADT-derive.stderr b/tests/ui/span/issue-43927-non-ADT-derive.stderr index 27ed561f5be8a..8742f34c45c62 100644 --- a/tests/ui/span/issue-43927-non-ADT-derive.stderr +++ b/tests/ui/span/issue-43927-non-ADT-derive.stderr @@ -5,7 +5,7 @@ LL | #![derive(Debug, PartialEq, Eq)] // should be an outer attribute! | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LL | LL | struct DerivedOn; - | --------- the inner attribute doesn't annotate this struct + | ----------------- the inner attribute doesn't annotate this item | help: perhaps you meant to use an outer attribute | From 1c6a8feef8db897e0ab956eeb0a450cf3d24f798 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Wed, 4 Mar 2026 00:26:38 +0000 Subject: [PATCH 090/101] check earlier for attributes that are placed in `where` predicate --- compiler/rustc_attr_parsing/src/errors.rs | 9 ++ compiler/rustc_attr_parsing/src/interface.rs | 6 + .../rustc_attr_parsing/src/target_checking.rs | 35 +++++- compiler/rustc_passes/src/check_attr.rs | 21 ---- compiler/rustc_passes/src/errors.rs | 8 -- tests/ui/where-clauses/cfg_attribute.a.stderr | 112 +++++++++--------- tests/ui/where-clauses/cfg_attribute.b.stderr | 112 +++++++++--------- .../unsupported_attribute.stderr | 64 +++++----- 8 files changed, 191 insertions(+), 176 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/errors.rs b/compiler/rustc_attr_parsing/src/errors.rs index f45b613c06e82..4eb9425b34bb0 100644 --- a/compiler/rustc_attr_parsing/src/errors.rs +++ b/compiler/rustc_attr_parsing/src/errors.rs @@ -1,3 +1,4 @@ +use rustc_errors::MultiSpan; use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_span::{Span, Symbol}; @@ -24,3 +25,11 @@ pub(crate) struct ItemFollowingInnerAttr { #[primary_span] pub span: Span, } + +#[derive(Diagnostic)] +#[diag("most attributes are not supported in `where` clauses")] +#[help("only `#[cfg]` and `#[cfg_attr]` are supported")] +pub(crate) struct UnsupportedAttributesInWhere { + #[primary_span] + pub span: MultiSpan, +} diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index ddfe5953b8523..4d4537aaa5f6d 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -439,6 +439,12 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { } } + if !matches!(self.stage.should_emit(), ShouldEmit::Nothing) + && target == Target::WherePredicate + { + self.check_invalid_where_predicate_attrs(attributes.iter()); + } + attributes } diff --git a/compiler/rustc_attr_parsing/src/target_checking.rs b/compiler/rustc_attr_parsing/src/target_checking.rs index a874278ed1c56..253a089e49f1a 100644 --- a/compiler/rustc_attr_parsing/src/target_checking.rs +++ b/compiler/rustc_attr_parsing/src/target_checking.rs @@ -1,15 +1,18 @@ use std::borrow::Cow; use rustc_ast::AttrStyle; -use rustc_errors::{DiagArgValue, StashKey}; +use rustc_errors::{DiagArgValue, MultiSpan, StashKey}; use rustc_feature::Features; +use rustc_hir::attrs::AttributeKind; use rustc_hir::lints::AttributeLintKind; -use rustc_hir::{AttrItem, MethodKind, Target}; +use rustc_hir::{AttrItem, Attribute, MethodKind, Target}; use rustc_span::{BytePos, Span, Symbol, sym}; use crate::AttributeParser; use crate::context::{AcceptContext, Stage}; -use crate::errors::{InvalidAttrAtCrateLevel, ItemFollowingInnerAttr}; +use crate::errors::{ + InvalidAttrAtCrateLevel, ItemFollowingInnerAttr, UnsupportedAttributesInWhere, +}; use crate::session_diagnostics::InvalidTarget; use crate::target_checking::Policy::Allow; @@ -264,6 +267,32 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { .ok() .flatten() } + + pub(crate) fn check_invalid_where_predicate_attrs<'attr>( + &self, + attrs: impl IntoIterator, + ) { + // FIXME(where_clause_attrs): Currently, as the following check shows, + // only `#[cfg]` and `#[cfg_attr]` are allowed, but it should be removed + // if we allow more attributes (e.g., tool attributes and `allow/deny/warn`) + // in where clauses. After that, this function would become useless. + let spans = attrs + .into_iter() + // FIXME: We shouldn't need to special-case `doc`! + .filter(|attr| { + matches!( + attr, + Attribute::Parsed(AttributeKind::DocComment { .. } | AttributeKind::Doc(_)) + | Attribute::Unparsed(_) + ) + }) + .map(|attr| attr.span()) + .collect::>(); + if !spans.is_empty() { + self.dcx() + .emit_err(UnsupportedAttributesInWhere { span: MultiSpan::from_spans(spans) }); + } + } } /// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to. diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 508bcb03731df..26ba2b0e8f42d 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1907,27 +1907,6 @@ impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> { } fn visit_where_predicate(&mut self, where_predicate: &'tcx hir::WherePredicate<'tcx>) { - // FIXME(where_clause_attrs): Currently, as the following check shows, - // only `#[cfg]` and `#[cfg_attr]` are allowed, but it should be removed - // if we allow more attributes (e.g., tool attributes and `allow/deny/warn`) - // in where clauses. After that, only `self.check_attributes` should be enough. - let spans = self - .tcx - .hir_attrs(where_predicate.hir_id) - .iter() - // FIXME: We shouldn't need to special-case `doc`! - .filter(|attr| { - matches!( - attr, - Attribute::Parsed(AttributeKind::DocComment { .. } | AttributeKind::Doc(_)) - | Attribute::Unparsed(_) - ) - }) - .map(|attr| attr.span()) - .collect::>(); - if !spans.is_empty() { - self.tcx.dcx().emit_err(errors::UnsupportedAttributesInWhere { span: spans.into() }); - } self.check_attributes( where_predicate.hir_id, where_predicate.span, diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 7560ddede895e..f9dc696f320e3 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1150,14 +1150,6 @@ pub(crate) struct RustcConstStableIndirectPairing { pub span: Span, } -#[derive(Diagnostic)] -#[diag("most attributes are not supported in `where` clauses")] -#[help("only `#[cfg]` and `#[cfg_attr]` are supported")] -pub(crate) struct UnsupportedAttributesInWhere { - #[primary_span] - pub span: MultiSpan, -} - #[derive(Diagnostic)] pub(crate) enum UnexportableItem<'a> { #[diag("{$descr}'s are not exportable")] diff --git a/tests/ui/where-clauses/cfg_attribute.a.stderr b/tests/ui/where-clauses/cfg_attribute.a.stderr index ee81072e70062..5bfa65df6032b 100644 --- a/tests/ui/where-clauses/cfg_attribute.a.stderr +++ b/tests/ui/where-clauses/cfg_attribute.a.stderr @@ -93,39 +93,39 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:65:5 + --> $DIR/cfg_attribute.rs:42:9 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:68:5 + --> $DIR/cfg_attribute.rs:45:9 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():; + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:99:5 + --> $DIR/cfg_attribute.rs:53:9 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:102:5 + --> $DIR/cfg_attribute.rs:56:9 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():; + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:113:5 + --> $DIR/cfg_attribute.rs:65:5 | LL | #[derive(Clone)] ():, | ^^^^^^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL | #[derive(Clone)] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:116:5 + --> $DIR/cfg_attribute.rs:68:5 | LL | #[rustfmt::skip] ():, | ^^^^^^^^^^^^^^^^ @@ -141,39 +141,39 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:128:5 + --> $DIR/cfg_attribute.rs:75:9 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:131:5 + --> $DIR/cfg_attribute.rs:78:9 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():; + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:143:5 + --> $DIR/cfg_attribute.rs:86:9 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:146:5 + --> $DIR/cfg_attribute.rs:89:9 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:154:5 + --> $DIR/cfg_attribute.rs:99:5 | LL | #[derive(Clone)] ():, | ^^^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL | #[derive(Clone)] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:157:5 + --> $DIR/cfg_attribute.rs:102:5 | LL | #[rustfmt::skip] ():, | ^^^^^^^^^^^^^^^^ @@ -189,7 +189,7 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:177:5 + --> $DIR/cfg_attribute.rs:113:5 | LL | #[derive(Clone)] ():, | ^^^^^^^^^^^^^^^^ @@ -197,7 +197,7 @@ LL | #[derive(Clone)] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:180:5 + --> $DIR/cfg_attribute.rs:116:5 | LL | #[rustfmt::skip] ():, | ^^^^^^^^^^^^^^^^ @@ -205,55 +205,55 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:42:9 + --> $DIR/cfg_attribute.rs:128:5 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:45:9 + --> $DIR/cfg_attribute.rs:131:5 | -LL | #[rustfmt::skip] ():; - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:53:9 + --> $DIR/cfg_attribute.rs:143:5 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:56:9 + --> $DIR/cfg_attribute.rs:146:5 | -LL | #[rustfmt::skip] ():; - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:75:9 + --> $DIR/cfg_attribute.rs:154:5 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:78:9 + --> $DIR/cfg_attribute.rs:157:5 | -LL | #[rustfmt::skip] ():; - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:86:9 + --> $DIR/cfg_attribute.rs:164:9 | LL | #[derive(Clone)] ():, | ^^^^^^^^^^^^^^^^ @@ -261,7 +261,7 @@ LL | #[derive(Clone)] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:89:9 + --> $DIR/cfg_attribute.rs:167:9 | LL | #[rustfmt::skip] ():, | ^^^^^^^^^^^^^^^^ @@ -269,18 +269,18 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:164:9 + --> $DIR/cfg_attribute.rs:177:5 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:167:9 + --> $DIR/cfg_attribute.rs:180:5 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported diff --git a/tests/ui/where-clauses/cfg_attribute.b.stderr b/tests/ui/where-clauses/cfg_attribute.b.stderr index ee81072e70062..5bfa65df6032b 100644 --- a/tests/ui/where-clauses/cfg_attribute.b.stderr +++ b/tests/ui/where-clauses/cfg_attribute.b.stderr @@ -93,39 +93,39 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:65:5 + --> $DIR/cfg_attribute.rs:42:9 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:68:5 + --> $DIR/cfg_attribute.rs:45:9 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():; + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:99:5 + --> $DIR/cfg_attribute.rs:53:9 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:102:5 + --> $DIR/cfg_attribute.rs:56:9 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():; + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:113:5 + --> $DIR/cfg_attribute.rs:65:5 | LL | #[derive(Clone)] ():, | ^^^^^^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL | #[derive(Clone)] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:116:5 + --> $DIR/cfg_attribute.rs:68:5 | LL | #[rustfmt::skip] ():, | ^^^^^^^^^^^^^^^^ @@ -141,39 +141,39 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:128:5 + --> $DIR/cfg_attribute.rs:75:9 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:131:5 + --> $DIR/cfg_attribute.rs:78:9 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():; + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:143:5 + --> $DIR/cfg_attribute.rs:86:9 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:146:5 + --> $DIR/cfg_attribute.rs:89:9 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:154:5 + --> $DIR/cfg_attribute.rs:99:5 | LL | #[derive(Clone)] ():, | ^^^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL | #[derive(Clone)] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:157:5 + --> $DIR/cfg_attribute.rs:102:5 | LL | #[rustfmt::skip] ():, | ^^^^^^^^^^^^^^^^ @@ -189,7 +189,7 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:177:5 + --> $DIR/cfg_attribute.rs:113:5 | LL | #[derive(Clone)] ():, | ^^^^^^^^^^^^^^^^ @@ -197,7 +197,7 @@ LL | #[derive(Clone)] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:180:5 + --> $DIR/cfg_attribute.rs:116:5 | LL | #[rustfmt::skip] ():, | ^^^^^^^^^^^^^^^^ @@ -205,55 +205,55 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:42:9 + --> $DIR/cfg_attribute.rs:128:5 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:45:9 + --> $DIR/cfg_attribute.rs:131:5 | -LL | #[rustfmt::skip] ():; - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:53:9 + --> $DIR/cfg_attribute.rs:143:5 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:56:9 + --> $DIR/cfg_attribute.rs:146:5 | -LL | #[rustfmt::skip] ():; - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:75:9 + --> $DIR/cfg_attribute.rs:154:5 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:78:9 + --> $DIR/cfg_attribute.rs:157:5 | -LL | #[rustfmt::skip] ():; - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:86:9 + --> $DIR/cfg_attribute.rs:164:9 | LL | #[derive(Clone)] ():, | ^^^^^^^^^^^^^^^^ @@ -261,7 +261,7 @@ LL | #[derive(Clone)] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:89:9 + --> $DIR/cfg_attribute.rs:167:9 | LL | #[rustfmt::skip] ():, | ^^^^^^^^^^^^^^^^ @@ -269,18 +269,18 @@ LL | #[rustfmt::skip] ():, = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:164:9 + --> $DIR/cfg_attribute.rs:177:5 | -LL | #[derive(Clone)] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[derive(Clone)] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported error: most attributes are not supported in `where` clauses - --> $DIR/cfg_attribute.rs:167:9 + --> $DIR/cfg_attribute.rs:180:5 | -LL | #[rustfmt::skip] ():, - | ^^^^^^^^^^^^^^^^ +LL | #[rustfmt::skip] ():, + | ^^^^^^^^^^^^^^^^ | = help: only `#[cfg]` and `#[cfg_attr]` are supported diff --git a/tests/ui/where-clauses/unsupported_attribute.stderr b/tests/ui/where-clauses/unsupported_attribute.stderr index e55a7c380c2be..11f13ffa4d007 100644 --- a/tests/ui/where-clauses/unsupported_attribute.stderr +++ b/tests/ui/where-clauses/unsupported_attribute.stderr @@ -10,6 +10,22 @@ error: expected non-macro attribute, found attribute macro `derive` LL | #[derive(Clone)] 'a: 'static, | ^^^^^^ not a non-macro attribute +error: most attributes are not supported in `where` clauses + --> $DIR/unsupported_attribute.rs:13:5 + | +LL | #[doc = "doc"] T: Trait, + | ^^^^^^^^^^^^^^ + | + = help: only `#[cfg]` and `#[cfg_attr]` are supported + +error: most attributes are not supported in `where` clauses + --> $DIR/unsupported_attribute.rs:14:5 + | +LL | #[doc = "doc"] 'a: 'static, + | ^^^^^^^^^^^^^^ + | + = help: only `#[cfg]` and `#[cfg_attr]` are supported + error: `#[ignore]` attribute cannot be used on where predicates --> $DIR/unsupported_attribute.rs:15:5 | @@ -58,6 +74,22 @@ LL | #[macro_use] 'a: 'static, | = help: `#[macro_use]` can be applied to crates, extern crates, and modules +error: most attributes are not supported in `where` clauses + --> $DIR/unsupported_attribute.rs:21:5 + | +LL | #[allow(unused)] T: Trait, + | ^^^^^^^^^^^^^^^^ + | + = help: only `#[cfg]` and `#[cfg_attr]` are supported + +error: most attributes are not supported in `where` clauses + --> $DIR/unsupported_attribute.rs:22:5 + | +LL | #[allow(unused)] 'a: 'static, + | ^^^^^^^^^^^^^^^^ + | + = help: only `#[cfg]` and `#[cfg_attr]` are supported + error: `#[deprecated]` attribute cannot be used on where predicates --> $DIR/unsupported_attribute.rs:23:5 | @@ -90,38 +122,6 @@ LL | #[automatically_derived] 'a: 'static, | = help: `#[automatically_derived]` can only be applied to trait impl blocks -error: most attributes are not supported in `where` clauses - --> $DIR/unsupported_attribute.rs:13:5 - | -LL | #[doc = "doc"] T: Trait, - | ^^^^^^^^^^^^^^ - | - = help: only `#[cfg]` and `#[cfg_attr]` are supported - -error: most attributes are not supported in `where` clauses - --> $DIR/unsupported_attribute.rs:14:5 - | -LL | #[doc = "doc"] 'a: 'static, - | ^^^^^^^^^^^^^^ - | - = help: only `#[cfg]` and `#[cfg_attr]` are supported - -error: most attributes are not supported in `where` clauses - --> $DIR/unsupported_attribute.rs:21:5 - | -LL | #[allow(unused)] T: Trait, - | ^^^^^^^^^^^^^^^^ - | - = help: only `#[cfg]` and `#[cfg_attr]` are supported - -error: most attributes are not supported in `where` clauses - --> $DIR/unsupported_attribute.rs:22:5 - | -LL | #[allow(unused)] 'a: 'static, - | ^^^^^^^^^^^^^^^^ - | - = help: only `#[cfg]` and `#[cfg_attr]` are supported - error: most attributes are not supported in `where` clauses --> $DIR/unsupported_attribute.rs:27:5 | From 29e9273185bd6d67145524733bbf4af4c4cc65c8 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Fri, 27 Feb 2026 15:32:44 +0000 Subject: [PATCH 091/101] drop derive helpers during ast lowering --- compiler/rustc_attr_parsing/src/interface.rs | 37 ++++++++++---------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 4d4537aaa5f6d..7305c4b7c2fa8 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -269,6 +269,11 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { mut emit_lint: impl FnMut(LintId, Span, AttributeLintKind), ) -> Vec { let mut attributes = Vec::new(); + // We store the attributes we intend to discard at the end of this function in order to + // check they are applied to the right target and error out if necessary. In practice, we + // end up dropping only derive attributes and derive helpers, both being fully processed + // at macro expansion. + let mut dropped_attributes = Vec::new(); let mut attr_paths: Vec> = Vec::new(); let mut early_parsed_state = EarlyParsedState::default(); @@ -393,21 +398,6 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { Self::check_target(&accept.allowed_targets, target, &mut cx); } } else { - // If we're here, we must be compiling a tool attribute... Or someone - // forgot to parse their fancy new attribute. Let's warn them in any case. - // If you are that person, and you really think your attribute should - // remain unparsed, carefully read the documentation in this module and if - // you still think so you can add an exception to this assertion. - - // FIXME(jdonszelmann): convert other attributes, and check with this that - // we caught em all - // const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg]; - // assert!( - // self.tools.contains(&parts[0]) || true, - // // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]), - // "attribute {path} wasn't parsed and isn't a know tool attribute", - // ); - let attr = AttrItem { path: attr_path.clone(), args: self @@ -423,8 +413,19 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { self.check_invalid_crate_level_attr_item(&attr, n.item.span()); } - attributes.push(Attribute::Unparsed(Box::new(attr))); - }; + let attr = Attribute::Unparsed(Box::new(attr)); + + if self.tools.contains(&parts[0]) + // FIXME: this can be removed once #152369 has been merged. + // https://github.com/rust-lang/rust/pull/152369 + || [sym::allow, sym::deny, sym::expect, sym::forbid, sym::warn] + .contains(&parts[0]) + { + attributes.push(attr); + } else { + dropped_attributes.push(attr); + } + } } } } @@ -442,7 +443,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { if !matches!(self.stage.should_emit(), ShouldEmit::Nothing) && target == Target::WherePredicate { - self.check_invalid_where_predicate_attrs(attributes.iter()); + self.check_invalid_where_predicate_attrs(attributes.iter().chain(&dropped_attributes)); } attributes From d52943a72612839518bd35e18bec606ab7b6d94e Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sun, 22 Mar 2026 20:11:53 +1100 Subject: [PATCH 092/101] Remove assertion in `load_from_disk_or_invoke_provider_green`. We can only reach this point if `try_load_from_disk_fn` fails, and the condition of this assertion is basically just the inverse of what `try_load_from_disk_fn` does. Basically it's like this: ``` fn foo() -> bool { a && b } fn bar() { if foo() { return; } assert!(!a || !b); } ``` The assertion is just confusing and provides little value. --- compiler/rustc_query_impl/src/execution.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index 8fc6c851c3da9..2dae7c129a08f 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -528,13 +528,6 @@ fn load_from_disk_or_invoke_provider_green<'tcx, C: QueryCache>( "missing on-disk cache entry for {dep_node:?}" ); - // Sanity check for the logic in `ensure`: if the node is green and the result loadable, - // we should actually be able to load it. - debug_assert!( - !((query.will_cache_on_disk_for_key_fn)(tcx, key) && loadable_from_disk(tcx, prev_index)), - "missing on-disk cache entry for loadable {dep_node:?}" - ); - // We could not load a result from the on-disk cache, so // recompute. let prof_timer = tcx.prof.query_provider(); From f1c710b4ce46dba3fe204a4edcbd9c23911f0324 Mon Sep 17 00:00:00 2001 From: Amit5601 Date: Sun, 22 Mar 2026 16:24:02 +0530 Subject: [PATCH 093/101] internal: Remove clone_for_update from apply_demorgan.rs --- .../crates/ide-assists/src/handlers/apply_demorgan.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs index 80d0a6da12434..4ee49702489d2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs @@ -197,7 +197,7 @@ pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_> let (name, arg_expr) = validate_method_call_expr(ctx, &method_call)?; let ast::Expr::ClosureExpr(closure_expr) = arg_expr else { return None }; - let closure_body = closure_expr.body()?.clone_for_update(); + let closure_body = closure_expr.body()?; let op_range = method_call.syntax().text_range(); let label = format!("Apply De Morgan's law to `Iterator::{}`", name.text().as_str()); From be8dee11410ade2cd2f2f0ced7b9885aeebaa4c1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 22 Mar 2026 17:36:39 +0100 Subject: [PATCH 094/101] interpret/validity: remove unreachable error kind --- compiler/rustc_const_eval/src/interpret/validity.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 41a4cb2f23a1c..6466d533421bc 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -128,7 +128,6 @@ enum ExpectedKind { Reference, Box, RawPtr, - InitScalar, Bool, Char, Float, @@ -143,7 +142,6 @@ impl fmt::Display for ExpectedKind { ExpectedKind::Reference => "expected a reference", ExpectedKind::Box => "expected a box", ExpectedKind::RawPtr => "expected a raw pointer", - ExpectedKind::InitScalar => "expected initialized scalar value", ExpectedKind::Bool => "expected a boolean", ExpectedKind::Char => "expected a unicode scalar value", ExpectedKind::Float => "expected a floating point number", @@ -1478,7 +1476,9 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt, BackendRepr::Scalar(scalar_layout) => { if !scalar_layout.is_uninit_valid() { // There is something to check here. - let scalar = self.read_scalar(val, ExpectedKind::InitScalar)?; + // We read directly via `ecx` since the read cannot fail -- we already read + // this field above when recursing into the field. + let scalar = self.ecx.read_scalar(val)?; self.visit_scalar(scalar, scalar_layout)?; } } @@ -1487,8 +1487,9 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt, // FIXME: find a way to also check ScalarPair when one side can be uninit but // the other must be init. if !a_layout.is_uninit_valid() && !b_layout.is_uninit_valid() { - let (a, b) = - self.read_immediate(val, ExpectedKind::InitScalar)?.to_scalar_pair(); + // We read directly via `ecx` since the read cannot fail -- we already read + // this field above when recursing into the field. + let (a, b) = self.ecx.read_immediate(val)?.to_scalar_pair(); self.visit_scalar(a, a_layout)?; self.visit_scalar(b, b_layout)?; } From 8befc9d082de5184353b04d7ca5ca2d31078538b Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Sat, 21 Mar 2026 17:15:29 -0600 Subject: [PATCH 095/101] refactor RangeFromIter overflow-checks impl Crates with different overflow-checks settings accessing the same RangeFromIter resulted in incorrect values being yielded --- library/core/src/range/iter.rs | 65 ++++++++++++------- .../fromrangeiter-overflow-checks.rs | 31 +++++---- .../rangefrom-overflow-2crates-ocno.rs | 10 +++ .../rangefrom-overflow-2crates-ocyes.rs | 10 +++ .../iterators/rangefrom-overflow-2crates.rs | 40 ++++++++++++ 5 files changed, 122 insertions(+), 34 deletions(-) create mode 100644 tests/ui/iterators/auxiliary/rangefrom-overflow-2crates-ocno.rs create mode 100644 tests/ui/iterators/auxiliary/rangefrom-overflow-2crates-ocyes.rs create mode 100644 tests/ui/iterators/rangefrom-overflow-2crates.rs diff --git a/library/core/src/range/iter.rs b/library/core/src/range/iter.rs index c1d4fbbd23ad4..23ace6e1ba253 100644 --- a/library/core/src/range/iter.rs +++ b/library/core/src/range/iter.rs @@ -298,9 +298,9 @@ range_incl_exact_iter_impl! { #[derive(Debug, Clone)] pub struct RangeFromIter { start: A, - /// Whether the first element of the iterator has yielded. + /// Whether the maximum value of the iterator has yielded. /// Only used when overflow checks are enabled. - first: bool, + exhausted: bool, } impl RangeFromIter { @@ -309,10 +309,12 @@ impl RangeFromIter { #[rustc_inherit_overflow_checks] #[unstable(feature = "new_range_api", issue = "125687")] pub fn remainder(self) -> RangeFrom { - if intrinsics::overflow_checks() { - if !self.first { - return RangeFrom { start: Step::forward(self.start, 1) }; - } + // Need to handle this case even if overflow-checks are disabled, + // because a `RangeFromIter` could be exhausted in a crate with + // overflow-checks enabled, but then passed to a crate with them + // disabled before this is called. + if self.exhausted { + return RangeFrom { start: Step::forward(self.start, 1) }; } RangeFrom { start: self.start } @@ -326,14 +328,29 @@ impl Iterator for RangeFromIter { #[inline] #[rustc_inherit_overflow_checks] fn next(&mut self) -> Option { + if self.exhausted { + // This should panic if overflow checks are enabled, since + // `forward_checked` returned `None` in prior iteration. + self.start = Step::forward(self.start.clone(), 1); + + // If we get here, if means this iterator was exhausted by a crate + // with overflow-checks enabled, but now we're iterating in a crate with + // overflow-checks disabled. Since we successfully incremented `self.start` + // above (in many cases this will wrap around to MIN), we now unset + // the flag so we don't repeat this process in the next iteration. + // + // This could also happen if `forward_checked` returned None but + // (for whatever reason, not applicable to any std implementors) + // `forward` doesn't panic when overflow-checks are enabled. In that + // case, this is also the correct behavior. + self.exhausted = false; + } if intrinsics::overflow_checks() { - if self.first { - self.first = false; + let Some(n) = Step::forward_checked(self.start.clone(), 1) else { + self.exhausted = true; return Some(self.start.clone()); - } - - self.start = Step::forward(self.start.clone(), 1); - return Some(self.start.clone()); + }; + return Some(mem::replace(&mut self.start, n)); } let n = Step::forward(self.start.clone(), 1); @@ -348,18 +365,22 @@ impl Iterator for RangeFromIter { #[inline] #[rustc_inherit_overflow_checks] fn nth(&mut self, n: usize) -> Option { + // Typically `forward` will cause an overflow-check panic here, + // but unset the exhausted flag to handle the uncommon cases. + // See the comments in `next` for more details. + if self.exhausted { + self.start = Step::forward(self.start.clone(), 1); + self.exhausted = false; + } if intrinsics::overflow_checks() { - if self.first { - self.first = false; - - let plus_n = Step::forward(self.start.clone(), n); + let plus_n = Step::forward(self.start.clone(), n); + if let Some(plus_n1) = Step::forward_checked(plus_n.clone(), 1) { + self.start = plus_n1; + } else { self.start = plus_n.clone(); - return Some(plus_n); + self.exhausted = true; } - - let plus_n = Step::forward(self.start.clone(), n); - self.start = Step::forward(plus_n.clone(), 1); - return Some(self.start.clone()); + return Some(plus_n); } let plus_n = Step::forward(self.start.clone(), n); @@ -380,6 +401,6 @@ impl IntoIterator for RangeFrom { type IntoIter = RangeFromIter; fn into_iter(self) -> Self::IntoIter { - RangeFromIter { start: self.start, first: true } + RangeFromIter { start: self.start, exhausted: false } } } diff --git a/tests/codegen-llvm/fromrangeiter-overflow-checks.rs b/tests/codegen-llvm/fromrangeiter-overflow-checks.rs index 4d27f118ddd37..455c81c633407 100644 --- a/tests/codegen-llvm/fromrangeiter-overflow-checks.rs +++ b/tests/codegen-llvm/fromrangeiter-overflow-checks.rs @@ -2,7 +2,7 @@ // runtime check that panics after yielding the maximum value of the range bound type. That is // tested for by tests/ui/iterators/rangefrom-overflow-overflow-checks.rs // -// This test ensures that such a runtime check is *not* emitted when debug-assertions are +// This test ensures such runtime checks are optimized out when debug-assertions are // enabled, but overflow-checks are explicitly disabled. //@ revisions: DEBUG NOCHECKS @@ -11,17 +11,24 @@ #![crate_type = "lib"] #![feature(new_range_api)] -use std::range::{RangeFrom, RangeFromIter}; +use std::range::RangeFrom; -// CHECK-LABEL: @iterrangefrom_remainder( +// CHECK-LABEL: @rangefrom_increments( #[no_mangle] -pub unsafe fn iterrangefrom_remainder(x: RangeFromIter) -> RangeFrom { - // DEBUG: i32 noundef %x - // NOCHECKS: i32 noundef returned %x - // DEBUG: br i1 - // DEBUG: call core::panicking::panic_const::panic_const_add_overflow - // DEBUG: unreachable - // NOCHECKS-NOT: unreachable - // NOCHECKS: ret i32 %x - x.remainder() +pub unsafe fn rangefrom_increments(range: RangeFrom) -> RangeFrom { + // Iterator is contained entirely within this function, so the optimizer should + // be able to see that `exhausted` is never set and optimize out any branches. + + // CHECK: i32 noundef %range + // DEBUG: switch i32 %range + // DEBUG: call core::panicking::panic_const::panic_const_add_overflow + // DEBUG: unreachable + // NOCHECKS-NOT: unreachable + // NOCHECKS: [[REM:%[a-z_0-9.]+]] = add i32 %range, 2 + // NOCHECKS-NEXT: ret i32 [[REM]] + + let mut iter = range.into_iter(); + let _ = iter.next(); + let _ = iter.next(); + iter.remainder() } diff --git a/tests/ui/iterators/auxiliary/rangefrom-overflow-2crates-ocno.rs b/tests/ui/iterators/auxiliary/rangefrom-overflow-2crates-ocno.rs new file mode 100644 index 0000000000000..ebba4ae92c47f --- /dev/null +++ b/tests/ui/iterators/auxiliary/rangefrom-overflow-2crates-ocno.rs @@ -0,0 +1,10 @@ +//@ compile-flags: -C overflow-checks=no + +#![crate_type = "lib"] +#![feature(new_range_api)] + +use std::range::RangeFromIter; + +pub fn next(iter: &mut RangeFromIter) -> u8 { + iter.next().unwrap() +} diff --git a/tests/ui/iterators/auxiliary/rangefrom-overflow-2crates-ocyes.rs b/tests/ui/iterators/auxiliary/rangefrom-overflow-2crates-ocyes.rs new file mode 100644 index 0000000000000..8eb5392c7bac7 --- /dev/null +++ b/tests/ui/iterators/auxiliary/rangefrom-overflow-2crates-ocyes.rs @@ -0,0 +1,10 @@ +//@ compile-flags: -C overflow-checks=yes + +#![crate_type = "lib"] +#![feature(new_range_api)] + +use std::range::RangeFromIter; + +pub fn next(iter: &mut RangeFromIter) -> u8 { + iter.next().unwrap() +} diff --git a/tests/ui/iterators/rangefrom-overflow-2crates.rs b/tests/ui/iterators/rangefrom-overflow-2crates.rs new file mode 100644 index 0000000000000..c35c96f99322e --- /dev/null +++ b/tests/ui/iterators/rangefrom-overflow-2crates.rs @@ -0,0 +1,40 @@ +//@ run-pass +//@ needs-unwind +//@ aux-build:rangefrom-overflow-2crates-ocno.rs +//@ aux-build:rangefrom-overflow-2crates-ocyes.rs + +// For #154124 +// Test that two crates with different overflow-checks have the same results, +// even when the iterator is passed between them. + +#![feature(new_range_api)] + +extern crate rangefrom_overflow_2crates_ocno; +extern crate rangefrom_overflow_2crates_ocyes; + +use rangefrom_overflow_2crates_ocno::next as next_ocno; +use rangefrom_overflow_2crates_ocyes::next as next_ocyes; + +fn main() { + let mut iter_ocyes = std::range::RangeFrom::from(0_u8..).into_iter(); + let mut iter_ocno = iter_ocyes.clone(); + + for n in 0_u8..=255 { + assert_eq!(n, next_ocno(&mut iter_ocyes.clone())); + assert_eq!(n, next_ocyes(&mut iter_ocyes)); + assert_eq!(n, next_ocyes(&mut iter_ocno.clone())); + assert_eq!(n, next_ocno(&mut iter_ocno)); + } + + // `iter_ocno` should have wrapped + assert_eq!(0, next_ocyes(&mut iter_ocno.clone())); + assert_eq!(0, next_ocno(&mut iter_ocno)); + // `iter_ocyes` should be exhausted, + // which will wrap when called without overflow-checks + assert_eq!(0, next_ocno(&mut iter_ocyes.clone())); + // and panic when called with overflow-checks + let r = std::panic::catch_unwind(move || { + let _ = next_ocyes(&mut iter_ocyes); + }); + assert!(r.is_err()); +} From fa3e85a7e8dbe96f32b69cd978c3e345c76d2fa0 Mon Sep 17 00:00:00 2001 From: Usman Akinyemi Date: Mon, 23 Mar 2026 01:24:23 +0530 Subject: [PATCH 096/101] diagnostics: avoid ICE in confusable_method_name for associated functions Avoid unconditionally slicing `inputs()[1..]`, which assumes a `self` parameter. Use `is_method()` to conditionally skip the receiver, preventing out-of-bounds access and fixing suggestions for associated functions. Signed-off-by: Usman Akinyemi --- .../rustc_hir_typeck/src/method/suggest.rs | 5 ++- .../attributes/rustc_confusables_assoc_fn.rs | 22 +++++++++++++ .../rustc_confusables_assoc_fn.stderr | 33 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/ui/attributes/rustc_confusables_assoc_fn.rs create mode 100644 tests/ui/attributes/rustc_confusables_assoc_fn.stderr diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 9d496c66f9686..c86cfe00abbc5 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -2291,8 +2291,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_sig, ); let name = inherent_method.name(); + let inputs = fn_sig.inputs(); + let expected_inputs = + if inherent_method.is_method() { &inputs[1..] } else { inputs }; if let Some(ref args) = call_args - && fn_sig.inputs()[1..] + && expected_inputs .iter() .eq_by(args, |expected, found| self.may_coerce(*expected, *found)) { diff --git a/tests/ui/attributes/rustc_confusables_assoc_fn.rs b/tests/ui/attributes/rustc_confusables_assoc_fn.rs new file mode 100644 index 0000000000000..5612ece0a8d95 --- /dev/null +++ b/tests/ui/attributes/rustc_confusables_assoc_fn.rs @@ -0,0 +1,22 @@ +#![feature(rustc_attrs)] + +struct S; + +impl S { + #[rustc_confusables("bar")] + fn foo() {} + + #[rustc_confusables("baz")] + fn qux(&self, x: i32) {} +} + +fn main() { + S::bar(); + //~^ ERROR no function or associated item named `bar` + //~| HELP you might have meant to use `foo` + + let s = S; + s.baz(10); + //~^ ERROR no method named `baz` + //~| HELP you might have meant to use `qux` +} diff --git a/tests/ui/attributes/rustc_confusables_assoc_fn.stderr b/tests/ui/attributes/rustc_confusables_assoc_fn.stderr new file mode 100644 index 0000000000000..657ca8098b802 --- /dev/null +++ b/tests/ui/attributes/rustc_confusables_assoc_fn.stderr @@ -0,0 +1,33 @@ +error[E0599]: no function or associated item named `bar` found for struct `S` in the current scope + --> $DIR/rustc_confusables_assoc_fn.rs:14:8 + | +LL | struct S; + | -------- function or associated item `bar` not found for this struct +... +LL | S::bar(); + | ^^^ function or associated item not found in `S` + | +help: you might have meant to use `foo` + | +LL - S::bar(); +LL + S::foo(); + | + +error[E0599]: no method named `baz` found for struct `S` in the current scope + --> $DIR/rustc_confusables_assoc_fn.rs:19:7 + | +LL | struct S; + | -------- method `baz` not found for this struct +... +LL | s.baz(10); + | ^^^ method not found in `S` + | +help: you might have meant to use `qux` + | +LL - s.baz(10); +LL + s.qux(10); + | + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0599`. From cde59f09044e35ddc62ace83b91319490118b43f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 23 Mar 2026 09:10:59 +1100 Subject: [PATCH 097/101] Remove another assertion in `load_from_disk_or_invoke_provider_green`. This one just irritates me, and I don't think it adds much value. --- compiler/rustc_query_impl/src/execution.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index 2dae7c129a08f..39dba1f199c13 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -520,14 +520,6 @@ fn load_from_disk_or_invoke_provider_green<'tcx, C: QueryCache>( return value; } - // We always expect to find a cached result for things that - // can be forced from `DepNode`. - debug_assert!( - !(query.will_cache_on_disk_for_key_fn)(tcx, key) - || !tcx.key_fingerprint_style(dep_node.kind).is_maybe_recoverable(), - "missing on-disk cache entry for {dep_node:?}" - ); - // We could not load a result from the on-disk cache, so // recompute. let prof_timer = tcx.prof.query_provider(); From 6f084377888d0c22a791c65d363a9b34cc12280e Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 22 Mar 2026 22:12:20 +0100 Subject: [PATCH 098/101] improve inline assembly error messages by - using `DiagSymbolList` to get nicely formatted lists - mentioning the `modifier` when an invalid modifier is used. This is useful in case the span cannot be resolved (which I ran into). --- compiler/rustc_ast_lowering/src/asm.rs | 29 +++------- compiler/rustc_ast_lowering/src/errors.rs | 13 +++-- tests/ui/asm/aarch64/bad-options.stderr | 2 +- tests/ui/asm/aarch64/bad-reg.rs | 13 +++-- tests/ui/asm/aarch64/bad-reg.stderr | 58 +++++++++---------- tests/ui/asm/x86_64/bad-clobber-abi.stderr | 8 +-- tests/ui/asm/x86_64/bad-options.stderr | 2 +- .../x86_64/bad-reg.experimental_reg.stderr | 10 ++-- tests/ui/asm/x86_64/bad-reg.rs | 4 +- tests/ui/asm/x86_64/bad-reg.stable.stderr | 10 ++-- tests/ui/asm/x86_64/issue-82869.stderr | 4 +- 11 files changed, 74 insertions(+), 79 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/asm.rs b/compiler/rustc_ast_lowering/src/asm.rs index 51994c2e92f96..3a27962feca39 100644 --- a/compiler/rustc_ast_lowering/src/asm.rs +++ b/compiler/rustc_ast_lowering/src/asm.rs @@ -1,5 +1,4 @@ use std::collections::hash_map::Entry; -use std::fmt::Write; use rustc_ast::*; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; @@ -124,13 +123,9 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { self.dcx().emit_err(ClobberAbiNotSupported { abi_span: *abi_span }); } Err(supported_abis) => { - let mut abis = format!("`{}`", supported_abis[0]); - for m in &supported_abis[1..] { - let _ = write!(abis, ", `{m}`"); - } self.dcx().emit_err(InvalidAbiClobberAbi { abi_span: *abi_span, - supported_abis: abis, + supported_abis: supported_abis.to_vec().into(), }); } } @@ -164,15 +159,12 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { asm::InlineAsmRegOrRegClass::RegClass(if let Some(asm_arch) = asm_arch { asm::InlineAsmRegClass::parse(asm_arch, reg_class).unwrap_or_else( |supported_register_classes| { - let mut register_classes = - format!("`{}`", supported_register_classes[0]); - for m in &supported_register_classes[1..] { - let _ = write!(register_classes, ", `{m}`"); - } self.dcx().emit_err(InvalidRegisterClass { op_span: *op_sp, reg_class, - supported_register_classes: register_classes, + supported_register_classes: supported_register_classes + .to_vec() + .into(), }); asm::InlineAsmRegClass::Err }, @@ -272,23 +264,20 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { } let valid_modifiers = class.valid_modifiers(asm_arch.unwrap()); if !valid_modifiers.contains(&modifier) { - let sub = if !valid_modifiers.is_empty() { - let mut mods = format!("`{}`", valid_modifiers[0]); - for m in &valid_modifiers[1..] { - let _ = write!(mods, ", `{m}`"); - } - InvalidAsmTemplateModifierRegClassSub::SupportModifier { + let sub = if valid_modifiers.is_empty() { + InvalidAsmTemplateModifierRegClassSub::DoesNotSupportModifier { class_name: class.name(), - modifiers: mods, } } else { - InvalidAsmTemplateModifierRegClassSub::DoesNotSupportModifier { + InvalidAsmTemplateModifierRegClassSub::SupportModifier { class_name: class.name(), + modifiers: valid_modifiers.to_vec().into(), } }; self.dcx().emit_err(InvalidAsmTemplateModifierRegClass { placeholder_span, op_span: op_sp, + modifier: modifier.to_string(), sub, }); } diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs index 1eb72727df667..95b8bb48c6a9c 100644 --- a/compiler/rustc_ast_lowering/src/errors.rs +++ b/compiler/rustc_ast_lowering/src/errors.rs @@ -1,5 +1,5 @@ -use rustc_errors::DiagArgFromDisplay; use rustc_errors::codes::*; +use rustc_errors::{DiagArgFromDisplay, DiagSymbolList}; use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_span::{Ident, Span, Symbol}; @@ -191,10 +191,10 @@ pub(crate) struct ClobberAbiNotSupported { #[derive(Diagnostic)] #[note("the following ABIs are supported on this target: {$supported_abis}")] #[diag("invalid ABI for `clobber_abi`")] -pub(crate) struct InvalidAbiClobberAbi { +pub(crate) struct InvalidAbiClobberAbi<'a> { #[primary_span] pub abi_span: Span, - pub supported_abis: String, + pub supported_abis: DiagSymbolList<&'a str>, } #[derive(Diagnostic)] @@ -215,17 +215,18 @@ pub(crate) struct InvalidRegisterClass { #[primary_span] pub op_span: Span, pub reg_class: Symbol, - pub supported_register_classes: String, + pub supported_register_classes: DiagSymbolList, } #[derive(Diagnostic)] -#[diag("invalid asm template modifier for this register class")] +#[diag("invalid asm template modifier `{$modifier}` for this register class")] pub(crate) struct InvalidAsmTemplateModifierRegClass { #[primary_span] #[label("template modifier")] pub placeholder_span: Span, #[label("argument")] pub op_span: Span, + pub modifier: String, #[subdiagnostic] pub sub: InvalidAsmTemplateModifierRegClassSub, } @@ -235,7 +236,7 @@ pub(crate) enum InvalidAsmTemplateModifierRegClassSub { #[note( "the `{$class_name}` register class supports the following template modifiers: {$modifiers}" )] - SupportModifier { class_name: Symbol, modifiers: String }, + SupportModifier { class_name: Symbol, modifiers: DiagSymbolList }, #[note("the `{$class_name}` register class does not support template modifiers")] DoesNotSupportModifier { class_name: Symbol }, } diff --git a/tests/ui/asm/aarch64/bad-options.stderr b/tests/ui/asm/aarch64/bad-options.stderr index 54ab7cafa49f7..f7252d0ad374d 100644 --- a/tests/ui/asm/aarch64/bad-options.stderr +++ b/tests/ui/asm/aarch64/bad-options.stderr @@ -78,7 +78,7 @@ error: invalid ABI for `clobber_abi` LL | asm!("", clobber_abi("foo")); | ^^^^^^^^^^^^^^^^^^ | - = note: the following ABIs are supported on this target: `C`, `system`, `efiapi` + = note: the following ABIs are supported on this target: `C`, `system`, and `efiapi` error: aborting due to 13 previous errors diff --git a/tests/ui/asm/aarch64/bad-reg.rs b/tests/ui/asm/aarch64/bad-reg.rs index b99e5fe4b9e33..676d736e15d7a 100644 --- a/tests/ui/asm/aarch64/bad-reg.rs +++ b/tests/ui/asm/aarch64/bad-reg.rs @@ -1,7 +1,12 @@ +//@ add-minicore //@ only-aarch64 //@ compile-flags: -C target-feature=+neon +#![crate_type = "lib"] +#![feature(no_core)] +#![no_core] -use std::arch::asm; +extern crate minicore; +use minicore::*; fn main() { let mut foo = 0; @@ -14,11 +19,11 @@ fn main() { asm!("", in("foo") foo); //~^ ERROR invalid register `foo`: unknown register asm!("{:z}", in(reg) foo); - //~^ ERROR invalid asm template modifier for this register class + //~^ ERROR invalid asm template modifier `z` for this register class asm!("{:r}", in(vreg) foo); - //~^ ERROR invalid asm template modifier for this register class + //~^ ERROR invalid asm template modifier `r` for this register class asm!("{:r}", in(vreg_low16) foo); - //~^ ERROR invalid asm template modifier for this register class + //~^ ERROR invalid asm template modifier `r` for this register class asm!("{:a}", const 0); //~^ ERROR asm template modifiers are not allowed for `const` arguments asm!("{:a}", sym main); diff --git a/tests/ui/asm/aarch64/bad-reg.stderr b/tests/ui/asm/aarch64/bad-reg.stderr index c76722f32a74e..62f66cf3424f9 100644 --- a/tests/ui/asm/aarch64/bad-reg.stderr +++ b/tests/ui/asm/aarch64/bad-reg.stderr @@ -1,49 +1,49 @@ error: invalid register class `foo`: unknown register class - --> $DIR/bad-reg.rs:12:20 + --> $DIR/bad-reg.rs:17:20 | LL | asm!("{}", in(foo) foo); | ^^^^^^^^^^^ | - = note: the following register classes are supported on this target: `reg`, `vreg`, `vreg_low16`, `preg` + = note: the following register classes are supported on this target: `reg`, `vreg`, `vreg_low16`, and `preg` error: invalid register `foo`: unknown register - --> $DIR/bad-reg.rs:14:18 + --> $DIR/bad-reg.rs:19:18 | LL | asm!("", in("foo") foo); | ^^^^^^^^^^^^^ -error: invalid asm template modifier for this register class - --> $DIR/bad-reg.rs:16:15 +error: invalid asm template modifier `z` for this register class + --> $DIR/bad-reg.rs:21:15 | LL | asm!("{:z}", in(reg) foo); | ^^^^ ----------- argument | | | template modifier | - = note: the `reg` register class supports the following template modifiers: `w`, `x` + = note: the `reg` register class supports the following template modifiers: `w` and `x` -error: invalid asm template modifier for this register class - --> $DIR/bad-reg.rs:18:15 +error: invalid asm template modifier `r` for this register class + --> $DIR/bad-reg.rs:23:15 | LL | asm!("{:r}", in(vreg) foo); | ^^^^ ------------ argument | | | template modifier | - = note: the `vreg` register class supports the following template modifiers: `b`, `h`, `s`, `d`, `q`, `v` + = note: the `vreg` register class supports the following template modifiers: `b`, `h`, `s`, `d`, `q`, and `v` -error: invalid asm template modifier for this register class - --> $DIR/bad-reg.rs:20:15 +error: invalid asm template modifier `r` for this register class + --> $DIR/bad-reg.rs:25:15 | LL | asm!("{:r}", in(vreg_low16) foo); | ^^^^ ------------------ argument | | | template modifier | - = note: the `vreg_low16` register class supports the following template modifiers: `b`, `h`, `s`, `d`, `q`, `v` + = note: the `vreg_low16` register class supports the following template modifiers: `b`, `h`, `s`, `d`, `q`, and `v` error: asm template modifiers are not allowed for `const` arguments - --> $DIR/bad-reg.rs:22:15 + --> $DIR/bad-reg.rs:27:15 | LL | asm!("{:a}", const 0); | ^^^^ ------- argument @@ -51,7 +51,7 @@ LL | asm!("{:a}", const 0); | template modifier error: asm template modifiers are not allowed for `sym` arguments - --> $DIR/bad-reg.rs:24:15 + --> $DIR/bad-reg.rs:29:15 | LL | asm!("{:a}", sym main); | ^^^^ -------- argument @@ -59,49 +59,49 @@ LL | asm!("{:a}", sym main); | template modifier error: invalid register `x29`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:26:18 + --> $DIR/bad-reg.rs:31:18 | LL | asm!("", in("x29") foo); | ^^^^^^^^^^^^^ error: invalid register `sp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:28:18 + --> $DIR/bad-reg.rs:33:18 | LL | asm!("", in("sp") foo); | ^^^^^^^^^^^^ error: invalid register `xzr`: the zero register cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:30:18 + --> $DIR/bad-reg.rs:35:18 | LL | asm!("", in("xzr") foo); | ^^^^^^^^^^^^^ error: invalid register `x19`: x19 is used internally by LLVM and cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:32:18 + --> $DIR/bad-reg.rs:37:18 | LL | asm!("", in("x19") foo); | ^^^^^^^^^^^^^ error: register class `preg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:35:18 + --> $DIR/bad-reg.rs:40:18 | LL | asm!("", in("p0") foo); | ^^^^^^^^^^^^ error: register class `preg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:39:20 + --> $DIR/bad-reg.rs:44:20 | LL | asm!("{}", in(preg) foo); | ^^^^^^^^^^^^ error: register class `preg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:42:20 + --> $DIR/bad-reg.rs:47:20 | LL | asm!("{}", out(preg) _); | ^^^^^^^^^^^ error: register `w0` conflicts with register `x0` - --> $DIR/bad-reg.rs:48:32 + --> $DIR/bad-reg.rs:53:32 | LL | asm!("", in("x0") foo, in("w0") bar); | ------------ ^^^^^^^^^^^^ register `w0` @@ -109,7 +109,7 @@ LL | asm!("", in("x0") foo, in("w0") bar); | register `x0` error: register `x0` conflicts with register `x0` - --> $DIR/bad-reg.rs:50:32 + --> $DIR/bad-reg.rs:55:32 | LL | asm!("", in("x0") foo, out("x0") bar); | ------------ ^^^^^^^^^^^^^ register `x0` @@ -117,13 +117,13 @@ LL | asm!("", in("x0") foo, out("x0") bar); | register `x0` | help: use `lateout` instead of `out` to avoid conflict - --> $DIR/bad-reg.rs:50:18 + --> $DIR/bad-reg.rs:55:18 | LL | asm!("", in("x0") foo, out("x0") bar); | ^^^^^^^^^^^^ error: register `q0` conflicts with register `v0` - --> $DIR/bad-reg.rs:53:32 + --> $DIR/bad-reg.rs:58:32 | LL | asm!("", in("v0") foo, in("q0") bar); | ------------ ^^^^^^^^^^^^ register `q0` @@ -131,7 +131,7 @@ LL | asm!("", in("v0") foo, in("q0") bar); | register `v0` error: register `q0` conflicts with register `v0` - --> $DIR/bad-reg.rs:55:32 + --> $DIR/bad-reg.rs:60:32 | LL | asm!("", in("v0") foo, out("q0") bar); | ------------ ^^^^^^^^^^^^^ register `q0` @@ -139,13 +139,13 @@ LL | asm!("", in("v0") foo, out("q0") bar); | register `v0` | help: use `lateout` instead of `out` to avoid conflict - --> $DIR/bad-reg.rs:55:18 + --> $DIR/bad-reg.rs:60:18 | LL | asm!("", in("v0") foo, out("q0") bar); | ^^^^^^^^^^^^ error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:35:27 + --> $DIR/bad-reg.rs:40:27 | LL | asm!("", in("p0") foo); | ^^^ @@ -153,7 +153,7 @@ LL | asm!("", in("p0") foo); = note: register class `preg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:39:29 + --> $DIR/bad-reg.rs:44:29 | LL | asm!("{}", in(preg) foo); | ^^^ diff --git a/tests/ui/asm/x86_64/bad-clobber-abi.stderr b/tests/ui/asm/x86_64/bad-clobber-abi.stderr index 46e91a3951fb5..ec0d85f727fc7 100644 --- a/tests/ui/asm/x86_64/bad-clobber-abi.stderr +++ b/tests/ui/asm/x86_64/bad-clobber-abi.stderr @@ -4,7 +4,7 @@ error: invalid ABI for `clobber_abi` LL | asm!("", clobber_abi("foo")); | ^^^^^^^^^^^^^^^^^^ | - = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, `sysv64` + = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, and `sysv64` error: invalid ABI for `clobber_abi` --> $DIR/bad-clobber-abi.rs:13:35 @@ -12,7 +12,7 @@ error: invalid ABI for `clobber_abi` LL | asm!("", clobber_abi("C", "foo")); | ^^^^^ | - = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, `sysv64` + = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, and `sysv64` error: `C` ABI specified multiple times --> $DIR/bad-clobber-abi.rs:15:35 @@ -38,7 +38,7 @@ error: invalid ABI for `clobber_abi` LL | asm!("", clobber_abi("C", "foo", "C")); | ^^^^^ | - = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, `sysv64` + = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, and `sysv64` error: `C` ABI specified multiple times --> $DIR/bad-clobber-abi.rs:20:42 @@ -54,7 +54,7 @@ error: invalid ABI for `clobber_abi` LL | asm!("", clobber_abi("win64", "foo", "efiapi")); | ^^^^^ | - = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, `sysv64` + = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, and `sysv64` error: `win64` ABI specified multiple times --> $DIR/bad-clobber-abi.rs:23:46 diff --git a/tests/ui/asm/x86_64/bad-options.stderr b/tests/ui/asm/x86_64/bad-options.stderr index 366eb7cb90f39..a09f02e68ea96 100644 --- a/tests/ui/asm/x86_64/bad-options.stderr +++ b/tests/ui/asm/x86_64/bad-options.stderr @@ -93,7 +93,7 @@ error: invalid ABI for `clobber_abi` LL | asm!("", clobber_abi("foo")); | ^^^^^^^^^^^^^^^^^^ | - = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, `sysv64` + = note: the following ABIs are supported on this target: `C`, `system`, `efiapi`, `win64`, and `sysv64` error: `C` ABI specified multiple times --> $DIR/bad-options.rs:28:52 diff --git a/tests/ui/asm/x86_64/bad-reg.experimental_reg.stderr b/tests/ui/asm/x86_64/bad-reg.experimental_reg.stderr index 133921b894dd0..fe2a53aec5d48 100644 --- a/tests/ui/asm/x86_64/bad-reg.experimental_reg.stderr +++ b/tests/ui/asm/x86_64/bad-reg.experimental_reg.stderr @@ -4,7 +4,7 @@ error: invalid register class `foo`: unknown register class LL | asm!("{}", in(foo) foo); | ^^^^^^^^^^^ | - = note: the following register classes are supported on this target: `reg`, `reg_abcd`, `reg_byte`, `xmm_reg`, `ymm_reg`, `zmm_reg`, `kreg`, `kreg0`, `mmx_reg`, `x87_reg`, `tmm_reg` + = note: the following register classes are supported on this target: `reg`, `reg_abcd`, `reg_byte`, `xmm_reg`, `ymm_reg`, `zmm_reg`, `kreg`, `kreg0`, `mmx_reg`, `x87_reg`, and `tmm_reg` error: invalid register `foo`: unknown register --> $DIR/bad-reg.rs:22:18 @@ -12,7 +12,7 @@ error: invalid register `foo`: unknown register LL | asm!("", in("foo") foo); | ^^^^^^^^^^^^^ -error: invalid asm template modifier for this register class +error: invalid asm template modifier `z` for this register class --> $DIR/bad-reg.rs:24:15 | LL | asm!("{:z}", in(reg) foo); @@ -20,9 +20,9 @@ LL | asm!("{:z}", in(reg) foo); | | | template modifier | - = note: the `reg` register class supports the following template modifiers: `l`, `x`, `e`, `r` + = note: the `reg` register class supports the following template modifiers: `l`, `x`, `e`, and `r` -error: invalid asm template modifier for this register class +error: invalid asm template modifier `r` for this register class --> $DIR/bad-reg.rs:26:15 | LL | asm!("{:r}", in(xmm_reg) foo); @@ -30,7 +30,7 @@ LL | asm!("{:r}", in(xmm_reg) foo); | | | template modifier | - = note: the `xmm_reg` register class supports the following template modifiers: `x`, `y`, `z` + = note: the `xmm_reg` register class supports the following template modifiers: `x`, `y`, and `z` error: asm template modifiers are not allowed for `const` arguments --> $DIR/bad-reg.rs:28:15 diff --git a/tests/ui/asm/x86_64/bad-reg.rs b/tests/ui/asm/x86_64/bad-reg.rs index 4b3005d5675b3..d5b5cd39bf776 100644 --- a/tests/ui/asm/x86_64/bad-reg.rs +++ b/tests/ui/asm/x86_64/bad-reg.rs @@ -22,9 +22,9 @@ fn main() { asm!("", in("foo") foo); //~^ ERROR invalid register `foo`: unknown register asm!("{:z}", in(reg) foo); - //~^ ERROR invalid asm template modifier for this register class + //~^ ERROR invalid asm template modifier `z` for this register class asm!("{:r}", in(xmm_reg) foo); - //~^ ERROR invalid asm template modifier for this register class + //~^ ERROR invalid asm template modifier `r` for this register class asm!("{:a}", const 0); //~^ ERROR asm template modifiers are not allowed for `const` arguments asm!("{:a}", sym main); diff --git a/tests/ui/asm/x86_64/bad-reg.stable.stderr b/tests/ui/asm/x86_64/bad-reg.stable.stderr index a1c2792a5b1fc..d8a37933065e1 100644 --- a/tests/ui/asm/x86_64/bad-reg.stable.stderr +++ b/tests/ui/asm/x86_64/bad-reg.stable.stderr @@ -4,7 +4,7 @@ error: invalid register class `foo`: unknown register class LL | asm!("{}", in(foo) foo); | ^^^^^^^^^^^ | - = note: the following register classes are supported on this target: `reg`, `reg_abcd`, `reg_byte`, `xmm_reg`, `ymm_reg`, `zmm_reg`, `kreg`, `kreg0`, `mmx_reg`, `x87_reg`, `tmm_reg` + = note: the following register classes are supported on this target: `reg`, `reg_abcd`, `reg_byte`, `xmm_reg`, `ymm_reg`, `zmm_reg`, `kreg`, `kreg0`, `mmx_reg`, `x87_reg`, and `tmm_reg` error: invalid register `foo`: unknown register --> $DIR/bad-reg.rs:22:18 @@ -12,7 +12,7 @@ error: invalid register `foo`: unknown register LL | asm!("", in("foo") foo); | ^^^^^^^^^^^^^ -error: invalid asm template modifier for this register class +error: invalid asm template modifier `z` for this register class --> $DIR/bad-reg.rs:24:15 | LL | asm!("{:z}", in(reg) foo); @@ -20,9 +20,9 @@ LL | asm!("{:z}", in(reg) foo); | | | template modifier | - = note: the `reg` register class supports the following template modifiers: `l`, `x`, `e`, `r` + = note: the `reg` register class supports the following template modifiers: `l`, `x`, `e`, and `r` -error: invalid asm template modifier for this register class +error: invalid asm template modifier `r` for this register class --> $DIR/bad-reg.rs:26:15 | LL | asm!("{:r}", in(xmm_reg) foo); @@ -30,7 +30,7 @@ LL | asm!("{:r}", in(xmm_reg) foo); | | | template modifier | - = note: the `xmm_reg` register class supports the following template modifiers: `x`, `y`, `z` + = note: the `xmm_reg` register class supports the following template modifiers: `x`, `y`, and `z` error: asm template modifiers are not allowed for `const` arguments --> $DIR/bad-reg.rs:28:15 diff --git a/tests/ui/asm/x86_64/issue-82869.stderr b/tests/ui/asm/x86_64/issue-82869.stderr index 56e4909956913..5cb7e6e27efaa 100644 --- a/tests/ui/asm/x86_64/issue-82869.stderr +++ b/tests/ui/asm/x86_64/issue-82869.stderr @@ -4,7 +4,7 @@ error: invalid register class `vreg`: unknown register class LL | asm!("add {:d}, {:d}, d0", out(vreg) c, in(vreg) a, in("d0") { | ^^^^^^^^^^^ | - = note: the following register classes are supported on this target: `reg`, `reg_abcd`, `reg_byte`, `xmm_reg`, `ymm_reg`, `zmm_reg`, `kreg`, `kreg0`, `mmx_reg`, `x87_reg`, `tmm_reg` + = note: the following register classes are supported on this target: `reg`, `reg_abcd`, `reg_byte`, `xmm_reg`, `ymm_reg`, `zmm_reg`, `kreg`, `kreg0`, `mmx_reg`, `x87_reg`, and `tmm_reg` error: invalid register class `vreg`: unknown register class --> $DIR/issue-82869.rs:11:45 @@ -12,7 +12,7 @@ error: invalid register class `vreg`: unknown register class LL | asm!("add {:d}, {:d}, d0", out(vreg) c, in(vreg) a, in("d0") { | ^^^^^^^^^^ | - = note: the following register classes are supported on this target: `reg`, `reg_abcd`, `reg_byte`, `xmm_reg`, `ymm_reg`, `zmm_reg`, `kreg`, `kreg0`, `mmx_reg`, `x87_reg`, `tmm_reg` + = note: the following register classes are supported on this target: `reg`, `reg_abcd`, `reg_byte`, `xmm_reg`, `ymm_reg`, `zmm_reg`, `kreg`, `kreg0`, `mmx_reg`, `x87_reg`, and `tmm_reg` error: invalid register `d0`: unknown register --> $DIR/issue-82869.rs:11:57 From 327216d31ab48fe7a2521143b2ae3421b67cce32 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 23 Mar 2026 09:11:31 +1100 Subject: [PATCH 099/101] Refactor `load_from_disk_or_invoke_provider_green`. By removing the early return and using a `match` instead. - The two paths are of similar conceptual weight, and `match` reflects that. - This lets the `incremental_verify_ich` call be factored out. --- compiler/rustc_query_impl/src/execution.rs | 101 ++++++++++----------- 1 file changed, 47 insertions(+), 54 deletions(-) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index 39dba1f199c13..78ead0cf14f1b 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -488,66 +488,59 @@ fn load_from_disk_or_invoke_provider_green<'tcx, C: QueryCache>( debug_assert!(dep_graph_data.is_index_green(prev_index)); - // First we try to load the result from the on-disk cache. - // Some things are never cached on disk. - if let Some(value) = (query.try_load_from_disk_fn)(tcx, key, prev_index, dep_node_index) { - if std::intrinsics::unlikely(tcx.sess.opts.unstable_opts.query_dep_graph) { - dep_graph_data.mark_debug_loaded_from_disk(*dep_node) + // First try to load the result from the on-disk cache. Some things are never cached on disk. + let value; + let verify; + match (query.try_load_from_disk_fn)(tcx, key, prev_index, dep_node_index) { + Some(loaded_value) => { + if std::intrinsics::unlikely(tcx.sess.opts.unstable_opts.query_dep_graph) { + dep_graph_data.mark_debug_loaded_from_disk(*dep_node) + } + + value = loaded_value; + + let prev_fingerprint = dep_graph_data.prev_value_fingerprint_of(prev_index); + // If `-Zincremental-verify-ich` is specified, re-hash results from + // the cache and make sure that they have the expected fingerprint. + // + // If not, we still seek to verify a subset of fingerprints loaded + // from disk. Re-hashing results is fairly expensive, so we can't + // currently afford to verify every hash. This subset should still + // give us some coverage of potential bugs. + verify = prev_fingerprint.split().1.as_u64().is_multiple_of(32) + || tcx.sess.opts.unstable_opts.incremental_verify_ich; } + None => { + // We could not load a result from the on-disk cache, so recompute. The dep-graph for + // this computation is already in-place, so we can just call the query provider. + let prof_timer = tcx.prof.query_provider(); + value = tcx.dep_graph.with_ignore(|| (query.invoke_provider_fn)(tcx, key)); + prof_timer.finish_with_query_invocation_id(dep_node_index.into()); - let prev_fingerprint = dep_graph_data.prev_value_fingerprint_of(prev_index); - // If `-Zincremental-verify-ich` is specified, re-hash results from - // the cache and make sure that they have the expected fingerprint. - // - // If not, we still seek to verify a subset of fingerprints loaded - // from disk. Re-hashing results is fairly expensive, so we can't - // currently afford to verify every hash. This subset should still - // give us some coverage of potential bugs though. - let try_verify = prev_fingerprint.split().1.as_u64().is_multiple_of(32); - if std::intrinsics::unlikely( - try_verify || tcx.sess.opts.unstable_opts.incremental_verify_ich, - ) { - incremental_verify_ich( - tcx, - dep_graph_data, - &value, - prev_index, - query.hash_value_fn, - query.format_value, - ); + verify = true; } + }; - return value; + if verify { + // Verify that re-running the query produced a result with the expected hash. + // This catches bugs in query implementations, turning them into ICEs. + // For example, a query might sort its result by `DefId` - since `DefId`s are + // not stable across compilation sessions, the result could get up getting sorted + // in a different order when the query is re-run, even though all of the inputs + // (e.g. `DefPathHash` values) were green. + // + // See issue #82920 for an example of a miscompilation that would get turned into + // an ICE by this check + incremental_verify_ich( + tcx, + dep_graph_data, + &value, + prev_index, + query.hash_value_fn, + query.format_value, + ); } - // We could not load a result from the on-disk cache, so - // recompute. - let prof_timer = tcx.prof.query_provider(); - - // The dep-graph for this computation is already in-place. - // Call the query provider. - let value = tcx.dep_graph.with_ignore(|| (query.invoke_provider_fn)(tcx, key)); - - prof_timer.finish_with_query_invocation_id(dep_node_index.into()); - - // Verify that re-running the query produced a result with the expected hash - // This catches bugs in query implementations, turning them into ICEs. - // For example, a query might sort its result by `DefId` - since `DefId`s are - // not stable across compilation sessions, the result could get up getting sorted - // in a different order when the query is re-run, even though all of the inputs - // (e.g. `DefPathHash` values) were green. - // - // See issue #82920 for an example of a miscompilation that would get turned into - // an ICE by this check - incremental_verify_ich( - tcx, - dep_graph_data, - &value, - prev_index, - query.hash_value_fn, - query.format_value, - ); - value } From 8dcb2578e2a982ecfc96f572198704fd70e52e63 Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas) Chirananthavat" Date: Mon, 23 Mar 2026 10:55:10 +0700 Subject: [PATCH 100/101] Document consteval behavior of ub_checks & overflow_checks. --- library/core/src/intrinsics/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 68e4f1c2aa787..d26a65c00c97e 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2590,6 +2590,12 @@ pub const unsafe fn typed_swap_nonoverlapping(x: *mut T, y: *mut T) { /// assertions are enabled whenever the *user crate* has UB checks enabled. However, if the /// user has UB checks disabled, the checks will still get optimized out. This intrinsic is /// primarily used by [`crate::ub_checks::assert_unsafe_precondition`]. +/// +/// # Consteval +/// +/// In consteval, this function currently returns `true`. This is because the value of the `ub_checks` +/// configuration can differ across crates, but we need this function to always return the same +/// value in consteval in order to avoid unsoundness. #[rustc_intrinsic_const_stable_indirect] // just for UB checks #[inline(always)] #[rustc_intrinsic] @@ -2609,6 +2615,12 @@ pub const fn ub_checks() -> bool { /// `#[inline]`), gating assertions on `overflow_checks()` rather than `cfg!(overflow_checks)` means that /// assertions are enabled whenever the *user crate* has overflow checks enabled. However if the /// user has overflow checks disabled, the checks will still get optimized out. +/// +/// # Consteval +/// +/// In consteval, this function currently returns `true`. This is because the value of the `overflow_checks` +/// configuration can differ across crates, but we need this function to always return the same +/// value in consteval in order to avoid unsoundness. #[inline(always)] #[rustc_intrinsic] pub const fn overflow_checks() -> bool { From 338deef451efc6b6751db5e48ae983aab5cb1601 Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas) Chirananthavat" Date: Mon, 23 Mar 2026 10:56:33 +0700 Subject: [PATCH 101/101] Remove outdated consteval docs for is_val_statically_known. We've already stabilized float operations in const, which means we've already accepted that a `const fn` might behave differently in consteval vs at run time. --- library/core/src/intrinsics/mod.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index d26a65c00c97e..128df654e078d 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2502,14 +2502,6 @@ pub(crate) macro const_eval_select { /// particular value, ever. However, the compiler will generally make it /// return `true` only if the value of the argument is actually known. /// -/// # Stability concerns -/// -/// While it is safe to call, this intrinsic may behave differently in -/// a `const` context than otherwise. See the [`const_eval_select()`] -/// documentation for an explanation of the issues this can cause. Unlike -/// `const_eval_select`, this intrinsic isn't guaranteed to behave -/// deterministically even in a `const` context. -/// /// # Type Requirements /// /// `T` must be either a `bool`, a `char`, a primitive numeric type (e.g. `f32`,