From 4586a31e7b7b2217175d286ab69c2b77890ff373 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 6 Nov 2025 14:50:02 +0800 Subject: [PATCH 01/27] Fix not complete after inner-attr in source-file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example --- ```rust #![attr] $0 ``` **Before this PR** Empty completion list **After this PR** ```text ma makro!(…) macro_rules! makro md module kw async kw const kw crate:: kw enum kw extern kw fn kw impl kw impl for kw mod kw pub kw pub(crate) kw pub(super) kw self:: kw static kw struct kw trait kw type kw union kw unsafe kw use sn macro_rules sn tfn (Test function) sn tmod (Test module) ``` --- .../ide-completion/src/context/analysis.rs | 2 +- .../ide-completion/src/tests/item_list.rs | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) 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 d6d3978385d9b..7b5481073f49c 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 @@ -1635,7 +1635,7 @@ fn classify_name_ref<'db>( && let Some(t) = top.first_token() && let Some(prev) = t.prev_token().and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev)) - && ![T![;], T!['}'], T!['{']].contains(&prev.kind()) + && ![T![;], T!['}'], T!['{'], T![']']].contains(&prev.kind()) { // This was inferred to be an item position path, but it seems // to be part of some other broken node which leaked into an item diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs index ac32649d4ffbd..c031856a7061f 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs @@ -105,6 +105,40 @@ fn in_item_list_after_attr() { ) } +#[test] +fn in_item_list_after_inner_attr() { + check_with_base_items( + r#"#![attr] $0"#, + expect![[r#" + ma makro!(…) macro_rules! makro + md module + kw async + kw const + kw crate:: + kw enum + kw extern + kw fn + kw impl + kw impl for + kw mod + kw pub + kw pub(crate) + kw pub(super) + kw self:: + kw static + kw struct + kw trait + kw type + kw union + kw unsafe + kw use + sn macro_rules + sn tfn (Test function) + sn tmod (Test module) + "#]], + ) +} + #[test] fn in_qualified_path() { check_with_base_items( From c03118499fea8340147fb8a31b75a95611d171b1 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 12 Nov 2025 21:06:58 +0800 Subject: [PATCH 02/27] Fix not complete type alias in pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example --- ```rust enum Enum { Unit, Tuple(T), } type EnumAlias = Enum; fn f(x: EnumAlias) { match x { $0 => (), _ => (), } } ``` **Before this PR** ```text en Enum bn Enum::Tuple(…) Enum::Tuple($1)$0 bn Enum::Unit Enum::Unit$0 kw mut kw ref ``` **After this PR** ```text en Enum ta EnumAlias bn Enum::Tuple(…) Enum::Tuple($1)$0 bn Enum::Unit Enum::Unit$0 kw mut kw ref ``` --- .../ide-completion/src/completions/pattern.rs | 1 + .../ide-completion/src/tests/pattern.rs | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs index dcddc24890ac4..eeb2c65e48449 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs @@ -101,6 +101,7 @@ pub(crate) fn complete_pattern( hir::ModuleDef::Const(..) => refutable, hir::ModuleDef::Module(..) => true, hir::ModuleDef::Macro(mac) => mac.is_fn_like(ctx.db), + hir::ModuleDef::TypeAlias(_) => true, _ => false, }, hir::ScopeDef::ImplSelfType(impl_) => match impl_.self_ty(ctx.db).as_adt() { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs index 6eb0b818d6970..380bab9150142 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs @@ -782,6 +782,37 @@ fn f(x: EnumAlias) { ); } +#[test] +fn through_alias_it_self() { + check( + r#" +enum Enum { + Unit, + Tuple(T), +} + +type EnumAlias = Enum; + +fn f(x: EnumAlias) { + match x { + $0 => (), + _ => (), + } + +} + +"#, + expect![[r#" + en Enum + ta EnumAlias + bn Enum::Tuple(…) Enum::Tuple($1)$0 + bn Enum::Unit Enum::Unit$0 + kw mut + kw ref + "#]], + ); +} + #[test] fn pat_no_unstable_item_on_stable() { check( From 0234d7c132c7ceb9ed06d79a8323c066e0416646 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 22 Nov 2025 16:34:18 +0800 Subject: [PATCH 03/27] Fix skipiter not applicable in autoderef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example --- **std example**: ```rust fn foo(nums: std::rc::Rc<[i32]>) { nums.$0 } ``` --- **minicore example**: ```rust struct Foo; impl Foo { fn iter(&self) -> Iter { Iter } } impl IntoIterator for &Foo { type Item = (); type IntoIter = Iter; fn into_iter(self) -> Self::IntoIter { Iter } } struct Ref; impl core::ops::Deref for Ref { type Target = Foo; fn deref(&self) -> &Self::Target { &Foo } } struct Iter; impl Iterator for Iter { type Item = (); fn next(&mut self) -> Option { None } } fn foo() { Ref.$0 } ``` **Before this PR** ```text me deref() (use core::ops::Deref) fn(&self) -> &::Target me into_iter() (as IntoIterator) fn(self) -> ::IntoIter me iter() fn(&self) -> Iter ``` **After this PR** ```text me deref() (use core::ops::Deref) fn(&self) -> &::Target me into_iter() (as IntoIterator) fn(self) -> ::IntoIter me iter() fn(&self) -> Iter me iter().by_ref() (as Iterator) fn(&mut self) -> &mut Self me iter().into_iter() (as IntoIterator) fn(self) -> ::IntoIter me iter().next() (as Iterator) fn(&mut self) -> Option<::Item> me iter().nth(…) (as Iterator) fn(&mut self, usize) -> Option<::Item> ``` --- .../ide-completion/src/completions/dot.rs | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs index c9f4405872039..9c2e0dcf1c625 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs @@ -91,9 +91,9 @@ pub(crate) fn complete_dot( // its return type, so we instead check for `<&Self as IntoIterator>::IntoIter`. // Does <&receiver_ty as IntoIterator>::IntoIter` exist? Assume `iter` is valid let iter = receiver_ty - .strip_references() - .add_reference(hir::Mutability::Shared) - .into_iterator_iter(ctx.db) + .autoderef(ctx.db) + .map(|ty| ty.strip_references().add_reference(hir::Mutability::Shared)) + .find_map(|ty| ty.into_iterator_iter(ctx.db)) .map(|ty| (ty, SmolStr::new_static("iter()"))); // Does ::IntoIter` exist? let into_iter = || { @@ -1466,6 +1466,40 @@ fn foo() { me into_iter().nth(…) (as Iterator) fn(&mut self, usize) -> Option<::Item> "#]], ); + check_no_kw( + r#" +//- minicore: iterator, deref +struct Foo; +impl Foo { fn iter(&self) -> Iter { Iter } } +impl IntoIterator for &Foo { + type Item = (); + type IntoIter = Iter; + fn into_iter(self) -> Self::IntoIter { Iter } +} +struct Ref; +impl core::ops::Deref for Ref { + type Target = Foo; + fn deref(&self) -> &Self::Target { &Foo } +} +struct Iter; +impl Iterator for Iter { + type Item = (); + fn next(&mut self) -> Option { None } +} +fn foo() { + Ref.$0 +} +"#, + expect![[r#" + me deref() (use core::ops::Deref) fn(&self) -> &::Target + me into_iter() (as IntoIterator) fn(self) -> ::IntoIter + me iter() fn(&self) -> Iter + me iter().by_ref() (as Iterator) fn(&mut self) -> &mut Self + me iter().into_iter() (as IntoIterator) fn(self) -> ::IntoIter + me iter().next() (as Iterator) fn(&mut self) -> Option<::Item> + me iter().nth(…) (as Iterator) fn(&mut self, usize) -> Option<::Item> + "#]], + ); } #[test] From 9bf3695a0dd0509e27762e95f029fc825447a8f8 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 18 Sep 2025 12:04:46 +0800 Subject: [PATCH 04/27] Add multiple generate for enum generate is, as, try_into Examples --- ```rust enum Variant { Undefined, $0Minor, M$0ajor, } ``` -> ```rust enum Variant { Undefined, Minor, Major, } impl Variant { /// Returns `true` if the variant is [`Minor`]. /// /// [`Minor`]: Variant::Minor #[must_use] fn is_minor(&self) -> bool { matches!(self, Self::Minor) } /// Returns `true` if the variant is [`Major`]. /// /// [`Major`]: Variant::Major #[must_use] fn is_major(&self) -> bool { matches!(self, Self::Major) } } ``` --- ```rust enum Value { Unit(()), $0Number(i32), Text(String)$0, } ``` -> ```rust enum Value { Unit(()), Number(i32), Text(String), } impl Value { fn try_into_number(self) -> Result { if let Self::Number(v) = self { Ok(v) } else { Err(self) } } fn try_into_text(self) -> Result { if let Self::Text(v) = self { Ok(v) } else { Err(self) } } } ``` --- ```rust enum Value { Unit(()), $0Number(i32), Text(String)$0, } ``` -> ```rust enum Value { Unit(()), Number(i32), Text(String), } impl Value { fn as_number(&self) -> Option<&i32> { if let Self::Number(v) = self { Some(v) } else { None } } fn as_text(&self) -> Option<&String> { if let Self::Text(v) = self { Some(v) } else { None } } } ``` --- .../src/handlers/generate_enum_is_method.rs | 92 ++++++++-- .../generate_enum_projection_method.rs | 166 ++++++++++++++---- 2 files changed, 210 insertions(+), 48 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs index 517906b429a6f..b866022a7dfd6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs @@ -1,13 +1,12 @@ -use std::slice; - use ide_db::assists::GroupLabel; +use itertools::Itertools; use stdx::to_lower_snake_case; use syntax::ast::HasVisibility; use syntax::ast::{self, AstNode, HasName}; use crate::{ AssistContext, AssistId, Assists, - utils::{add_method_to_adt, find_struct_impl}, + utils::{add_method_to_adt, find_struct_impl, is_selected}, }; // Assist: generate_enum_is_method @@ -41,20 +40,21 @@ use crate::{ // ``` pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let variant = ctx.find_node_at_offset::()?; - let variant_name = variant.name()?; let parent_enum = ast::Adt::Enum(variant.parent_enum()); - let pattern_suffix = match variant.kind() { - ast::StructKind::Record(_) => " { .. }", - ast::StructKind::Tuple(_) => "(..)", - ast::StructKind::Unit => "", - }; - + let variants = variant + .parent_enum() + .variant_list()? + .variants() + .filter(|it| is_selected(it, ctx.selection_trimmed(), true)) + .collect::>(); + let methods = variants.iter().map(Method::new).collect::>>()?; let enum_name = parent_enum.name()?; let enum_lowercase_name = to_lower_snake_case(&enum_name.to_string()).replace('_', " "); - let fn_name = format!("is_{}", &to_lower_snake_case(&variant_name.text())); + let fn_names = methods.iter().map(|it| it.fn_name.clone()).collect::>(); + stdx::never!(variants.is_empty()); // Return early if we've found an existing new fn - let impl_def = find_struct_impl(ctx, &parent_enum, slice::from_ref(&fn_name))?; + let impl_def = find_struct_impl(ctx, &parent_enum, &fn_names)?; let target = variant.syntax().text_range(); acc.add_group( @@ -64,21 +64,47 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_> target, |builder| { let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} ")); - let method = format!( - " /// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`]. + let method = methods + .iter() + .map(|Method { pattern_suffix, fn_name, variant_name }| { + format!( + " \ + /// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`]. /// /// [`{variant_name}`]: {enum_name}::{variant_name} #[must_use] {vis}fn {fn_name}(&self) -> bool {{ matches!(self, Self::{variant_name}{pattern_suffix}) }}", - ); + ) + }) + .join("\n\n"); add_method_to_adt(builder, &parent_enum, impl_def, &method); }, ) } +struct Method { + pattern_suffix: &'static str, + fn_name: String, + variant_name: ast::Name, +} + +impl Method { + fn new(variant: &ast::Variant) -> Option { + let pattern_suffix = match variant.kind() { + ast::StructKind::Record(_) => " { .. }", + ast::StructKind::Tuple(_) => "(..)", + ast::StructKind::Unit => "", + }; + + let variant_name = variant.name()?; + let fn_name = format!("is_{}", &to_lower_snake_case(&variant_name.text())); + Some(Method { pattern_suffix, fn_name, variant_name }) + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -113,6 +139,42 @@ impl Variant { ); } + #[test] + fn test_generate_enum_is_from_multiple_variant() { + check_assist( + generate_enum_is_method, + r#" +enum Variant { + Undefined, + $0Minor, + M$0ajor, +}"#, + r#"enum Variant { + Undefined, + Minor, + Major, +} + +impl Variant { + /// Returns `true` if the variant is [`Minor`]. + /// + /// [`Minor`]: Variant::Minor + #[must_use] + fn is_minor(&self) -> bool { + matches!(self, Self::Minor) + } + + /// Returns `true` if the variant is [`Major`]. + /// + /// [`Major`]: Variant::Major + #[must_use] + fn is_major(&self) -> bool { + matches!(self, Self::Major) + } +}"#, + ); + } + #[test] fn test_generate_enum_is_already_implemented() { check_assist_not_applicable( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs index e4b0f83049768..39a6382b7cc71 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs @@ -1,5 +1,3 @@ -use std::slice; - use ide_db::assists::GroupLabel; use itertools::Itertools; use stdx::to_lower_snake_case; @@ -8,7 +6,7 @@ use syntax::ast::{self, AstNode, HasName}; use crate::{ AssistContext, AssistId, Assists, - utils::{add_method_to_adt, find_struct_impl}, + utils::{add_method_to_adt, find_struct_impl, is_selected}, }; // Assist: generate_enum_try_into_method @@ -128,29 +126,22 @@ fn generate_enum_projection_method( } = props; let variant = ctx.find_node_at_offset::()?; - let variant_name = variant.name()?; let parent_enum = ast::Adt::Enum(variant.parent_enum()); - - let (pattern_suffix, field_type, bound_name) = match variant.kind() { - ast::StructKind::Record(record) => { - let (field,) = record.fields().collect_tuple()?; - let name = field.name()?.to_string(); - let ty = field.ty()?; - let pattern_suffix = format!(" {{ {name} }}"); - (pattern_suffix, ty, name) - } - ast::StructKind::Tuple(tuple) => { - let (field,) = tuple.fields().collect_tuple()?; - let ty = field.ty()?; - ("(v)".to_owned(), ty, "v".to_owned()) - } - ast::StructKind::Unit => return None, - }; - - let fn_name = format!("{fn_name_prefix}_{}", &to_lower_snake_case(&variant_name.text())); + let variants = variant + .parent_enum() + .variant_list()? + .variants() + .filter(|it| is_selected(it, ctx.selection_trimmed(), true)) + .collect::>(); + let methods = variants + .iter() + .map(|variant| Method::new(variant, fn_name_prefix)) + .collect::>>()?; + let fn_names = methods.iter().map(|it| it.fn_name.clone()).collect::>(); + stdx::never!(variants.is_empty()); // Return early if we've found an existing new fn - let impl_def = find_struct_impl(ctx, &parent_enum, slice::from_ref(&fn_name))?; + let impl_def = find_struct_impl(ctx, &parent_enum, &fn_names)?; let target = variant.syntax().text_range(); acc.add_group( @@ -161,29 +152,66 @@ fn generate_enum_projection_method( |builder| { let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} ")); - let field_type_syntax = field_type.syntax(); + let must_use = if ctx.config.assist_emit_must_use { "#[must_use]\n " } else { "" }; - let must_use = if ctx.config.assist_emit_must_use { - "#[must_use]\n " - } else { - "" - }; - - let method = format!( - " {must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type_syntax}{return_suffix} {{ + let method = methods + .iter() + .map(|Method { pattern_suffix, field_type, bound_name, fn_name, variant_name }| { + format!( + " \ + {must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{ if let Self::{variant_name}{pattern_suffix} = self {{ {happy_case}({bound_name}) }} else {{ {sad_case} }} }}" - ); + ) + }) + .join("\n\n"); add_method_to_adt(builder, &parent_enum, impl_def, &method); }, ) } +struct Method { + pattern_suffix: String, + field_type: ast::Type, + bound_name: String, + fn_name: String, + variant_name: ast::Name, +} + +impl Method { + fn new(variant: &ast::Variant, fn_name_prefix: &str) -> Option { + let variant_name = variant.name()?; + let fn_name = format!("{fn_name_prefix}_{}", &to_lower_snake_case(&variant_name.text())); + + match variant.kind() { + ast::StructKind::Record(record) => { + let (field,) = record.fields().collect_tuple()?; + let name = field.name()?.to_string(); + let field_type = field.ty()?; + let pattern_suffix = format!(" {{ {name} }}"); + Some(Method { pattern_suffix, field_type, bound_name: name, fn_name, variant_name }) + } + ast::StructKind::Tuple(tuple) => { + let (field,) = tuple.fields().collect_tuple()?; + let field_type = field.ty()?; + Some(Method { + pattern_suffix: "(v)".to_owned(), + field_type, + bound_name: "v".to_owned(), + variant_name, + fn_name, + }) + } + ast::StructKind::Unit => None, + } + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -216,6 +244,42 @@ impl Value { ); } + #[test] + fn test_generate_enum_multiple_try_into_tuple_variant() { + check_assist( + generate_enum_try_into_method, + r#" +enum Value { + Unit(()), + $0Number(i32), + Text(String)$0, +}"#, + r#"enum Value { + Unit(()), + Number(i32), + Text(String), +} + +impl Value { + fn try_into_number(self) -> Result { + if let Self::Number(v) = self { + Ok(v) + } else { + Err(self) + } + } + + fn try_into_text(self) -> Result { + if let Self::Text(v) = self { + Ok(v) + } else { + Err(self) + } + } +}"#, + ); + } + #[test] fn test_generate_enum_try_into_already_implemented() { check_assist_not_applicable( @@ -323,6 +387,42 @@ impl Value { ); } + #[test] + fn test_generate_enum_as_multiple_tuple_variant() { + check_assist( + generate_enum_as_method, + r#" +enum Value { + Unit(()), + $0Number(i32), + Text(String)$0, +}"#, + r#"enum Value { + Unit(()), + Number(i32), + Text(String), +} + +impl Value { + fn as_number(&self) -> Option<&i32> { + if let Self::Number(v) = self { + Some(v) + } else { + None + } + } + + fn as_text(&self) -> Option<&String> { + if let Self::Text(v) = self { + Some(v) + } else { + None + } + } +}"#, + ); + } + #[test] fn test_generate_enum_as_record_variant() { check_assist( From 83edf8fa09dece965f3b6ff305f47721036a2a85 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 7 Nov 2025 23:58:27 +0530 Subject: [PATCH 05/27] make postcard first class member of proc-macro-srv-cli --- .../crates/proc-macro-srv-cli/Cargo.toml | 3 +- .../crates/proc-macro-srv-cli/src/main.rs | 7 +- .../proc-macro-srv-cli/src/main_loop.rs | 146 +++++++++++++++++- 3 files changed, 146 insertions(+), 10 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml index dd31e74915bfd..f6022cf2c7bda 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml @@ -14,14 +14,13 @@ publish = false proc-macro-srv.workspace = true proc-macro-api.workspace = true tt.workspace = true +postcard.workspace = true clap = {version = "4.5.42", default-features = false, features = ["std"]} -postcard = { version = "1.1.3", optional = true } [features] default = ["postcard"] sysroot-abi = ["proc-macro-srv/sysroot-abi", "proc-macro-api/sysroot-abi"] in-rust-tree = ["proc-macro-srv/in-rust-tree", "sysroot-abi"] -postcard = ["dep:postcard"] [[bin]] diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs index 9d74fa637aa97..9149adb4687ea 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs @@ -31,7 +31,7 @@ fn main() -> std::io::Result<()> { clap::Arg::new("format") .long("format") .action(clap::ArgAction::Set) - .default_value("json") + .default_value("postcard") .value_parser(clap::builder::EnumValueParser::::new()), clap::Arg::new("version") .long("version") @@ -51,26 +51,23 @@ fn main() -> std::io::Result<()> { #[derive(Copy, Clone)] enum ProtocolFormat { Json, - #[cfg(feature = "postcard")] Postcard, } impl ValueEnum for ProtocolFormat { fn value_variants<'a>() -> &'a [Self] { - &[ProtocolFormat::Json] + &[ProtocolFormat::Json, ProtocolFormat::Postcard] } fn to_possible_value(&self) -> Option { match self { ProtocolFormat::Json => Some(clap::builder::PossibleValue::new("json")), - #[cfg(feature = "postcard")] ProtocolFormat::Postcard => Some(clap::builder::PossibleValue::new("postcard")), } } fn from_str(input: &str, _ignore_case: bool) -> Result { match input { "json" => Ok(ProtocolFormat::Json), - #[cfg(feature = "postcard")] "postcard" => Ok(ProtocolFormat::Postcard), _ => Err(format!("unknown protocol format: {input}")), } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs index 55331075704a3..c48ae3c2ed0c5 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -1,5 +1,5 @@ //! The main loop of the proc-macro server. -use std::io; +use std::{io, thread}; use proc_macro_api::{ legacy_protocol::{ @@ -14,6 +14,7 @@ use proc_macro_api::{ use proc_macro_srv::{EnvSnapshot, SpanId}; use crate::ProtocolFormat; +use std::io::BufReader; struct SpanTrans; @@ -37,8 +38,7 @@ impl SpanTransformer for SpanTrans { pub(crate) fn run(format: ProtocolFormat) -> io::Result<()> { match format { ProtocolFormat::Json => run_json(), - #[cfg(feature = "postcard")] - ProtocolFormat::Postcard => unimplemented!(), + ProtocolFormat::Postcard => run_postcard(), } } @@ -171,3 +171,143 @@ fn run_json() -> io::Result<()> { Ok(()) } + +fn run_postcard() -> io::Result<()> { + fn macro_kind_to_api(kind: proc_macro_srv::ProcMacroKind) -> proc_macro_api::ProcMacroKind { + match kind { + proc_macro_srv::ProcMacroKind::CustomDerive => { + proc_macro_api::ProcMacroKind::CustomDerive + } + proc_macro_srv::ProcMacroKind::Bang => proc_macro_api::ProcMacroKind::Bang, + proc_macro_srv::ProcMacroKind::Attr => proc_macro_api::ProcMacroKind::Attr, + } + } + + let stdin = io::stdin(); + let stdout = io::stdout(); + let mut reader = BufReader::new(stdin.lock()); + let mut writer = stdout.lock(); + let mut buf = vec![0; 1024]; + + let env = proc_macro_srv::EnvSnapshot::default(); + let srv = proc_macro_srv::ProcMacroSrv::new(&env); + + let mut span_mode = msg::SpanMode::Id; + use proc_macro_api::legacy_protocol::postcard_wire; + + while let Some(req) = postcard_wire::read_postcard(&mut reader, &mut buf)? { + let Ok(req) = postcard_wire::decode_cobs(req) else { + thread::sleep(std::time::Duration::from_secs(1)); + continue; + }; + dbg!(&req); + + let res = match req { + msg::Request::ListMacros { dylib_path } => { + msg::Response::ListMacros(srv.list_macros(&dylib_path).map(|macros| { + macros.into_iter().map(|(name, kind)| (name, macro_kind_to_api(kind))).collect() + })) + } + msg::Request::ExpandMacro(task) => { + let msg::ExpandMacro { + lib, + env, + current_dir, + data: + msg::ExpandMacroData { + macro_body, + macro_name, + attributes, + has_global_spans: + msg::ExpnGlobals { serialize: _, def_site, call_site, mixed_site }, + span_data_table, + }, + } = *task; + match span_mode { + msg::SpanMode::Id => msg::Response::ExpandMacro({ + let def_site = proc_macro_srv::SpanId(def_site as u32); + let call_site = proc_macro_srv::SpanId(call_site as u32); + let mixed_site = proc_macro_srv::SpanId(mixed_site as u32); + + let macro_body = + macro_body.to_subtree_unresolved::(CURRENT_API_VERSION); + let attributes = attributes + .map(|it| it.to_subtree_unresolved::(CURRENT_API_VERSION)); + + srv.expand( + lib, + &env, + current_dir, + ¯o_name, + macro_body, + attributes, + def_site, + call_site, + mixed_site, + ) + .map(|it| { + msg::FlatTree::new_raw::( + tt::SubtreeView::new(&it), + CURRENT_API_VERSION, + ) + }) + .map_err(|e| e.into_string().unwrap_or_default()) + .map_err(msg::PanicMessage) + }), + msg::SpanMode::RustAnalyzer => msg::Response::ExpandMacroExtended({ + let mut span_data_table = + msg::deserialize_span_data_index_map(&span_data_table); + + let def_site = span_data_table[def_site]; + let call_site = span_data_table[call_site]; + let mixed_site = span_data_table[mixed_site]; + + let macro_body = + macro_body.to_subtree_resolved(CURRENT_API_VERSION, &span_data_table); + let attributes = attributes.map(|it| { + it.to_subtree_resolved(CURRENT_API_VERSION, &span_data_table) + }); + srv.expand( + lib, + &env, + current_dir, + ¯o_name, + macro_body, + attributes, + def_site, + call_site, + mixed_site, + ) + .map(|it| { + ( + msg::FlatTree::new( + tt::SubtreeView::new(&it), + CURRENT_API_VERSION, + &mut span_data_table, + ), + msg::serialize_span_data_index_map(&span_data_table), + ) + }) + .map(|(tree, span_data_table)| msg::ExpandMacroExtended { + tree, + span_data_table, + }) + .map_err(|e| e.into_string().unwrap_or_default()) + .map_err(msg::PanicMessage) + }), + } + } + msg::Request::ApiVersionCheck {} => msg::Response::ApiVersionCheck(CURRENT_API_VERSION), + msg::Request::SetConfig(config) => { + span_mode = config.span_mode; + msg::Response::SetConfig(config) + } + }; + + dbg!(&res); + let res = postcard_wire::encode_cobs(&res).unwrap(); + postcard_wire::write_postcard(&mut writer, &res)?; + } + + Ok(()) +} From 4284804eb55eaddd5fb4ceae1eb8a6447c533259 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 7 Nov 2025 23:58:55 +0530 Subject: [PATCH 06/27] add postcard related methods to proc-macro-apo --- src/tools/rust-analyzer/Cargo.lock | 15 +++++ src/tools/rust-analyzer/Cargo.toml | 1 + .../crates/proc-macro-api/Cargo.toml | 1 + .../proc-macro-api/src/legacy_protocol.rs | 38 ++++++++++- .../src/legacy_protocol/postcard_wire.rs | 27 ++++++++ .../crates/proc-macro-api/src/lib.rs | 3 +- .../crates/proc-macro-api/src/process.rs | 64 ++++++++++++++++++- .../proc-macro-srv-cli/src/main_loop.rs | 3 - 8 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard_wire.rs diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 4de8d09dcab87..d78c0f765d887 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -560,6 +560,18 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "ena" version = "0.14.3" @@ -1785,6 +1797,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" dependencies = [ "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", "heapless", "serde", ] @@ -1820,6 +1834,7 @@ dependencies = [ "indexmap", "intern", "paths", + "postcard", "proc-macro-srv", "rustc-hash 2.1.1", "serde", diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 8ff7e0e8a2a95..946e54b40b022 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -127,6 +127,7 @@ object = { version = "0.36.7", default-features = false, features = [ "macho", "pe", ] } +postcard = {version = "1.1.3", features = ["alloc"]} process-wrap = { version = "8.2.1", features = ["std"] } pulldown-cmark-to-cmark = "10.0.4" pulldown-cmark = { version = "0.9.6", default-features = false } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml index 63745b9f74931..4077e11b71850 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml @@ -29,6 +29,7 @@ proc-macro-srv = {workspace = true, optional = true} span = { path = "../span", version = "0.0.0", default-features = false} intern.workspace = true +postcard.workspace = true [features] sysroot-abi = ["proc-macro-srv", "proc-macro-srv/sysroot-abi"] diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs index 0a72052cc53b4..5d590520c8c86 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs @@ -2,6 +2,7 @@ pub mod json; pub mod msg; +pub mod postcard_wire; use std::{ io::{BufRead, Write}, @@ -151,7 +152,11 @@ fn send_task(srv: &ProcMacroServerProcess, req: Request) -> Result, +) -> Result, ServerError> { + let bytes = postcard_wire::encode_cobs(&req) + .map_err(|_| ServerError { message: "failed to write request".into(), io: None })?; + + postcard_wire::write_postcard(&mut writer, &bytes).map_err(|err| ServerError { + message: "failed to write request".into(), + io: Some(Arc::new(err)), + })?; + + let frame = postcard_wire::read_postcard(&mut reader, buf).map_err(|err| ServerError { + message: "failed to read response".into(), + io: Some(Arc::new(err)), + })?; + + match frame { + None => Ok(None), + Some(bytes) => { + let resp: Response = postcard_wire::decode_cobs(bytes).map_err(|e| ServerError { + message: format!("failed to decode message: {e}"), + io: None, + })?; + Ok(Some(resp)) + } + } +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard_wire.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard_wire.rs new file mode 100644 index 0000000000000..2c8bb5a0c9141 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard_wire.rs @@ -0,0 +1,27 @@ +//! Postcard encode and decode implementations. + +use std::io::{self, BufRead, Write}; + +pub fn read_postcard<'a>( + input: &mut impl BufRead, + buf: &'a mut Vec, +) -> io::Result> { + buf.clear(); + let n = input.read_until(0, buf)?; + if n == 0 { + return Ok(None); + } + Ok(Some(&mut buf[..])) +} +pub fn write_postcard(out: &mut impl Write, msg: &[u8]) -> io::Result<()> { + out.write_all(msg)?; + out.flush() +} + +pub fn encode_cobs(value: &T) -> Result, postcard::Error> { + postcard::to_allocvec_cobs(value) +} + +pub fn decode_cobs(bytes: &mut [u8]) -> Result { + postcard::from_bytes_cobs(bytes) +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs index 870d81f97684f..877c45f56cfdc 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs @@ -31,9 +31,10 @@ pub mod version { /// Whether literals encode their kind as an additional u32 field and idents their rawness as a u32 field. pub const EXTENDED_LEAF_DATA: u32 = 5; pub const HASHED_AST_ID: u32 = 6; + pub const POSTCARD_WIRE: u32 = 7; /// Current API version of the proc-macro protocol. - pub const CURRENT_API_VERSION: u32 = HASHED_AST_ID; + pub const CURRENT_API_VERSION: u32 = POSTCARD_WIRE; } /// Represents different kinds of procedural macros that can be expanded by the external server. diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs index fe274a027a80f..95b12d0b24e06 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs @@ -31,6 +31,7 @@ pub(crate) struct ProcMacroServerProcess { #[derive(Debug)] enum Protocol { LegacyJson { mode: SpanMode }, + Postcard { mode: SpanMode }, } /// Maintains the state of the proc-macro server process. @@ -82,7 +83,11 @@ impl ProcMacroServerProcess { if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT && let Ok(mode) = srv.enable_rust_analyzer_spans() { - srv.protocol = Protocol::LegacyJson { mode }; + if srv.version >= version::POSTCARD_WIRE { + srv.protocol = Protocol::Postcard { mode }; + } else { + srv.protocol = Protocol::LegacyJson { mode }; + } } tracing::info!("Proc-macro server protocol: {:?}", srv.protocol); Ok(srv) @@ -99,6 +104,10 @@ impl ProcMacroServerProcess { self.exited.get().map(|it| &it.0) } + pub(crate) fn use_postcard(&self) -> bool { + matches!(self.protocol, Protocol::Postcard { .. }) + } + /// Retrieves the API version of the proc-macro server. pub(crate) fn version(&self) -> u32 { self.version @@ -108,6 +117,7 @@ impl ProcMacroServerProcess { pub(crate) fn rust_analyzer_spans(&self) -> bool { match self.protocol { Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer, + Protocol::Postcard { mode } => mode == SpanMode::RustAnalyzer, } } @@ -115,6 +125,7 @@ impl ProcMacroServerProcess { fn version_check(&self) -> Result { match self.protocol { Protocol::LegacyJson { .. } => legacy_protocol::version_check(self), + Protocol::Postcard { .. } => legacy_protocol::version_check(self), } } @@ -122,6 +133,7 @@ impl ProcMacroServerProcess { fn enable_rust_analyzer_spans(&self) -> Result { match self.protocol { Protocol::LegacyJson { .. } => legacy_protocol::enable_rust_analyzer_spans(self), + Protocol::Postcard { .. } => legacy_protocol::enable_rust_analyzer_spans(self), } } @@ -132,6 +144,7 @@ impl ProcMacroServerProcess { ) -> Result, String>, ServerError> { match self.protocol { Protocol::LegacyJson { .. } => legacy_protocol::find_proc_macros(self, dylib_path), + Protocol::Postcard { .. } => legacy_protocol::find_proc_macros(self, dylib_path), } } @@ -188,6 +201,55 @@ impl ProcMacroServerProcess { } }) } + + pub(crate) fn send_task_bin( + &self, + serialize_req: impl FnOnce( + &mut dyn Write, + &mut dyn BufRead, + Request, + &mut Vec, + ) -> Result, ServerError>, + req: Request, + ) -> Result { + let state = &mut *self.state.lock().unwrap(); + let mut buf = Vec::::new(); + serialize_req(&mut state.stdin, &mut state.stdout, req, &mut buf) + .and_then(|res| { + res.ok_or_else(|| ServerError { + message: "proc-macro server did not respond with data".to_owned(), + io: Some(Arc::new(io::Error::new( + io::ErrorKind::BrokenPipe, + "proc-macro server did not respond with data", + ))), + }) + }) + .map_err(|e| { + if e.io.as_ref().map(|it| it.kind()) == Some(io::ErrorKind::BrokenPipe) { + match state.process.child.try_wait() { + Ok(None) | Err(_) => e, + Ok(Some(status)) => { + let mut msg = String::new(); + if !status.success() + && let Some(stderr) = state.process.child.stderr.as_mut() + { + _ = stderr.read_to_string(&mut msg); + } + let server_error = ServerError { + message: format!( + "proc-macro server exited with {status}{}{msg}", + if msg.is_empty() { "" } else { ": " } + ), + io: None, + }; + self.exited.get_or_init(|| AssertUnwindSafe(server_error)).0.clone() + } + } + } else { + e + } + }) + } } /// Manages the execution of the proc-macro server process. diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs index c48ae3c2ed0c5..017efb72220c8 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -200,8 +200,6 @@ fn run_postcard() -> io::Result<()> { thread::sleep(std::time::Duration::from_secs(1)); continue; }; - dbg!(&req); - let res = match req { msg::Request::ListMacros { dylib_path } => { msg::Response::ListMacros(srv.list_macros(&dylib_path).map(|macros| { @@ -304,7 +302,6 @@ fn run_postcard() -> io::Result<()> { } }; - dbg!(&res); let res = postcard_wire::encode_cobs(&res).unwrap(); postcard_wire::write_postcard(&mut writer, &res)?; } From aea45c3961bec3932f3b87550a86af086b41f1cb Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 22 Nov 2025 21:06:51 +0530 Subject: [PATCH 07/27] add postcard abstraction inside Message trait --- .../proc-macro-api/src/legacy_protocol.rs | 10 +++--- .../proc-macro-api/src/legacy_protocol/msg.rs | 34 ++++++++++++++++++- .../{postcard_wire.rs => postcard.rs} | 8 ++--- .../proc-macro-srv-cli/src/main_loop.rs | 25 +++++--------- 4 files changed, 51 insertions(+), 26 deletions(-) rename src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/{postcard_wire.rs => postcard.rs} (64%) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs index 5d590520c8c86..cb869fddbcb39 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs @@ -2,7 +2,7 @@ pub mod json; pub mod msg; -pub mod postcard_wire; +pub mod postcard; use std::{ io::{BufRead, Write}, @@ -183,15 +183,15 @@ fn send_request_postcard( req: Request, buf: &mut Vec, ) -> Result, ServerError> { - let bytes = postcard_wire::encode_cobs(&req) + let bytes = postcard::encode_cobs(&req) .map_err(|_| ServerError { message: "failed to write request".into(), io: None })?; - postcard_wire::write_postcard(&mut writer, &bytes).map_err(|err| ServerError { + postcard::write_postcard(&mut writer, &bytes).map_err(|err| ServerError { message: "failed to write request".into(), io: Some(Arc::new(err)), })?; - let frame = postcard_wire::read_postcard(&mut reader, buf).map_err(|err| ServerError { + let frame = postcard::read_postcard(&mut reader, buf).map_err(|err| ServerError { message: "failed to read response".into(), io: Some(Arc::new(err)), })?; @@ -199,7 +199,7 @@ fn send_request_postcard( match frame { None => Ok(None), Some(bytes) => { - let resp: Response = postcard_wire::decode_cobs(bytes).map_err(|e| ServerError { + let resp: Response = postcard::decode_cobs(bytes).map_err(|e| ServerError { message: format!("failed to decode message: {e}"), io: None, })?; diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs index 487f50b145e82..d3744dee0ee52 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs @@ -8,7 +8,10 @@ use paths::Utf8PathBuf; use serde::de::DeserializeOwned; use serde_derive::{Deserialize, Serialize}; -use crate::ProcMacroKind; +use crate::{ + ProcMacroKind, + legacy_protocol::postcard::{decode_cobs, encode_cobs}, +}; /// Represents requests sent from the client to the proc-macro-srv. #[derive(Debug, Serialize, Deserialize)] @@ -169,6 +172,26 @@ pub trait Message: serde::Serialize + DeserializeOwned { let text = serde_json::to_string(&self)?; to_proto(out, &text) } + + fn read_postcard( + from_proto: ProtocolReadPostcard, + inp: &mut R, + buf: &mut Vec, + ) -> io::Result> { + Ok(match from_proto(inp, buf)? { + None => None, + Some(buf) => Some(decode_cobs(buf)?), + }) + } + + fn write_postcard( + self, + to_proto: ProtocolWritePostcard, + out: &mut W, + ) -> io::Result<()> { + let buf = encode_cobs(&self)?; + to_proto(out, &buf) + } } impl Message for Request {} @@ -182,6 +205,15 @@ type ProtocolRead = #[allow(type_alias_bounds)] type ProtocolWrite = for<'o, 'msg> fn(out: &'o mut W, msg: &'msg str) -> io::Result<()>; +/// Type alias for a function that reads protocol postcard messages from a buffered input stream. +#[allow(type_alias_bounds)] +type ProtocolReadPostcard = + for<'i, 'buf> fn(inp: &'i mut R, buf: &'buf mut Vec) -> io::Result>; +/// Type alias for a function that writes protocol postcard messages to an output stream. +#[allow(type_alias_bounds)] +type ProtocolWritePostcard = + for<'o, 'msg> fn(out: &'o mut W, msg: &'msg [u8]) -> io::Result<()>; + #[cfg(test)] mod tests { use intern::{Symbol, sym}; diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard_wire.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs similarity index 64% rename from src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard_wire.rs rename to src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs index 2c8bb5a0c9141..eab26439db129 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard_wire.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs @@ -18,10 +18,10 @@ pub fn write_postcard(out: &mut impl Write, msg: &[u8]) -> io::Result<()> { out.flush() } -pub fn encode_cobs(value: &T) -> Result, postcard::Error> { - postcard::to_allocvec_cobs(value) +pub fn encode_cobs(value: &T) -> io::Result> { + postcard::to_allocvec_cobs(value).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } -pub fn decode_cobs(bytes: &mut [u8]) -> Result { - postcard::from_bytes_cobs(bytes) +pub fn decode_cobs(bytes: &mut [u8]) -> io::Result { + postcard::from_bytes_cobs(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs index 017efb72220c8..b0e7108d20a58 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -1,5 +1,5 @@ //! The main loop of the proc-macro server. -use std::{io, thread}; +use std::io; use proc_macro_api::{ legacy_protocol::{ @@ -8,14 +8,13 @@ use proc_macro_api::{ self, ExpandMacroData, ExpnGlobals, Message, SpanMode, SpanTransformer, deserialize_span_data_index_map, serialize_span_data_index_map, }, + postcard::{read_postcard, write_postcard}, }, version::CURRENT_API_VERSION, }; use proc_macro_srv::{EnvSnapshot, SpanId}; use crate::ProtocolFormat; -use std::io::BufReader; - struct SpanTrans; impl SpanTransformer for SpanTrans { @@ -183,23 +182,18 @@ fn run_postcard() -> io::Result<()> { } } - let stdin = io::stdin(); - let stdout = io::stdout(); - let mut reader = BufReader::new(stdin.lock()); - let mut writer = stdout.lock(); - let mut buf = vec![0; 1024]; + let mut buf = Vec::new(); + let mut read_request = + || msg::Request::read_postcard(read_postcard, &mut io::stdin().lock(), &mut buf); + let write_response = + |msg: msg::Response| msg.write_postcard(write_postcard, &mut io::stdout().lock()); let env = proc_macro_srv::EnvSnapshot::default(); let srv = proc_macro_srv::ProcMacroSrv::new(&env); let mut span_mode = msg::SpanMode::Id; - use proc_macro_api::legacy_protocol::postcard_wire; - while let Some(req) = postcard_wire::read_postcard(&mut reader, &mut buf)? { - let Ok(req) = postcard_wire::decode_cobs(req) else { - thread::sleep(std::time::Duration::from_secs(1)); - continue; - }; + while let Some(req) = read_request()? { let res = match req { msg::Request::ListMacros { dylib_path } => { msg::Response::ListMacros(srv.list_macros(&dylib_path).map(|macros| { @@ -302,8 +296,7 @@ fn run_postcard() -> io::Result<()> { } }; - let res = postcard_wire::encode_cobs(&res).unwrap(); - postcard_wire::write_postcard(&mut writer, &res)?; + write_response(res)?; } Ok(()) From c3708c7b43d9a689114f6ac8e51dbe34b1be7351 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sun, 23 Nov 2025 23:22:27 +0530 Subject: [PATCH 08/27] add review suggestions --- .../proc-macro-api/src/legacy_protocol.rs | 26 +-- .../src/legacy_protocol/json.rs | 4 +- .../proc-macro-api/src/legacy_protocol/msg.rs | 24 +-- .../src/legacy_protocol/postcard.rs | 8 +- .../crates/proc-macro-api/src/lib.rs | 5 +- .../crates/proc-macro-api/src/process.rs | 180 +++++++++--------- .../crates/proc-macro-srv-cli/src/main.rs | 2 +- 7 files changed, 112 insertions(+), 137 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs index cb869fddbcb39..6d521d00cd90b 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs @@ -21,6 +21,7 @@ use crate::{ ServerConfig, SpanDataIndexMap, deserialize_span_data_index_map, flat::serialize_span_data_index_map, }, + postcard::{read_postcard, write_postcard}, }, process::ProcMacroServerProcess, version, @@ -153,7 +154,7 @@ fn send_task(srv: &ProcMacroServerProcess, req: Request) -> Result, ) -> Result, ServerError> { - let bytes = postcard::encode_cobs(&req) - .map_err(|_| ServerError { message: "failed to write request".into(), io: None })?; - - postcard::write_postcard(&mut writer, &bytes).map_err(|err| ServerError { + req.write_postcard(write_postcard, &mut writer).map_err(|err| ServerError { message: "failed to write request".into(), io: Some(Arc::new(err)), })?; - - let frame = postcard::read_postcard(&mut reader, buf).map_err(|err| ServerError { - message: "failed to read response".into(), - io: Some(Arc::new(err)), + let res = Response::read_postcard(read_postcard, &mut reader, buf).map_err(|err| { + ServerError { message: "failed to read response".into(), io: Some(Arc::new(err)) } })?; - - match frame { - None => Ok(None), - Some(bytes) => { - let resp: Response = postcard::decode_cobs(bytes).map_err(|e| ServerError { - message: format!("failed to decode message: {e}"), - io: None, - })?; - Ok(Some(resp)) - } - } + Ok(res) } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/json.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/json.rs index c8f774031b584..cf8535f77d53c 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/json.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/json.rs @@ -5,7 +5,7 @@ use std::io::{self, BufRead, Write}; pub fn read_json<'a>( inp: &mut impl BufRead, buf: &'a mut String, -) -> io::Result> { +) -> io::Result> { loop { buf.clear(); @@ -28,7 +28,7 @@ pub fn read_json<'a>( } /// Writes a JSON message to the output stream. -pub fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> { +pub fn write_json(out: &mut impl Write, msg: &String) -> io::Result<()> { tracing::debug!("> {}", msg); out.write_all(msg.as_bytes())?; out.write_all(b"\n")?; diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs index d3744dee0ee52..6df184630de7f 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs @@ -153,7 +153,7 @@ impl ExpnGlobals { pub trait Message: serde::Serialize + DeserializeOwned { fn read( - from_proto: ProtocolRead, + from_proto: ProtocolRead, inp: &mut R, buf: &mut String, ) -> io::Result> { @@ -168,13 +168,13 @@ pub trait Message: serde::Serialize + DeserializeOwned { } }) } - fn write(self, to_proto: ProtocolWrite, out: &mut W) -> io::Result<()> { + fn write(self, to_proto: ProtocolWrite, out: &mut W) -> io::Result<()> { let text = serde_json::to_string(&self)?; to_proto(out, &text) } fn read_postcard( - from_proto: ProtocolReadPostcard, + from_proto: ProtocolRead>, inp: &mut R, buf: &mut Vec, ) -> io::Result> { @@ -186,7 +186,7 @@ pub trait Message: serde::Serialize + DeserializeOwned { fn write_postcard( self, - to_proto: ProtocolWritePostcard, + to_proto: ProtocolWrite>, out: &mut W, ) -> io::Result<()> { let buf = encode_cobs(&self)?; @@ -199,20 +199,12 @@ impl Message for Response {} /// Type alias for a function that reads protocol messages from a buffered input stream. #[allow(type_alias_bounds)] -type ProtocolRead = - for<'i, 'buf> fn(inp: &'i mut R, buf: &'buf mut String) -> io::Result>; +type ProtocolRead = + for<'i, 'buf> fn(inp: &'i mut R, buf: &'buf mut Buf) -> io::Result>; /// Type alias for a function that writes protocol messages to an output stream. #[allow(type_alias_bounds)] -type ProtocolWrite = for<'o, 'msg> fn(out: &'o mut W, msg: &'msg str) -> io::Result<()>; - -/// Type alias for a function that reads protocol postcard messages from a buffered input stream. -#[allow(type_alias_bounds)] -type ProtocolReadPostcard = - for<'i, 'buf> fn(inp: &'i mut R, buf: &'buf mut Vec) -> io::Result>; -/// Type alias for a function that writes protocol postcard messages to an output stream. -#[allow(type_alias_bounds)] -type ProtocolWritePostcard = - for<'o, 'msg> fn(out: &'o mut W, msg: &'msg [u8]) -> io::Result<()>; +type ProtocolWrite = + for<'o, 'msg> fn(out: &'o mut W, msg: &'msg Buf) -> io::Result<()>; #[cfg(test)] mod tests { diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs index eab26439db129..305e4de934156 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs @@ -5,15 +5,17 @@ use std::io::{self, BufRead, Write}; pub fn read_postcard<'a>( input: &mut impl BufRead, buf: &'a mut Vec, -) -> io::Result> { +) -> io::Result>> { buf.clear(); let n = input.read_until(0, buf)?; if n == 0 { return Ok(None); } - Ok(Some(&mut buf[..])) + Ok(Some(buf)) } -pub fn write_postcard(out: &mut impl Write, msg: &[u8]) -> io::Result<()> { + +#[allow(clippy::ptr_arg)] +pub fn write_postcard(out: &mut impl Write, msg: &Vec) -> io::Result<()> { out.write_all(msg)?; out.flush() } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs index 877c45f56cfdc..2cdb33ff81eb3 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs @@ -31,10 +31,9 @@ pub mod version { /// Whether literals encode their kind as an additional u32 field and idents their rawness as a u32 field. pub const EXTENDED_LEAF_DATA: u32 = 5; pub const HASHED_AST_ID: u32 = 6; - pub const POSTCARD_WIRE: u32 = 7; /// Current API version of the proc-macro protocol. - pub const CURRENT_API_VERSION: u32 = POSTCARD_WIRE; + pub const CURRENT_API_VERSION: u32 = HASHED_AST_ID; } /// Represents different kinds of procedural macros that can be expanded by the external server. @@ -124,7 +123,7 @@ impl ProcMacroClient { Item = (impl AsRef, &'a Option>), > + Clone, ) -> io::Result { - let process = ProcMacroServerProcess::run(process_path, env)?; + let process = ProcMacroServerProcess::run(process_path, env, process::Protocol::default())?; Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() }) } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs index 95b12d0b24e06..7f0cd05c80584 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs @@ -28,12 +28,18 @@ pub(crate) struct ProcMacroServerProcess { exited: OnceLock>, } -#[derive(Debug)] -enum Protocol { +#[derive(Debug, Clone)] +pub(crate) enum Protocol { LegacyJson { mode: SpanMode }, Postcard { mode: SpanMode }, } +impl Default for Protocol { + fn default() -> Self { + Protocol::Postcard { mode: SpanMode::Id } + } +} + /// Maintains the state of the proc-macro server process. #[derive(Debug)] struct ProcessSrvState { @@ -49,54 +55,83 @@ impl ProcMacroServerProcess { env: impl IntoIterator< Item = (impl AsRef, &'a Option>), > + Clone, + protocol: Protocol, ) -> io::Result { - let create_srv = || { - let mut process = Process::run(process_path, env.clone())?; + let mut srv = { + let mut process = match Process::run(process_path, env.clone(), &protocol) { + Ok(process) => process, + Err(e) => { + // fallback + if matches!(protocol, Protocol::Postcard { .. }) { + // retry with json + return Self::run( + process_path, + env, + Protocol::LegacyJson { mode: SpanMode::Id }, + ); + } + return Err(e); + } + }; let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); - io::Result::Ok(ProcMacroServerProcess { + ProcMacroServerProcess { state: Mutex::new(ProcessSrvState { process, stdin, stdout }), version: 0, - protocol: Protocol::LegacyJson { mode: SpanMode::Id }, + protocol: protocol.clone(), exited: OnceLock::new(), - }) + } }; - let mut srv = create_srv()?; tracing::info!("sending proc-macro server version check"); - match srv.version_check() { - Ok(v) if v > version::CURRENT_API_VERSION => { - #[allow(clippy::disallowed_methods)] - let process_version = Command::new(process_path) - .arg("--version") - .output() - .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_owned()) - .unwrap_or_else(|_| "unknown version".to_owned()); - Err(io::Error::other(format!( - "Your installed proc-macro server is too new for your rust-analyzer. API version: {}, server version: {process_version}. \ - This will prevent proc-macro expansion from working. Please consider updating your rust-analyzer to ensure compatibility with your current toolchain.", - version::CURRENT_API_VERSION - ))) - } - Ok(v) => { - tracing::info!("Proc-macro server version: {v}"); - srv.version = v; - if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT - && let Ok(mode) = srv.enable_rust_analyzer_spans() - { - if srv.version >= version::POSTCARD_WIRE { - srv.protocol = Protocol::Postcard { mode }; - } else { - srv.protocol = Protocol::LegacyJson { mode }; - } - } - tracing::info!("Proc-macro server protocol: {:?}", srv.protocol); - Ok(srv) - } + let version = match srv.version_check() { + Ok(v) => v, Err(e) => { + if matches!(protocol, Protocol::Postcard { .. }) { + // retry with json + return Self::run( + process_path, + env, + Protocol::LegacyJson { mode: SpanMode::Id }, + ); + } + tracing::info!(%e, "proc-macro version check failed"); - Err(io::Error::other(format!("proc-macro server version check failed: {e}"))) + return Err(io::Error::other(format!( + "proc-macro server version check failed: {e}" + ))); } + }; + + if version > version::CURRENT_API_VERSION { + #[allow(clippy::disallowed_methods)] + let process_version = Command::new(process_path) + .arg("--version") + .output() + .map(|out| String::from_utf8_lossy(&out.stdout).trim().to_owned()) + .unwrap_or_else(|_| "unknown version".to_owned()); + + return Err(io::Error::other(format!( + "Your installed proc-macro server is too new for your rust-analyzer. API version: {}, server version: {process_version}. \ + This will prevent proc-macro expansion from working. Please consider updating your rust-analyzer to ensure compatibility with your current toolchain.", + version::CURRENT_API_VERSION + ))); + } + + tracing::info!("proc-macro server version: {version}"); + + srv.version = version; + + if version >= version::RUST_ANALYZER_SPAN_SUPPORT + && let Ok(mode) = srv.enable_rust_analyzer_spans() + { + srv.protocol = match protocol { + Protocol::Postcard { .. } => Protocol::Postcard { mode }, + Protocol::LegacyJson { .. } => Protocol::LegacyJson { mode }, + }; } + + tracing::info!("proc-macro server protocol: {:?}", srv.protocol); + Ok(srv) } /// Returns the server error if the process has exited. @@ -148,18 +183,21 @@ impl ProcMacroServerProcess { } } - pub(crate) fn send_task( + pub(crate) fn send_task( &self, serialize_req: impl FnOnce( &mut dyn Write, &mut dyn BufRead, Request, - &mut String, + &mut Buf, ) -> Result, ServerError>, req: Request, - ) -> Result { + ) -> Result + where + Buf: Default, + { let state = &mut *self.state.lock().unwrap(); - let mut buf = String::new(); + let mut buf = Buf::default(); serialize_req(&mut state.stdin, &mut state.stdout, req, &mut buf) .and_then(|res| { res.ok_or_else(|| { @@ -201,55 +239,6 @@ impl ProcMacroServerProcess { } }) } - - pub(crate) fn send_task_bin( - &self, - serialize_req: impl FnOnce( - &mut dyn Write, - &mut dyn BufRead, - Request, - &mut Vec, - ) -> Result, ServerError>, - req: Request, - ) -> Result { - let state = &mut *self.state.lock().unwrap(); - let mut buf = Vec::::new(); - serialize_req(&mut state.stdin, &mut state.stdout, req, &mut buf) - .and_then(|res| { - res.ok_or_else(|| ServerError { - message: "proc-macro server did not respond with data".to_owned(), - io: Some(Arc::new(io::Error::new( - io::ErrorKind::BrokenPipe, - "proc-macro server did not respond with data", - ))), - }) - }) - .map_err(|e| { - if e.io.as_ref().map(|it| it.kind()) == Some(io::ErrorKind::BrokenPipe) { - match state.process.child.try_wait() { - Ok(None) | Err(_) => e, - Ok(Some(status)) => { - let mut msg = String::new(); - if !status.success() - && let Some(stderr) = state.process.child.stderr.as_mut() - { - _ = stderr.read_to_string(&mut msg); - } - let server_error = ServerError { - message: format!( - "proc-macro server exited with {status}{}{msg}", - if msg.is_empty() { "" } else { ": " } - ), - io: None, - }; - self.exited.get_or_init(|| AssertUnwindSafe(server_error)).0.clone() - } - } - } else { - e - } - }) - } } /// Manages the execution of the proc-macro server process. @@ -265,8 +254,9 @@ impl Process { env: impl IntoIterator< Item = (impl AsRef, &'a Option>), >, + protocol: &Protocol, ) -> io::Result { - let child = JodChild(mk_child(path, env)?); + let child = JodChild(mk_child(path, env, protocol)?); Ok(Process { child }) } @@ -286,9 +276,15 @@ fn mk_child<'a>( extra_env: impl IntoIterator< Item = (impl AsRef, &'a Option>), >, + protocol: &Protocol, ) -> io::Result { #[allow(clippy::disallowed_methods)] let mut cmd = Command::new(path); + if matches!(protocol, Protocol::LegacyJson { .. }) { + cmd.args(["--format", "json"]); + } else { + cmd.args(["--format", "postcard"]); + } for env in extra_env { match env { (key, Some(val)) => cmd.env(key, val), diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs index 9149adb4687ea..b6c38da454754 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs @@ -31,7 +31,7 @@ fn main() -> std::io::Result<()> { clap::Arg::new("format") .long("format") .action(clap::ArgAction::Set) - .default_value("postcard") + .default_value("json") .value_parser(clap::builder::EnumValueParser::::new()), clap::Arg::new("version") .long("version") From 35e056e80ae832142032f79fff5ab5dce7607d5a Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 23 Nov 2025 17:52:07 +1100 Subject: [PATCH 09/27] Inline macro sub-namespace lookup into `sub_namespace_match` --- .../rust-analyzer/crates/hir-def/src/nameres.rs | 13 +++++++++---- .../crates/hir-def/src/nameres/collector.rs | 7 +------ .../crates/hir-def/src/nameres/path_resolution.rs | 9 ++------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index f44187ec59c15..b2c50f35be694 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -842,9 +842,14 @@ impl MacroSubNs { /// We ignore resolutions from one sub-namespace when searching names in scope for another. /// /// [rustc]: https://github.com/rust-lang/rust/blob/1.69.0/compiler/rustc_resolve/src/macros.rs#L75 -fn sub_namespace_match(candidate: Option, expected: Option) -> bool { - match (candidate, expected) { - (Some(candidate), Some(expected)) => candidate == expected, - _ => true, +fn sub_namespace_match( + db: &dyn DefDatabase, + macro_id: MacroId, + expected: Option, +) -> bool { + let candidate = MacroSubNs::from_id(db, macro_id); + match expected { + Some(expected) => candidate == expected, + None => true, } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index a2ce538356515..82c09156ea95f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -2429,12 +2429,7 @@ impl ModCollector<'_, '_> { }) .or_else(|| def_map[self.module_id].scope.get(name).take_macros()) .or_else(|| Some(def_map.macro_use_prelude.get(name).copied()?.0)) - .filter(|&id| { - sub_namespace_match( - Some(MacroSubNs::from_id(db, id)), - Some(MacroSubNs::Bang), - ) - }) + .filter(|&id| sub_namespace_match(db, id, Some(MacroSubNs::Bang))) .map(|it| self.def_collector.db.macro_def(it)) }) }, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs index 4641b220daadc..184a57410d02d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs @@ -85,10 +85,7 @@ impl PerNs { db: &dyn DefDatabase, expected: Option, ) -> Self { - self.macros = self.macros.filter(|def| { - let this = MacroSubNs::from_id(db, def.def); - sub_namespace_match(Some(this), expected) - }); + self.macros = self.macros.filter(|def| sub_namespace_match(db, def.def, expected)); self } @@ -668,9 +665,7 @@ impl DefMap { // FIXME: shadowing .and_then(|it| it.last()) .copied() - .filter(|&id| { - sub_namespace_match(Some(MacroSubNs::from_id(db, id)), expected_macro_subns) - }) + .filter(|&id| sub_namespace_match(db, id, expected_macro_subns)) .map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public, None)); let from_scope = self[module].scope.get(name).filter_macro(db, expected_macro_subns); let from_builtin = match self.block { From 0ce6a0614e5765dccf677b14d679d4981566723f Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 23 Nov 2025 16:47:52 +1100 Subject: [PATCH 10/27] Basic support for declarative attribute/derive macros --- src/tools/rust-analyzer/Cargo.lock | 1 + .../rust-analyzer/crates/hir-def/src/db.rs | 2 +- .../crates/hir-def/src/item_scope.rs | 17 ++- .../rust-analyzer/crates/hir-def/src/lib.rs | 6 +- .../crates/hir-def/src/nameres.rs | 46 ++++--- .../crates/hir-def/src/nameres/collector.rs | 10 +- .../hir-def/src/nameres/tests/incremental.rs | 8 +- .../hir-def/src/nameres/tests/macros.rs | 129 ++++++++++++++++++ .../rust-analyzer/crates/hir-expand/src/db.rs | 86 ++++++------ .../crates/hir-expand/src/declarative.rs | 5 +- .../crates/hir-expand/src/eager.rs | 2 +- .../crates/hir-expand/src/lib.rs | 38 ++++-- src/tools/rust-analyzer/crates/hir/src/lib.rs | 4 +- .../test_symbol_index_collection.txt | 8 +- .../crates/intern/src/symbol/symbols.rs | 1 + src/tools/rust-analyzer/crates/mbe/Cargo.toml | 1 + .../rust-analyzer/crates/mbe/src/benchmark.rs | 11 +- .../rust-analyzer/crates/mbe/src/expander.rs | 11 +- src/tools/rust-analyzer/crates/mbe/src/lib.rs | 34 ++++- .../crates/mbe/src/macro_call_style.rs | 32 +++++ .../rust-analyzer/crates/mbe/src/parser.rs | 29 +++- .../rust-analyzer/crates/mbe/src/tests.rs | 1 + 22 files changed, 372 insertions(+), 110 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/mbe/src/macro_call_style.rs diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 4de8d09dcab87..64f34bee5aed6 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1456,6 +1456,7 @@ name = "mbe" version = "0.0.0" dependencies = [ "arrayvec", + "bitflags 2.9.4", "cov-mark", "expect-test", "intern", diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs index 925a078e82c94..49aafb2b86a35 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -338,7 +338,7 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId { let kind = |expander, file_id, m| { let in_file = InFile::new(file_id, m); match expander { - MacroExpander::Declarative => MacroDefKind::Declarative(in_file), + MacroExpander::Declarative { styles } => MacroDefKind::Declarative(in_file, styles), MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(in_file, it), MacroExpander::BuiltInAttr(it) => MacroDefKind::BuiltInAttr(in_file, it), MacroExpander::BuiltInDerive(it) => MacroDefKind::BuiltInDerive(in_file, it), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs index 51c42c995c9fd..1bfe649ebdf9c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs @@ -17,9 +17,8 @@ use thin_vec::ThinVec; use crate::{ AdtId, BuiltinType, ConstId, ExternBlockId, ExternCrateId, FxIndexMap, HasModule, ImplId, - LocalModuleId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId, UseId, + LocalModuleId, Lookup, MacroCallStyles, MacroId, ModuleDefId, ModuleId, TraitId, UseId, db::DefDatabase, - nameres::MacroSubNs, per_ns::{Item, MacrosItem, PerNs, TypesItem, ValuesItem}, visibility::Visibility, }; @@ -740,11 +739,15 @@ impl ItemScope { let mut entries: Vec<_> = self.resolutions().collect(); entries.sort_by_key(|(name, _)| name.clone()); - let print_macro_sub_ns = - |buf: &mut String, macro_id: MacroId| match MacroSubNs::from_id(db, macro_id) { - MacroSubNs::Bang => buf.push('!'), - MacroSubNs::Attr => buf.push('#'), - }; + let print_macro_sub_ns = |buf: &mut String, macro_id: MacroId| { + let styles = crate::nameres::macro_styles_from_id(db, macro_id); + if styles.contains(MacroCallStyles::FN_LIKE) { + buf.push('!'); + } + if styles.contains(MacroCallStyles::ATTR) || styles.contains(MacroCallStyles::DERIVE) { + buf.push('#'); + } + }; for (name, def) in entries { let display_name: &dyn fmt::Display = match &name { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs index e5c213ca937c8..52d99911ac0cc 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -61,8 +61,8 @@ use std::hash::{Hash, Hasher}; use base_db::{Crate, impl_intern_key}; use hir_expand::{ - AstId, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, - MacroDefKind, + AstId, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallStyles, + MacroDefId, MacroDefKind, builtin::{BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, eager::expand_eager_macro_input, @@ -403,7 +403,7 @@ bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum MacroExpander { - Declarative, + Declarative { styles: MacroCallStyles }, BuiltIn(BuiltinFnLikeExpander), BuiltInAttr(BuiltinAttrExpander), BuiltInDerive(BuiltinDeriveExpander), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index b2c50f35be694..f910008833500 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -77,7 +77,7 @@ use tt::TextRange; use crate::{ AstId, BlockId, BlockLoc, CrateRootModuleId, ExternCrateId, FunctionId, FxIndexMap, - LocalModuleId, Lookup, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId, + LocalModuleId, Lookup, MacroCallStyles, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId, db::DefDatabase, item_scope::{BuiltinShadowMode, ItemScope}, item_tree::TreeId, @@ -813,26 +813,25 @@ pub enum MacroSubNs { Attr, } -impl MacroSubNs { - pub(crate) fn from_id(db: &dyn DefDatabase, macro_id: MacroId) -> Self { - let expander = match macro_id { - MacroId::Macro2Id(it) => it.lookup(db).expander, - MacroId::MacroRulesId(it) => it.lookup(db).expander, - MacroId::ProcMacroId(it) => { - return match it.lookup(db).kind { - ProcMacroKind::CustomDerive | ProcMacroKind::Attr => Self::Attr, - ProcMacroKind::Bang => Self::Bang, - }; - } - }; +pub(crate) fn macro_styles_from_id(db: &dyn DefDatabase, macro_id: MacroId) -> MacroCallStyles { + let expander = match macro_id { + MacroId::Macro2Id(it) => it.lookup(db).expander, + MacroId::MacroRulesId(it) => it.lookup(db).expander, + MacroId::ProcMacroId(it) => { + return match it.lookup(db).kind { + ProcMacroKind::CustomDerive => MacroCallStyles::DERIVE, + ProcMacroKind::Bang => MacroCallStyles::FN_LIKE, + ProcMacroKind::Attr => MacroCallStyles::ATTR, + }; + } + }; + match expander { + MacroExpander::Declarative { styles } => styles, // Eager macros aren't *guaranteed* to be bang macros, but they *are* all bang macros currently. - match expander { - MacroExpander::Declarative - | MacroExpander::BuiltIn(_) - | MacroExpander::BuiltInEager(_) => Self::Bang, - MacroExpander::BuiltInAttr(_) | MacroExpander::BuiltInDerive(_) => Self::Attr, - } + MacroExpander::BuiltIn(_) | MacroExpander::BuiltInEager(_) => MacroCallStyles::FN_LIKE, + MacroExpander::BuiltInAttr(_) => MacroCallStyles::ATTR, + MacroExpander::BuiltInDerive(_) => MacroCallStyles::DERIVE, } } @@ -847,9 +846,14 @@ fn sub_namespace_match( macro_id: MacroId, expected: Option, ) -> bool { - let candidate = MacroSubNs::from_id(db, macro_id); + let candidate = macro_styles_from_id(db, macro_id); match expected { - Some(expected) => candidate == expected, + Some(MacroSubNs::Bang) => candidate.contains(MacroCallStyles::FN_LIKE), + Some(MacroSubNs::Attr) => { + candidate.contains(MacroCallStyles::ATTR) || candidate.contains(MacroCallStyles::DERIVE) + } + // If we aren't expecting a specific sub-namespace + // (e.g. in `use` declarations), match any macro. None => true, } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index 82c09156ea95f..a030ed1e0dfca 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -2300,7 +2300,10 @@ impl ModCollector<'_, '_> { } } else { // Case 2: normal `macro_rules!` macro - MacroExpander::Declarative + let id = InFile::new(self.file_id(), ast_id); + let decl_expander = self.def_collector.db.decl_macro_expander(krate, id.upcast()); + let styles = decl_expander.mac.rule_styles(); + MacroExpander::Declarative { styles } }; let allow_internal_unsafe = attrs.by_key(sym::allow_internal_unsafe).exists(); @@ -2369,7 +2372,10 @@ impl ModCollector<'_, '_> { } } else { // Case 2: normal `macro` - MacroExpander::Declarative + let id = InFile::new(self.file_id(), ast_id); + let decl_expander = self.def_collector.db.decl_macro_expander(krate, id.upcast()); + let styles = decl_expander.mac.rule_styles(); + MacroExpander::Declarative { styles } }; let allow_internal_unsafe = attrs.by_key(sym::allow_internal_unsafe).exists(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs index 6afa04bc412aa..40283f67cc5c3 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs @@ -222,6 +222,7 @@ pub struct S {} "ast_id_map_shim", "parse_shim", "real_span_map_shim", + "decl_macro_expander_shim", "file_item_tree_query", "ast_id_map_shim", "parse_shim", @@ -235,7 +236,6 @@ pub struct S {} "ast_id_map_shim", "parse_macro_expansion_shim", "macro_arg_shim", - "decl_macro_expander_shim", ] "#]], expect![[r#" @@ -404,6 +404,7 @@ pub struct S {} "ast_id_map_shim", "parse_shim", "real_span_map_shim", + "decl_macro_expander_shim", "file_item_tree_query", "ast_id_map_shim", "parse_shim", @@ -423,7 +424,6 @@ pub struct S {} "ast_id_map_shim", "parse_macro_expansion_shim", "macro_arg_shim", - "decl_macro_expander_shim", "crate_local_def_map", "proc_macros_for_crate_shim", "file_item_tree_query", @@ -446,9 +446,9 @@ pub struct S {} "file_item_tree_query", "real_span_map_shim", "macro_arg_shim", - "macro_arg_shim", "decl_macro_expander_shim", "macro_arg_shim", + "macro_arg_shim", ] "#]], ); @@ -520,6 +520,7 @@ m!(Z); "ast_id_map_shim", "parse_shim", "real_span_map_shim", + "decl_macro_expander_shim", "file_item_tree_query", "ast_id_map_shim", "parse_shim", @@ -533,7 +534,6 @@ m!(Z); "ast_id_map_shim", "parse_macro_expansion_shim", "macro_arg_shim", - "decl_macro_expander_shim", "file_item_tree_query", "ast_id_map_shim", "parse_macro_expansion_shim", diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs index 43b6e12e1357b..a5fd0488e785c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs @@ -1651,3 +1651,132 @@ pub mod prelude { "#]], ); } + +#[test] +fn macro_rules_mixed_style() { + check( + r#" + +macro_rules! foo { + () => {}; + attr() () => {}; + derive() () => {}; +} + +use foo; +"#, + expect![[r#" + crate + - foo : macro!# (import) + - (legacy) foo : macro!# +"#]], + ); +} + +#[test] +fn macro_2_mixed_style() { + check( + r#" + +macro foo { + () => {}; + attr() () => {}; + derive() () => {}; +} + +use foo; +"#, + expect![[r#" + crate + - foo : macro!# + "#]], + ); +} + +#[test] +fn macro_rules_attr() { + check( + r#" + +macro_rules! my_attr { + attr() ($($tt:tt)*) => { fn attr_fn() {} } +} + +#[my_attr] +enum MyEnum {} + +"#, + expect![[r#" + crate + - attr_fn : value + - (legacy) my_attr : macro# +"#]], + ); +} + +#[test] +fn macro_2_attr() { + check( + r#" + +macro my_attr { + attr() ($($tt:tt)*) => { fn attr_fn() {} } +} + +#[my_attr] +enum MyEnum {} + +"#, + expect![[r#" + crate + - attr_fn : value + - my_attr : macro# +"#]], + ); +} + +#[test] +fn macro_rules_derive() { + check( + r#" +//- minicore: derive + +macro_rules! MyDerive { + derive() ($($tt:tt)*) => { fn derived_fn() {} } +} + +#[derive(MyDerive)] +enum MyEnum {} + +"#, + expect![[r#" + crate + - MyEnum : type + - derived_fn : value + - (legacy) MyDerive : macro# + "#]], + ); +} + +#[test] +fn macro_2_derive() { + check( + r#" +//- minicore: derive + +macro MyDerive { + derive() ($($tt:tt)*) => { fn derived_fn() {} } +} + +#[derive(MyDerive)] +enum MyEnum {} + +"#, + expect![[r#" + crate + - MyDerive : macro# + - MyEnum : type + - derived_fn : value + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index 888c1405a6bb1..f9f10c177ed24 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -297,9 +297,9 @@ pub fn expand_speculative( MacroDefKind::BuiltInAttr(_, it) if it.is_derive() => { pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?, span) } - MacroDefKind::Declarative(it) => { - db.decl_macro_expander(loc.krate, it).expand_unhygienic(tt, span, loc.def.edition) - } + MacroDefKind::Declarative(it, _) => db + .decl_macro_expander(loc.krate, it) + .expand_unhygienic(tt, loc.kind.call_style(), span, loc.def.edition), MacroDefKind::BuiltIn(_, it) => { it.expand(db, actual_macro_call, &tt, span).map_err(Into::into) } @@ -585,7 +585,7 @@ fn attr_source(invoc_attr_index: AttrId, node: &ast::Item) -> Option impl TokenExpander { fn macro_expander(db: &dyn ExpandDatabase, id: MacroDefId) -> TokenExpander { match id.kind { - MacroDefKind::Declarative(ast_id) => { + MacroDefKind::Declarative(ast_id, _) => { TokenExpander::DeclarativeMacro(db.decl_macro_expander(id.krate, ast_id)) } MacroDefKind::BuiltIn(_, expander) => TokenExpander::BuiltIn(expander), @@ -618,48 +618,46 @@ fn macro_expand( db.macro_arg_considering_derives(macro_call_id, &loc.kind); let arg = &*macro_arg; - let res = - match loc.def.kind { - MacroDefKind::Declarative(id) => db - .decl_macro_expander(loc.def.krate, id) - .expand(db, arg.clone(), macro_call_id, span), - MacroDefKind::BuiltIn(_, it) => { - it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None) - } - MacroDefKind::BuiltInDerive(_, it) => { - it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None) - } - MacroDefKind::BuiltInEager(_, it) => { - // This might look a bit odd, but we do not expand the inputs to eager macros here. - // Eager macros inputs are expanded, well, eagerly when we collect the macro calls. - // That kind of expansion uses the ast id map of an eager macros input though which goes through - // the HirFileId machinery. As eager macro inputs are assigned a macro file id that query - // will end up going through here again, whereas we want to just want to inspect the raw input. - // As such we just return the input subtree here. - let eager = match &loc.kind { - MacroCallKind::FnLike { eager: None, .. } => { - return ExpandResult::ok(CowArc::Arc(macro_arg.clone())) - .zip_val(None); - } - MacroCallKind::FnLike { eager: Some(eager), .. } => Some(&**eager), - _ => None, - }; - - let mut res = it.expand(db, macro_call_id, arg, span).map_err(Into::into); - - if let Some(EagerCallInfo { error, .. }) = eager { - // FIXME: We should report both errors! - res.err = error.clone().or(res.err); + let res = match loc.def.kind { + MacroDefKind::Declarative(id, _) => db + .decl_macro_expander(loc.def.krate, id) + .expand(db, arg.clone(), macro_call_id, span), + MacroDefKind::BuiltIn(_, it) => { + it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None) + } + MacroDefKind::BuiltInDerive(_, it) => { + it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None) + } + MacroDefKind::BuiltInEager(_, it) => { + // This might look a bit odd, but we do not expand the inputs to eager macros here. + // Eager macros inputs are expanded, well, eagerly when we collect the macro calls. + // That kind of expansion uses the ast id map of an eager macros input though which goes through + // the HirFileId machinery. As eager macro inputs are assigned a macro file id that query + // will end up going through here again, whereas we want to just want to inspect the raw input. + // As such we just return the input subtree here. + let eager = match &loc.kind { + MacroCallKind::FnLike { eager: None, .. } => { + return ExpandResult::ok(CowArc::Arc(macro_arg.clone())).zip_val(None); } - res.zip_val(None) - } - MacroDefKind::BuiltInAttr(_, it) => { - let mut res = it.expand(db, macro_call_id, arg, span); - fixup::reverse_fixups(&mut res.value, &undo_info); - res.zip_val(None) + MacroCallKind::FnLike { eager: Some(eager), .. } => Some(&**eager), + _ => None, + }; + + let mut res = it.expand(db, macro_call_id, arg, span).map_err(Into::into); + + if let Some(EagerCallInfo { error, .. }) = eager { + // FIXME: We should report both errors! + res.err = error.clone().or(res.err); } - MacroDefKind::ProcMacro(_, _, _) => unreachable!(), - }; + res.zip_val(None) + } + MacroDefKind::BuiltInAttr(_, it) => { + let mut res = it.expand(db, macro_call_id, arg, span); + fixup::reverse_fixups(&mut res.value, &undo_info); + res.zip_val(None) + } + MacroDefKind::ProcMacro(_, _, _) => unreachable!(), + }; (ExpandResult { value: res.value, err: res.err }, span) } }; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/declarative.rs b/src/tools/rust-analyzer/crates/hir-expand/src/declarative.rs index 0d100c1364ab1..e4375e05d2154 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/declarative.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/declarative.rs @@ -10,6 +10,7 @@ use triomphe::Arc; use crate::{ AstId, ExpandError, ExpandErrorKind, ExpandResult, HirFileId, Lookup, MacroCallId, + MacroCallStyle, attrs::RawAttrs, db::ExpandDatabase, hygiene::{Transparency, apply_mark}, @@ -46,6 +47,7 @@ impl DeclarativeMacroExpander { s.ctx = apply_mark(db, s.ctx, call_id.into(), self.transparency, self.edition) }, + loc.kind.call_style(), span, loc.def.edition, ) @@ -56,6 +58,7 @@ impl DeclarativeMacroExpander { pub fn expand_unhygienic( &self, tt: tt::TopSubtree, + call_style: MacroCallStyle, call_site: Span, def_site_edition: Edition, ) -> ExpandResult { @@ -66,7 +69,7 @@ impl DeclarativeMacroExpander { ), None => self .mac - .expand(&tt, |_| (), call_site, def_site_edition) + .expand(&tt, |_| (), call_style, call_site, def_site_edition) .map(TupleExt::head) .map_err(Into::into), } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs index 28d3fcdab9dba..9b65bdac65c09 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs @@ -238,7 +238,7 @@ fn eager_macro_recur( None => ExpandResult { value: None, err }, } } - MacroDefKind::Declarative(_) + MacroDefKind::Declarative(..) | MacroDefKind::BuiltIn(..) | MacroDefKind::BuiltInAttr(..) | MacroDefKind::BuiltInDerive(..) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index 472ec83ffef5b..77f61dd830b0e 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -61,7 +61,7 @@ pub use crate::{ }; pub use base_db::EditionedFileId; -pub use mbe::{DeclarativeMacro, ValueResult}; +pub use mbe::{DeclarativeMacro, MacroCallStyle, MacroCallStyles, ValueResult}; pub mod tt { pub use span::Span; @@ -266,7 +266,7 @@ pub struct MacroDefId { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum MacroDefKind { - Declarative(AstId), + Declarative(AstId, MacroCallStyles), BuiltIn(AstId, BuiltinFnLikeExpander), BuiltInAttr(AstId, BuiltinAttrExpander), BuiltInDerive(AstId, BuiltinDeriveExpander), @@ -340,6 +340,16 @@ pub enum MacroCallKind { }, } +impl MacroCallKind { + pub(crate) fn call_style(&self) -> MacroCallStyle { + match self { + MacroCallKind::FnLike { .. } => MacroCallStyle::FnLike, + MacroCallKind::Derive { .. } => MacroCallStyle::Derive, + MacroCallKind::Attr { .. } => MacroCallStyle::Attr, + } + } +} + impl HirFileId { pub fn edition(self, db: &dyn ExpandDatabase) -> Edition { match self { @@ -511,7 +521,7 @@ impl MacroDefId { pub fn definition_range(&self, db: &dyn ExpandDatabase) -> InFile { match self.kind { - MacroDefKind::Declarative(id) + MacroDefKind::Declarative(id, _) | MacroDefKind::BuiltIn(id, _) | MacroDefKind::BuiltInAttr(id, _) | MacroDefKind::BuiltInDerive(id, _) @@ -527,7 +537,7 @@ impl MacroDefId { pub fn ast_id(&self) -> Either, AstId> { match self.kind { MacroDefKind::ProcMacro(id, ..) => Either::Right(id), - MacroDefKind::Declarative(id) + MacroDefKind::Declarative(id, _) | MacroDefKind::BuiltIn(id, _) | MacroDefKind::BuiltInAttr(id, _) | MacroDefKind::BuiltInDerive(id, _) @@ -540,18 +550,22 @@ impl MacroDefId { } pub fn is_attribute(&self) -> bool { - matches!( - self.kind, - MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Attr) - ) + match self.kind { + MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Attr) => { + true + } + MacroDefKind::Declarative(_, styles) => styles.contains(MacroCallStyles::ATTR), + _ => false, + } } pub fn is_derive(&self) -> bool { - matches!( - self.kind, + match self.kind { MacroDefKind::BuiltInDerive(..) - | MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive) - ) + | MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive) => true, + MacroDefKind::Declarative(_, styles) => styles.contains(MacroCallStyles::DERIVE), + _ => false, + } } pub fn is_fn_like(&self) -> bool { diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 5400003f5946b..2d70a8dca1d77 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -3184,7 +3184,7 @@ impl Macro { pub fn kind(&self, db: &dyn HirDatabase) -> MacroKind { match self.id { MacroId::Macro2Id(it) => match it.lookup(db).expander { - MacroExpander::Declarative => MacroKind::Declarative, + MacroExpander::Declarative { .. } => MacroKind::Declarative, MacroExpander::BuiltIn(_) | MacroExpander::BuiltInEager(_) => { MacroKind::DeclarativeBuiltIn } @@ -3192,7 +3192,7 @@ impl Macro { MacroExpander::BuiltInDerive(_) => MacroKind::DeriveBuiltIn, }, MacroId::MacroRulesId(it) => match it.lookup(db).expander { - MacroExpander::Declarative => MacroKind::Declarative, + MacroExpander::Declarative { .. } => MacroKind::Declarative, MacroExpander::BuiltIn(_) | MacroExpander::BuiltInEager(_) => { MacroKind::DeclarativeBuiltIn } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt index 973256c470f34..5ef0ecbcf819b 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -356,7 +356,7 @@ loc: DeclarationLocation { hir_file_id: MacroFile( MacroCallId( - Id(3800), + Id(3c00), ), ), ptr: SyntaxNodePtr { @@ -694,7 +694,7 @@ Macro { id: MacroRulesId( MacroRulesId( - 3401, + 3801, ), ), }, @@ -796,7 +796,7 @@ Macro { id: MacroRulesId( MacroRulesId( - 3400, + 3800, ), ), }, @@ -862,7 +862,7 @@ Macro { id: MacroRulesId( MacroRulesId( - 3401, + 3801, ), ), }, diff --git a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs index 06244670e0d56..37eb3d4101c6e 100644 --- a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs +++ b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs @@ -127,6 +127,7 @@ define_symbols! { as_str, asm, assert, + attr, attributes, begin_panic, bench, diff --git a/src/tools/rust-analyzer/crates/mbe/Cargo.toml b/src/tools/rust-analyzer/crates/mbe/Cargo.toml index eef718b7062a5..9e262c3539687 100644 --- a/src/tools/rust-analyzer/crates/mbe/Cargo.toml +++ b/src/tools/rust-analyzer/crates/mbe/Cargo.toml @@ -18,6 +18,7 @@ rustc-hash.workspace = true smallvec.workspace = true arrayvec.workspace = true ra-ap-rustc_lexer.workspace = true +bitflags.workspace = true # local deps parser.workspace = true diff --git a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs index b185556b5c7b7..9e4b78c2d83df 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs @@ -16,7 +16,7 @@ use syntax_bridge::{ use test_utils::{bench, bench_fixture, skip_slow_tests}; use crate::{ - DeclarativeMacro, + DeclarativeMacro, MacroCallStyle, parser::{MetaVarKind, Op, RepeatKind, Separator}, }; @@ -52,7 +52,8 @@ fn benchmark_expand_macro_rules() { invocations .into_iter() .map(|(id, tt)| { - let res = rules[&id].expand(&tt, |_| (), DUMMY, Edition::CURRENT); + let res = + rules[&id].expand(&tt, |_| (), MacroCallStyle::FnLike, DUMMY, Edition::CURRENT); assert!(res.err.is_none()); res.value.0.0.len() }) @@ -123,7 +124,11 @@ fn invocation_fixtures( } let subtree = builder.build(); - if it.expand(&subtree, |_| (), DUMMY, Edition::CURRENT).err.is_none() { + if it + .expand(&subtree, |_| (), MacroCallStyle::FnLike, DUMMY, Edition::CURRENT) + .err + .is_none() + { res.push((name.clone(), subtree)); break; } diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander.rs b/src/tools/rust-analyzer/crates/mbe/src/expander.rs index f910f9f9d753f..507402197e699 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander.rs @@ -9,17 +9,26 @@ use intern::Symbol; use rustc_hash::FxHashMap; use span::{Edition, Span}; -use crate::{ExpandError, ExpandErrorKind, ExpandResult, MatchedArmIndex, parser::MetaVarKind}; +use crate::{ + ExpandError, ExpandErrorKind, ExpandResult, MacroCallStyle, MatchedArmIndex, + parser::MetaVarKind, +}; pub(crate) fn expand_rules( rules: &[crate::Rule], input: &tt::TopSubtree, marker: impl Fn(&mut Span) + Copy, + call_style: MacroCallStyle, call_site: Span, def_site_edition: Edition, ) -> ExpandResult<(tt::TopSubtree, MatchedArmIndex)> { let mut match_: Option<(matcher::Match<'_>, &crate::Rule, usize)> = None; for (idx, rule) in rules.iter().enumerate() { + // Skip any rules that aren't relevant to the call style (fn-like/attr/derive). + if call_style != rule.style { + continue; + } + let new_match = matcher::match_(&rule.lhs, input, def_site_edition); if new_match.err.is_none() { diff --git a/src/tools/rust-analyzer/crates/mbe/src/lib.rs b/src/tools/rust-analyzer/crates/mbe/src/lib.rs index 9f9fa36abd46a..843c2889a01ed 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/lib.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/lib.rs @@ -14,6 +14,7 @@ extern crate ra_ap_rustc_lexer as rustc_lexer; extern crate rustc_lexer; mod expander; +mod macro_call_style; mod parser; #[cfg(test)] @@ -29,6 +30,7 @@ use tt::iter::TtIter; use std::fmt; use std::sync::Arc; +pub use crate::macro_call_style::{MacroCallStyle, MacroCallStyles}; use crate::parser::{MetaTemplate, MetaVarKind, Op}; pub use tt::{Delimiter, DelimiterKind, Punct}; @@ -137,6 +139,8 @@ pub struct DeclarativeMacro { #[derive(Clone, Debug, PartialEq, Eq)] struct Rule { + /// Is this a normal fn-like rule, an `attr()` rule, or a `derive()` rule? + style: MacroCallStyle, lhs: MetaTemplate, rhs: MetaTemplate, } @@ -195,13 +199,18 @@ impl DeclarativeMacro { let mut err = None; if let Some(args) = args { + // The presence of an argument list means that this macro uses the + // "simple" syntax, where the body is the RHS of a single rule. cov_mark::hit!(parse_macro_def_simple); let rule = (|| { let lhs = MetaTemplate::parse_pattern(ctx_edition, args.iter())?; let rhs = MetaTemplate::parse_template(ctx_edition, body.iter())?; - Ok(crate::Rule { lhs, rhs }) + // In the "simple" syntax, there is apparently no way to specify + // that the single rule is an attribute or derive rule, so it + // must be a function-like rule. + Ok(crate::Rule { style: MacroCallStyle::FnLike, lhs, rhs }) })(); match rule { @@ -209,6 +218,8 @@ impl DeclarativeMacro { Err(e) => err = Some(Box::new(e)), } } else { + // There was no top-level argument list, so this macro uses the + // list-of-rules syntax, similar to `macro_rules!`. cov_mark::hit!(parse_macro_def_rules); let mut src = body.iter(); while !src.is_empty() { @@ -249,14 +260,28 @@ impl DeclarativeMacro { self.rules.len() } + pub fn rule_styles(&self) -> MacroCallStyles { + if self.rules.is_empty() { + // No rules could be parsed, so fall back to assuming that this + // is intended to be a function-like macro. + MacroCallStyles::FN_LIKE + } else { + self.rules + .iter() + .map(|rule| MacroCallStyles::from(rule.style)) + .fold(MacroCallStyles::empty(), |a, b| a | b) + } + } + pub fn expand( &self, tt: &tt::TopSubtree, marker: impl Fn(&mut Span) + Copy, + call_style: MacroCallStyle, call_site: Span, def_site_edition: Edition, ) -> ExpandResult<(tt::TopSubtree, MatchedArmIndex)> { - expander::expand_rules(&self.rules, tt, marker, call_site, def_site_edition) + expander::expand_rules(&self.rules, tt, marker, call_style, call_site, def_site_edition) } } @@ -265,6 +290,9 @@ impl Rule { edition: impl Copy + Fn(SyntaxContext) -> Edition, src: &mut TtIter<'_, Span>, ) -> Result { + // Parse an optional `attr()` or `derive()` prefix before the LHS pattern. + let style = parser::parse_rule_style(src)?; + let (_, lhs) = src.expect_subtree().map_err(|()| ParseError::expected("expected subtree"))?; src.expect_char('=').map_err(|()| ParseError::expected("expected `=`"))?; @@ -275,7 +303,7 @@ impl Rule { let lhs = MetaTemplate::parse_pattern(edition, lhs)?; let rhs = MetaTemplate::parse_template(edition, rhs)?; - Ok(crate::Rule { lhs, rhs }) + Ok(crate::Rule { style, lhs, rhs }) } } diff --git a/src/tools/rust-analyzer/crates/mbe/src/macro_call_style.rs b/src/tools/rust-analyzer/crates/mbe/src/macro_call_style.rs new file mode 100644 index 0000000000000..311f0cb98dbab --- /dev/null +++ b/src/tools/rust-analyzer/crates/mbe/src/macro_call_style.rs @@ -0,0 +1,32 @@ +//! Types representing the three basic "styles" of macro calls in Rust source: +//! - Function-like macros ("bang macros"), e.g. `foo!(...)` +//! - Attribute macros, e.g. `#[foo]` +//! - Derive macros, e.g. `#[derive(Foo)]` + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MacroCallStyle { + FnLike, + Attr, + Derive, +} + +bitflags::bitflags! { + /// A set of `MacroCallStyle` values, allowing macros to indicate that + /// they support more than one style. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct MacroCallStyles: u8 { + const FN_LIKE = (1 << 0); + const ATTR = (1 << 1); + const DERIVE = (1 << 2); + } +} + +impl From for MacroCallStyles { + fn from(kind: MacroCallStyle) -> Self { + match kind { + MacroCallStyle::FnLike => Self::FN_LIKE, + MacroCallStyle::Attr => Self::ATTR, + MacroCallStyle::Derive => Self::DERIVE, + } + } +} diff --git a/src/tools/rust-analyzer/crates/mbe/src/parser.rs b/src/tools/rust-analyzer/crates/mbe/src/parser.rs index 711101260a072..e1cb98abae778 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/parser.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/parser.rs @@ -11,7 +11,34 @@ use tt::{ iter::{TtElement, TtIter}, }; -use crate::ParseError; +use crate::{MacroCallStyle, ParseError}; + +pub(crate) fn parse_rule_style(src: &mut TtIter<'_, Span>) -> Result { + // Skip an optional `unsafe`. This is only actually allowed for `attr` + // rules, but we'll let rustc worry about that. + if let Some(TtElement::Leaf(tt::Leaf::Ident(ident))) = src.peek() + && ident.sym == sym::unsafe_ + { + src.next().expect("already peeked"); + } + + let kind = match src.peek() { + Some(TtElement::Leaf(tt::Leaf::Ident(ident))) if ident.sym == sym::attr => { + src.next().expect("already peeked"); + // FIXME: Add support for `attr(..)` rules with attribute arguments, + // which would be inside these parens. + src.expect_subtree().map_err(|_| ParseError::expected("expected `()`"))?; + MacroCallStyle::Attr + } + Some(TtElement::Leaf(tt::Leaf::Ident(ident))) if ident.sym == sym::derive => { + src.next().expect("already peeked"); + src.expect_subtree().map_err(|_| ParseError::expected("expected `()`"))?; + MacroCallStyle::Derive + } + _ => MacroCallStyle::FnLike, + }; + Ok(kind) +} /// Consider /// diff --git a/src/tools/rust-analyzer/crates/mbe/src/tests.rs b/src/tools/rust-analyzer/crates/mbe/src/tests.rs index 56034516ef3b2..110a2664ec50c 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/tests.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/tests.rs @@ -51,6 +51,7 @@ fn check_( let res = mac.expand( &arg_tt, |_| (), + crate::MacroCallStyle::FnLike, Span { range: TextRange::up_to(TextSize::of(arg)), anchor: call_anchor, From 09cf06733ec3f7aa180d48ef1157eaaaf259323f Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Thu, 26 Jun 2025 16:06:18 +0200 Subject: [PATCH 11/27] Record inferred type for type placeholders This adds a type_of_type_placeholder arena to InferenceResult to record which type a given type placeholder gets inferred to. --- .../crates/hir-def/src/hir/type_ref.rs | 14 ++++--- .../rust-analyzer/crates/hir-ty/src/infer.rs | 40 ++++++++++++++++++- .../rust-analyzer/crates/hir-ty/src/lib.rs | 29 ++++++++++++++ .../rust-analyzer/crates/hir-ty/src/tests.rs | 33 +++++++++++++++ .../hir-ty/src/tests/display_source_code.rs | 19 +++++++++ 5 files changed, 128 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs index da0f058a9cb5c..ad8535413d671 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs @@ -195,12 +195,16 @@ impl TypeRef { TypeRef::Tuple(ThinVec::new()) } - pub fn walk(this: TypeRefId, map: &ExpressionStore, f: &mut impl FnMut(&TypeRef)) { + pub fn walk(this: TypeRefId, map: &ExpressionStore, f: &mut impl FnMut(TypeRefId, &TypeRef)) { go(this, f, map); - fn go(type_ref: TypeRefId, f: &mut impl FnMut(&TypeRef), map: &ExpressionStore) { - let type_ref = &map[type_ref]; - f(type_ref); + fn go( + type_ref_id: TypeRefId, + f: &mut impl FnMut(TypeRefId, &TypeRef), + map: &ExpressionStore, + ) { + let type_ref = &map[type_ref_id]; + f(type_ref_id, type_ref); match type_ref { TypeRef::Fn(fn_) => { fn_.params.iter().for_each(|&(_, param_type)| go(param_type, f, map)) @@ -224,7 +228,7 @@ impl TypeRef { }; } - fn go_path(path: &Path, f: &mut impl FnMut(&TypeRef), map: &ExpressionStore) { + fn go_path(path: &Path, f: &mut impl FnMut(TypeRefId, &TypeRef), map: &ExpressionStore) { if let Some(type_ref) = path.type_anchor() { go(type_ref, f, map); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index 02b8ab8cdde6c..d507afecd31da 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -41,7 +41,7 @@ use hir_def::{ layout::Integer, resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, signatures::{ConstSignature, StaticSignature}, - type_ref::{ConstRef, LifetimeRefId, TypeRefId}, + type_ref::{ConstRef, LifetimeRefId, TypeRef, TypeRefId}, }; use hir_expand::{mod_path::ModPath, name::Name}; use indexmap::IndexSet; @@ -60,6 +60,7 @@ use triomphe::Arc; use crate::{ ImplTraitId, IncorrectGenericsLenKind, PathLoweringDiagnostic, TargetFeatures, + collect_type_inference_vars, db::{HirDatabase, InternedClosureId, InternedOpaqueTyId}, infer::{ coerce::{CoerceMany, DynamicCoerceMany}, @@ -497,6 +498,7 @@ pub struct InferenceResult<'db> { /// unresolved or missing subpatterns or subpatterns of mismatched types. pub(crate) type_of_pat: ArenaMap>, pub(crate) type_of_binding: ArenaMap>, + pub(crate) type_of_type_placeholder: ArenaMap>, pub(crate) type_of_opaque: FxHashMap>, pub(crate) type_mismatches: FxHashMap>, /// Whether there are any type-mismatching errors in the result. @@ -542,6 +544,7 @@ impl<'db> InferenceResult<'db> { type_of_expr: Default::default(), type_of_pat: Default::default(), type_of_binding: Default::default(), + type_of_type_placeholder: Default::default(), type_of_opaque: Default::default(), type_mismatches: Default::default(), has_errors: Default::default(), @@ -606,6 +609,9 @@ impl<'db> InferenceResult<'db> { _ => None, }) } + pub fn placeholder_types(&self) -> impl Iterator)> { + self.type_of_type_placeholder.iter() + } pub fn closure_info(&self, closure: InternedClosureId) -> &(Vec>, FnTrait) { self.closure_info.get(&closure).unwrap() } @@ -1014,6 +1020,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { type_of_expr, type_of_pat, type_of_binding, + type_of_type_placeholder, type_of_opaque, type_mismatches, has_errors, @@ -1046,6 +1053,11 @@ impl<'body, 'db> InferenceContext<'body, 'db> { *has_errors = *has_errors || ty.references_non_lt_error(); } type_of_binding.shrink_to_fit(); + for ty in type_of_type_placeholder.values_mut() { + *ty = table.resolve_completely(*ty); + *has_errors = *has_errors || ty.references_non_lt_error(); + } + type_of_type_placeholder.shrink_to_fit(); type_of_opaque.shrink_to_fit(); *has_errors |= !type_mismatches.is_empty(); @@ -1285,6 +1297,10 @@ impl<'body, 'db> InferenceContext<'body, 'db> { self.result.type_of_pat.insert(pat, ty); } + fn write_type_placeholder_ty(&mut self, type_ref: TypeRefId, ty: Ty<'db>) { + self.result.type_of_type_placeholder.insert(type_ref, ty); + } + fn write_binding_ty(&mut self, id: BindingId, ty: Ty<'db>) { self.result.type_of_binding.insert(id, ty); } @@ -1333,7 +1349,27 @@ impl<'body, 'db> InferenceContext<'body, 'db> { ) -> Ty<'db> { let ty = self .with_ty_lowering(store, type_source, lifetime_elision, |ctx| ctx.lower_ty(type_ref)); - self.process_user_written_ty(ty) + let ty = self.process_user_written_ty(ty); + + // Record the association from placeholders' TypeRefId to type variables. + // We only record them if their number matches. This assumes TypeRef::walk and TypeVisitable process the items in the same order. + let type_variables = collect_type_inference_vars(&ty); + let mut placeholder_ids = vec![]; + TypeRef::walk(type_ref, store, &mut |type_ref_id, type_ref| { + if matches!(type_ref, TypeRef::Placeholder) { + placeholder_ids.push(type_ref_id); + } + }); + + if placeholder_ids.len() == type_variables.len() { + for (placeholder_id, type_variable) in + placeholder_ids.into_iter().zip(type_variables.into_iter()) + { + self.write_type_placeholder_ty(placeholder_id, type_variable); + } + } + + ty } pub(crate) fn make_body_ty(&mut self, type_ref: TypeRefId) -> Ty<'db> { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index b29c7d252b506..8819307c539b5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -569,6 +569,35 @@ where Vec::from_iter(collector.params) } +struct TypeInferenceVarCollector<'db> { + type_inference_vars: Vec>, +} + +impl<'db> rustc_type_ir::TypeVisitor> for TypeInferenceVarCollector<'db> { + type Result = (); + + fn visit_ty(&mut self, ty: Ty<'db>) -> Self::Result { + use crate::rustc_type_ir::Flags; + if ty.is_ty_var() { + self.type_inference_vars.push(ty); + } else if ty.flags().intersects(rustc_type_ir::TypeFlags::HAS_TY_INFER) { + ty.super_visit_with(self); + } else { + // Fast path: don't visit inner types (e.g. generic arguments) when `flags` indicate + // that there are no placeholders. + } + } +} + +pub fn collect_type_inference_vars<'db, T>(value: &T) -> Vec> +where + T: ?Sized + rustc_type_ir::TypeVisitable>, +{ + let mut collector = TypeInferenceVarCollector { type_inference_vars: vec![] }; + value.visit_with(&mut collector); + collector.type_inference_vars +} + pub fn known_const_to_ast<'db>( konst: Const<'db>, db: &'db dyn HirDatabase, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs index 95a02d534b8ed..002d58961d225 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs @@ -23,6 +23,7 @@ use hir_def::{ item_scope::ItemScope, nameres::DefMap, src::HasSource, + type_ref::TypeRefId, }; use hir_expand::{FileRange, InFile, db::ExpandDatabase}; use itertools::Itertools; @@ -219,6 +220,24 @@ fn check_impl( } } } + + for (type_ref, ty) in inference_result.placeholder_types() { + let node = match type_node(&body_source_map, type_ref, &db) { + Some(value) => value, + None => continue, + }; + let range = node.as_ref().original_file_range_rooted(&db); + if let Some(expected) = types.remove(&range) { + let actual = salsa::attach(&db, || { + if display_source { + ty.display_source_code(&db, def.module(&db), true).unwrap() + } else { + ty.display_test(&db, display_target).to_string() + } + }); + assert_eq!(actual, expected, "type annotation differs at {:#?}", range.range); + } + } } let mut buf = String::new(); @@ -275,6 +294,20 @@ fn pat_node( }) } +fn type_node( + body_source_map: &BodySourceMap, + type_ref: TypeRefId, + db: &TestDB, +) -> Option> { + Some(match body_source_map.type_syntax(type_ref) { + Ok(sp) => { + let root = db.parse_or_expand(sp.file_id); + sp.map(|ptr| ptr.to_node(&root).syntax().clone()) + } + Err(SyntheticSyntax) => return None, + }) +} + fn infer(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> String { infer_with_mismatches(ra_fixture, false) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs index a986b54a7b064..dc3869930dcf1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs @@ -246,3 +246,22 @@ fn test() { "#, ); } + +#[test] +fn type_placeholder_type() { + check_types_source_code( + r#" +struct S(T); +fn test() { + let f: S<_> = S(3); + //^ i32 + let f: [_; _] = [4_u32, 5, 6]; + //^ u32 + let f: (_, _, _) = (1_u32, 1_i32, false); + //^ u32 + //^ i32 + //^ bool +} +"#, + ); +} From eb5401efe4f4ec3ae27c7036c8afcf3ed344a974 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Thu, 26 Jun 2025 16:06:18 +0200 Subject: [PATCH 12/27] Use inferred type in SourceAnalyzer::type_of_type This uses the new InferenceResult::type_of_type_placeholder data to turn type references into completely resolved types instead of just returning the lexical type. --- .../rust-analyzer/crates/hir-ty/src/infer.rs | 3 ++ .../crates/hir/src/source_analyzer.rs | 32 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index d507afecd31da..15eb355128085 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -612,6 +612,9 @@ impl<'db> InferenceResult<'db> { pub fn placeholder_types(&self) -> impl Iterator)> { self.type_of_type_placeholder.iter() } + pub fn type_of_type_placeholder(&self, type_ref: TypeRefId) -> Option> { + self.type_of_type_placeholder.get(type_ref).copied() + } pub fn closure_info(&self, closure: InternedClosureId) -> &(Vec>, FnTrait) { self.closure_info.get(&closure).unwrap() } diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index ae328a9680023..858426ceab732 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -21,7 +21,7 @@ use hir_def::{ lang_item::LangItem, nameres::MacroSubNs, resolver::{HasResolver, Resolver, TypeNs, ValueNs, resolver_for_scope}, - type_ref::{Mutability, TypeRefId}, + type_ref::{Mutability, TypeRef, TypeRefId}, }; use hir_expand::{ HirFileId, InFile, @@ -267,8 +267,11 @@ impl<'db> SourceAnalyzer<'db> { db: &'db dyn HirDatabase, ty: &ast::Type, ) -> Option> { + let interner = DbInterner::new_with(db, None, None); + let type_ref = self.type_id(ty)?; - let ty = TyLoweringContext::new( + + let mut ty = TyLoweringContext::new( db, &self.resolver, self.store()?, @@ -279,6 +282,31 @@ impl<'db> SourceAnalyzer<'db> { LifetimeElisionKind::Infer, ) .lower_ty(type_ref); + + // Try and substitute unknown types using InferenceResult + if let Some(infer) = self.infer() + && let Some(store) = self.store() + { + let mut inferred_types = vec![]; + TypeRef::walk(type_ref, store, &mut |type_ref_id, type_ref| { + if matches!(type_ref, TypeRef::Placeholder) { + inferred_types.push(infer.type_of_type_placeholder(type_ref_id)); + } + }); + let mut inferred_types = inferred_types.into_iter(); + + let substituted_ty = hir_ty::next_solver::fold::fold_tys(interner, ty, |ty| { + if ty.is_ty_error() { inferred_types.next().flatten().unwrap_or(ty) } else { ty } + }); + + // Only used the result if the placeholder and unknown type counts matched + let success = + inferred_types.next().is_none() && !substituted_ty.references_non_lt_error(); + if success { + ty = substituted_ty; + } + } + Some(Type::new_with_resolver(db, &self.resolver, ty)) } From b331c523a87c067f8a8556e99f33985557f4726a Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Thu, 26 Jun 2025 16:06:18 +0200 Subject: [PATCH 13/27] extract_type_alias assist extracts inferred type when possible When met with types with placeholders, this ensures this assist extracts the inferred type instead of the type with placeholders. For instance, when met with this code: ``` fn main() { let vec: Vec<_> = vec![4]; } ``` selecting Vec<_> and extracting an alias will now yield `Vec` instead of `Vec<_>`. --- .../src/handlers/extract_type_alias.rs | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs index 59522458af9e6..769bbd976a264 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -1,4 +1,5 @@ use either::Either; +use hir::HirDisplay; use ide_db::syntax_helpers::node_ext::walk_ty; use syntax::{ ast::{self, AstNode, HasGenericArgs, HasGenericParams, HasName, edit::IndentLevel, make}, @@ -39,6 +40,15 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> ); let target = ty.syntax().text_range(); + let resolved_ty = ctx.sema.resolve_type(&ty)?; + let resolved_ty = if !resolved_ty.contains_unknown() { + let module = ctx.sema.scope(ty.syntax())?.module(); + let resolved_ty = resolved_ty.display_source_code(ctx.db(), module.into(), false).ok()?; + make::ty(&resolved_ty) + } else { + ty.clone() + }; + acc.add( AssistId::refactor_extract("extract_type_alias"), "Extract type as type alias", @@ -72,7 +82,7 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> // Insert new alias let ty_alias = - make::ty_alias(None, "Type", generic_params, None, None, Some((ty, None))) + make::ty_alias(None, "Type", generic_params, None, None, Some((resolved_ty, None))) .clone_for_update(); if let Some(cap) = ctx.config.snippet_cap @@ -391,4 +401,50 @@ where "#, ); } + + #[test] + fn inferred_generic_type_parameter() { + check_assist( + extract_type_alias, + r#" +struct Wrap(T); + +fn main() { + let wrap: $0Wrap<_>$0 = Wrap::<_>(3i32); +} + "#, + r#" +struct Wrap(T); + +type $0Type = Wrap; + +fn main() { + let wrap: Type = Wrap::<_>(3i32); +} + "#, + ) + } + + #[test] + fn inferred_type() { + check_assist( + extract_type_alias, + r#" +struct Wrap(T); + +fn main() { + let wrap: Wrap<$0_$0> = Wrap::<_>(3i32); +} + "#, + r#" +struct Wrap(T); + +type $0Type = i32; + +fn main() { + let wrap: Wrap = Wrap::<_>(3i32); +} + "#, + ) + } } From 93de020afd0ca47b21b45fec9b7882094133a336 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Mon, 30 Jun 2025 11:19:42 +0200 Subject: [PATCH 14/27] Show inlay hints for type placeholders With the extra InferenceResult that maps type placeholders to their inferred type, we can now easily display inlay hints for them. --- .../crates/ide/src/inlay_hints.rs | 5 ++ .../ide/src/inlay_hints/placeholders.rs | 76 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/tools/rust-analyzer/crates/ide/src/inlay_hints/placeholders.rs diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs index 2b4fe54fc3d90..6dd9e84a57e4f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs @@ -40,6 +40,7 @@ mod implicit_static; mod implied_dyn_trait; mod lifetime; mod param_name; +mod placeholders; mod ra_fixture; mod range_exclusive; @@ -291,6 +292,10 @@ fn hints( implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_)); Some(()) }, + ast::Type::InferType(placeholder) => { + placeholders::type_hints(hints, famous_defs, config, display_target, placeholder); + Some(()) + }, _ => Some(()), }, ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, it), diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/placeholders.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/placeholders.rs new file mode 100644 index 0000000000000..96d2c17c03bb8 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/placeholders.rs @@ -0,0 +1,76 @@ +//! Implementation of type placeholder inlay hints: +//! ```no_run +//! let a = Vec<_> = vec![4]; +//! //^ = i32 +//! ``` + +use hir::DisplayTarget; +use ide_db::famous_defs::FamousDefs; +use syntax::{ + AstNode, + ast::{InferType, Type}, +}; + +use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, inlay_hints::label_of_ty}; + +pub(super) fn type_hints( + acc: &mut Vec, + famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, + config: &InlayHintsConfig<'_>, + display_target: DisplayTarget, + placeholder: InferType, +) -> Option<()> { + if !config.type_hints { + return None; + } + + let syntax = placeholder.syntax(); + let range = syntax.text_range(); + + let ty = sema.resolve_type(&Type::InferType(placeholder))?; + + let mut label = label_of_ty(famous_defs, config, &ty, display_target)?; + label.prepend_str("= "); + + acc.push(InlayHint { + range, + kind: InlayKind::Type, + label, + text_edit: None, + position: InlayHintPosition::After, + pad_left: true, + pad_right: false, + resolve_parent: None, + }); + Some(()) +} + +#[cfg(test)] +mod tests { + use crate::{ + InlayHintsConfig, + inlay_hints::tests::{DISABLED_CONFIG, check_with_config}, + }; + + #[track_caller] + fn check_type_infer(#[rust_analyzer::rust_fixture] ra_fixture: &str) { + check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture); + } + + #[test] + fn inferred_types() { + check_type_infer( + r#" +struct S(T); + +fn foo() { + let t: (_, _, [_; _]) = (1_u32, S(2), [false] as _); + //^ = u32 + //^ = S + //^ = bool + //^ = [bool; 1] +} +"#, + ); + } +} From d73c941d8cb5fdf0372bdfb66095fb423abc2d56 Mon Sep 17 00:00:00 2001 From: Hegui Dai Date: Mon, 24 Nov 2025 22:35:48 +0800 Subject: [PATCH 15/27] add regression tests for add_missing_impl_members --- .../src/handlers/add_missing_impl_members.rs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 636cbfe9132fb..e970bb7167d10 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -2500,6 +2500,40 @@ impl dep::Foo for Bar { ${0:todo!()} } } +"#, + ); + } + + #[test] + fn regression_test_for_when_impl_for_unit() { + check_assist( + add_missing_impl_members, + r#" +trait Test { + fn f() + where + B: IntoIterator, + ::Item: Copy; +} +impl Test for () { + $0 +} +"#, + r#" +trait Test { + fn f() + where + B: IntoIterator, + ::Item: Copy; +} +impl Test for () { + fn f() + where + B: IntoIterator, + ::Item: Copy { + ${0:todo!()} + } +} "#, ); } From ddefc4b7b4e68e8dace3ddd9f485a663c40b0017 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 24 Nov 2025 11:18:14 +0530 Subject: [PATCH 16/27] add codec and framing to abstract encoding and decoding logic from run --- .../crates/proc-macro-api/src/codec.rs | 12 ++ .../crates/proc-macro-api/src/framing.rs | 14 ++ .../proc-macro-api/src/legacy_protocol.rs | 33 ++-- .../src/legacy_protocol/json.rs | 74 ++++++--- .../proc-macro-api/src/legacy_protocol/msg.rs | 56 +------ .../src/legacy_protocol/postcard.rs | 49 +++--- .../crates/proc-macro-api/src/lib.rs | 11 +- .../crates/proc-macro-api/src/process.rs | 13 +- .../crates/proc-macro-srv-cli/Cargo.toml | 2 +- .../proc-macro-srv-cli/src/main_loop.rs | 148 ++---------------- 10 files changed, 142 insertions(+), 270 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/proc-macro-api/src/codec.rs create mode 100644 src/tools/rust-analyzer/crates/proc-macro-api/src/framing.rs diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/codec.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/codec.rs new file mode 100644 index 0000000000000..baccaa6be4c20 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/codec.rs @@ -0,0 +1,12 @@ +//! Protocol codec + +use std::io; + +use serde::de::DeserializeOwned; + +use crate::framing::Framing; + +pub trait Codec: Framing { + fn encode(msg: &T) -> io::Result; + fn decode(buf: &mut Self::Buf) -> io::Result; +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/framing.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/framing.rs new file mode 100644 index 0000000000000..a1e6fc05ca110 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/framing.rs @@ -0,0 +1,14 @@ +//! Protocol framing + +use std::io::{self, BufRead, Write}; + +pub trait Framing { + type Buf: Default; + + fn read<'a, R: BufRead>( + inp: &mut R, + buf: &'a mut Self::Buf, + ) -> io::Result>; + + fn write(out: &mut W, buf: &Self::Buf) -> io::Result<()>; +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs index 6d521d00cd90b..c2b132ddcc1db 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol.rs @@ -14,14 +14,15 @@ use span::Span; use crate::{ ProcMacro, ProcMacroKind, ServerError, + codec::Codec, legacy_protocol::{ - json::{read_json, write_json}, + json::JsonProtocol, msg::{ ExpandMacro, ExpandMacroData, ExpnGlobals, FlatTree, Message, Request, Response, ServerConfig, SpanDataIndexMap, deserialize_span_data_index_map, flat::serialize_span_data_index_map, }, - postcard::{read_postcard, write_postcard}, + postcard::PostcardProtocol, }, process::ProcMacroServerProcess, version, @@ -154,42 +155,26 @@ fn send_task(srv: &ProcMacroServerProcess, req: Request) -> Result, req) } else { - srv.send_task(send_request, req) + srv.send_task(send_request::, req) } } /// Sends a request to the server and reads the response. -fn send_request( +fn send_request( mut writer: &mut dyn Write, mut reader: &mut dyn BufRead, req: Request, - buf: &mut String, + buf: &mut P::Buf, ) -> Result, ServerError> { - req.write(write_json, &mut writer).map_err(|err| ServerError { + req.write::<_, P>(&mut writer).map_err(|err| ServerError { message: "failed to write request".into(), io: Some(Arc::new(err)), })?; - let res = Response::read(read_json, &mut reader, buf).map_err(|err| ServerError { + let res = Response::read::<_, P>(&mut reader, buf).map_err(|err| ServerError { message: "failed to read response".into(), io: Some(Arc::new(err)), })?; Ok(res) } - -fn send_request_postcard( - mut writer: &mut dyn Write, - mut reader: &mut dyn BufRead, - req: Request, - buf: &mut Vec, -) -> Result, ServerError> { - req.write_postcard(write_postcard, &mut writer).map_err(|err| ServerError { - message: "failed to write request".into(), - io: Some(Arc::new(err)), - })?; - let res = Response::read_postcard(read_postcard, &mut reader, buf).map_err(|err| { - ServerError { message: "failed to read response".into(), io: Some(Arc::new(err)) } - })?; - Ok(res) -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/json.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/json.rs index cf8535f77d53c..1359c0568402a 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/json.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/json.rs @@ -1,36 +1,58 @@ //! Protocol functions for json. use std::io::{self, BufRead, Write}; -/// Reads a JSON message from the input stream. -pub fn read_json<'a>( - inp: &mut impl BufRead, - buf: &'a mut String, -) -> io::Result> { - loop { - buf.clear(); - - inp.read_line(buf)?; - buf.pop(); // Remove trailing '\n' - - if buf.is_empty() { - return Ok(None); - } +use serde::{Serialize, de::DeserializeOwned}; + +use crate::{codec::Codec, framing::Framing}; + +pub struct JsonProtocol; + +impl Framing for JsonProtocol { + type Buf = String; + + fn read<'a, R: BufRead>( + inp: &mut R, + buf: &'a mut String, + ) -> io::Result> { + loop { + buf.clear(); + + inp.read_line(buf)?; + buf.pop(); // Remove trailing '\n' - // Some ill behaved macro try to use stdout for debugging - // We ignore it here - if !buf.starts_with('{') { - tracing::error!("proc-macro tried to print : {}", buf); - continue; + if buf.is_empty() { + return Ok(None); + } + + // Some ill behaved macro try to use stdout for debugging + // We ignore it here + if !buf.starts_with('{') { + tracing::error!("proc-macro tried to print : {}", buf); + continue; + } + + return Ok(Some(buf)); } + } - return Ok(Some(buf)); + fn write(out: &mut W, buf: &String) -> io::Result<()> { + tracing::debug!("> {}", buf); + out.write_all(buf.as_bytes())?; + out.write_all(b"\n")?; + out.flush() } } -/// Writes a JSON message to the output stream. -pub fn write_json(out: &mut impl Write, msg: &String) -> io::Result<()> { - tracing::debug!("> {}", msg); - out.write_all(msg.as_bytes())?; - out.write_all(b"\n")?; - out.flush() +impl Codec for JsonProtocol { + fn encode(msg: &T) -> io::Result { + Ok(serde_json::to_string(msg)?) + } + + fn decode(buf: &mut String) -> io::Result { + let mut deserializer = serde_json::Deserializer::from_str(buf); + // Note that some proc-macro generate very deep syntax tree + // We have to disable the current limit of serde here + deserializer.disable_recursion_limit(); + Ok(T::deserialize(&mut deserializer)?) + } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs index 6df184630de7f..1c77863aac344 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs @@ -8,10 +8,7 @@ use paths::Utf8PathBuf; use serde::de::DeserializeOwned; use serde_derive::{Deserialize, Serialize}; -use crate::{ - ProcMacroKind, - legacy_protocol::postcard::{decode_cobs, encode_cobs}, -}; +use crate::{ProcMacroKind, codec::Codec}; /// Represents requests sent from the client to the proc-macro-srv. #[derive(Debug, Serialize, Deserialize)] @@ -152,60 +149,21 @@ impl ExpnGlobals { } pub trait Message: serde::Serialize + DeserializeOwned { - fn read( - from_proto: ProtocolRead, - inp: &mut R, - buf: &mut String, - ) -> io::Result> { - Ok(match from_proto(inp, buf)? { + fn read(inp: &mut R, buf: &mut C::Buf) -> io::Result> { + Ok(match C::read(inp, buf)? { None => None, - Some(text) => { - let mut deserializer = serde_json::Deserializer::from_str(text); - // Note that some proc-macro generate very deep syntax tree - // We have to disable the current limit of serde here - deserializer.disable_recursion_limit(); - Some(Self::deserialize(&mut deserializer)?) - } - }) - } - fn write(self, to_proto: ProtocolWrite, out: &mut W) -> io::Result<()> { - let text = serde_json::to_string(&self)?; - to_proto(out, &text) - } - - fn read_postcard( - from_proto: ProtocolRead>, - inp: &mut R, - buf: &mut Vec, - ) -> io::Result> { - Ok(match from_proto(inp, buf)? { - None => None, - Some(buf) => Some(decode_cobs(buf)?), + Some(buf) => C::decode(buf)?, }) } - - fn write_postcard( - self, - to_proto: ProtocolWrite>, - out: &mut W, - ) -> io::Result<()> { - let buf = encode_cobs(&self)?; - to_proto(out, &buf) + fn write(self, out: &mut W) -> io::Result<()> { + let value = C::encode(&self)?; + C::write(out, &value) } } impl Message for Request {} impl Message for Response {} -/// Type alias for a function that reads protocol messages from a buffered input stream. -#[allow(type_alias_bounds)] -type ProtocolRead = - for<'i, 'buf> fn(inp: &'i mut R, buf: &'buf mut Buf) -> io::Result>; -/// Type alias for a function that writes protocol messages to an output stream. -#[allow(type_alias_bounds)] -type ProtocolWrite = - for<'o, 'msg> fn(out: &'o mut W, msg: &'msg Buf) -> io::Result<()>; - #[cfg(test)] mod tests { use intern::{Symbol, sym}; diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs index 305e4de934156..c28a9bfe3a1aa 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs @@ -2,28 +2,39 @@ use std::io::{self, BufRead, Write}; -pub fn read_postcard<'a>( - input: &mut impl BufRead, - buf: &'a mut Vec, -) -> io::Result>> { - buf.clear(); - let n = input.read_until(0, buf)?; - if n == 0 { - return Ok(None); +use serde::{Serialize, de::DeserializeOwned}; + +use crate::{codec::Codec, framing::Framing}; + +pub struct PostcardProtocol; + +impl Framing for PostcardProtocol { + type Buf = Vec; + + fn read<'a, R: BufRead>( + inp: &mut R, + buf: &'a mut Vec, + ) -> io::Result>> { + buf.clear(); + let n = inp.read_until(0, buf)?; + if n == 0 { + return Ok(None); + } + Ok(Some(buf)) } - Ok(Some(buf)) -} -#[allow(clippy::ptr_arg)] -pub fn write_postcard(out: &mut impl Write, msg: &Vec) -> io::Result<()> { - out.write_all(msg)?; - out.flush() + fn write(out: &mut W, buf: &Vec) -> io::Result<()> { + out.write_all(buf)?; + out.flush() + } } -pub fn encode_cobs(value: &T) -> io::Result> { - postcard::to_allocvec_cobs(value).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) -} +impl Codec for PostcardProtocol { + fn encode(msg: &T) -> io::Result> { + postcard::to_allocvec_cobs(msg).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } -pub fn decode_cobs(bytes: &mut [u8]) -> io::Result { - postcard::from_bytes_cobs(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + fn decode(buf: &mut Self::Buf) -> io::Result { + postcard::from_bytes_cobs(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs index 2cdb33ff81eb3..a725b94f04b21 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs @@ -12,6 +12,8 @@ )] #![allow(internal_features)] +mod codec; +mod framing; pub mod legacy_protocol; mod process; @@ -19,7 +21,8 @@ use paths::{AbsPath, AbsPathBuf}; use span::{ErasedFileAstId, FIXUP_ERASED_FILE_AST_ID_MARKER, Span}; use std::{fmt, io, sync::Arc, time::SystemTime}; -use crate::process::ProcMacroServerProcess; +pub use crate::codec::Codec; +use crate::{legacy_protocol::SpanMode, process::ProcMacroServerProcess}; /// The versions of the server protocol pub mod version { @@ -123,7 +126,11 @@ impl ProcMacroClient { Item = (impl AsRef, &'a Option>), > + Clone, ) -> io::Result { - let process = ProcMacroServerProcess::run(process_path, env, process::Protocol::default())?; + let process = ProcMacroServerProcess::run( + process_path, + env, + process::Protocol::Postcard { mode: SpanMode::Id }, + )?; Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() }) } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs index 7f0cd05c80584..1365245f9846b 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs @@ -34,12 +34,6 @@ pub(crate) enum Protocol { Postcard { mode: SpanMode }, } -impl Default for Protocol { - fn default() -> Self { - Protocol::Postcard { mode: SpanMode::Id } - } -} - /// Maintains the state of the proc-macro server process. #[derive(Debug)] struct ProcessSrvState { @@ -122,11 +116,10 @@ impl ProcMacroServerProcess { srv.version = version; if version >= version::RUST_ANALYZER_SPAN_SUPPORT - && let Ok(mode) = srv.enable_rust_analyzer_spans() + && let Ok(new_mode) = srv.enable_rust_analyzer_spans() { - srv.protocol = match protocol { - Protocol::Postcard { .. } => Protocol::Postcard { mode }, - Protocol::LegacyJson { .. } => Protocol::LegacyJson { mode }, + match &mut srv.protocol { + Protocol::Postcard { mode } | Protocol::LegacyJson { mode } => *mode = new_mode, }; } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml index f6022cf2c7bda..aa153897fa964 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml @@ -18,7 +18,7 @@ postcard.workspace = true clap = {version = "4.5.42", default-features = false, features = ["std"]} [features] -default = ["postcard"] +default = [] sysroot-abi = ["proc-macro-srv/sysroot-abi", "proc-macro-api/sysroot-abi"] in-rust-tree = ["proc-macro-srv/in-rust-tree", "sysroot-abi"] diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs index b0e7108d20a58..029ab6eca9413 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -2,13 +2,14 @@ use std::io; use proc_macro_api::{ + Codec, legacy_protocol::{ - json::{read_json, write_json}, + json::JsonProtocol, msg::{ self, ExpandMacroData, ExpnGlobals, Message, SpanMode, SpanTransformer, deserialize_span_data_index_map, serialize_span_data_index_map, }, - postcard::{read_postcard, write_postcard}, + postcard::PostcardProtocol, }, version::CURRENT_API_VERSION, }; @@ -36,12 +37,12 @@ impl SpanTransformer for SpanTrans { pub(crate) fn run(format: ProtocolFormat) -> io::Result<()> { match format { - ProtocolFormat::Json => run_json(), - ProtocolFormat::Postcard => run_postcard(), + ProtocolFormat::Json => run_::(), + ProtocolFormat::Postcard => run_::(), } } -fn run_json() -> io::Result<()> { +fn run_() -> io::Result<()> { fn macro_kind_to_api(kind: proc_macro_srv::ProcMacroKind) -> proc_macro_api::ProcMacroKind { match kind { proc_macro_srv::ProcMacroKind::CustomDerive => { @@ -52,9 +53,9 @@ fn run_json() -> io::Result<()> { } } - let mut buf = String::new(); - let mut read_request = || msg::Request::read(read_json, &mut io::stdin().lock(), &mut buf); - let write_response = |msg: msg::Response| msg.write(write_json, &mut io::stdout().lock()); + let mut buf = C::Buf::default(); + let mut read_request = || msg::Request::read::<_, C>(&mut io::stdin().lock(), &mut buf); + let write_response = |msg: msg::Response| msg.write::<_, C>(&mut io::stdout().lock()); let env = EnvSnapshot::default(); let srv = proc_macro_srv::ProcMacroSrv::new(&env); @@ -170,134 +171,3 @@ fn run_json() -> io::Result<()> { Ok(()) } - -fn run_postcard() -> io::Result<()> { - fn macro_kind_to_api(kind: proc_macro_srv::ProcMacroKind) -> proc_macro_api::ProcMacroKind { - match kind { - proc_macro_srv::ProcMacroKind::CustomDerive => { - proc_macro_api::ProcMacroKind::CustomDerive - } - proc_macro_srv::ProcMacroKind::Bang => proc_macro_api::ProcMacroKind::Bang, - proc_macro_srv::ProcMacroKind::Attr => proc_macro_api::ProcMacroKind::Attr, - } - } - - let mut buf = Vec::new(); - let mut read_request = - || msg::Request::read_postcard(read_postcard, &mut io::stdin().lock(), &mut buf); - let write_response = - |msg: msg::Response| msg.write_postcard(write_postcard, &mut io::stdout().lock()); - - let env = proc_macro_srv::EnvSnapshot::default(); - let srv = proc_macro_srv::ProcMacroSrv::new(&env); - - let mut span_mode = msg::SpanMode::Id; - - while let Some(req) = read_request()? { - let res = match req { - msg::Request::ListMacros { dylib_path } => { - msg::Response::ListMacros(srv.list_macros(&dylib_path).map(|macros| { - macros.into_iter().map(|(name, kind)| (name, macro_kind_to_api(kind))).collect() - })) - } - msg::Request::ExpandMacro(task) => { - let msg::ExpandMacro { - lib, - env, - current_dir, - data: - msg::ExpandMacroData { - macro_body, - macro_name, - attributes, - has_global_spans: - msg::ExpnGlobals { serialize: _, def_site, call_site, mixed_site }, - span_data_table, - }, - } = *task; - match span_mode { - msg::SpanMode::Id => msg::Response::ExpandMacro({ - let def_site = proc_macro_srv::SpanId(def_site as u32); - let call_site = proc_macro_srv::SpanId(call_site as u32); - let mixed_site = proc_macro_srv::SpanId(mixed_site as u32); - - let macro_body = - macro_body.to_subtree_unresolved::(CURRENT_API_VERSION); - let attributes = attributes - .map(|it| it.to_subtree_unresolved::(CURRENT_API_VERSION)); - - srv.expand( - lib, - &env, - current_dir, - ¯o_name, - macro_body, - attributes, - def_site, - call_site, - mixed_site, - ) - .map(|it| { - msg::FlatTree::new_raw::( - tt::SubtreeView::new(&it), - CURRENT_API_VERSION, - ) - }) - .map_err(|e| e.into_string().unwrap_or_default()) - .map_err(msg::PanicMessage) - }), - msg::SpanMode::RustAnalyzer => msg::Response::ExpandMacroExtended({ - let mut span_data_table = - msg::deserialize_span_data_index_map(&span_data_table); - - let def_site = span_data_table[def_site]; - let call_site = span_data_table[call_site]; - let mixed_site = span_data_table[mixed_site]; - - let macro_body = - macro_body.to_subtree_resolved(CURRENT_API_VERSION, &span_data_table); - let attributes = attributes.map(|it| { - it.to_subtree_resolved(CURRENT_API_VERSION, &span_data_table) - }); - srv.expand( - lib, - &env, - current_dir, - ¯o_name, - macro_body, - attributes, - def_site, - call_site, - mixed_site, - ) - .map(|it| { - ( - msg::FlatTree::new( - tt::SubtreeView::new(&it), - CURRENT_API_VERSION, - &mut span_data_table, - ), - msg::serialize_span_data_index_map(&span_data_table), - ) - }) - .map(|(tree, span_data_table)| msg::ExpandMacroExtended { - tree, - span_data_table, - }) - .map_err(|e| e.into_string().unwrap_or_default()) - .map_err(msg::PanicMessage) - }), - } - } - msg::Request::ApiVersionCheck {} => msg::Response::ApiVersionCheck(CURRENT_API_VERSION), - msg::Request::SetConfig(config) => { - span_mode = config.span_mode; - msg::Response::SetConfig(config) - } - }; - - write_response(res)?; - } - - Ok(()) -} From a54b1950f381f4179b4b51d77ce2d3c743a4abf9 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 25 Nov 2025 16:13:59 +0800 Subject: [PATCH 17/27] Fix invalid completion arg nr Example --- ```rust fn foo() { bar(, $0); } fn bar(x: u32, y: i32) {} ``` **Before this PR** ```text ty: u32, name: x ``` **After this PR** ```text ty: i32, name: y ``` --- .../ide-completion/src/context/analysis.rs | 2 +- .../crates/ide-completion/src/context/tests.rs | 14 ++++++++++++++ .../crates/ide-db/src/active_parameter.rs | 17 ++++++++++++----- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index d6d3978385d9b..d5a1d2113674e 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 @@ -657,7 +657,7 @@ fn expected_type_and_name<'db>( cov_mark::hit!(expected_type_fn_param); ActiveParameter::at_token( sema, - token.clone(), + token.clone(), ).map(|ap| { let name = ap.ident().map(NameOrNameRef::Name); (Some(ap.ty), name) 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 51d28bd4ff98c..41f0db3c52823 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 @@ -90,6 +90,20 @@ fn bar(x: u32) {} "#, expect![[r#"ty: u32, name: x"#]], ); + check_expected_type_and_name( + r#" +fn foo() { bar(, $0); } +fn bar(x: u32, y: i32) {} +"#, + expect![[r#"ty: i32, name: y"#]], + ); + check_expected_type_and_name( + r#" +fn foo() { bar(, c$0); } +fn bar(x: u32, y: i32) {} +"#, + expect![[r#"ty: i32, name: y"#]], + ); } #[test] diff --git a/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs index 4fb7d142ed5f1..f5a5b76c336ad 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs @@ -5,7 +5,7 @@ use hir::{InFile, Semantics, Type}; use parser::T; use span::TextSize; use syntax::{ - AstNode, NodeOrToken, SyntaxToken, + AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, ast::{self, AstChildren, HasArgList, HasAttrs, HasName}, match_ast, }; @@ -102,8 +102,7 @@ pub fn callable_for_node<'db>( arg_list .syntax() .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .filter(|t| t.kind() == T![,]) + .filter_map(into_comma) .take_while(|t| t.text_range().start() <= offset) .count() }); @@ -162,8 +161,7 @@ pub fn generic_def_for_node( let active_param = generic_arg_list .syntax() .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .filter(|t| t.kind() == T![,]) + .filter_map(into_comma) .take_while(|t| t.text_range().start() <= token.text_range().start()) .count(); @@ -174,3 +172,12 @@ pub fn generic_def_for_node( Some((def, active_param, first_arg_is_non_lifetime, variant)) } + +fn into_comma(it: NodeOrToken) -> Option { + let token = match it { + NodeOrToken::Token(it) => it, + NodeOrToken::Node(node) if node.kind() == SyntaxKind::ERROR => node.first_token()?, + NodeOrToken::Node(_) => return None, + }; + (token.kind() == T![,]).then_some(token) +} From 416d88b31bcb01328bbd71a5829f0b8b08f2be0c Mon Sep 17 00:00:00 2001 From: Colin Finck Date: Wed, 12 Nov 2025 14:15:55 +0100 Subject: [PATCH 18/27] Build releases with static CRT for `-windows-msvc` targets. This increases the binary size of `rust-analyzer.exe` from 42.4 MB to 42.6 MB. Which should be acceptable for eliminating 7 DLL dependencies. --- src/tools/rust-analyzer/xtask/src/dist.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/xtask/src/dist.rs b/src/tools/rust-analyzer/xtask/src/dist.rs index 1b1fb532cae96..57a6a0eae1be8 100644 --- a/src/tools/rust-analyzer/xtask/src/dist.rs +++ b/src/tools/rust-analyzer/xtask/src/dist.rs @@ -134,8 +134,19 @@ fn dist_server( }; let mut cmd = build_command(sh, command, &target_name, features, dev_rel); + let mut rustflags = Vec::new(); + if let Some(profile) = pgo_profile { - cmd = cmd.env("RUSTFLAGS", format!("-Cprofile-use={}", profile.to_str().unwrap())); + rustflags.push(format!("-Cprofile-use={}", profile.to_str().unwrap())); + } + + if target_name.ends_with("-windows-msvc") { + // https://github.com/rust-lang/rust-analyzer/issues/20970 + rustflags.push("-Ctarget-feature=+crt-static".to_owned()); + } + + if !rustflags.is_empty() { + cmd = cmd.env("RUSTFLAGS", rustflags.join(" ")); } cmd.run().context("cannot build Rust Analyzer")?; From b0d1c9a7e7b05b78d19e9907052b9ae9e629650a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 26 Nov 2025 08:01:52 +0100 Subject: [PATCH 19/27] completions: Fix completions disregarding snippet capabilities --- .../ide-completion/src/completions/attribute/cfg.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs index 1350e581337f0..0d36fb7a405b8 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs @@ -59,9 +59,14 @@ pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) { .into_iter() .map(|x| match x { hir::CfgAtom::Flag(key) => (key.as_str(), "".into()), - hir::CfgAtom::KeyValue { key, .. } => { - (key.as_str(), SmolStr::from_iter([key.as_str(), " = $0"])) - } + hir::CfgAtom::KeyValue { key, .. } => ( + key.as_str(), + if ctx.config.snippet_cap.is_some() { + SmolStr::from_iter([key.as_str(), " = $0"]) + } else { + SmolStr::default() + }, + ), }) .chain(CFG_CONDITION.iter().map(|&(k, snip)| (k, SmolStr::new_static(snip)))) .unique_by(|&(s, _)| s) From e5058c437fd56e9496fd3eb4ab5240464c71aaf8 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 26 Nov 2025 08:15:35 +0100 Subject: [PATCH 20/27] minor: Rename proc-macro-srv protocol flags --- .../crates/proc-macro-api/src/process.rs | 4 ++-- .../crates/proc-macro-srv-cli/src/main.rs | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs index 1365245f9846b..7bddb940e2beb 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs @@ -274,9 +274,9 @@ fn mk_child<'a>( #[allow(clippy::disallowed_methods)] let mut cmd = Command::new(path); if matches!(protocol, Protocol::LegacyJson { .. }) { - cmd.args(["--format", "json"]); + cmd.args(["--format", "json-legacy"]); } else { - cmd.args(["--format", "postcard"]); + cmd.args(["--format", "postcard-legacy"]); } for env in extra_env { match env { diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs index b6c38da454754..813ac339a91da 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs @@ -31,7 +31,7 @@ fn main() -> std::io::Result<()> { clap::Arg::new("format") .long("format") .action(clap::ArgAction::Set) - .default_value("json") + .default_value("json-legacy") .value_parser(clap::builder::EnumValueParser::::new()), clap::Arg::new("version") .long("version") @@ -50,25 +50,27 @@ fn main() -> std::io::Result<()> { #[derive(Copy, Clone)] enum ProtocolFormat { - Json, - Postcard, + JsonLegacy, + PostcardLegacy, } impl ValueEnum for ProtocolFormat { fn value_variants<'a>() -> &'a [Self] { - &[ProtocolFormat::Json, ProtocolFormat::Postcard] + &[ProtocolFormat::JsonLegacy, ProtocolFormat::PostcardLegacy] } fn to_possible_value(&self) -> Option { match self { - ProtocolFormat::Json => Some(clap::builder::PossibleValue::new("json")), - ProtocolFormat::Postcard => Some(clap::builder::PossibleValue::new("postcard")), + ProtocolFormat::JsonLegacy => Some(clap::builder::PossibleValue::new("json-legacy")), + ProtocolFormat::PostcardLegacy => { + Some(clap::builder::PossibleValue::new("postcard-legacy")) + } } } fn from_str(input: &str, _ignore_case: bool) -> Result { match input { - "json" => Ok(ProtocolFormat::Json), - "postcard" => Ok(ProtocolFormat::Postcard), + "json-legacy" => Ok(ProtocolFormat::JsonLegacy), + "postcard-legacy" => Ok(ProtocolFormat::PostcardLegacy), _ => Err(format!("unknown protocol format: {input}")), } } From c414fc5beebdd4a9b78bbbb762ef8c5d1fc04c2a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 26 Nov 2025 08:42:12 +0100 Subject: [PATCH 21/27] fix: Do not try to connect via postcard to proc-macro-srv --- src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs | 2 +- .../rust-analyzer/crates/proc-macro-api/src/process.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs index a725b94f04b21..b438efd6dcee0 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs @@ -129,7 +129,7 @@ impl ProcMacroClient { let process = ProcMacroServerProcess::run( process_path, env, - process::Protocol::Postcard { mode: SpanMode::Id }, + process::Protocol::LegacyJson { mode: SpanMode::Id }, )?; Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() }) } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs index 7bddb940e2beb..9717aeff15967 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs @@ -30,8 +30,13 @@ pub(crate) struct ProcMacroServerProcess { #[derive(Debug, Clone)] pub(crate) enum Protocol { - LegacyJson { mode: SpanMode }, - Postcard { mode: SpanMode }, + LegacyJson { + mode: SpanMode, + }, + #[expected(dead_code)] + Postcard { + mode: SpanMode, + }, } /// Maintains the state of the proc-macro server process. From 6eb6936b5be034df7208087ec018a83be9ebf1a8 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 26 Nov 2025 14:56:46 +0530 Subject: [PATCH 22/27] return Some(T) and not T in read method Message trait --- .../crates/proc-macro-api/src/legacy_protocol/msg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs index 1c77863aac344..b0e80dedcde62 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg.rs @@ -152,7 +152,7 @@ pub trait Message: serde::Serialize + DeserializeOwned { fn read(inp: &mut R, buf: &mut C::Buf) -> io::Result> { Ok(match C::read(inp, buf)? { None => None, - Some(buf) => C::decode(buf)?, + Some(buf) => Some(C::decode(buf)?), }) } fn write(self, out: &mut W) -> io::Result<()> { From c076ef3c3b2cb52f0f86e496c59d2c45398e249b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 26 Nov 2025 09:02:44 +0100 Subject: [PATCH 23/27] Revert to spawning proc-macro-srv without format flag --- .../crates/proc-macro-api/src/lib.rs | 8 +- .../crates/proc-macro-api/src/process.rs | 107 ++++++------------ .../proc-macro-srv-cli/src/main_loop.rs | 4 +- 3 files changed, 38 insertions(+), 81 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs index b438efd6dcee0..7e823444d19d8 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs @@ -22,7 +22,7 @@ use span::{ErasedFileAstId, FIXUP_ERASED_FILE_AST_ID_MARKER, Span}; use std::{fmt, io, sync::Arc, time::SystemTime}; pub use crate::codec::Codec; -use crate::{legacy_protocol::SpanMode, process::ProcMacroServerProcess}; +use crate::process::ProcMacroServerProcess; /// The versions of the server protocol pub mod version { @@ -126,11 +126,7 @@ impl ProcMacroClient { Item = (impl AsRef, &'a Option>), > + Clone, ) -> io::Result { - let process = ProcMacroServerProcess::run( - process_path, - env, - process::Protocol::LegacyJson { mode: SpanMode::Id }, - )?; + let process = ProcMacroServerProcess::run(process_path, env)?; Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() }) } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs index 9717aeff15967..06d6c98681f4f 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs @@ -33,7 +33,7 @@ pub(crate) enum Protocol { LegacyJson { mode: SpanMode, }, - #[expected(dead_code)] + #[expect(dead_code)] Postcard { mode: SpanMode, }, @@ -54,82 +54,50 @@ impl ProcMacroServerProcess { env: impl IntoIterator< Item = (impl AsRef, &'a Option>), > + Clone, - protocol: Protocol, ) -> io::Result { - let mut srv = { - let mut process = match Process::run(process_path, env.clone(), &protocol) { - Ok(process) => process, - Err(e) => { - // fallback - if matches!(protocol, Protocol::Postcard { .. }) { - // retry with json - return Self::run( - process_path, - env, - Protocol::LegacyJson { mode: SpanMode::Id }, - ); - } - return Err(e); - } - }; + let create_srv = || { + let mut process = Process::run(process_path, env.clone())?; let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); - ProcMacroServerProcess { + io::Result::Ok(ProcMacroServerProcess { state: Mutex::new(ProcessSrvState { process, stdin, stdout }), version: 0, - protocol: protocol.clone(), + protocol: Protocol::LegacyJson { mode: SpanMode::Id }, exited: OnceLock::new(), - } + }) }; + let mut srv = create_srv()?; tracing::info!("sending proc-macro server version check"); - let version = match srv.version_check() { - Ok(v) => v, - Err(e) => { - if matches!(protocol, Protocol::Postcard { .. }) { - // retry with json - return Self::run( - process_path, - env, - Protocol::LegacyJson { mode: SpanMode::Id }, - ); + match srv.version_check() { + Ok(v) if v > version::CURRENT_API_VERSION => { + #[allow(clippy::disallowed_methods)] + let process_version = Command::new(process_path) + .arg("--version") + .output() + .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_owned()) + .unwrap_or_else(|_| "unknown version".to_owned()); + Err(io::Error::other(format!( + "Your installed proc-macro server is too new for your rust-analyzer. API version: {}, server version: {process_version}. \ + This will prevent proc-macro expansion from working. Please consider updating your rust-analyzer to ensure compatibility with your current toolchain.", + version::CURRENT_API_VERSION + ))) + } + Ok(v) => { + tracing::info!("Proc-macro server version: {v}"); + srv.version = v; + if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT + && let Ok(mode) = srv.enable_rust_analyzer_spans() + { + srv.protocol = Protocol::LegacyJson { mode }; } - + tracing::info!("Proc-macro server protocol: {:?}", srv.protocol); + Ok(srv) + } + Err(e) => { tracing::info!(%e, "proc-macro version check failed"); - return Err(io::Error::other(format!( - "proc-macro server version check failed: {e}" - ))); + Err(io::Error::other(format!("proc-macro server version check failed: {e}"))) } - }; - - if version > version::CURRENT_API_VERSION { - #[allow(clippy::disallowed_methods)] - let process_version = Command::new(process_path) - .arg("--version") - .output() - .map(|out| String::from_utf8_lossy(&out.stdout).trim().to_owned()) - .unwrap_or_else(|_| "unknown version".to_owned()); - - return Err(io::Error::other(format!( - "Your installed proc-macro server is too new for your rust-analyzer. API version: {}, server version: {process_version}. \ - This will prevent proc-macro expansion from working. Please consider updating your rust-analyzer to ensure compatibility with your current toolchain.", - version::CURRENT_API_VERSION - ))); - } - - tracing::info!("proc-macro server version: {version}"); - - srv.version = version; - - if version >= version::RUST_ANALYZER_SPAN_SUPPORT - && let Ok(new_mode) = srv.enable_rust_analyzer_spans() - { - match &mut srv.protocol { - Protocol::Postcard { mode } | Protocol::LegacyJson { mode } => *mode = new_mode, - }; } - - tracing::info!("proc-macro server protocol: {:?}", srv.protocol); - Ok(srv) } /// Returns the server error if the process has exited. @@ -252,9 +220,8 @@ impl Process { env: impl IntoIterator< Item = (impl AsRef, &'a Option>), >, - protocol: &Protocol, ) -> io::Result { - let child = JodChild(mk_child(path, env, protocol)?); + let child = JodChild(mk_child(path, env)?); Ok(Process { child }) } @@ -274,15 +241,9 @@ fn mk_child<'a>( extra_env: impl IntoIterator< Item = (impl AsRef, &'a Option>), >, - protocol: &Protocol, ) -> io::Result { #[allow(clippy::disallowed_methods)] let mut cmd = Command::new(path); - if matches!(protocol, Protocol::LegacyJson { .. }) { - cmd.args(["--format", "json-legacy"]); - } else { - cmd.args(["--format", "postcard-legacy"]); - } for env in extra_env { match env { (key, Some(val)) => cmd.env(key, val), diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs index 029ab6eca9413..e4456d2e971e4 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -37,8 +37,8 @@ impl SpanTransformer for SpanTrans { pub(crate) fn run(format: ProtocolFormat) -> io::Result<()> { match format { - ProtocolFormat::Json => run_::(), - ProtocolFormat::Postcard => run_::(), + ProtocolFormat::JsonLegacy => run_::(), + ProtocolFormat::PostcardLegacy => run_::(), } } From c18ec026c7afc297d2054c1a7045e1df4d8a9a28 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 26 Nov 2025 17:30:45 +0100 Subject: [PATCH 24/27] Gate spawning proc-macro-srv with --format on toolchain version --- src/tools/rust-analyzer/Cargo.lock | 1 + .../crates/load-cargo/src/lib.rs | 9 +- .../crates/proc-macro-api/Cargo.toml | 1 + .../crates/proc-macro-api/src/lib.rs | 4 +- .../crates/proc-macro-api/src/process.rs | 128 +++++++++++------- .../crates/rust-analyzer/src/reload.rs | 2 +- 6 files changed, 89 insertions(+), 56 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 2a0d56d72c4e3..fe839f2a70443 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1838,6 +1838,7 @@ dependencies = [ "postcard", "proc-macro-srv", "rustc-hash 2.1.1", + "semver", "serde", "serde_derive", "serde_json", 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 ad838a6550eca..a486219efa209 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -96,12 +96,13 @@ pub fn load_workspace_into_db( tracing::debug!(?load_config, "LoadCargoConfig"); let proc_macro_server = match &load_config.with_proc_macro_server { ProcMacroServerChoice::Sysroot => ws.find_sysroot_proc_macro_srv().map(|it| { - it.and_then(|it| ProcMacroClient::spawn(&it, extra_env).map_err(Into::into)).map_err( - |e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str()), - ) + it.and_then(|it| { + ProcMacroClient::spawn(&it, extra_env, ws.toolchain.as_ref()).map_err(Into::into) + }) + .map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str())) }), ProcMacroServerChoice::Explicit(path) => { - Some(ProcMacroClient::spawn(path, extra_env).map_err(|e| { + Some(ProcMacroClient::spawn(path, extra_env, ws.toolchain.as_ref()).map_err(|e| { ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str()) })) } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml index 4077e11b71850..18a2408c4035d 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml @@ -30,6 +30,7 @@ span = { path = "../span", version = "0.0.0", default-features = false} intern.workspace = true postcard.workspace = true +semver.workspace = true [features] sysroot-abi = ["proc-macro-srv", "proc-macro-srv/sysroot-abi"] diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs index 7e823444d19d8..f0c7ce7efd1c9 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs @@ -18,6 +18,7 @@ pub mod legacy_protocol; mod process; use paths::{AbsPath, AbsPathBuf}; +use semver::Version; use span::{ErasedFileAstId, FIXUP_ERASED_FILE_AST_ID_MARKER, Span}; use std::{fmt, io, sync::Arc, time::SystemTime}; @@ -125,8 +126,9 @@ impl ProcMacroClient { env: impl IntoIterator< Item = (impl AsRef, &'a Option>), > + Clone, + version: Option<&Version>, ) -> io::Result { - let process = ProcMacroServerProcess::run(process_path, env)?; + let process = ProcMacroServerProcess::run(process_path, env, version)?; Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() }) } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs index 06d6c98681f4f..e31ab86bdd2d6 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs @@ -8,6 +8,7 @@ use std::{ }; use paths::AbsPath; +use semver::Version; use stdx::JodChild; use crate::{ @@ -30,13 +31,8 @@ pub(crate) struct ProcMacroServerProcess { #[derive(Debug, Clone)] pub(crate) enum Protocol { - LegacyJson { - mode: SpanMode, - }, - #[expect(dead_code)] - Postcard { - mode: SpanMode, - }, + LegacyJson { mode: SpanMode }, + LegacyPostcard { mode: SpanMode }, } /// Maintains the state of the proc-macro server process. @@ -54,50 +50,76 @@ impl ProcMacroServerProcess { env: impl IntoIterator< Item = (impl AsRef, &'a Option>), > + Clone, + version: Option<&Version>, ) -> io::Result { - let create_srv = || { - let mut process = Process::run(process_path, env.clone())?; - let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); + const VERSION: Version = Version::new(1, 93, 0); + // we do `>` for nightly as this started working in the middle of the 1.93 nightly release, so we dont want to break on half of the nightlies + let has_working_format_flag = version.map_or(false, |v| { + if v.pre.as_str() == "nightly" { *v > VERSION } else { *v >= VERSION } + }); - io::Result::Ok(ProcMacroServerProcess { - state: Mutex::new(ProcessSrvState { process, stdin, stdout }), - version: 0, - protocol: Protocol::LegacyJson { mode: SpanMode::Id }, - exited: OnceLock::new(), - }) + let formats: &[_] = if has_working_format_flag { + &[ + (Some("postcard-legacy"), Protocol::LegacyPostcard { mode: SpanMode::Id }), + (Some("json-legacy"), Protocol::LegacyJson { mode: SpanMode::Id }), + ] + } else { + &[(None, Protocol::LegacyJson { mode: SpanMode::Id })] }; - let mut srv = create_srv()?; - tracing::info!("sending proc-macro server version check"); - match srv.version_check() { - Ok(v) if v > version::CURRENT_API_VERSION => { - #[allow(clippy::disallowed_methods)] - let process_version = Command::new(process_path) - .arg("--version") - .output() - .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_owned()) - .unwrap_or_else(|_| "unknown version".to_owned()); - Err(io::Error::other(format!( - "Your installed proc-macro server is too new for your rust-analyzer. API version: {}, server version: {process_version}. \ + + let mut err = None; + for &(format, ref protocol) in formats { + let create_srv = || { + let mut process = Process::run(process_path, env.clone(), format)?; + let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); + + io::Result::Ok(ProcMacroServerProcess { + state: Mutex::new(ProcessSrvState { process, stdin, stdout }), + version: 0, + protocol: protocol.clone(), + exited: OnceLock::new(), + }) + }; + let mut srv = create_srv()?; + tracing::info!("sending proc-macro server version check"); + match srv.version_check() { + Ok(v) if v > version::CURRENT_API_VERSION => { + #[allow(clippy::disallowed_methods)] + let process_version = Command::new(process_path) + .arg("--version") + .output() + .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_owned()) + .unwrap_or_else(|_| "unknown version".to_owned()); + err = Some(io::Error::other(format!( + "Your installed proc-macro server is too new for your rust-analyzer. API version: {}, server version: {process_version}. \ This will prevent proc-macro expansion from working. Please consider updating your rust-analyzer to ensure compatibility with your current toolchain.", - version::CURRENT_API_VERSION - ))) - } - Ok(v) => { - tracing::info!("Proc-macro server version: {v}"); - srv.version = v; - if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT - && let Ok(mode) = srv.enable_rust_analyzer_spans() - { - srv.protocol = Protocol::LegacyJson { mode }; + version::CURRENT_API_VERSION + ))); + } + Ok(v) => { + tracing::info!("Proc-macro server version: {v}"); + srv.version = v; + if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT + && let Ok(new_mode) = srv.enable_rust_analyzer_spans() + { + match &mut srv.protocol { + Protocol::LegacyJson { mode } | Protocol::LegacyPostcard { mode } => { + *mode = new_mode + } + } + } + tracing::info!("Proc-macro server protocol: {:?}", srv.protocol); + return Ok(srv); + } + Err(e) => { + tracing::info!(%e, "proc-macro version check failed"); + err = Some(io::Error::other(format!( + "proc-macro server version check failed: {e}" + ))) } - tracing::info!("Proc-macro server protocol: {:?}", srv.protocol); - Ok(srv) - } - Err(e) => { - tracing::info!(%e, "proc-macro version check failed"); - Err(io::Error::other(format!("proc-macro server version check failed: {e}"))) } } + Err(err.unwrap()) } /// Returns the server error if the process has exited. @@ -106,7 +128,7 @@ impl ProcMacroServerProcess { } pub(crate) fn use_postcard(&self) -> bool { - matches!(self.protocol, Protocol::Postcard { .. }) + matches!(self.protocol, Protocol::LegacyPostcard { .. }) } /// Retrieves the API version of the proc-macro server. @@ -118,7 +140,7 @@ impl ProcMacroServerProcess { pub(crate) fn rust_analyzer_spans(&self) -> bool { match self.protocol { Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer, - Protocol::Postcard { mode } => mode == SpanMode::RustAnalyzer, + Protocol::LegacyPostcard { mode } => mode == SpanMode::RustAnalyzer, } } @@ -126,7 +148,7 @@ impl ProcMacroServerProcess { fn version_check(&self) -> Result { match self.protocol { Protocol::LegacyJson { .. } => legacy_protocol::version_check(self), - Protocol::Postcard { .. } => legacy_protocol::version_check(self), + Protocol::LegacyPostcard { .. } => legacy_protocol::version_check(self), } } @@ -134,7 +156,7 @@ impl ProcMacroServerProcess { fn enable_rust_analyzer_spans(&self) -> Result { match self.protocol { Protocol::LegacyJson { .. } => legacy_protocol::enable_rust_analyzer_spans(self), - Protocol::Postcard { .. } => legacy_protocol::enable_rust_analyzer_spans(self), + Protocol::LegacyPostcard { .. } => legacy_protocol::enable_rust_analyzer_spans(self), } } @@ -145,7 +167,7 @@ impl ProcMacroServerProcess { ) -> Result, String>, ServerError> { match self.protocol { Protocol::LegacyJson { .. } => legacy_protocol::find_proc_macros(self, dylib_path), - Protocol::Postcard { .. } => legacy_protocol::find_proc_macros(self, dylib_path), + Protocol::LegacyPostcard { .. } => legacy_protocol::find_proc_macros(self, dylib_path), } } @@ -220,8 +242,9 @@ impl Process { env: impl IntoIterator< Item = (impl AsRef, &'a Option>), >, + format: Option<&str>, ) -> io::Result { - let child = JodChild(mk_child(path, env)?); + let child = JodChild(mk_child(path, env, format)?); Ok(Process { child }) } @@ -241,6 +264,7 @@ fn mk_child<'a>( extra_env: impl IntoIterator< Item = (impl AsRef, &'a Option>), >, + format: Option<&str>, ) -> io::Result { #[allow(clippy::disallowed_methods)] let mut cmd = Command::new(path); @@ -250,6 +274,10 @@ fn mk_child<'a>( (key, None) => cmd.env_remove(key), }; } + if let Some(format) = format { + cmd.arg("--format"); + cmd.arg(format); + } cmd.env("RUST_ANALYZER_INTERNALS_DO_NOT_USE", "this is unstable") .stdin(Stdio::piped()) .stdout(Stdio::piped()) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index bb971eb13bed6..8876b850be15e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -700,7 +700,7 @@ impl GlobalState { }; info!("Using proc-macro server at {path}"); - Some(ProcMacroClient::spawn(&path, &env).map_err(|err| { + Some(ProcMacroClient::spawn(&path, &env, ws.toolchain.as_ref()).map_err(|err| { tracing::error!( "Failed to run proc-macro server from path {path}, error: {err:?}", ); From 8c5f7a3b7e06e5116992f488e9751c29b549fb4b Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Thu, 27 Nov 2025 04:08:44 +0000 Subject: [PATCH 25/27] Prepare for merging from rust-lang/rust This updates the rust-version file to 1be6b13be73dc12e98e51b403add4c41a0b77759. --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index f545ef4d2b852..bddb68a06b02c 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -6159a44067ebce42b38f062cc7df267a1348e092 +1be6b13be73dc12e98e51b403add4c41a0b77759 From 2578d40f3452909a6ce0d3b41a410e4fbae2c4a3 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 27 Nov 2025 13:50:52 +0100 Subject: [PATCH 26/27] proc-macro-srv: Fix `::fmt` impl producing trailing whitespace --- .../src/legacy_protocol/msg/flat.rs | 21 ++- .../proc-macro-srv-cli/src/main_loop.rs | 19 +- .../proc-macro-test/imp/src/lib.rs | 5 + .../crates/proc-macro-srv/src/lib.rs | 29 +++ .../crates/proc-macro-srv/src/tests/mod.rs | 176 +++++++++++++++++- .../crates/proc-macro-srv/src/token_stream.rs | 31 ++- 6 files changed, 251 insertions(+), 30 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs index 7f19506048de3..d22e3f1899b21 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs @@ -303,6 +303,7 @@ impl FlatTree { pub fn to_tokenstream_unresolved>( self, version: u32, + span_join: impl Fn(T::Span, T::Span) -> T::Span, ) -> proc_macro_srv::TokenStream { Reader:: { subtree: if version >= ENCODE_CLOSE_SPAN_VERSION { @@ -326,13 +327,14 @@ impl FlatTree { span_data_table: &(), version, } - .read_tokenstream() + .read_tokenstream(span_join) } pub fn to_tokenstream_resolved( self, version: u32, span_data_table: &SpanDataIndexMap, + span_join: impl Fn(Span, Span) -> Span, ) -> proc_macro_srv::TokenStream { Reader:: { subtree: if version >= ENCODE_CLOSE_SPAN_VERSION { @@ -356,7 +358,7 @@ impl FlatTree { span_data_table, version, } - .read_tokenstream() + .read_tokenstream(span_join) } } @@ -842,7 +844,10 @@ impl Reader<'_, T> { #[cfg(feature = "sysroot-abi")] impl Reader<'_, T> { - pub(crate) fn read_tokenstream(self) -> proc_macro_srv::TokenStream { + pub(crate) fn read_tokenstream( + self, + span_join: impl Fn(T::Span, T::Span) -> T::Span, + ) -> proc_macro_srv::TokenStream { let mut res: Vec>> = vec![None; self.subtree.len()]; let read_span = |id| T::span_for_token_id(self.span_data_table, id); for i in (0..self.subtree.len()).rev() { @@ -935,6 +940,8 @@ impl Reader<'_, T> { } }) .collect::>(); + let open = read_span(repr.open); + let close = read_span(repr.close); let g = proc_macro_srv::Group { delimiter: match repr.kind { tt::DelimiterKind::Parenthesis => proc_macro_srv::Delimiter::Parenthesis, @@ -944,10 +951,10 @@ impl Reader<'_, T> { }, stream: if stream.is_empty() { None } else { Some(TokenStream::new(stream)) }, span: proc_macro_srv::DelimSpan { - open: read_span(repr.open), - close: read_span(repr.close), - // FIXME - entire: read_span(repr.close), + open, + close, + // FIXME: The protocol does not yet encode entire spans ... + entire: span_join(open, close), }, }; res[i] = Some(g); diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs index e4456d2e971e4..df54f38cbccbe 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -90,10 +90,10 @@ fn run_() -> io::Result<()> { let call_site = SpanId(call_site as u32); let mixed_site = SpanId(mixed_site as u32); - let macro_body = - macro_body.to_tokenstream_unresolved::(CURRENT_API_VERSION); + let macro_body = macro_body + .to_tokenstream_unresolved::(CURRENT_API_VERSION, |_, b| b); let attributes = attributes.map(|it| { - it.to_tokenstream_unresolved::(CURRENT_API_VERSION) + it.to_tokenstream_unresolved::(CURRENT_API_VERSION, |_, b| b) }); srv.expand( @@ -124,10 +124,17 @@ fn run_() -> io::Result<()> { let call_site = span_data_table[call_site]; let mixed_site = span_data_table[mixed_site]; - let macro_body = macro_body - .to_tokenstream_resolved(CURRENT_API_VERSION, &span_data_table); + let macro_body = macro_body.to_tokenstream_resolved( + CURRENT_API_VERSION, + &span_data_table, + |a, b| srv.join_spans(a, b).unwrap_or(b), + ); let attributes = attributes.map(|it| { - it.to_tokenstream_resolved(CURRENT_API_VERSION, &span_data_table) + it.to_tokenstream_resolved( + CURRENT_API_VERSION, + &span_data_table, + |a, b| srv.join_spans(a, b).unwrap_or(b), + ) }); srv.expand( lib, diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs index 2a72e50f911e3..b4fac26d6e72c 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs @@ -94,6 +94,11 @@ pub fn attr_error(args: TokenStream, item: TokenStream) -> TokenStream { format!("compile_error!(\"#[attr_error({})] {}\");", args, item).parse().unwrap() } +#[proc_macro_derive(DeriveReemit, attributes(helper))] +pub fn derive_reemit(item: TokenStream) -> TokenStream { + item +} + #[proc_macro_derive(DeriveEmpty)] pub fn derive_empty(_item: TokenStream) -> TokenStream { TokenStream::default() diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs index aff4dc50378c5..a96cf2bdb22d6 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs @@ -81,6 +81,35 @@ impl<'env> ProcMacroSrv<'env> { temp_dir: TempDir::with_prefix("proc-macro-srv").unwrap(), } } + + pub fn join_spans(&self, first: Span, second: Span) -> Option { + // We can't modify the span range for fixup spans, those are meaningful to fixup, so just + // prefer the non-fixup span. + if first.anchor.ast_id == span::FIXUP_ERASED_FILE_AST_ID_MARKER { + return Some(second); + } + if second.anchor.ast_id == span::FIXUP_ERASED_FILE_AST_ID_MARKER { + return Some(first); + } + // FIXME: Once we can talk back to the client, implement a "long join" request for anchors + // that differ in [AstId]s as joining those spans requires resolving the AstIds. + if first.anchor != second.anchor { + return None; + } + // Differing context, we can't merge these so prefer the one that's root + if first.ctx != second.ctx { + if first.ctx.is_root() { + return Some(second); + } else if second.ctx.is_root() { + return Some(first); + } + } + Some(Span { + range: first.range.cover(second.range), + anchor: second.anchor, + ctx: second.ctx, + }) + } } const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs index 1e2e8da60cde3..4f4fbeae1c60a 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs @@ -52,6 +52,165 @@ fn test_derive_empty() { ); } +#[test] +fn test_derive_reemit_helpers() { + assert_expand( + "DeriveReemit", + r#" +#[helper(build_fn(private, name = "partial_build"))] +pub struct Foo { + /// The domain where this federated instance is running + #[helper(setter(into))] + pub(crate) domain: String, +} +"#, + expect![[r#" + PUNCT 1 # [joint] + GROUP [] 1 1 1 + IDENT 1 helper + GROUP () 1 1 1 + IDENT 1 build_fn + GROUP () 1 1 1 + IDENT 1 private + PUNCT 1 , [alone] + IDENT 1 name + PUNCT 1 = [alone] + LITER 1 Str partial_build + IDENT 1 pub + IDENT 1 struct + IDENT 1 Foo + GROUP {} 1 1 1 + PUNCT 1 # [alone] + GROUP [] 1 1 1 + IDENT 1 doc + PUNCT 1 = [alone] + LITER 1 Str / The domain where this federated instance is running + PUNCT 1 # [joint] + GROUP [] 1 1 1 + IDENT 1 helper + GROUP () 1 1 1 + IDENT 1 setter + GROUP () 1 1 1 + IDENT 1 into + IDENT 1 pub + GROUP () 1 1 1 + IDENT 1 crate + IDENT 1 domain + PUNCT 1 : [alone] + IDENT 1 String + PUNCT 1 , [alone] + + + PUNCT 1 # [joint] + GROUP [] 1 1 1 + IDENT 1 helper + GROUP () 1 1 1 + IDENT 1 build_fn + GROUP () 1 1 1 + IDENT 1 private + PUNCT 1 , [alone] + IDENT 1 name + PUNCT 1 = [alone] + LITER 1 Str partial_build + IDENT 1 pub + IDENT 1 struct + IDENT 1 Foo + GROUP {} 1 1 1 + PUNCT 1 # [alone] + GROUP [] 1 1 1 + IDENT 1 doc + PUNCT 1 = [alone] + LITER 1 Str / The domain where this federated instance is running + PUNCT 1 # [joint] + GROUP [] 1 1 1 + IDENT 1 helper + GROUP () 1 1 1 + IDENT 1 setter + GROUP () 1 1 1 + IDENT 1 into + IDENT 1 pub + GROUP () 1 1 1 + IDENT 1 crate + IDENT 1 domain + PUNCT 1 : [alone] + IDENT 1 String + PUNCT 1 , [alone] + "#]], + expect![[r#" + PUNCT 42:Root[0000, 0]@1..2#ROOT2024 # [joint] + GROUP [] 42:Root[0000, 0]@2..3#ROOT2024 42:Root[0000, 0]@52..53#ROOT2024 42:Root[0000, 0]@2..53#ROOT2024 + IDENT 42:Root[0000, 0]@3..9#ROOT2024 helper + GROUP () 42:Root[0000, 0]@9..10#ROOT2024 42:Root[0000, 0]@51..52#ROOT2024 42:Root[0000, 0]@9..52#ROOT2024 + IDENT 42:Root[0000, 0]@10..18#ROOT2024 build_fn + GROUP () 42:Root[0000, 0]@18..19#ROOT2024 42:Root[0000, 0]@50..51#ROOT2024 42:Root[0000, 0]@18..51#ROOT2024 + IDENT 42:Root[0000, 0]@19..26#ROOT2024 private + PUNCT 42:Root[0000, 0]@26..27#ROOT2024 , [alone] + IDENT 42:Root[0000, 0]@28..32#ROOT2024 name + PUNCT 42:Root[0000, 0]@33..34#ROOT2024 = [alone] + LITER 42:Root[0000, 0]@35..50#ROOT2024 Str partial_build + IDENT 42:Root[0000, 0]@54..57#ROOT2024 pub + IDENT 42:Root[0000, 0]@58..64#ROOT2024 struct + IDENT 42:Root[0000, 0]@65..68#ROOT2024 Foo + GROUP {} 42:Root[0000, 0]@69..70#ROOT2024 42:Root[0000, 0]@190..191#ROOT2024 42:Root[0000, 0]@69..191#ROOT2024 + PUNCT 42:Root[0000, 0]@0..0#ROOT2024 # [alone] + GROUP [] 42:Root[0000, 0]@0..0#ROOT2024 42:Root[0000, 0]@0..0#ROOT2024 42:Root[0000, 0]@0..0#ROOT2024 + IDENT 42:Root[0000, 0]@0..0#ROOT2024 doc + PUNCT 42:Root[0000, 0]@0..0#ROOT2024 = [alone] + LITER 42:Root[0000, 0]@75..130#ROOT2024 Str / The domain where this federated instance is running + PUNCT 42:Root[0000, 0]@135..136#ROOT2024 # [joint] + GROUP [] 42:Root[0000, 0]@136..137#ROOT2024 42:Root[0000, 0]@157..158#ROOT2024 42:Root[0000, 0]@136..158#ROOT2024 + IDENT 42:Root[0000, 0]@137..143#ROOT2024 helper + GROUP () 42:Root[0000, 0]@143..144#ROOT2024 42:Root[0000, 0]@156..157#ROOT2024 42:Root[0000, 0]@143..157#ROOT2024 + IDENT 42:Root[0000, 0]@144..150#ROOT2024 setter + GROUP () 42:Root[0000, 0]@150..151#ROOT2024 42:Root[0000, 0]@155..156#ROOT2024 42:Root[0000, 0]@150..156#ROOT2024 + IDENT 42:Root[0000, 0]@151..155#ROOT2024 into + IDENT 42:Root[0000, 0]@163..166#ROOT2024 pub + GROUP () 42:Root[0000, 0]@166..167#ROOT2024 42:Root[0000, 0]@172..173#ROOT2024 42:Root[0000, 0]@166..173#ROOT2024 + IDENT 42:Root[0000, 0]@167..172#ROOT2024 crate + IDENT 42:Root[0000, 0]@174..180#ROOT2024 domain + PUNCT 42:Root[0000, 0]@180..181#ROOT2024 : [alone] + IDENT 42:Root[0000, 0]@182..188#ROOT2024 String + PUNCT 42:Root[0000, 0]@188..189#ROOT2024 , [alone] + + + PUNCT 42:Root[0000, 0]@1..2#ROOT2024 # [joint] + GROUP [] 42:Root[0000, 0]@2..3#ROOT2024 42:Root[0000, 0]@52..53#ROOT2024 42:Root[0000, 0]@2..53#ROOT2024 + IDENT 42:Root[0000, 0]@3..9#ROOT2024 helper + GROUP () 42:Root[0000, 0]@9..10#ROOT2024 42:Root[0000, 0]@51..52#ROOT2024 42:Root[0000, 0]@9..52#ROOT2024 + IDENT 42:Root[0000, 0]@10..18#ROOT2024 build_fn + GROUP () 42:Root[0000, 0]@18..19#ROOT2024 42:Root[0000, 0]@50..51#ROOT2024 42:Root[0000, 0]@18..51#ROOT2024 + IDENT 42:Root[0000, 0]@19..26#ROOT2024 private + PUNCT 42:Root[0000, 0]@26..27#ROOT2024 , [alone] + IDENT 42:Root[0000, 0]@28..32#ROOT2024 name + PUNCT 42:Root[0000, 0]@33..34#ROOT2024 = [alone] + LITER 42:Root[0000, 0]@35..50#ROOT2024 Str partial_build + IDENT 42:Root[0000, 0]@54..57#ROOT2024 pub + IDENT 42:Root[0000, 0]@58..64#ROOT2024 struct + IDENT 42:Root[0000, 0]@65..68#ROOT2024 Foo + GROUP {} 42:Root[0000, 0]@69..70#ROOT2024 42:Root[0000, 0]@190..191#ROOT2024 42:Root[0000, 0]@69..191#ROOT2024 + PUNCT 42:Root[0000, 0]@0..0#ROOT2024 # [alone] + GROUP [] 42:Root[0000, 0]@0..0#ROOT2024 42:Root[0000, 0]@0..0#ROOT2024 42:Root[0000, 0]@0..0#ROOT2024 + IDENT 42:Root[0000, 0]@0..0#ROOT2024 doc + PUNCT 42:Root[0000, 0]@0..0#ROOT2024 = [alone] + LITER 42:Root[0000, 0]@75..130#ROOT2024 Str / The domain where this federated instance is running + PUNCT 42:Root[0000, 0]@135..136#ROOT2024 # [joint] + GROUP [] 42:Root[0000, 0]@136..137#ROOT2024 42:Root[0000, 0]@157..158#ROOT2024 42:Root[0000, 0]@136..158#ROOT2024 + IDENT 42:Root[0000, 0]@137..143#ROOT2024 helper + GROUP () 42:Root[0000, 0]@143..144#ROOT2024 42:Root[0000, 0]@156..157#ROOT2024 42:Root[0000, 0]@143..157#ROOT2024 + IDENT 42:Root[0000, 0]@144..150#ROOT2024 setter + GROUP () 42:Root[0000, 0]@150..151#ROOT2024 42:Root[0000, 0]@155..156#ROOT2024 42:Root[0000, 0]@150..156#ROOT2024 + IDENT 42:Root[0000, 0]@151..155#ROOT2024 into + IDENT 42:Root[0000, 0]@163..166#ROOT2024 pub + GROUP () 42:Root[0000, 0]@166..167#ROOT2024 42:Root[0000, 0]@172..173#ROOT2024 42:Root[0000, 0]@166..173#ROOT2024 + IDENT 42:Root[0000, 0]@167..172#ROOT2024 crate + IDENT 42:Root[0000, 0]@174..180#ROOT2024 domain + PUNCT 42:Root[0000, 0]@180..181#ROOT2024 : [alone] + IDENT 42:Root[0000, 0]@182..188#ROOT2024 String + PUNCT 42:Root[0000, 0]@188..189#ROOT2024 , [alone] + "#]], + ); +} + #[test] fn test_derive_error() { assert_expand( @@ -69,7 +228,7 @@ fn test_derive_error() { IDENT 1 compile_error PUNCT 1 ! [joint] GROUP () 1 1 1 - LITER 1 Str #[derive(DeriveError)] struct S {field 58 u32 } + LITER 1 Str #[derive(DeriveError)] struct S {field 58 u32} PUNCT 1 ; [alone] "#]], expect![[r#" @@ -83,9 +242,9 @@ fn test_derive_error() { IDENT 42:Root[0000, 0]@0..13#ROOT2024 compile_error PUNCT 42:Root[0000, 0]@13..14#ROOT2024 ! [joint] - GROUP () 42:Root[0000, 0]@14..15#ROOT2024 42:Root[0000, 0]@64..65#ROOT2024 42:Root[0000, 0]@14..65#ROOT2024 - LITER 42:Root[0000, 0]@15..64#ROOT2024 Str #[derive(DeriveError)] struct S {field 58 u32 } - PUNCT 42:Root[0000, 0]@65..66#ROOT2024 ; [alone] + GROUP () 42:Root[0000, 0]@14..15#ROOT2024 42:Root[0000, 0]@63..64#ROOT2024 42:Root[0000, 0]@14..64#ROOT2024 + LITER 42:Root[0000, 0]@15..63#ROOT2024 Str #[derive(DeriveError)] struct S {field 58 u32} + PUNCT 42:Root[0000, 0]@64..65#ROOT2024 ; [alone] "#]], ); } @@ -472,7 +631,7 @@ fn test_attr_macro() { IDENT 1 compile_error PUNCT 1 ! [joint] GROUP () 1 1 1 - LITER 1 Str #[attr_error(some arguments )] mod m {} + LITER 1 Str #[attr_error(some arguments)] mod m {} PUNCT 1 ; [alone] "#]], expect![[r#" @@ -487,9 +646,9 @@ fn test_attr_macro() { IDENT 42:Root[0000, 0]@0..13#ROOT2024 compile_error PUNCT 42:Root[0000, 0]@13..14#ROOT2024 ! [joint] - GROUP () 42:Root[0000, 0]@14..15#ROOT2024 42:Root[0000, 0]@56..57#ROOT2024 42:Root[0000, 0]@14..57#ROOT2024 - LITER 42:Root[0000, 0]@15..56#ROOT2024 Str #[attr_error(some arguments )] mod m {} - PUNCT 42:Root[0000, 0]@57..58#ROOT2024 ; [alone] + GROUP () 42:Root[0000, 0]@14..15#ROOT2024 42:Root[0000, 0]@55..56#ROOT2024 42:Root[0000, 0]@14..56#ROOT2024 + LITER 42:Root[0000, 0]@15..55#ROOT2024 Str #[attr_error(some arguments)] mod m {} + PUNCT 42:Root[0000, 0]@56..57#ROOT2024 ; [alone] "#]], ); } @@ -535,6 +694,7 @@ fn list_test_macros() { attr_noop [Attr] attr_panic [Attr] attr_error [Attr] + DeriveReemit [CustomDerive] DeriveEmpty [CustomDerive] DerivePanic [CustomDerive] DeriveError [CustomDerive]"#]] diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs index 628d6942392c2..931ed12c99ff2 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs @@ -1,7 +1,7 @@ //! The proc-macro server token stream implementation. use core::fmt; -use std::sync::Arc; +use std::{mem, sync::Arc}; use intern::Symbol; use proc_macro::Delimiter; @@ -431,14 +431,22 @@ impl TokenStream { impl fmt::Display for TokenStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut emit_whitespace = false; for tt in self.0.iter() { - display_token_tree(tt, f)?; + display_token_tree(tt, &mut emit_whitespace, f)?; } Ok(()) } } -fn display_token_tree(tt: &TokenTree, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +fn display_token_tree( + tt: &TokenTree, + emit_whitespace: &mut bool, + f: &mut std::fmt::Formatter<'_>, +) -> std::fmt::Result { + if mem::take(emit_whitespace) { + write!(f, " ")?; + } match tt { TokenTree::Group(Group { delimiter, stream, span: _ }) => { write!( @@ -466,13 +474,15 @@ fn display_token_tree(tt: &TokenTree, f: &mut std::fmt::Formatter<'_>) -> )?; } TokenTree::Punct(Punct { ch, joint, span: _ }) => { - write!(f, "{ch}{}", if *joint { "" } else { " " })? + *emit_whitespace = !*joint; + write!(f, "{ch}")?; } TokenTree::Ident(Ident { sym, is_raw, span: _ }) => { if *is_raw { write!(f, "r#")?; } - write!(f, "{sym} ")?; + write!(f, "{sym}")?; + *emit_whitespace = true; } TokenTree::Literal(lit) => { display_fmt_literal(lit, f)?; @@ -485,9 +495,7 @@ fn display_token_tree(tt: &TokenTree, f: &mut std::fmt::Formatter<'_>) -> | LitKind::CStrRaw(_) => true, _ => false, }; - if !joint { - write!(f, " ")?; - } + *emit_whitespace = !joint; } } Ok(()) @@ -739,7 +747,12 @@ mod tests { #[test] fn roundtrip() { let token_stream = TokenStream::from_str("struct T {\"string\"}", ()).unwrap(); - token_stream.to_string(); assert_eq!(token_stream.to_string(), "struct T {\"string\"}"); } + + #[test] + fn ident_ts_no_trailing_whitespace_to_string() { + let token_stream = TokenStream::from_str("this_is_an_ident", ()).unwrap(); + assert_eq!(token_stream.to_string(), "this_is_an_ident"); + } } From 6bbdb1f6ae91b3d7683f20c9905aeb4e34c9edcd Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 27 Nov 2025 16:45:11 +0100 Subject: [PATCH 27/27] proc-macro-srv: Fix `::fmt` impl rendering puncts as u8 --- .../crates/proc-macro-srv/src/tests/mod.rs | 8 ++++---- .../crates/proc-macro-srv/src/token_stream.rs | 16 ++++++---------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs index 4f4fbeae1c60a..ad3d9eef957f2 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs @@ -228,7 +228,7 @@ fn test_derive_error() { IDENT 1 compile_error PUNCT 1 ! [joint] GROUP () 1 1 1 - LITER 1 Str #[derive(DeriveError)] struct S {field 58 u32} + LITER 1 Str #[derive(DeriveError)] struct S {field : u32} PUNCT 1 ; [alone] "#]], expect![[r#" @@ -242,9 +242,9 @@ fn test_derive_error() { IDENT 42:Root[0000, 0]@0..13#ROOT2024 compile_error PUNCT 42:Root[0000, 0]@13..14#ROOT2024 ! [joint] - GROUP () 42:Root[0000, 0]@14..15#ROOT2024 42:Root[0000, 0]@63..64#ROOT2024 42:Root[0000, 0]@14..64#ROOT2024 - LITER 42:Root[0000, 0]@15..63#ROOT2024 Str #[derive(DeriveError)] struct S {field 58 u32} - PUNCT 42:Root[0000, 0]@64..65#ROOT2024 ; [alone] + GROUP () 42:Root[0000, 0]@14..15#ROOT2024 42:Root[0000, 0]@62..63#ROOT2024 42:Root[0000, 0]@14..63#ROOT2024 + LITER 42:Root[0000, 0]@15..62#ROOT2024 Str #[derive(DeriveError)] struct S {field : u32} + PUNCT 42:Root[0000, 0]@63..64#ROOT2024 ; [alone] "#]], ); } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs index 931ed12c99ff2..e134a47f8c97e 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs @@ -475,7 +475,7 @@ fn display_token_tree( } TokenTree::Punct(Punct { ch, joint, span: _ }) => { *emit_whitespace = !*joint; - write!(f, "{ch}")?; + write!(f, "{}", *ch as char)?; } TokenTree::Ident(Ident { sym, is_raw, span: _ }) => { if *is_raw { @@ -745,14 +745,10 @@ mod tests { use super::*; #[test] - fn roundtrip() { - let token_stream = TokenStream::from_str("struct T {\"string\"}", ()).unwrap(); - assert_eq!(token_stream.to_string(), "struct T {\"string\"}"); - } - - #[test] - fn ident_ts_no_trailing_whitespace_to_string() { - let token_stream = TokenStream::from_str("this_is_an_ident", ()).unwrap(); - assert_eq!(token_stream.to_string(), "this_is_an_ident"); + fn ts_to_string() { + let token_stream = + TokenStream::from_str("{} () [] <> ;/., \"gfhdgfuiofghd\" 0f32 r#\"dff\"# 'r#lt", ()) + .unwrap(); + assert_eq!(token_stream.to_string(), "{}()[]<> ;/., \"gfhdgfuiofghd\"0f32 r#\"dff\"#'r#lt"); } }