diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 4de8d09dcab87..fe839f2a70443 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" @@ -1456,6 +1468,7 @@ name = "mbe" version = "0.0.0" dependencies = [ "arrayvec", + "bitflags 2.9.4", "cov-mark", "expect-test", "intern", @@ -1785,6 +1798,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,8 +1835,10 @@ dependencies = [ "indexmap", "intern", "paths", + "postcard", "proc-macro-srv", "rustc-hash 2.1.1", + "semver", "serde", "serde_derive", "serde_json", 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/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/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-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 f44187ec59c15..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, } } @@ -842,9 +841,19 @@ 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 = macro_styles_from_id(db, macro_id); + match 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 a2ce538356515..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(); @@ -2429,12 +2435,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 { 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-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index 02b8ab8cdde6c..15eb355128085 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,12 @@ impl<'db> InferenceResult<'db> { _ => None, }) } + 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() } @@ -1014,6 +1023,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 +1056,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 +1300,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 +1352,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 +} +"#, + ); +} 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/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)) } 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!()} + } +} "#, ); } 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); +} + "#, + ) + } } 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( 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) 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] 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/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index e4076fc55588f..6758e80c3a363 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) @@ -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/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-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( 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 a765fd1278ddd..b8728028bb160 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 @@ -821,6 +821,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( 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) +} 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/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] +} +"#, + ); + } +} 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/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/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, 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..18a2408c4035d 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,8 @@ proc-macro-srv = {workspace = true, optional = true} 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/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 0a72052cc53b4..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 @@ -2,6 +2,7 @@ pub mod json; pub mod msg; +pub mod postcard; use std::{ io::{BufRead, Write}, @@ -13,13 +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::PostcardProtocol, }, process::ProcMacroServerProcess, version, @@ -151,21 +154,25 @@ fn send_task(srv: &ProcMacroServerProcess, req: Request) -> Result, req) + } else { + 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)), })?; 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..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: &str) -> 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 487f50b145e82..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 @@ -8,7 +8,7 @@ use paths::Utf8PathBuf; use serde::de::DeserializeOwned; use serde_derive::{Deserialize, Serialize}; -use crate::ProcMacroKind; +use crate::{ProcMacroKind, codec::Codec}; /// Represents requests sent from the client to the proc-macro-srv. #[derive(Debug, Serialize, Deserialize)] @@ -149,39 +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)?) - } + Some(buf) => Some(C::decode(buf)?), }) } - fn write(self, to_proto: ProtocolWrite, out: &mut W) -> io::Result<()> { - let text = serde_json::to_string(&self)?; - to_proto(out, &text) + 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 String) -> 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<()>; - #[cfg(test)] mod tests { use intern::{Symbol, sym}; 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-api/src/legacy_protocol/postcard.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs new file mode 100644 index 0000000000000..c28a9bfe3a1aa --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs @@ -0,0 +1,40 @@ +//! Postcard encode and decode implementations. + +use std::io::{self, BufRead, Write}; + +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)) + } + + fn write(out: &mut W, buf: &Vec) -> io::Result<()> { + out.write_all(buf)?; + out.flush() + } +} + +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)) + } + + 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 870d81f97684f..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 @@ -12,13 +12,17 @@ )] #![allow(internal_features)] +mod codec; +mod framing; 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}; +pub use crate::codec::Codec; use crate::process::ProcMacroServerProcess; /// The versions of the server protocol @@ -122,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 fe274a027a80f..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::{ @@ -28,9 +29,10 @@ pub(crate) struct ProcMacroServerProcess { exited: OnceLock>, } -#[derive(Debug)] -enum Protocol { +#[derive(Debug, Clone)] +pub(crate) enum Protocol { LegacyJson { mode: SpanMode }, + LegacyPostcard { mode: SpanMode }, } /// Maintains the state of the proc-macro server process. @@ -48,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"); - - io::Result::Ok(ProcMacroServerProcess { - state: Mutex::new(ProcessSrvState { process, stdin, stdout }), - version: 0, - protocol: Protocol::LegacyJson { mode: SpanMode::Id }, - exited: OnceLock::new(), - }) + 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 } + }); + + 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. @@ -99,6 +127,10 @@ impl ProcMacroServerProcess { self.exited.get().map(|it| &it.0) } + pub(crate) fn use_postcard(&self) -> bool { + matches!(self.protocol, Protocol::LegacyPostcard { .. }) + } + /// Retrieves the API version of the proc-macro server. pub(crate) fn version(&self) -> u32 { self.version @@ -108,6 +140,7 @@ impl ProcMacroServerProcess { pub(crate) fn rust_analyzer_spans(&self) -> bool { match self.protocol { Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer, + Protocol::LegacyPostcard { mode } => mode == SpanMode::RustAnalyzer, } } @@ -115,6 +148,7 @@ impl ProcMacroServerProcess { fn version_check(&self) -> Result { match self.protocol { Protocol::LegacyJson { .. } => legacy_protocol::version_check(self), + Protocol::LegacyPostcard { .. } => legacy_protocol::version_check(self), } } @@ -122,6 +156,7 @@ impl ProcMacroServerProcess { fn enable_rust_analyzer_spans(&self) -> Result { match self.protocol { Protocol::LegacyJson { .. } => legacy_protocol::enable_rust_analyzer_spans(self), + Protocol::LegacyPostcard { .. } => legacy_protocol::enable_rust_analyzer_spans(self), } } @@ -132,21 +167,25 @@ impl ProcMacroServerProcess { ) -> Result, String>, ServerError> { match self.protocol { Protocol::LegacyJson { .. } => legacy_protocol::find_proc_macros(self, dylib_path), + Protocol::LegacyPostcard { .. } => legacy_protocol::find_proc_macros(self, dylib_path), } } - 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(|| { @@ -203,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 }) } @@ -224,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); @@ -233,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/proc-macro-srv-cli/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml index dd31e74915bfd..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 @@ -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"] +default = [] 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..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,28 +50,27 @@ fn main() -> std::io::Result<()> { #[derive(Copy, Clone)] enum ProtocolFormat { - Json, - #[cfg(feature = "postcard")] - Postcard, + JsonLegacy, + PostcardLegacy, } impl ValueEnum for ProtocolFormat { fn value_variants<'a>() -> &'a [Self] { - &[ProtocolFormat::Json] + &[ProtocolFormat::JsonLegacy, ProtocolFormat::PostcardLegacy] } 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")), + 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), - #[cfg(feature = "postcard")] - "postcard" => Ok(ProtocolFormat::Postcard), + "json-legacy" => Ok(ProtocolFormat::JsonLegacy), + "postcard-legacy" => Ok(ProtocolFormat::PostcardLegacy), _ => 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..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 @@ -2,19 +2,20 @@ 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::PostcardProtocol, }, version::CURRENT_API_VERSION, }; use proc_macro_srv::{EnvSnapshot, SpanId}; use crate::ProtocolFormat; - struct SpanTrans; impl SpanTransformer for SpanTrans { @@ -36,13 +37,12 @@ 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::JsonLegacy => run_::(), + ProtocolFormat::PostcardLegacy => 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 => { @@ -53,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); @@ -90,10 +90,10 @@ fn run_json() -> 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_json() -> 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..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 @@ -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 : 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]@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] "#]], ); } @@ -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..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 @@ -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 as char)?; } 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(()) @@ -737,9 +745,10 @@ mod tests { use super::*; #[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\"}"); + 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"); } } 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:?}", ); 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 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")?;