From 562b485585a8da7fd0aee010d684a0c4663fafaa Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 7 Jul 2025 22:05:33 +0300 Subject: [PATCH 01/77] Provide a setting to disable showing rename conflicts --- .../crates/ide-assists/src/assist_config.rs | 6 +++ .../src/handlers/remove_underscore.rs | 4 +- .../crates/ide-assists/src/tests.rs | 4 ++ .../rust-analyzer/crates/ide-db/src/rename.rs | 53 ++++++++++++------- .../src/handlers/incorrect_case.rs | 7 ++- .../crates/ide-diagnostics/src/lib.rs | 7 +++ src/tools/rust-analyzer/crates/ide/src/lib.rs | 3 +- .../rust-analyzer/crates/ide/src/rename.rs | 21 ++++++-- .../rust-analyzer/src/cli/analysis_stats.rs | 1 + .../crates/rust-analyzer/src/config.rs | 6 +++ .../rust-analyzer/src/handlers/request.rs | 6 ++- .../src/integrated_benchmarks.rs | 1 + .../docs/book/src/configuration_generated.md | 7 +++ .../rust-analyzer/editors/code/package.json | 10 ++++ 14 files changed, 108 insertions(+), 28 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs b/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs index 7d5070ace6190..636acf3f00887 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs @@ -9,6 +9,7 @@ use ide_db::{ SnippetCap, assists::ExprFillDefaultMode, imports::{import_assets::ImportPathConfig, insert_use::InsertUseConfig}, + rename::RenameConfig, }; use crate::AssistKind; @@ -27,6 +28,7 @@ pub struct AssistConfig { pub code_action_grouping: bool, pub expr_fill_default: ExprFillDefaultMode, pub prefer_self_ty: bool, + pub show_rename_conflicts: bool, } impl AssistConfig { @@ -46,4 +48,8 @@ impl AssistConfig { allow_unstable, } } + + pub fn rename_config(&self) -> RenameConfig { + RenameConfig { show_conflicts: self.show_rename_conflicts } + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_underscore.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_underscore.rs index a8e27416d5ce8..1de1c15cf720b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_underscore.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_underscore.rs @@ -62,7 +62,9 @@ pub(crate) fn remove_underscore(acc: &mut Assists, ctx: &AssistContext<'_>) -> O "Remove underscore from a used variable", text_range, |builder| { - let changes = def.rename(&ctx.sema, new_name, RenameDefinition::Yes).unwrap(); + let changes = def + .rename(&ctx.sema, new_name, RenameDefinition::Yes, &ctx.config.rename_config()) + .unwrap(); builder.source_change = changes; }, ) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs index ade60691b57bc..112619119269e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs @@ -38,6 +38,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { code_action_grouping: true, expr_fill_default: ExprFillDefaultMode::Todo, prefer_self_ty: false, + show_rename_conflicts: true, }; pub(crate) const TEST_CONFIG_NO_GROUPING: AssistConfig = AssistConfig { @@ -59,6 +60,7 @@ pub(crate) const TEST_CONFIG_NO_GROUPING: AssistConfig = AssistConfig { code_action_grouping: false, expr_fill_default: ExprFillDefaultMode::Todo, prefer_self_ty: false, + show_rename_conflicts: true, }; pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { @@ -80,6 +82,7 @@ pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { code_action_grouping: true, expr_fill_default: ExprFillDefaultMode::Todo, prefer_self_ty: false, + show_rename_conflicts: true, }; pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig { @@ -101,6 +104,7 @@ pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig { code_action_grouping: true, expr_fill_default: ExprFillDefaultMode::Todo, prefer_self_ty: false, + show_rename_conflicts: true, }; fn assists( diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs index a8800c142a22e..aeb7c06582bc0 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs @@ -45,6 +45,11 @@ use crate::{ traits::convert_to_def_in_trait, }; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RenameConfig { + pub show_conflicts: bool, +} + pub type Result = std::result::Result; #[derive(Debug)] @@ -81,6 +86,7 @@ impl Definition { sema: &Semantics<'_, RootDatabase>, new_name: &str, rename_definition: RenameDefinition, + config: &RenameConfig, ) -> Result { // self.krate() returns None if // self is a built-in attr, built-in type or tool module. @@ -109,10 +115,15 @@ impl Definition { bail!("Cannot rename a builtin attr.") } Definition::SelfType(_) => bail!("Cannot rename `Self`"), - Definition::Macro(mac) => { - rename_reference(sema, Definition::Macro(mac), new_name, rename_definition, edition) - } - def => rename_reference(sema, def, new_name, rename_definition, edition), + Definition::Macro(mac) => rename_reference( + sema, + Definition::Macro(mac), + new_name, + rename_definition, + edition, + config, + ), + def => rename_reference(sema, def, new_name, rename_definition, edition, config), } } @@ -338,6 +349,7 @@ fn rename_reference( new_name: &str, rename_definition: RenameDefinition, edition: Edition, + config: &RenameConfig, ) -> Result { let (mut new_name, ident_kind) = IdentifierKind::classify(edition, new_name)?; @@ -396,7 +408,8 @@ fn rename_reference( if rename_definition == RenameDefinition::Yes { // This needs to come after the references edits, because we change the annotation of existing edits // if a conflict is detected. - let (file_id, edit) = source_edit_from_def(sema, def, &new_name, &mut source_change)?; + let (file_id, edit) = + source_edit_from_def(sema, config, def, &new_name, &mut source_change)?; source_change.insert_source_edit(file_id, edit); } Ok(source_change) @@ -554,6 +567,7 @@ fn source_edit_from_name_ref( fn source_edit_from_def( sema: &Semantics<'_, RootDatabase>, + config: &RenameConfig, def: Definition, new_name: &Name, source_change: &mut SourceChange, @@ -562,21 +576,22 @@ fn source_edit_from_def( if let Definition::Local(local) = def { let mut file_id = None; - let conflict_annotation = if !sema.rename_conflicts(&local, new_name).is_empty() { - Some( - source_change.insert_annotation(ChangeAnnotation { - label: "This rename will change the program's meaning".to_owned(), - needs_confirmation: true, - description: Some( - "Some variable(s) will shadow the renamed variable \ + let conflict_annotation = + if config.show_conflicts && !sema.rename_conflicts(&local, new_name).is_empty() { + Some( + source_change.insert_annotation(ChangeAnnotation { + label: "This rename will change the program's meaning".to_owned(), + needs_confirmation: true, + description: Some( + "Some variable(s) will shadow the renamed variable \ or be shadowed by it if the rename is performed" - .to_owned(), - ), - }), - ) - } else { - None - }; + .to_owned(), + ), + }), + ) + } else { + None + }; for source in local.sources(sema.db) { let source = match source.source.clone().original_ast_node_rooted(sema.db) { diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs index 519ff192799d6..f52e575133fb1 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -44,7 +44,12 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option RenameConfig { + RenameConfig { show_conflicts: self.show_rename_conflicts } + } } struct DiagnosticsContext<'a> { diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index ece5bac6df4b8..0cb0d33259df8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -846,8 +846,9 @@ impl Analysis { &self, file_id: FileId, new_name_stem: &str, + config: &RenameConfig, ) -> Cancellable> { - self.with_db(|db| rename::will_rename_file(db, file_id, new_name_stem)) + self.with_db(|db| rename::will_rename_file(db, file_id, new_name_stem, config)) } pub fn structural_search_replace( diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs index ce5963919d9a9..59343f494a028 100644 --- a/src/tools/rust-analyzer/crates/ide/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs @@ -31,6 +31,7 @@ pub struct RenameConfig { pub prefer_no_std: bool, pub prefer_prelude: bool, pub prefer_absolute: bool, + pub show_conflicts: bool, } impl RenameConfig { @@ -42,6 +43,10 @@ impl RenameConfig { allow_unstable: true, } } + + fn ide_db_config(&self) -> ide_db::rename::RenameConfig { + ide_db::rename::RenameConfig { show_conflicts: self.show_conflicts } + } } /// This is similar to `collect::, _>>`, but unlike it, it succeeds if there is *any* `Ok` item. @@ -190,7 +195,7 @@ pub(crate) fn rename( return rename_to_self(&sema, local); } } - def.rename(&sema, new_name.as_str(), rename_def) + def.rename(&sema, new_name.as_str(), rename_def, &config.ide_db_config()) })), }; @@ -205,11 +210,13 @@ pub(crate) fn will_rename_file( db: &RootDatabase, file_id: FileId, new_name_stem: &str, + config: &RenameConfig, ) -> Option { let sema = Semantics::new(db); let module = sema.file_to_module_def(file_id)?; let def = Definition::Module(module); - let mut change = def.rename(&sema, new_name_stem, RenameDefinition::Yes).ok()?; + let mut change = + def.rename(&sema, new_name_stem, RenameDefinition::Yes, &config.ide_db_config()).ok()?; change.file_system_edits.clear(); Some(change) } @@ -803,8 +810,12 @@ mod tests { use super::{RangeInfo, RenameConfig, RenameError}; - const TEST_CONFIG: RenameConfig = - RenameConfig { prefer_no_std: false, prefer_prelude: true, prefer_absolute: false }; + const TEST_CONFIG: RenameConfig = RenameConfig { + prefer_no_std: false, + prefer_prelude: true, + prefer_absolute: false, + show_conflicts: true, + }; #[track_caller] fn check( @@ -893,7 +904,7 @@ mod tests { ) { let (analysis, position) = fixture::position(ra_fixture); let source_change = analysis - .will_rename_file(position.file_id, new_name) + .will_rename_file(position.file_id, new_name, &TEST_CONFIG) .unwrap() .expect("Expect returned a RenameError"); expect.assert_eq(&filter_expect(source_change)) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index de24bc09ff0fa..9d3756e641030 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -1153,6 +1153,7 @@ impl flags::AnalysisStats { style_lints: false, term_search_fuel: 400, term_search_borrowck: true, + show_rename_conflicts: true, }, ide::AssistResolveStrategy::All, analysis.editioned_file_id_to_vfs(file_id), diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 6d2907ee56aac..295e453527281 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -707,6 +707,9 @@ config_data! { /// /// E.g. `use ::std::io::Read;`. imports_prefixExternPrelude: bool = false, + + /// Whether to warn when a rename will cause conflicts (change the meaning of the code). + rename_showConflicts: bool = true, } } @@ -1702,6 +1705,7 @@ impl Config { ExprFillDefaultDef::Underscore => ExprFillDefaultMode::Underscore, }, prefer_self_ty: *self.assist_preferSelf(source_root), + show_rename_conflicts: *self.rename_showConflicts(source_root), } } @@ -1710,6 +1714,7 @@ impl Config { prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(), + show_conflicts: *self.rename_showConflicts(source_root), } } @@ -1809,6 +1814,7 @@ impl Config { style_lints: self.diagnostics_styleLints_enable(source_root).to_owned(), term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64, term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(), + show_rename_conflicts: *self.rename_showConflicts(source_root), } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 2976441d762ac..773729be78b31 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -787,7 +787,11 @@ pub(crate) fn handle_will_rename_files( } }) .filter_map(|(file_id, new_name)| { - snap.analysis.will_rename_file(file_id?, &new_name).ok()? + let file_id = file_id?; + let source_root = snap.analysis.source_root_id(file_id).ok(); + snap.analysis + .will_rename_file(file_id, &new_name, &snap.config.rename(source_root)) + .ok()? }) .collect(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs index 38ee9cbe7fc86..c61825b99fecc 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -363,6 +363,7 @@ fn integrated_diagnostics_benchmark() { prefer_absolute: false, term_search_fuel: 400, term_search_borrowck: true, + show_rename_conflicts: true, }; host.analysis() .full_diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id) diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index d768993f501fc..227282f192bdd 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -1303,6 +1303,13 @@ Default: `false` Exclude tests from find-all-references and call-hierarchy. +## rust-analyzer.rename.showConflicts {#rename.showConflicts} + +Default: `true` + +Whether to warn when a rename will cause conflicts (change the meaning of the code). + + ## rust-analyzer.runnables.command {#runnables.command} Default: `null` diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index d659421a0299b..f0414c9622955 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -2769,6 +2769,16 @@ } } }, + { + "title": "Rename", + "properties": { + "rust-analyzer.rename.showConflicts": { + "markdownDescription": "Whether to warn when a rename will cause conflicts (change the meaning of the code).", + "default": true, + "type": "boolean" + } + } + }, { "title": "Runnables", "properties": { From ef4a74134fd0907d5285621c56bf6637f7591922 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 27 Nov 2025 18:56:47 +0530 Subject: [PATCH 02/77] Add bidirectional messaging proc-macro-srv --- src/tools/rust-analyzer/Cargo.lock | 3 + .../hir-def/src/macro_expansion_tests/mod.rs | 3 +- .../crates/hir-expand/src/proc_macro.rs | 4 +- .../crates/load-cargo/src/lib.rs | 6 +- .../crates/proc-macro-api/Cargo.toml | 1 + .../src/bidirectional_protocol.rs | 296 +++++++++++++++++ .../src/bidirectional_protocol/msg.rs | 114 +++++++ .../proc-macro-api/src/legacy_protocol.rs | 23 +- .../proc-macro-api/src/legacy_protocol/msg.rs | 2 +- .../crates/proc-macro-api/src/lib.rs | 11 +- .../crates/proc-macro-api/src/process.rs | 173 +++++++--- .../crates/proc-macro-api/src/transport.rs | 3 + .../src/{ => transport}/codec.rs | 5 +- .../codec}/json.rs | 6 +- .../codec}/postcard.rs | 6 +- .../src/{ => transport}/framing.rs | 4 +- .../crates/proc-macro-srv-cli/Cargo.toml | 1 + .../crates/proc-macro-srv-cli/src/main.rs | 6 + .../proc-macro-srv-cli/src/main_loop.rs | 309 +++++++++++++++++- .../crates/proc-macro-srv/Cargo.toml | 1 + .../crates/proc-macro-srv/src/dylib.rs | 26 ++ .../proc-macro-srv/src/dylib/proc_macros.rs | 77 ++++- .../crates/proc-macro-srv/src/lib.rs | 86 ++++- .../src/server_impl/rust_analyzer_span.rs | 24 +- .../src/server_impl/token_id.rs | 4 + .../crates/test-fixture/src/lib.rs | 10 + 26 files changed, 1112 insertions(+), 92 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs create mode 100644 src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs create mode 100644 src/tools/rust-analyzer/crates/proc-macro-api/src/transport.rs rename src/tools/rust-analyzer/crates/proc-macro-api/src/{ => transport}/codec.rs (76%) rename src/tools/rust-analyzer/crates/proc-macro-api/src/{legacy_protocol => transport/codec}/json.rs (89%) rename src/tools/rust-analyzer/crates/proc-macro-api/src/{legacy_protocol => transport/codec}/postcard.rs (84%) rename src/tools/rust-analyzer/crates/proc-macro-api/src/{ => transport}/framing.rs (63%) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index efe56cb7f61cb..060a62b112b7f 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1836,6 +1836,7 @@ dependencies = [ name = "proc-macro-api" version = "0.0.0" dependencies = [ + "base-db", "indexmap", "intern", "paths", @@ -1856,6 +1857,7 @@ dependencies = [ name = "proc-macro-srv" version = "0.0.0" dependencies = [ + "crossbeam-channel", "expect-test", "intern", "libc", @@ -1874,6 +1876,7 @@ name = "proc-macro-srv-cli" version = "0.0.0" dependencies = [ "clap", + "crossbeam-channel", "postcard", "proc-macro-api", "proc-macro-srv", diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs index 78af976e1b132..07cad9695b612 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -16,7 +16,7 @@ mod proc_macros; use std::{any::TypeId, iter, ops::Range, sync}; -use base_db::RootQueryDb; +use base_db::{RootQueryDb, SourceDatabase}; use expect_test::Expect; use hir_expand::{ AstId, InFile, MacroCallId, MacroCallKind, MacroKind, @@ -374,6 +374,7 @@ struct IdentityWhenValidProcMacroExpander; impl ProcMacroExpander for IdentityWhenValidProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &base_db::Env, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs index f97d721dfa884..d2614aa5f1491 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs @@ -4,7 +4,7 @@ use core::fmt; use std::any::Any; use std::{panic::RefUnwindSafe, sync}; -use base_db::{Crate, CrateBuilderId, CratesIdMap, Env, ProcMacroLoadingError}; +use base_db::{Crate, CrateBuilderId, CratesIdMap, Env, ProcMacroLoadingError, SourceDatabase}; use intern::Symbol; use rustc_hash::FxHashMap; use span::Span; @@ -25,6 +25,7 @@ pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe + Any { /// [`ProcMacroKind::Attr`]), environment variables, and span information. fn expand( &self, + db: &dyn SourceDatabase, subtree: &tt::TopSubtree, attrs: Option<&tt::TopSubtree>, env: &Env, @@ -309,6 +310,7 @@ impl CustomProcMacroExpander { let current_dir = calling_crate.data(db).proc_macro_cwd.to_string(); match proc_macro.expander.expand( + db, tt, attr_arg, env, 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 28fbfecfde802..e043e4ac76344 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -17,7 +17,9 @@ use hir_expand::proc_macro::{ }; use ide_db::{ ChangeWithProcMacros, FxHashMap, RootDatabase, - base_db::{CrateGraphBuilder, Env, ProcMacroLoadingError, SourceRoot, SourceRootId}, + base_db::{ + CrateGraphBuilder, Env, ProcMacroLoadingError, SourceDatabase, SourceRoot, SourceRootId, + }, prime_caches, }; use itertools::Itertools; @@ -522,6 +524,7 @@ struct Expander(proc_macro_api::ProcMacro); impl ProcMacroExpander for Expander { fn expand( &self, + db: &dyn SourceDatabase, subtree: &tt::TopSubtree, attrs: Option<&tt::TopSubtree>, env: &Env, @@ -531,6 +534,7 @@ impl ProcMacroExpander for Expander { current_dir: String, ) -> Result, ProcMacroExpansionError> { match self.0.expand( + db, subtree.view(), attrs.map(|attrs| attrs.view()), env.clone().into(), 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 4de1a3e5dd7d5..7e56d68964ce5 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml @@ -19,6 +19,7 @@ serde_json = { workspace = true, features = ["unbounded_depth"] } tracing.workspace = true rustc-hash.workspace = true indexmap.workspace = true +base-db.workspace = true # local deps paths = { workspace = true, features = ["serde1"] } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs new file mode 100644 index 0000000000000..246f70a10196f --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs @@ -0,0 +1,296 @@ +//! Bidirectional protocol methods + +use std::{ + io::{self, BufRead, Write}, + sync::Arc, +}; + +use base_db::SourceDatabase; +use paths::AbsPath; +use span::{FileId, Span}; + +use crate::{ + Codec, ProcMacro, ProcMacroKind, ServerError, + bidirectional_protocol::msg::{ + Envelope, ExpandMacro, ExpandMacroData, ExpnGlobals, Kind, Payload, Request, RequestId, + Response, SubRequest, SubResponse, + }, + legacy_protocol::{ + SpanMode, + msg::{ + FlatTree, ServerConfig, SpanDataIndexMap, deserialize_span_data_index_map, + serialize_span_data_index_map, + }, + }, + process::ProcMacroServerProcess, + transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, + version, +}; + +pub mod msg; + +pub trait ClientCallbacks { + fn handle_sub_request(&mut self, id: u64, req: SubRequest) -> Result; +} + +pub fn run_conversation( + writer: &mut dyn Write, + reader: &mut dyn BufRead, + buf: &mut C::Buf, + id: RequestId, + initial: Payload, + callbacks: &mut dyn ClientCallbacks, +) -> Result { + let msg = Envelope { id, kind: Kind::Request, payload: initial }; + let encoded = C::encode(&msg).map_err(wrap_encode)?; + C::write(writer, &encoded).map_err(wrap_io("failed to write initial request"))?; + + loop { + let maybe_buf = C::read(reader, buf).map_err(wrap_io("failed to read message"))?; + let Some(b) = maybe_buf else { + return Err(ServerError { + message: "proc-macro server closed the stream".into(), + io: Some(Arc::new(io::Error::new(io::ErrorKind::UnexpectedEof, "closed"))), + }); + }; + + let msg: Envelope = C::decode(b).map_err(wrap_decode)?; + + if msg.id != id { + return Err(ServerError { + message: format!("unexpected message id {}, expected {}", msg.id, id), + io: None, + }); + } + + match (msg.kind, msg.payload) { + (Kind::SubRequest, Payload::SubRequest(sr)) => { + let resp = callbacks.handle_sub_request(id, sr)?; + let reply = + Envelope { id, kind: Kind::SubResponse, payload: Payload::SubResponse(resp) }; + let encoded = C::encode(&reply).map_err(wrap_encode)?; + C::write(writer, &encoded).map_err(wrap_io("failed to write sub-response"))?; + } + (Kind::Response, payload) => { + return Ok(payload); + } + (kind, payload) => { + return Err(ServerError { + message: format!( + "unexpected message kind {:?} with payload {:?}", + kind, payload + ), + io: None, + }); + } + } + } +} + +fn wrap_io(msg: &'static str) -> impl Fn(io::Error) -> ServerError { + move |err| ServerError { message: msg.into(), io: Some(Arc::new(err)) } +} + +fn wrap_encode(err: io::Error) -> ServerError { + ServerError { message: "failed to encode message".into(), io: Some(Arc::new(err)) } +} + +fn wrap_decode(err: io::Error) -> ServerError { + ServerError { message: "failed to decode message".into(), io: Some(Arc::new(err)) } +} + +pub(crate) fn version_check(srv: &ProcMacroServerProcess) -> Result { + let request = Payload::Request(Request::ApiVersionCheck {}); + + struct NoCallbacks; + impl ClientCallbacks for NoCallbacks { + fn handle_sub_request( + &mut self, + _id: u64, + _req: SubRequest, + ) -> Result { + Err(ServerError { message: "sub-request not supported here".into(), io: None }) + } + } + + let mut callbacks = NoCallbacks; + + let response_payload = + run_bidirectional(srv, (0, Kind::Request, request).into(), &mut callbacks)?; + + match response_payload { + Payload::Response(Response::ApiVersionCheck(version)) => Ok(version), + other => { + Err(ServerError { message: format!("unexpected response: {:?}", other), io: None }) + } + } +} + +/// Enable support for rust-analyzer span mode if the server supports it. +pub(crate) fn enable_rust_analyzer_spans( + srv: &ProcMacroServerProcess, +) -> Result { + let request = + Payload::Request(Request::SetConfig(ServerConfig { span_mode: SpanMode::RustAnalyzer })); + + struct NoCallbacks; + impl ClientCallbacks for NoCallbacks { + fn handle_sub_request( + &mut self, + _id: u64, + _req: SubRequest, + ) -> Result { + Err(ServerError { message: "sub-request not supported here".into(), io: None }) + } + } + + let mut callbacks = NoCallbacks; + + let response_payload = + run_bidirectional(srv, (0, Kind::Request, request).into(), &mut callbacks)?; + + match response_payload { + Payload::Response(Response::SetConfig(ServerConfig { span_mode })) => Ok(span_mode), + _ => Err(ServerError { message: "unexpected response".to_owned(), io: None }), + } +} + +/// Finds proc-macros in a given dynamic library. +pub(crate) fn find_proc_macros( + srv: &ProcMacroServerProcess, + dylib_path: &AbsPath, +) -> Result, String>, ServerError> { + let request = + Payload::Request(Request::ListMacros { dylib_path: dylib_path.to_path_buf().into() }); + + struct NoCallbacks; + impl ClientCallbacks for NoCallbacks { + fn handle_sub_request( + &mut self, + _id: u64, + _req: SubRequest, + ) -> Result { + Err(ServerError { message: "sub-request not supported here".into(), io: None }) + } + } + + let mut callbacks = NoCallbacks; + + let response_payload = + run_bidirectional(srv, (0, Kind::Request, request).into(), &mut callbacks)?; + + match response_payload { + Payload::Response(Response::ListMacros(it)) => Ok(it), + _ => Err(ServerError { message: "unexpected response".to_owned(), io: None }), + } +} + +pub(crate) fn expand( + proc_macro: &ProcMacro, + db: &dyn SourceDatabase, + subtree: tt::SubtreeView<'_, Span>, + attr: Option>, + env: Vec<(String, String)>, + def_site: Span, + call_site: Span, + mixed_site: Span, + current_dir: String, +) -> Result>, String>, crate::ServerError> +{ + let version = proc_macro.process.version(); + let mut span_data_table = SpanDataIndexMap::default(); + let def_site = span_data_table.insert_full(def_site).0; + let call_site = span_data_table.insert_full(call_site).0; + let mixed_site = span_data_table.insert_full(mixed_site).0; + let task = Payload::Request(Request::ExpandMacro(Box::new(ExpandMacro { + data: ExpandMacroData { + macro_body: FlatTree::from_subtree(subtree, version, &mut span_data_table), + macro_name: proc_macro.name.to_string(), + attributes: attr + .map(|subtree| FlatTree::from_subtree(subtree, version, &mut span_data_table)), + has_global_spans: ExpnGlobals { + serialize: version >= version::HAS_GLOBAL_SPANS, + def_site, + call_site, + mixed_site, + }, + span_data_table: if proc_macro.process.rust_analyzer_spans() { + serialize_span_data_index_map(&span_data_table) + } else { + Vec::new() + }, + }, + lib: proc_macro.dylib_path.to_path_buf().into(), + env, + current_dir: Some(current_dir), + }))); + + struct Callbacks<'de> { + db: &'de dyn SourceDatabase, + } + impl<'db> ClientCallbacks for Callbacks<'db> { + fn handle_sub_request( + &mut self, + _id: u64, + req: SubRequest, + ) -> Result { + match req { + SubRequest::SourceText { file_id, start, end } => { + let file = FileId::from_raw(file_id); + let text = self.db.file_text(file).text(self.db); + + let slice = text.get(start as usize..end as usize).map(|s| s.to_owned()); + + Ok(SubResponse::SourceTextResult { text: slice }) + } + } + } + } + + let mut callbacks = Callbacks { db }; + + let response_payload = + run_bidirectional(&proc_macro.process, (0, Kind::Request, task).into(), &mut callbacks)?; + + match response_payload { + Payload::Response(Response::ExpandMacro(it)) => Ok(it + .map(|tree| { + let mut expanded = FlatTree::to_subtree_resolved(tree, version, &span_data_table); + if proc_macro.needs_fixup_change() { + proc_macro.change_fixup_to_match_old_server(&mut expanded); + } + expanded + }) + .map_err(|msg| msg.0)), + Payload::Response(Response::ExpandMacroExtended(it)) => Ok(it + .map(|resp| { + let mut expanded = FlatTree::to_subtree_resolved( + resp.tree, + version, + &deserialize_span_data_index_map(&resp.span_data_table), + ); + if proc_macro.needs_fixup_change() { + proc_macro.change_fixup_to_match_old_server(&mut expanded); + } + expanded + }) + .map_err(|msg| msg.0)), + _ => Err(ServerError { message: "unexpected response".to_owned(), io: None }), + } +} + +fn run_bidirectional( + srv: &ProcMacroServerProcess, + msg: Envelope, + callbacks: &mut dyn ClientCallbacks, +) -> Result { + if let Some(server_error) = srv.exited() { + return Err(server_error.clone()); + } + + if srv.use_postcard() { + srv.run_bidirectional::(msg.id, msg.payload, callbacks) + } else { + srv.run_bidirectional::(msg.id, msg.payload, callbacks) + } +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs new file mode 100644 index 0000000000000..7aed3ae1e6072 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs @@ -0,0 +1,114 @@ +//! Bidirectional protocol messages + +use paths::Utf8PathBuf; +use serde::{Deserialize, Serialize}; + +use crate::{ + ProcMacroKind, + legacy_protocol::msg::{FlatTree, Message, PanicMessage, ServerConfig}, +}; + +pub type RequestId = u64; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Envelope { + pub id: RequestId, + pub kind: Kind, + pub payload: Payload, +} + +impl From<(RequestId, Kind, Payload)> for Envelope { + fn from(value: (RequestId, Kind, Payload)) -> Self { + Envelope { id: value.0, kind: value.1, payload: value.2 } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +pub enum Kind { + Request, + Response, + SubRequest, + SubResponse, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum SubRequest { + SourceText { file_id: u32, start: u32, end: u32 }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum SubResponse { + SourceTextResult { text: Option }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Payload { + Request(Request), + Response(Response), + SubRequest(SubRequest), + SubResponse(SubResponse), +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Request { + ListMacros { dylib_path: Utf8PathBuf }, + ExpandMacro(Box), + ApiVersionCheck {}, + SetConfig(ServerConfig), +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Response { + ListMacros(Result, String>), + ExpandMacro(Result), + ApiVersionCheck(u32), + SetConfig(ServerConfig), + ExpandMacroExtended(Result), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ExpandMacro { + pub lib: Utf8PathBuf, + pub env: Vec<(String, String)>, + pub current_dir: Option, + #[serde(flatten)] + pub data: ExpandMacroData, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ExpandMacroExtended { + pub tree: FlatTree, + pub span_data_table: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ExpandMacroData { + pub macro_body: FlatTree, + pub macro_name: String, + pub attributes: Option, + #[serde(skip_serializing_if = "ExpnGlobals::skip_serializing_if")] + #[serde(default)] + pub has_global_spans: ExpnGlobals, + + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] + pub span_data_table: Vec, +} + +#[derive(Clone, Copy, Default, Debug, Serialize, Deserialize)] +pub struct ExpnGlobals { + #[serde(skip_serializing)] + #[serde(default)] + pub serialize: bool, + pub def_site: usize, + pub call_site: usize, + pub mixed_site: usize, +} + +impl ExpnGlobals { + fn skip_serializing_if(&self) -> bool { + !self.serialize + } +} + +impl Message for Envelope {} 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 c2b132ddcc1db..81a9f391812c6 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 @@ -1,30 +1,26 @@ //! The initial proc-macro-srv protocol, soon to be deprecated. -pub mod json; pub mod msg; -pub mod postcard; use std::{ io::{BufRead, Write}, sync::Arc, }; +use base_db::SourceDatabase; use paths::AbsPath; use span::Span; use crate::{ ProcMacro, ProcMacroKind, ServerError, - codec::Codec, - legacy_protocol::{ - 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, + legacy_protocol::msg::{ + ExpandMacro, ExpandMacroData, ExpnGlobals, FlatTree, Message, Request, Response, + ServerConfig, SpanDataIndexMap, deserialize_span_data_index_map, + flat::serialize_span_data_index_map, }, process::ProcMacroServerProcess, + transport::codec::Codec, + transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, version, }; @@ -82,6 +78,7 @@ pub(crate) fn find_proc_macros( pub(crate) fn expand( proc_macro: &ProcMacro, + _db: &dyn SourceDatabase, subtree: tt::SubtreeView<'_, Span>, attr: Option>, env: Vec<(String, String)>, @@ -155,9 +152,9 @@ fn send_task(srv: &ProcMacroServerProcess, req: Request) -> Result, req) + srv.send_task::<_, _, PostcardProtocol>(send_request::, req) } else { - srv.send_task(send_request::, req) + srv.send_task::<_, _, JsonProtocol>(send_request::, req) } } 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 a6e228d977db7..0ebb0e9f93d59 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, codec::Codec}; +use crate::{Codec, ProcMacroKind}; /// Represents requests sent from the client to the proc-macro-srv. #[derive(Debug, Serialize, Deserialize)] 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 85b250eddfd43..7b9b5b39ab1c8 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 @@ -16,18 +16,19 @@ #[cfg(feature = "in-rust-tree")] extern crate rustc_driver as _; -mod codec; -mod framing; +pub mod bidirectional_protocol; pub mod legacy_protocol; mod process; +pub mod transport; +use base_db::SourceDatabase; 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; +pub use crate::transport::codec::Codec; /// The versions of the server protocol pub mod version { @@ -218,6 +219,7 @@ impl ProcMacro { /// This includes span information and environmental context. pub fn expand( &self, + db: &dyn SourceDatabase, subtree: tt::SubtreeView<'_, Span>, attr: Option>, env: Vec<(String, String)>, @@ -240,7 +242,8 @@ impl ProcMacro { } } - legacy_protocol::expand( + self.process.expand( + db, self, subtree, attr, 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 d6a8d27bfc42e..39d954855187d 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 @@ -7,12 +7,18 @@ use std::{ sync::{Arc, Mutex, OnceLock}, }; +use base_db::SourceDatabase; use paths::AbsPath; use semver::Version; +use span::Span; use stdx::JodChild; use crate::{ - ProcMacroKind, ServerError, + Codec, ProcMacro, ProcMacroKind, ServerError, + bidirectional_protocol::{ + self, ClientCallbacks, + msg::{Payload, RequestId}, + }, legacy_protocol::{self, SpanMode}, version, }; @@ -33,6 +39,8 @@ pub(crate) struct ProcMacroServerProcess { pub(crate) enum Protocol { LegacyJson { mode: SpanMode }, LegacyPostcard { mode: SpanMode }, + NewPostcard { mode: SpanMode }, + NewJson { mode: SpanMode }, } /// Maintains the state of the proc-macro server process. @@ -62,6 +70,8 @@ impl ProcMacroServerProcess { && has_working_format_flag { &[ + (Some("postcard-new"), Protocol::NewPostcard { mode: SpanMode::Id }), + (Some("json-new"), Protocol::NewJson { mode: SpanMode::Id }), (Some("postcard-legacy"), Protocol::LegacyPostcard { mode: SpanMode::Id }), (Some("json-legacy"), Protocol::LegacyJson { mode: SpanMode::Id }), ] @@ -105,9 +115,10 @@ impl ProcMacroServerProcess { && let Ok(new_mode) = srv.enable_rust_analyzer_spans() { match &mut srv.protocol { - Protocol::LegacyJson { mode } | Protocol::LegacyPostcard { mode } => { - *mode = new_mode - } + Protocol::LegacyJson { mode } + | Protocol::LegacyPostcard { mode } + | Protocol::NewJson { mode } + | Protocol::NewPostcard { mode } => *mode = new_mode, } } tracing::info!("Proc-macro server protocol: {:?}", srv.protocol); @@ -143,22 +154,32 @@ impl ProcMacroServerProcess { match self.protocol { Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer, Protocol::LegacyPostcard { mode } => mode == SpanMode::RustAnalyzer, + Protocol::NewJson { mode } => mode == SpanMode::RustAnalyzer, + Protocol::NewPostcard { mode } => mode == SpanMode::RustAnalyzer, } } /// Checks the API version of the running proc-macro server. fn version_check(&self) -> Result { match self.protocol { - Protocol::LegacyJson { .. } => legacy_protocol::version_check(self), - Protocol::LegacyPostcard { .. } => legacy_protocol::version_check(self), + Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { + legacy_protocol::version_check(self) + } + Protocol::NewJson { .. } | Protocol::NewPostcard { .. } => { + bidirectional_protocol::version_check(self) + } } } /// Enable support for rust-analyzer span mode if the server supports it. 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), + Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { + legacy_protocol::enable_rust_analyzer_spans(self) + } + Protocol::NewJson { .. } | Protocol::NewPostcard { .. } => { + bidirectional_protocol::enable_rust_analyzer_spans(self) + } } } @@ -168,28 +189,69 @@ impl ProcMacroServerProcess { dylib_path: &AbsPath, ) -> 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), + Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { + legacy_protocol::find_proc_macros(self, dylib_path) + } + Protocol::NewJson { .. } | Protocol::NewPostcard { .. } => { + bidirectional_protocol::find_proc_macros(self, dylib_path) + } + } + } + + pub(crate) fn expand( + &self, + db: &dyn SourceDatabase, + proc_macro: &ProcMacro, + subtree: tt::SubtreeView<'_, Span>, + attr: Option>, + env: Vec<(String, String)>, + def_site: Span, + call_site: Span, + mixed_site: Span, + current_dir: String, + ) -> Result, String>, ServerError> { + match self.protocol { + Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { + legacy_protocol::expand( + proc_macro, + db, + subtree, + attr, + env, + def_site, + call_site, + mixed_site, + current_dir, + ) + } + Protocol::NewJson { .. } | Protocol::NewPostcard { .. } => { + bidirectional_protocol::expand( + proc_macro, + db, + subtree, + attr, + env, + def_site, + call_site, + mixed_site, + current_dir, + ) + } } } - pub(crate) fn send_task( + pub(crate) fn send_task( &self, - serialize_req: impl FnOnce( + send: impl FnOnce( &mut dyn Write, &mut dyn BufRead, Request, - &mut Buf, + &mut C::Buf, ) -> Result, ServerError>, req: Request, - ) -> Result - where - Buf: Default, - { - let state = &mut *self.state.lock().unwrap(); - let mut buf = Buf::default(); - serialize_req(&mut state.stdin, &mut state.stdout, req, &mut buf) - .and_then(|res| { + ) -> Result { + self.with_locked_io::(|writer, reader, buf| { + send(writer, reader, req, buf).and_then(|res| { res.ok_or_else(|| { let message = "proc-macro server did not respond with data".to_owned(); ServerError { @@ -201,33 +263,54 @@ impl ProcMacroServerProcess { } }) }) - .map_err(|e| { - if e.io.as_ref().map(|it| it.kind()) == Some(io::ErrorKind::BrokenPipe) { - match state.process.child.try_wait() { - Ok(None) | Err(_) => e, - Ok(Some(status)) => { - let mut msg = String::new(); - if !status.success() - && let Some(stderr) = state.process.child.stderr.as_mut() - { - _ = stderr.read_to_string(&mut msg); - } - let server_error = ServerError { - message: format!( - "proc-macro server exited with {status}{}{msg}", - if msg.is_empty() { "" } else { ": " } - ), - io: None, - }; - // `AssertUnwindSafe` is fine here, we already correct initialized - // server_error at this point. - self.exited.get_or_init(|| AssertUnwindSafe(server_error)).0.clone() + }) + } + + pub(crate) fn with_locked_io( + &self, + f: impl FnOnce(&mut dyn Write, &mut dyn BufRead, &mut C::Buf) -> Result, + ) -> Result { + let state = &mut *self.state.lock().unwrap(); + let mut buf = C::Buf::default(); + + f(&mut state.stdin, &mut state.stdout, &mut buf).map_err(|e| { + if e.io.as_ref().map(|it| it.kind()) == Some(io::ErrorKind::BrokenPipe) { + match state.process.child.try_wait() { + Ok(None) | Err(_) => e, + Ok(Some(status)) => { + let mut msg = String::new(); + if !status.success() + && let Some(stderr) = state.process.child.stderr.as_mut() + { + _ = stderr.read_to_string(&mut msg); } + let server_error = ServerError { + message: format!( + "proc-macro server exited with {status}{}{msg}", + if msg.is_empty() { "" } else { ": " } + ), + io: None, + }; + self.exited.get_or_init(|| AssertUnwindSafe(server_error)).0.clone() } - } else { - e } - }) + } else { + e + } + }) + } + + pub(crate) fn run_bidirectional( + &self, + id: RequestId, + initial: Payload, + callbacks: &mut dyn ClientCallbacks, + ) -> Result { + self.with_locked_io::(|writer, reader, buf| { + bidirectional_protocol::run_conversation::( + writer, reader, buf, id, initial, callbacks, + ) + }) } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport.rs new file mode 100644 index 0000000000000..b7a1d8f7322c6 --- /dev/null +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport.rs @@ -0,0 +1,3 @@ +//! Contains construct for transport of messages. +pub mod codec; +pub mod framing; diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/codec.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec.rs similarity index 76% rename from src/tools/rust-analyzer/crates/proc-macro-api/src/codec.rs rename to src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec.rs index baccaa6be4c20..c9afad260a56a 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/codec.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec.rs @@ -4,7 +4,10 @@ use std::io; use serde::de::DeserializeOwned; -use crate::framing::Framing; +use crate::transport::framing::Framing; + +pub mod json; +pub mod postcard; pub trait Codec: Framing { fn encode(msg: &T) -> io::Result; 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/transport/codec/json.rs similarity index 89% rename from src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/json.rs rename to src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec/json.rs index 1359c0568402a..96db802e0bfde 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/transport/codec/json.rs @@ -3,14 +3,14 @@ use std::io::{self, BufRead, Write}; use serde::{Serialize, de::DeserializeOwned}; -use crate::{codec::Codec, framing::Framing}; +use crate::{Codec, transport::framing::Framing}; pub struct JsonProtocol; impl Framing for JsonProtocol { type Buf = String; - fn read<'a, R: BufRead>( + fn read<'a, R: BufRead + ?Sized>( inp: &mut R, buf: &'a mut String, ) -> io::Result> { @@ -35,7 +35,7 @@ impl Framing for JsonProtocol { } } - fn write(out: &mut W, buf: &String) -> io::Result<()> { + fn write(out: &mut W, buf: &String) -> io::Result<()> { tracing::debug!("> {}", buf); out.write_all(buf.as_bytes())?; out.write_all(b"\n")?; 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/transport/codec/postcard.rs similarity index 84% rename from src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs rename to src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec/postcard.rs index c28a9bfe3a1aa..6f5319e75b37d 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/postcard.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/codec/postcard.rs @@ -4,14 +4,14 @@ use std::io::{self, BufRead, Write}; use serde::{Serialize, de::DeserializeOwned}; -use crate::{codec::Codec, framing::Framing}; +use crate::{Codec, transport::framing::Framing}; pub struct PostcardProtocol; impl Framing for PostcardProtocol { type Buf = Vec; - fn read<'a, R: BufRead>( + fn read<'a, R: BufRead + ?Sized>( inp: &mut R, buf: &'a mut Vec, ) -> io::Result>> { @@ -23,7 +23,7 @@ impl Framing for PostcardProtocol { Ok(Some(buf)) } - fn write(out: &mut W, buf: &Vec) -> io::Result<()> { + fn write(out: &mut W, buf: &Vec) -> io::Result<()> { out.write_all(buf)?; out.flush() } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/framing.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs similarity index 63% rename from src/tools/rust-analyzer/crates/proc-macro-api/src/framing.rs rename to src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs index a1e6fc05ca110..2a11eb19c3654 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/framing.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs @@ -5,10 +5,10 @@ use std::io::{self, BufRead, Write}; pub trait Framing { type Buf: Default; - fn read<'a, R: BufRead>( + fn read<'a, R: BufRead + ?Sized>( inp: &mut R, buf: &'a mut Self::Buf, ) -> io::Result>; - fn write(out: &mut W, buf: &Self::Buf) -> io::Result<()>; + fn write(out: &mut W, buf: &Self::Buf) -> io::Result<()>; } 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 aa153897fa964..298592ee47632 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 @@ -15,6 +15,7 @@ proc-macro-srv.workspace = true proc-macro-api.workspace = true tt.workspace = true postcard.workspace = true +crossbeam-channel.workspace = true clap = {version = "4.5.42", default-features = false, features = ["std"]} [features] 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 813ac339a91da..d3dae0494fe4b 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 @@ -52,6 +52,8 @@ fn main() -> std::io::Result<()> { enum ProtocolFormat { JsonLegacy, PostcardLegacy, + JsonNew, + PostcardNew, } impl ValueEnum for ProtocolFormat { @@ -65,12 +67,16 @@ impl ValueEnum for ProtocolFormat { ProtocolFormat::PostcardLegacy => { Some(clap::builder::PossibleValue::new("postcard-legacy")) } + ProtocolFormat::JsonNew => Some(clap::builder::PossibleValue::new("json-new")), + ProtocolFormat::PostcardNew => Some(clap::builder::PossibleValue::new("postcard-new")), } } fn from_str(input: &str, _ignore_case: bool) -> Result { match input { "json-legacy" => Ok(ProtocolFormat::JsonLegacy), "postcard-legacy" => Ok(ProtocolFormat::PostcardLegacy), + "postcard-new" => Ok(ProtocolFormat::PostcardNew), + "json-new" => Ok(ProtocolFormat::JsonNew), _ => 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 df54f38cbccbe..e543260964fb7 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -1,16 +1,16 @@ //! The main loop of the proc-macro server. use std::io; +use crossbeam_channel::unbounded; +use proc_macro_api::bidirectional_protocol::msg::Request; use proc_macro_api::{ Codec, - legacy_protocol::{ - json::JsonProtocol, - msg::{ - self, ExpandMacroData, ExpnGlobals, Message, SpanMode, SpanTransformer, - deserialize_span_data_index_map, serialize_span_data_index_map, - }, - postcard::PostcardProtocol, + bidirectional_protocol::msg::{Envelope, Kind, Payload}, + legacy_protocol::msg::{ + self, ExpandMacroData, ExpnGlobals, Message, SpanMode, SpanTransformer, + deserialize_span_data_index_map, serialize_span_data_index_map, }, + transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, version::CURRENT_API_VERSION, }; use proc_macro_srv::{EnvSnapshot, SpanId}; @@ -39,9 +39,280 @@ pub(crate) fn run(format: ProtocolFormat) -> io::Result<()> { match format { ProtocolFormat::JsonLegacy => run_::(), ProtocolFormat::PostcardLegacy => run_::(), + ProtocolFormat::JsonNew => run_new::(), + ProtocolFormat::PostcardNew => run_new::(), } } +fn run_new() -> io::Result<()> { + fn macro_kind_to_api(kind: proc_macro_srv::ProcMacroKind) -> proc_macro_api::ProcMacroKind { + match kind { + proc_macro_srv::ProcMacroKind::CustomDerive => { + proc_macro_api::ProcMacroKind::CustomDerive + } + proc_macro_srv::ProcMacroKind::Bang => proc_macro_api::ProcMacroKind::Bang, + proc_macro_srv::ProcMacroKind::Attr => proc_macro_api::ProcMacroKind::Attr, + } + } + + let mut buf = C::Buf::default(); + let mut stdin = io::stdin().lock(); + let mut stdout = io::stdout().lock(); + + let env_snapshot = EnvSnapshot::default(); + let srv = proc_macro_srv::ProcMacroSrv::new(&env_snapshot); + + let mut span_mode = SpanMode::Id; + + 'outer: loop { + let req_opt = Envelope::read::<_, C>(&mut stdin, &mut buf)?; + let Some(req) = req_opt else { + break 'outer; + }; + + match (req.kind, req.payload) { + (Kind::Request, Payload::Request(request)) => match request { + Request::ListMacros { dylib_path } => { + let res = srv.list_macros(&dylib_path).map(|macros| { + macros + .into_iter() + .map(|(name, kind)| (name, macro_kind_to_api(kind))) + .collect() + }); + + let resp_env = Envelope { + id: req.id, + kind: Kind::Response, + payload: Payload::Response( + proc_macro_api::bidirectional_protocol::msg::Response::ListMacros(res), + ), + }; + + resp_env.write::<_, C>(&mut stdout)?; + } + + Request::ApiVersionCheck {} => { + let resp_env = Envelope { + id: req.id, + kind: Kind::Response, + payload: Payload::Response( + proc_macro_api::bidirectional_protocol::msg::Response::ApiVersionCheck( + CURRENT_API_VERSION, + ), + ), + }; + resp_env.write::<_, C>(&mut stdout)?; + } + + Request::SetConfig(config) => { + span_mode = config.span_mode; + let resp_env = Envelope { + id: req.id, + kind: Kind::Response, + payload: Payload::Response( + proc_macro_api::bidirectional_protocol::msg::Response::SetConfig( + config, + ), + ), + }; + resp_env.write::<_, C>(&mut stdout)?; + } + + Request::ExpandMacro(task) => { + let proc_macro_api::bidirectional_protocol::msg::ExpandMacro { + lib, + env, + current_dir, + data: + proc_macro_api::bidirectional_protocol::msg::ExpandMacroData { + macro_body, + macro_name, + attributes, + has_global_spans: + proc_macro_api::bidirectional_protocol::msg::ExpnGlobals { + serialize: _, + def_site, + call_site, + mixed_site, + }, + span_data_table, + }, + } = *task; + + match span_mode { + SpanMode::Id => { + let def_site = SpanId(def_site as u32); + 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, + |_, b| b, + ); + let attributes = attributes.map(|it| { + it.to_tokenstream_unresolved::( + CURRENT_API_VERSION, + |_, b| b, + ) + }); + + let res = srv + .expand( + lib, + &env, + current_dir, + ¯o_name, + macro_body, + attributes, + def_site, + call_site, + mixed_site, + ) + .map(|it| { + msg::FlatTree::from_tokenstream_raw::( + it, + call_site, + CURRENT_API_VERSION, + ) + }) + .map_err(|e| e.into_string().unwrap_or_default()) + .map_err(msg::PanicMessage); + + let resp_env = Envelope { + id: req.id, + kind: Kind::Response, + payload: Payload::Response(proc_macro_api::bidirectional_protocol::msg::Response::ExpandMacro(res)), + }; + + resp_env.write::<_, C>(&mut stdout)?; + } + + SpanMode::RustAnalyzer => { + let mut span_data_table = + deserialize_span_data_index_map(&span_data_table); + + let def_site_span = span_data_table[def_site]; + let call_site_span = span_data_table[call_site]; + let mixed_site_span = span_data_table[mixed_site]; + + let macro_body_ts = macro_body.to_tokenstream_resolved( + CURRENT_API_VERSION, + &span_data_table, + |a, b| srv.join_spans(a, b).unwrap_or(b), + ); + let attributes_ts = attributes.map(|it| { + it.to_tokenstream_resolved( + CURRENT_API_VERSION, + &span_data_table, + |a, b| srv.join_spans(a, b).unwrap_or(b), + ) + }); + + let (subreq_tx, subreq_rx) = unbounded::(); + let (subresp_tx, subresp_rx) = + unbounded::(); + let (result_tx, result_rx) = crossbeam_channel::bounded(1); + + std::thread::scope(|scope| { + let srv_ref = &srv; + + scope.spawn({ + let lib = lib.clone(); + let env = env.clone(); + let current_dir = current_dir.clone(); + let macro_name = macro_name.clone(); + move || { + let res = srv_ref + .expand_with_channels( + lib, + &env, + current_dir, + ¯o_name, + macro_body_ts, + attributes_ts, + def_site_span, + call_site_span, + mixed_site_span, + subresp_rx, + subreq_tx, + ) + .map(|it| { + ( + msg::FlatTree::from_tokenstream( + it, + CURRENT_API_VERSION, + call_site_span, + &mut span_data_table, + ), + serialize_span_data_index_map(&span_data_table), + ) + }) + .map(|(tree, span_data_table)| { + proc_macro_api::bidirectional_protocol::msg::ExpandMacroExtended { tree, span_data_table } + }) + .map_err(|e| e.into_string().unwrap_or_default()) + .map_err(msg::PanicMessage); + let _ = result_tx.send(res); + } + }); + + loop { + if let Ok(res) = result_rx.try_recv() { + let resp_env = Envelope { + id: req.id, + kind: Kind::Response, + payload: Payload::Response( + proc_macro_api::bidirectional_protocol::msg::Response::ExpandMacroExtended(res), + ), + }; + resp_env.write::<_, C>(&mut stdout).unwrap(); + break; + } + + let subreq = match subreq_rx.recv() { + Ok(r) => r, + Err(_) => { + break; + } + }; + + let sub_env = Envelope { + id: req.id, + kind: Kind::SubRequest, + payload: Payload::SubRequest(from_srv_req(subreq)), + }; + sub_env.write::<_, C>(&mut stdout).unwrap(); + + let resp_opt = + Envelope::read::<_, C>(&mut stdin, &mut buf).unwrap(); + let resp = match resp_opt { + Some(env) => env, + None => { + break; + } + }; + + match (resp.kind, resp.payload) { + (Kind::SubResponse, Payload::SubResponse(subresp)) => { + let _ = subresp_tx.send(from_client_res(subresp)); + } + _ => { + break; + } + } + } + }); + } + } + } + }, + _ => {} + } + } + + Ok(()) +} + fn run_() -> io::Result<()> { fn macro_kind_to_api(kind: proc_macro_srv::ProcMacroKind) -> proc_macro_api::ProcMacroKind { match kind { @@ -178,3 +449,27 @@ fn run_() -> io::Result<()> { Ok(()) } + +fn from_srv_req( + value: proc_macro_srv::SubRequest, +) -> proc_macro_api::bidirectional_protocol::msg::SubRequest { + match value { + proc_macro_srv::SubRequest::SourceText { file_id, start, end } => { + proc_macro_api::bidirectional_protocol::msg::SubRequest::SourceText { + file_id: file_id.file_id().index(), + start, + end, + } + } + } +} + +fn from_client_res( + value: proc_macro_api::bidirectional_protocol::msg::SubResponse, +) -> proc_macro_srv::SubResponse { + match value { + proc_macro_api::bidirectional_protocol::msg::SubResponse::SourceTextResult { text } => { + proc_macro_srv::SubResponse::SourceTextResult { text } + } + } +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml index 361017178409b..b2abda0bfd7f4 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml @@ -22,6 +22,7 @@ paths.workspace = true # span = {workspace = true, default-features = false} does not work span = { path = "../span", version = "0.0.0", default-features = false} intern.workspace = true +crossbeam-channel.workspace = true ra-ap-rustc_lexer.workspace = true diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs index 03433197b779c..ba089c9549d3a 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs @@ -54,6 +54,32 @@ impl Expander { .expand(macro_name, macro_body, attribute, def_site, call_site, mixed_site) } + pub(crate) fn expand_with_channels( + &self, + macro_name: &str, + macro_body: crate::token_stream::TokenStream, + attribute: Option>, + def_site: S, + call_site: S, + mixed_site: S, + cli_to_server: crossbeam_channel::Receiver, + server_to_cli: crossbeam_channel::Sender, + ) -> Result, crate::PanicMessage> + where + ::TokenStream: Default, + { + self.inner.proc_macros.expand_with_channels( + macro_name, + macro_body, + attribute, + def_site, + call_site, + mixed_site, + cli_to_server, + server_to_cli, + ) + } + pub(crate) fn list_macros(&self) -> impl Iterator { self.inner.proc_macros.list_macros() } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs index c879c7609d912..5b6f1cf2f3328 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs @@ -1,5 +1,4 @@ //! Proc macro ABI - use proc_macro::bridge; use crate::{ProcMacroKind, ProcMacroSrvSpan, token_stream::TokenStream}; @@ -32,7 +31,65 @@ impl ProcMacros { { let res = client.run( &bridge::server::SameThread, - S::make_server(call_site, def_site, mixed_site), + S::make_server(call_site, def_site, mixed_site, None, None), + macro_body, + cfg!(debug_assertions), + ); + return res.map_err(crate::PanicMessage::from); + } + bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => { + let res = client.run( + &bridge::server::SameThread, + S::make_server(call_site, def_site, mixed_site, None, None), + macro_body, + cfg!(debug_assertions), + ); + return res.map_err(crate::PanicMessage::from); + } + bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => { + let res = client.run( + &bridge::server::SameThread, + S::make_server(call_site, def_site, mixed_site, None, None), + parsed_attributes, + macro_body, + cfg!(debug_assertions), + ); + return res.map_err(crate::PanicMessage::from); + } + _ => continue, + } + } + + Err(bridge::PanicMessage::String(format!("proc-macro `{macro_name}` is missing")).into()) + } + + pub(crate) fn expand_with_channels( + &self, + macro_name: &str, + macro_body: TokenStream, + attribute: Option>, + def_site: S, + call_site: S, + mixed_site: S, + cli_to_server: crossbeam_channel::Receiver, + server_to_cli: crossbeam_channel::Sender, + ) -> Result, crate::PanicMessage> { + let parsed_attributes = attribute.unwrap_or_default(); + + for proc_macro in &self.0 { + match proc_macro { + bridge::client::ProcMacro::CustomDerive { trait_name, client, .. } + if *trait_name == macro_name => + { + let res = client.run( + &bridge::server::SameThread, + S::make_server( + call_site, + def_site, + mixed_site, + Some(cli_to_server), + Some(server_to_cli), + ), macro_body, cfg!(debug_assertions), ); @@ -41,7 +98,13 @@ impl ProcMacros { bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => { let res = client.run( &bridge::server::SameThread, - S::make_server(call_site, def_site, mixed_site), + S::make_server( + call_site, + def_site, + mixed_site, + Some(cli_to_server), + Some(server_to_cli), + ), macro_body, cfg!(debug_assertions), ); @@ -50,7 +113,13 @@ impl ProcMacros { bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => { let res = client.run( &bridge::server::SameThread, - S::make_server(call_site, def_site, mixed_site), + S::make_server( + call_site, + def_site, + mixed_site, + Some(cli_to_server), + Some(server_to_cli), + ), parsed_attributes, macro_body, cfg!(debug_assertions), 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 93319df824c0e..f369ab93a2a3c 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 @@ -47,7 +47,7 @@ use std::{ }; use paths::{Utf8Path, Utf8PathBuf}; -use span::Span; +use span::{EditionedFileId, Span}; use temp_dir::TempDir; pub use crate::server_impl::token_id::SpanId; @@ -91,6 +91,14 @@ impl<'env> ProcMacroSrv<'env> { } } +pub enum SubRequest { + SourceText { file_id: EditionedFileId, start: u32, end: u32 }, +} + +pub enum SubResponse { + SourceTextResult { text: Option }, +} + const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; impl ProcMacroSrv<'_> { @@ -133,6 +141,53 @@ impl ProcMacroSrv<'_> { result } + pub fn expand_with_channels( + &self, + lib: impl AsRef, + env: &[(String, String)], + current_dir: Option>, + macro_name: &str, + macro_body: token_stream::TokenStream, + attribute: Option>, + def_site: S, + call_site: S, + mixed_site: S, + cli_to_server: crossbeam_channel::Receiver, + server_to_cli: crossbeam_channel::Sender, + ) -> Result, PanicMessage> { + let snapped_env = self.env; + let expander = self.expander(lib.as_ref()).map_err(|err| PanicMessage { + message: Some(format!("failed to load macro: {err}")), + })?; + + let prev_env = EnvChange::apply(snapped_env, env, current_dir.as_ref().map(<_>::as_ref)); + + let result = thread::scope(|s| { + let thread = thread::Builder::new() + .stack_size(EXPANDER_STACK_SIZE) + .name(macro_name.to_owned()) + .spawn_scoped(s, move || { + expander.expand_with_channels( + macro_name, + macro_body, + attribute, + def_site, + call_site, + mixed_site, + cli_to_server, + server_to_cli, + ) + }); + match thread.unwrap().join() { + Ok(res) => res, + Err(e) => std::panic::resume_unwind(e), + } + }); + prev_env.rollback(); + + result + } + pub fn list_macros( &self, dylib_path: &Utf8Path, @@ -170,31 +225,54 @@ impl ProcMacroSrv<'_> { pub trait ProcMacroSrvSpan: Copy + Send + Sync { type Server: proc_macro::bridge::server::Server>; - fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server; + fn make_server( + call_site: Self, + def_site: Self, + mixed_site: Self, + cli_to_server: Option>, + server_to_cli: Option>, + ) -> Self::Server; } impl ProcMacroSrvSpan for SpanId { type Server = server_impl::token_id::SpanIdServer; - fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server { + fn make_server( + call_site: Self, + def_site: Self, + mixed_site: Self, + cli_to_server: Option>, + server_to_cli: Option>, + ) -> Self::Server { Self::Server { call_site, def_site, mixed_site, + cli_to_server, + server_to_cli, tracked_env_vars: Default::default(), tracked_paths: Default::default(), } } } + impl ProcMacroSrvSpan for Span { type Server = server_impl::rust_analyzer_span::RaSpanServer; - fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server { + fn make_server( + call_site: Self, + def_site: Self, + mixed_site: Self, + cli_to_server: Option>, + server_to_cli: Option>, + ) -> Self::Server { Self::Server { call_site, def_site, mixed_site, tracked_env_vars: Default::default(), tracked_paths: Default::default(), + cli_to_server, + server_to_cli, } } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index 7c685c2da734f..1a8f6d6730f74 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -14,6 +14,7 @@ use proc_macro::bridge::server; use span::{FIXUP_ERASED_FILE_AST_ID_MARKER, Span, TextRange, TextSize}; use crate::{ + SubRequest, SubResponse, bridge::{Diagnostic, ExpnGlobals, Literal, TokenTree}, server_impl::literal_from_str, }; @@ -28,6 +29,8 @@ pub struct RaSpanServer { pub call_site: Span, pub def_site: Span, pub mixed_site: Span, + pub cli_to_server: Option>, + pub server_to_cli: Option>, } impl server::Types for RaSpanServer { @@ -149,9 +152,26 @@ impl server::Span for RaSpanServer { /// /// See PR: /// https://github.com/rust-lang/rust/pull/55780 - fn source_text(&mut self, _span: Self::Span) -> Option { + fn source_text(&mut self, span: Self::Span) -> Option { // FIXME requires db, needs special handling wrt fixup spans - None + if self.server_to_cli.is_some() && self.cli_to_server.is_some() { + let file_id = span.anchor.file_id; + let start: u32 = span.range.start().into(); + let end: u32 = span.range.end().into(); + let _ = self.server_to_cli.clone().unwrap().send(SubRequest::SourceText { + file_id, + start, + end, + }); + self.cli_to_server + .clone() + .unwrap() + .recv() + .and_then(|SubResponse::SourceTextResult { text }| Ok(text)) + .expect("REASON") + } else { + None + } } fn parent(&mut self, _span: Self::Span) -> Option { diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs index 5ac263b9d5f64..268042b3bc076 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs @@ -9,6 +9,7 @@ use intern::Symbol; use proc_macro::bridge::server; use crate::{ + SubRequest, SubResponse, bridge::{Diagnostic, ExpnGlobals, Literal, TokenTree}, server_impl::literal_from_str, }; @@ -34,6 +35,8 @@ pub struct SpanIdServer { pub call_site: Span, pub def_site: Span, pub mixed_site: Span, + pub cli_to_server: Option>, + pub server_to_cli: Option>, } impl server::Types for SpanIdServer { @@ -139,6 +142,7 @@ impl server::Span for SpanIdServer { /// See PR: /// https://github.com/rust-lang/rust/pull/55780 fn source_text(&mut self, _span: Self::Span) -> Option { + // FIXME requires db, needs special handling wrt fixup spans None } diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index 5e8b250c24a07..e08a65c392042 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -732,6 +732,7 @@ struct IdentityProcMacroExpander; impl ProcMacroExpander for IdentityProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, @@ -754,6 +755,7 @@ struct Issue18089ProcMacroExpander; impl ProcMacroExpander for Issue18089ProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, @@ -789,6 +791,7 @@ struct AttributeInputReplaceProcMacroExpander; impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, _: &TopSubtree, attrs: Option<&TopSubtree>, _: &Env, @@ -812,6 +815,7 @@ struct Issue18840ProcMacroExpander; impl ProcMacroExpander for Issue18840ProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, fn_: &TopSubtree, _: Option<&TopSubtree>, _: &Env, @@ -847,6 +851,7 @@ struct MirrorProcMacroExpander; impl ProcMacroExpander for MirrorProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, input: &TopSubtree, _: Option<&TopSubtree>, _: &Env, @@ -885,6 +890,7 @@ struct ShortenProcMacroExpander; impl ProcMacroExpander for ShortenProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, input: &TopSubtree, _: Option<&TopSubtree>, _: &Env, @@ -927,6 +933,7 @@ struct Issue17479ProcMacroExpander; impl ProcMacroExpander for Issue17479ProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, @@ -956,6 +963,7 @@ struct Issue18898ProcMacroExpander; impl ProcMacroExpander for Issue18898ProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, @@ -1011,6 +1019,7 @@ struct DisallowCfgProcMacroExpander; impl ProcMacroExpander for DisallowCfgProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, @@ -1042,6 +1051,7 @@ struct GenerateSuffixedTypeProcMacroExpander; impl ProcMacroExpander for GenerateSuffixedTypeProcMacroExpander { fn expand( &self, + _: &dyn SourceDatabase, subtree: &TopSubtree, _attrs: Option<&TopSubtree>, _env: &Env, From 8bcb4db1e52397a7d4c44479759089f706550e17 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 15 Dec 2025 20:02:26 +0530 Subject: [PATCH 03/77] remove salsa dependency check on proc_macro_srv_cli --- src/tools/rust-analyzer/.github/workflows/ci.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index 5975272d871a7..623200aea0321 100644 --- a/src/tools/rust-analyzer/.github/workflows/ci.yaml +++ b/src/tools/rust-analyzer/.github/workflows/ci.yaml @@ -71,9 +71,6 @@ jobs: - name: Test run: cargo test --features sysroot-abi -p proc-macro-srv -p proc-macro-srv-cli -p proc-macro-api -- --quiet - - name: Check salsa dependency - run: "! (cargo tree -p proc-macro-srv-cli | grep -q salsa)" - rust: if: github.repository == 'rust-lang/rust-analyzer' name: Rust From a1f3d726e57e0f8ef12d04f679c0fec198d7fd94 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 15 Dec 2025 20:16:51 +0530 Subject: [PATCH 04/77] make json-new as default protocol choice for proc-macro-srv-cli --- .../rust-analyzer/crates/proc-macro-srv-cli/src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 d3dae0494fe4b..c9134b402dfa8 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-legacy") + .default_value("json-new") .value_parser(clap::builder::EnumValueParser::::new()), clap::Arg::new("version") .long("version") @@ -58,7 +58,12 @@ enum ProtocolFormat { impl ValueEnum for ProtocolFormat { fn value_variants<'a>() -> &'a [Self] { - &[ProtocolFormat::JsonLegacy, ProtocolFormat::PostcardLegacy] + &[ + ProtocolFormat::JsonLegacy, + ProtocolFormat::PostcardLegacy, + ProtocolFormat::JsonNew, + ProtocolFormat::PostcardNew, + ] } fn to_possible_value(&self) -> Option { From 4f57d5be5a021da14dc4efb65ed62fd8870428f8 Mon Sep 17 00:00:00 2001 From: Michael Gruenewald Date: Wed, 17 Dec 2025 14:20:43 +0100 Subject: [PATCH 05/77] Fix LSP configuration request handling --- .../rust-analyzer/editors/code/src/client.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/src/client.ts b/src/tools/rust-analyzer/editors/code/src/client.ts index cb71a01138b3f..5b358e3211fb9 100644 --- a/src/tools/rust-analyzer/editors/code/src/client.ts +++ b/src/tools/rust-analyzer/editors/code/src/client.ts @@ -30,17 +30,24 @@ export async function createClient( }, async configuration( params: lc.ConfigurationParams, - token: vscode.CancellationToken, - next: lc.ConfigurationRequest.HandlerSignature, + _token: vscode.CancellationToken, + _next: lc.ConfigurationRequest.HandlerSignature, ) { - const resp = await next(params, token); - if (resp && Array.isArray(resp)) { - return resp.map((val) => { - return prepareVSCodeConfig(val); - }); - } else { - return resp; + // The rust-analyzer LSP only ever asks for the "rust-analyzer" + // section, so we only need to support that. Instead of letting + // the vscode-languageclient handle it, use the `cfg` property + // in the config. + if ( + params.items.length !== 1 || + params.items[0]?.section !== "rust-analyzer" || + params.items[0]?.scopeUri !== undefined + ) { + return new lc.ResponseError( + lc.ErrorCodes.InvalidParams, + 'Only the "rust-analyzer" config section is supported.', + ); } + return [prepareVSCodeConfig(config.cfg)]; }, }, async handleDiagnostics( From 92890996a32ec2bd310ddfc43c6ccc51e59b3edd Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 18 Dec 2025 10:10:23 +0530 Subject: [PATCH 06/77] add request id to requests --- .../src/bidirectional_protocol.rs | 50 ++++++------------- .../crates/proc-macro-api/src/process.rs | 11 +++- .../src/server_impl/rust_analyzer_span.rs | 9 ++-- 3 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs index 246f70a10196f..728f0f707d715 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs @@ -30,7 +30,7 @@ use crate::{ pub mod msg; pub trait ClientCallbacks { - fn handle_sub_request(&mut self, id: u64, req: SubRequest) -> Result; + fn handle_sub_request(&mut self, req: SubRequest) -> Result; } pub fn run_conversation( @@ -65,7 +65,7 @@ pub fn run_conversation( match (msg.kind, msg.payload) { (Kind::SubRequest, Payload::SubRequest(sr)) => { - let resp = callbacks.handle_sub_request(id, sr)?; + let resp = callbacks.handle_sub_request(sr)?; let reply = Envelope { id, kind: Kind::SubResponse, payload: Payload::SubResponse(resp) }; let encoded = C::encode(&reply).map_err(wrap_encode)?; @@ -104,19 +104,14 @@ pub(crate) fn version_check(srv: &ProcMacroServerProcess) -> Result Result { + fn handle_sub_request(&mut self, _req: SubRequest) -> Result { Err(ServerError { message: "sub-request not supported here".into(), io: None }) } } let mut callbacks = NoCallbacks; - let response_payload = - run_bidirectional(srv, (0, Kind::Request, request).into(), &mut callbacks)?; + let response_payload = run_request(srv, request, &mut callbacks)?; match response_payload { Payload::Response(Response::ApiVersionCheck(version)) => Ok(version), @@ -135,19 +130,14 @@ pub(crate) fn enable_rust_analyzer_spans( struct NoCallbacks; impl ClientCallbacks for NoCallbacks { - fn handle_sub_request( - &mut self, - _id: u64, - _req: SubRequest, - ) -> Result { + fn handle_sub_request(&mut self, _req: SubRequest) -> Result { Err(ServerError { message: "sub-request not supported here".into(), io: None }) } } let mut callbacks = NoCallbacks; - let response_payload = - run_bidirectional(srv, (0, Kind::Request, request).into(), &mut callbacks)?; + let response_payload = run_request(srv, request, &mut callbacks)?; match response_payload { Payload::Response(Response::SetConfig(ServerConfig { span_mode })) => Ok(span_mode), @@ -165,19 +155,14 @@ pub(crate) fn find_proc_macros( struct NoCallbacks; impl ClientCallbacks for NoCallbacks { - fn handle_sub_request( - &mut self, - _id: u64, - _req: SubRequest, - ) -> Result { + fn handle_sub_request(&mut self, _req: SubRequest) -> Result { Err(ServerError { message: "sub-request not supported here".into(), io: None }) } } let mut callbacks = NoCallbacks; - let response_payload = - run_bidirectional(srv, (0, Kind::Request, request).into(), &mut callbacks)?; + let response_payload = run_request(srv, request, &mut callbacks)?; match response_payload { Payload::Response(Response::ListMacros(it)) => Ok(it), @@ -229,11 +214,7 @@ pub(crate) fn expand( db: &'de dyn SourceDatabase, } impl<'db> ClientCallbacks for Callbacks<'db> { - fn handle_sub_request( - &mut self, - _id: u64, - req: SubRequest, - ) -> Result { + fn handle_sub_request(&mut self, req: SubRequest) -> Result { match req { SubRequest::SourceText { file_id, start, end } => { let file = FileId::from_raw(file_id); @@ -249,8 +230,7 @@ pub(crate) fn expand( let mut callbacks = Callbacks { db }; - let response_payload = - run_bidirectional(&proc_macro.process, (0, Kind::Request, task).into(), &mut callbacks)?; + let response_payload = run_request(&proc_macro.process, task, &mut callbacks)?; match response_payload { Payload::Response(Response::ExpandMacro(it)) => Ok(it @@ -279,18 +259,20 @@ pub(crate) fn expand( } } -fn run_bidirectional( +fn run_request( srv: &ProcMacroServerProcess, - msg: Envelope, + msg: Payload, callbacks: &mut dyn ClientCallbacks, ) -> Result { if let Some(server_error) = srv.exited() { return Err(server_error.clone()); } + let id = srv.request_id(); + if srv.use_postcard() { - srv.run_bidirectional::(msg.id, msg.payload, callbacks) + srv.run_bidirectional::(id, msg, callbacks) } else { - srv.run_bidirectional::(msg.id, msg.payload, callbacks) + srv.run_bidirectional::(id, msg, callbacks) } } 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 39d954855187d..723fc928ff66c 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 @@ -4,7 +4,10 @@ use std::{ io::{self, BufRead, BufReader, Read, Write}, panic::AssertUnwindSafe, process::{Child, ChildStdin, ChildStdout, Command, Stdio}, - sync::{Arc, Mutex, OnceLock}, + sync::{ + Arc, Mutex, OnceLock, + atomic::{AtomicU64, Ordering}, + }, }; use base_db::SourceDatabase; @@ -33,6 +36,7 @@ pub(crate) struct ProcMacroServerProcess { protocol: Protocol, /// Populated when the server exits. exited: OnceLock>, + next_request_id: AtomicU64, } #[derive(Debug, Clone)] @@ -90,6 +94,7 @@ impl ProcMacroServerProcess { version: 0, protocol: protocol.clone(), exited: OnceLock::new(), + next_request_id: AtomicU64::new(1), }) }; let mut srv = create_srv()?; @@ -312,6 +317,10 @@ impl ProcMacroServerProcess { ) }) } + + pub(crate) fn request_id(&self) -> RequestId { + self.next_request_id.fetch_add(1, Ordering::Relaxed) + } } /// Manages the execution of the proc-macro server process. diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index 1a8f6d6730f74..cccb74429dd61 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -163,12 +163,9 @@ impl server::Span for RaSpanServer { start, end, }); - self.cli_to_server - .clone() - .unwrap() - .recv() - .and_then(|SubResponse::SourceTextResult { text }| Ok(text)) - .expect("REASON") + match self.cli_to_server.as_ref()?.recv().ok()? { + SubResponse::SourceTextResult { text } => text, + } } else { None } From abaf959d871a1d873f51e1fe9483f0f9efd285b0 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 18 Dec 2025 14:05:45 +0530 Subject: [PATCH 07/77] refactor the main loop in proc_macro-srv-cli --- .../proc-macro-srv-cli/src/main_loop.rs | 458 ++++++++++-------- 1 file changed, 247 insertions(+), 211 deletions(-) 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 e543260964fb7..aec971c776e27 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 @@ -80,236 +80,254 @@ fn run_new() -> io::Result<()> { .collect() }); - let resp_env = Envelope { - id: req.id, - kind: Kind::Response, - payload: Payload::Response( - proc_macro_api::bidirectional_protocol::msg::Response::ListMacros(res), - ), - }; - - resp_env.write::<_, C>(&mut stdout)?; + send_response::<_, C>( + &mut stdout, + req.id, + proc_macro_api::bidirectional_protocol::msg::Response::ListMacros(res), + )?; } Request::ApiVersionCheck {} => { - let resp_env = Envelope { - id: req.id, - kind: Kind::Response, - payload: Payload::Response( - proc_macro_api::bidirectional_protocol::msg::Response::ApiVersionCheck( - CURRENT_API_VERSION, - ), + send_response::<_, C>( + &mut stdout, + req.id, + proc_macro_api::bidirectional_protocol::msg::Response::ApiVersionCheck( + CURRENT_API_VERSION, ), - }; - resp_env.write::<_, C>(&mut stdout)?; + )?; } Request::SetConfig(config) => { span_mode = config.span_mode; - let resp_env = Envelope { - id: req.id, - kind: Kind::Response, - payload: Payload::Response( - proc_macro_api::bidirectional_protocol::msg::Response::SetConfig( - config, - ), - ), - }; - resp_env.write::<_, C>(&mut stdout)?; + send_response::<_, C>( + &mut stdout, + req.id, + proc_macro_api::bidirectional_protocol::msg::Response::SetConfig(config), + )?; } - Request::ExpandMacro(task) => { - let proc_macro_api::bidirectional_protocol::msg::ExpandMacro { + handle_expand::<_, _, C>( + &srv, + &mut stdin, + &mut stdout, + &mut buf, + req.id, + span_mode, + *task, + )?; + } + }, + _ => continue, + } + } + + Ok(()) +} + +fn handle_expand( + srv: &proc_macro_srv::ProcMacroSrv<'_>, + stdin: &mut R, + stdout: &mut W, + buf: &mut C::Buf, + req_id: u64, + span_mode: SpanMode, + task: proc_macro_api::bidirectional_protocol::msg::ExpandMacro, +) -> io::Result<()> { + match span_mode { + SpanMode::Id => handle_expand_id::<_, C>(srv, stdout, req_id, task), + SpanMode::RustAnalyzer => { + handle_expand_ra::<_, _, C>(srv, stdin, stdout, buf, req_id, task) + } + } +} + +fn handle_expand_id( + srv: &proc_macro_srv::ProcMacroSrv<'_>, + stdout: &mut W, + req_id: u64, + task: proc_macro_api::bidirectional_protocol::msg::ExpandMacro, +) -> io::Result<()> { + let proc_macro_api::bidirectional_protocol::msg::ExpandMacro { lib, env, current_dir, data } = + task; + let proc_macro_api::bidirectional_protocol::msg::ExpandMacroData { + macro_body, + macro_name, + attributes, + has_global_spans: + proc_macro_api::bidirectional_protocol::msg::ExpnGlobals { + def_site, + call_site, + mixed_site, + .. + }, + .. + } = data; + + let def_site = SpanId(def_site as u32); + 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, |_, b| b); + let attributes = attributes + .map(|it| it.to_tokenstream_unresolved::(CURRENT_API_VERSION, |_, b| b)); + + let res = srv + .expand( + lib, + &env, + current_dir, + ¯o_name, + macro_body, + attributes, + def_site, + call_site, + mixed_site, + ) + .map(|it| { + msg::FlatTree::from_tokenstream_raw::(it, call_site, CURRENT_API_VERSION) + }) + .map_err(|e| msg::PanicMessage(e.into_string().unwrap_or_default())); + + send_response::<_, C>( + stdout, + req_id, + proc_macro_api::bidirectional_protocol::msg::Response::ExpandMacro(res), + ) +} + +fn handle_expand_ra( + srv: &proc_macro_srv::ProcMacroSrv<'_>, + stdin: &mut R, + stdout: &mut W, + buf: &mut C::Buf, + req_id: u64, + task: proc_macro_api::bidirectional_protocol::msg::ExpandMacro, +) -> io::Result<()> { + let proc_macro_api::bidirectional_protocol::msg::ExpandMacro { + lib, + env, + current_dir, + data: + proc_macro_api::bidirectional_protocol::msg::ExpandMacroData { + macro_body, + macro_name, + attributes, + has_global_spans: + proc_macro_api::bidirectional_protocol::msg::ExpnGlobals { + serialize: _, + def_site, + call_site, + mixed_site, + }, + span_data_table, + }, + } = task; + + let mut span_data_table = deserialize_span_data_index_map(&span_data_table); + + let def_site_span = span_data_table[def_site]; + let call_site_span = span_data_table[call_site]; + let mixed_site_span = span_data_table[mixed_site]; + + let macro_body_ts = + macro_body.to_tokenstream_resolved(CURRENT_API_VERSION, &span_data_table, |a, b| { + srv.join_spans(a, b).unwrap_or(b) + }); + let attributes_ts = attributes.map(|it| { + it.to_tokenstream_resolved(CURRENT_API_VERSION, &span_data_table, |a, b| { + srv.join_spans(a, b).unwrap_or(b) + }) + }); + + let (subreq_tx, subreq_rx) = unbounded::(); + let (subresp_tx, subresp_rx) = unbounded::(); + let (result_tx, result_rx) = crossbeam_channel::bounded(1); + + std::thread::scope(|scope| { + let srv_ref = &srv; + + scope.spawn({ + let lib = lib.clone(); + let env = env.clone(); + let current_dir = current_dir.clone(); + let macro_name = macro_name.clone(); + move || { + let res = srv_ref + .expand_with_channels( lib, - env, + &env, current_dir, - data: - proc_macro_api::bidirectional_protocol::msg::ExpandMacroData { - macro_body, - macro_name, - attributes, - has_global_spans: - proc_macro_api::bidirectional_protocol::msg::ExpnGlobals { - serialize: _, - def_site, - call_site, - mixed_site, - }, - span_data_table, - }, - } = *task; - - match span_mode { - SpanMode::Id => { - let def_site = SpanId(def_site as u32); - let call_site = SpanId(call_site as u32); - let mixed_site = SpanId(mixed_site as u32); - - let macro_body = macro_body.to_tokenstream_unresolved::( + ¯o_name, + macro_body_ts, + attributes_ts, + def_site_span, + call_site_span, + mixed_site_span, + subresp_rx, + subreq_tx, + ) + .map(|it| { + ( + msg::FlatTree::from_tokenstream( + it, CURRENT_API_VERSION, - |_, b| b, - ); - let attributes = attributes.map(|it| { - it.to_tokenstream_unresolved::( - CURRENT_API_VERSION, - |_, b| b, - ) - }); - - let res = srv - .expand( - lib, - &env, - current_dir, - ¯o_name, - macro_body, - attributes, - def_site, - call_site, - mixed_site, - ) - .map(|it| { - msg::FlatTree::from_tokenstream_raw::( - it, - call_site, - CURRENT_API_VERSION, - ) - }) - .map_err(|e| e.into_string().unwrap_or_default()) - .map_err(msg::PanicMessage); - - let resp_env = Envelope { - id: req.id, - kind: Kind::Response, - payload: Payload::Response(proc_macro_api::bidirectional_protocol::msg::Response::ExpandMacro(res)), - }; - - resp_env.write::<_, C>(&mut stdout)?; + call_site_span, + &mut span_data_table, + ), + serialize_span_data_index_map(&span_data_table), + ) + }) + .map(|(tree, span_data_table)| { + proc_macro_api::bidirectional_protocol::msg::ExpandMacroExtended { + tree, + span_data_table, } + }) + .map_err(|e| e.into_string().unwrap_or_default()) + .map_err(msg::PanicMessage); + let _ = result_tx.send(res); + } + }); + + loop { + if let Ok(res) = result_rx.try_recv() { + send_response::<_, C>( + stdout, + req_id, + proc_macro_api::bidirectional_protocol::msg::Response::ExpandMacroExtended(res), + ) + .unwrap(); + break; + } - SpanMode::RustAnalyzer => { - let mut span_data_table = - deserialize_span_data_index_map(&span_data_table); + let subreq = match subreq_rx.recv() { + Ok(r) => r, + Err(_) => { + break; + } + }; - let def_site_span = span_data_table[def_site]; - let call_site_span = span_data_table[call_site]; - let mixed_site_span = span_data_table[mixed_site]; + send_subrequest::<_, C>(stdout, req_id, from_srv_req(subreq)).unwrap(); - let macro_body_ts = macro_body.to_tokenstream_resolved( - CURRENT_API_VERSION, - &span_data_table, - |a, b| srv.join_spans(a, b).unwrap_or(b), - ); - let attributes_ts = attributes.map(|it| { - it.to_tokenstream_resolved( - CURRENT_API_VERSION, - &span_data_table, - |a, b| srv.join_spans(a, b).unwrap_or(b), - ) - }); - - let (subreq_tx, subreq_rx) = unbounded::(); - let (subresp_tx, subresp_rx) = - unbounded::(); - let (result_tx, result_rx) = crossbeam_channel::bounded(1); - - std::thread::scope(|scope| { - let srv_ref = &srv; - - scope.spawn({ - let lib = lib.clone(); - let env = env.clone(); - let current_dir = current_dir.clone(); - let macro_name = macro_name.clone(); - move || { - let res = srv_ref - .expand_with_channels( - lib, - &env, - current_dir, - ¯o_name, - macro_body_ts, - attributes_ts, - def_site_span, - call_site_span, - mixed_site_span, - subresp_rx, - subreq_tx, - ) - .map(|it| { - ( - msg::FlatTree::from_tokenstream( - it, - CURRENT_API_VERSION, - call_site_span, - &mut span_data_table, - ), - serialize_span_data_index_map(&span_data_table), - ) - }) - .map(|(tree, span_data_table)| { - proc_macro_api::bidirectional_protocol::msg::ExpandMacroExtended { tree, span_data_table } - }) - .map_err(|e| e.into_string().unwrap_or_default()) - .map_err(msg::PanicMessage); - let _ = result_tx.send(res); - } - }); - - loop { - if let Ok(res) = result_rx.try_recv() { - let resp_env = Envelope { - id: req.id, - kind: Kind::Response, - payload: Payload::Response( - proc_macro_api::bidirectional_protocol::msg::Response::ExpandMacroExtended(res), - ), - }; - resp_env.write::<_, C>(&mut stdout).unwrap(); - break; - } - - let subreq = match subreq_rx.recv() { - Ok(r) => r, - Err(_) => { - break; - } - }; - - let sub_env = Envelope { - id: req.id, - kind: Kind::SubRequest, - payload: Payload::SubRequest(from_srv_req(subreq)), - }; - sub_env.write::<_, C>(&mut stdout).unwrap(); - - let resp_opt = - Envelope::read::<_, C>(&mut stdin, &mut buf).unwrap(); - let resp = match resp_opt { - Some(env) => env, - None => { - break; - } - }; - - match (resp.kind, resp.payload) { - (Kind::SubResponse, Payload::SubResponse(subresp)) => { - let _ = subresp_tx.send(from_client_res(subresp)); - } - _ => { - break; - } - } - } - }); - } - } + let resp_opt = Envelope::read::<_, C>(stdin, buf).unwrap(); + let resp = match resp_opt { + Some(env) => env, + None => { + break; } - }, - _ => {} - } - } + }; + match (resp.kind, resp.payload) { + (Kind::SubResponse, Payload::SubResponse(subresp)) => { + let _ = subresp_tx.send(from_client_res(subresp)); + } + _ => { + break; + } + } + } + }); Ok(()) } @@ -473,3 +491,21 @@ fn from_client_res( } } } + +fn send_response( + stdout: &mut W, + id: u64, + resp: proc_macro_api::bidirectional_protocol::msg::Response, +) -> io::Result<()> { + let resp = Envelope { id, kind: Kind::Response, payload: Payload::Response(resp) }; + resp.write::(stdout) +} + +fn send_subrequest( + stdout: &mut W, + id: u64, + resp: proc_macro_api::bidirectional_protocol::msg::SubRequest, +) -> io::Result<()> { + let resp = Envelope { id, kind: Kind::SubRequest, payload: Payload::SubRequest(resp) }; + resp.write::(stdout) +} From 5cdad62e1d86212de9900e6eec13615ddab07d6e Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 18 Dec 2025 14:25:15 +0530 Subject: [PATCH 08/77] make imports proper --- .../proc-macro-srv-cli/src/main_loop.rs | 253 +++++++++--------- 1 file changed, 120 insertions(+), 133 deletions(-) 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 aec971c776e27..1ad73d4daac29 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,23 +2,22 @@ use std::io; use crossbeam_channel::unbounded; -use proc_macro_api::bidirectional_protocol::msg::Request; use proc_macro_api::{ Codec, - bidirectional_protocol::msg::{Envelope, Kind, Payload}, - legacy_protocol::msg::{ - self, ExpandMacroData, ExpnGlobals, Message, SpanMode, SpanTransformer, - deserialize_span_data_index_map, serialize_span_data_index_map, - }, + bidirectional_protocol::msg as bidirectional, + legacy_protocol::msg as legacy, transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, version::CURRENT_API_VERSION, }; + +use legacy::Message; + use proc_macro_srv::{EnvSnapshot, SpanId}; use crate::ProtocolFormat; struct SpanTrans; -impl SpanTransformer for SpanTrans { +impl legacy::SpanTransformer for SpanTrans { type Table = (); type Span = SpanId; fn token_id_of( @@ -62,61 +61,61 @@ fn run_new() -> io::Result<()> { let env_snapshot = EnvSnapshot::default(); let srv = proc_macro_srv::ProcMacroSrv::new(&env_snapshot); - let mut span_mode = SpanMode::Id; + let mut span_mode = legacy::SpanMode::Id; 'outer: loop { - let req_opt = Envelope::read::<_, C>(&mut stdin, &mut buf)?; + let req_opt = bidirectional::Envelope::read::<_, C>(&mut stdin, &mut buf)?; let Some(req) = req_opt else { break 'outer; }; match (req.kind, req.payload) { - (Kind::Request, Payload::Request(request)) => match request { - Request::ListMacros { dylib_path } => { - let res = srv.list_macros(&dylib_path).map(|macros| { - macros - .into_iter() - .map(|(name, kind)| (name, macro_kind_to_api(kind))) - .collect() - }); - - send_response::<_, C>( - &mut stdout, - req.id, - proc_macro_api::bidirectional_protocol::msg::Response::ListMacros(res), - )?; - } - - Request::ApiVersionCheck {} => { - send_response::<_, C>( - &mut stdout, - req.id, - proc_macro_api::bidirectional_protocol::msg::Response::ApiVersionCheck( - CURRENT_API_VERSION, - ), - )?; - } + (bidirectional::Kind::Request, bidirectional::Payload::Request(request)) => { + match request { + bidirectional::Request::ListMacros { dylib_path } => { + let res = srv.list_macros(&dylib_path).map(|macros| { + macros + .into_iter() + .map(|(name, kind)| (name, macro_kind_to_api(kind))) + .collect() + }); - Request::SetConfig(config) => { - span_mode = config.span_mode; - send_response::<_, C>( - &mut stdout, - req.id, - proc_macro_api::bidirectional_protocol::msg::Response::SetConfig(config), - )?; - } - Request::ExpandMacro(task) => { - handle_expand::<_, _, C>( - &srv, - &mut stdin, - &mut stdout, - &mut buf, - req.id, - span_mode, - *task, - )?; + send_response::<_, C>( + &mut stdout, + req.id, + bidirectional::Response::ListMacros(res), + )?; + } + + bidirectional::Request::ApiVersionCheck {} => { + send_response::<_, C>( + &mut stdout, + req.id, + bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION), + )?; + } + + bidirectional::Request::SetConfig(config) => { + span_mode = config.span_mode; + send_response::<_, C>( + &mut stdout, + req.id, + bidirectional::Response::SetConfig(config), + )?; + } + bidirectional::Request::ExpandMacro(task) => { + handle_expand::<_, _, C>( + &srv, + &mut stdin, + &mut stdout, + &mut buf, + req.id, + span_mode, + *task, + )?; + } } - }, + } _ => continue, } } @@ -130,12 +129,12 @@ fn handle_expand( stdout: &mut W, buf: &mut C::Buf, req_id: u64, - span_mode: SpanMode, - task: proc_macro_api::bidirectional_protocol::msg::ExpandMacro, + span_mode: legacy::SpanMode, + task: bidirectional::ExpandMacro, ) -> io::Result<()> { match span_mode { - SpanMode::Id => handle_expand_id::<_, C>(srv, stdout, req_id, task), - SpanMode::RustAnalyzer => { + legacy::SpanMode::Id => handle_expand_id::<_, C>(srv, stdout, req_id, task), + legacy::SpanMode::RustAnalyzer => { handle_expand_ra::<_, _, C>(srv, stdin, stdout, buf, req_id, task) } } @@ -145,21 +144,14 @@ fn handle_expand_id( srv: &proc_macro_srv::ProcMacroSrv<'_>, stdout: &mut W, req_id: u64, - task: proc_macro_api::bidirectional_protocol::msg::ExpandMacro, + task: bidirectional::ExpandMacro, ) -> io::Result<()> { - let proc_macro_api::bidirectional_protocol::msg::ExpandMacro { lib, env, current_dir, data } = - task; - let proc_macro_api::bidirectional_protocol::msg::ExpandMacroData { + let bidirectional::ExpandMacro { lib, env, current_dir, data } = task; + let bidirectional::ExpandMacroData { macro_body, macro_name, attributes, - has_global_spans: - proc_macro_api::bidirectional_protocol::msg::ExpnGlobals { - def_site, - call_site, - mixed_site, - .. - }, + has_global_spans: bidirectional::ExpnGlobals { def_site, call_site, mixed_site, .. }, .. } = data; @@ -185,15 +177,11 @@ fn handle_expand_id( mixed_site, ) .map(|it| { - msg::FlatTree::from_tokenstream_raw::(it, call_site, CURRENT_API_VERSION) + legacy::FlatTree::from_tokenstream_raw::(it, call_site, CURRENT_API_VERSION) }) - .map_err(|e| msg::PanicMessage(e.into_string().unwrap_or_default())); + .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); - send_response::<_, C>( - stdout, - req_id, - proc_macro_api::bidirectional_protocol::msg::Response::ExpandMacro(res), - ) + send_response::<_, C>(stdout, req_id, bidirectional::Response::ExpandMacro(res)) } fn handle_expand_ra( @@ -202,29 +190,24 @@ fn handle_expand_ra( stdout: &mut W, buf: &mut C::Buf, req_id: u64, - task: proc_macro_api::bidirectional_protocol::msg::ExpandMacro, + task: bidirectional::ExpandMacro, ) -> io::Result<()> { - let proc_macro_api::bidirectional_protocol::msg::ExpandMacro { + let bidirectional::ExpandMacro { lib, env, current_dir, data: - proc_macro_api::bidirectional_protocol::msg::ExpandMacroData { + bidirectional::ExpandMacroData { macro_body, macro_name, attributes, has_global_spans: - proc_macro_api::bidirectional_protocol::msg::ExpnGlobals { - serialize: _, - def_site, - call_site, - mixed_site, - }, + bidirectional::ExpnGlobals { serialize: _, def_site, call_site, mixed_site }, span_data_table, }, } = task; - let mut span_data_table = deserialize_span_data_index_map(&span_data_table); + let mut span_data_table = legacy::deserialize_span_data_index_map(&span_data_table); let def_site_span = span_data_table[def_site]; let call_site_span = span_data_table[call_site]; @@ -269,23 +252,21 @@ fn handle_expand_ra( ) .map(|it| { ( - msg::FlatTree::from_tokenstream( + legacy::FlatTree::from_tokenstream( it, CURRENT_API_VERSION, call_site_span, &mut span_data_table, ), - serialize_span_data_index_map(&span_data_table), + legacy::serialize_span_data_index_map(&span_data_table), ) }) - .map(|(tree, span_data_table)| { - proc_macro_api::bidirectional_protocol::msg::ExpandMacroExtended { - tree, - span_data_table, - } + .map(|(tree, span_data_table)| bidirectional::ExpandMacroExtended { + tree, + span_data_table, }) .map_err(|e| e.into_string().unwrap_or_default()) - .map_err(msg::PanicMessage); + .map_err(legacy::PanicMessage); let _ = result_tx.send(res); } }); @@ -295,7 +276,7 @@ fn handle_expand_ra( send_response::<_, C>( stdout, req_id, - proc_macro_api::bidirectional_protocol::msg::Response::ExpandMacroExtended(res), + bidirectional::Response::ExpandMacroExtended(res), ) .unwrap(); break; @@ -310,7 +291,7 @@ fn handle_expand_ra( send_subrequest::<_, C>(stdout, req_id, from_srv_req(subreq)).unwrap(); - let resp_opt = Envelope::read::<_, C>(stdin, buf).unwrap(); + let resp_opt = bidirectional::Envelope::read::<_, C>(stdin, buf).unwrap(); let resp = match resp_opt { Some(env) => env, None => { @@ -319,7 +300,10 @@ fn handle_expand_ra( }; match (resp.kind, resp.payload) { - (Kind::SubResponse, Payload::SubResponse(subresp)) => { + ( + bidirectional::Kind::SubResponse, + bidirectional::Payload::SubResponse(subresp), + ) => { let _ = subresp_tx.send(from_client_res(subresp)); } _ => { @@ -343,38 +327,38 @@ fn run_() -> io::Result<()> { } 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 mut read_request = || legacy::Request::read::<_, C>(&mut io::stdin().lock(), &mut buf); + let write_response = |msg: legacy::Response| msg.write::<_, C>(&mut io::stdout().lock()); let env = EnvSnapshot::default(); let srv = proc_macro_srv::ProcMacroSrv::new(&env); - let mut span_mode = SpanMode::Id; + let mut span_mode = legacy::SpanMode::Id; while let Some(req) = read_request()? { let res = match req { - msg::Request::ListMacros { dylib_path } => { - msg::Response::ListMacros(srv.list_macros(&dylib_path).map(|macros| { + legacy::Request::ListMacros { dylib_path } => { + legacy::Response::ListMacros(srv.list_macros(&dylib_path).map(|macros| { macros.into_iter().map(|(name, kind)| (name, macro_kind_to_api(kind))).collect() })) } - msg::Request::ExpandMacro(task) => { - let msg::ExpandMacro { + legacy::Request::ExpandMacro(task) => { + let legacy::ExpandMacro { lib, env, current_dir, data: - ExpandMacroData { + legacy::ExpandMacroData { macro_body, macro_name, attributes, has_global_spans: - ExpnGlobals { serialize: _, def_site, call_site, mixed_site }, + legacy::ExpnGlobals { serialize: _, def_site, call_site, mixed_site }, span_data_table, }, } = *task; match span_mode { - SpanMode::Id => msg::Response::ExpandMacro({ + legacy::SpanMode::Id => legacy::Response::ExpandMacro({ let def_site = SpanId(def_site as u32); let call_site = SpanId(call_site as u32); let mixed_site = SpanId(mixed_site as u32); @@ -397,17 +381,18 @@ fn run_() -> io::Result<()> { mixed_site, ) .map(|it| { - msg::FlatTree::from_tokenstream_raw::( + legacy::FlatTree::from_tokenstream_raw::( it, call_site, CURRENT_API_VERSION, ) }) .map_err(|e| e.into_string().unwrap_or_default()) - .map_err(msg::PanicMessage) + .map_err(legacy::PanicMessage) }), - SpanMode::RustAnalyzer => msg::Response::ExpandMacroExtended({ - let mut span_data_table = deserialize_span_data_index_map(&span_data_table); + legacy::SpanMode::RustAnalyzer => legacy::Response::ExpandMacroExtended({ + let mut span_data_table = + legacy::deserialize_span_data_index_map(&span_data_table); let def_site = span_data_table[def_site]; let call_site = span_data_table[call_site]; @@ -438,28 +423,30 @@ fn run_() -> io::Result<()> { ) .map(|it| { ( - msg::FlatTree::from_tokenstream( + legacy::FlatTree::from_tokenstream( it, CURRENT_API_VERSION, call_site, &mut span_data_table, ), - serialize_span_data_index_map(&span_data_table), + legacy::serialize_span_data_index_map(&span_data_table), ) }) - .map(|(tree, span_data_table)| msg::ExpandMacroExtended { + .map(|(tree, span_data_table)| legacy::ExpandMacroExtended { tree, span_data_table, }) .map_err(|e| e.into_string().unwrap_or_default()) - .map_err(msg::PanicMessage) + .map_err(legacy::PanicMessage) }), } } - msg::Request::ApiVersionCheck {} => msg::Response::ApiVersionCheck(CURRENT_API_VERSION), - msg::Request::SetConfig(config) => { + legacy::Request::ApiVersionCheck {} => { + legacy::Response::ApiVersionCheck(CURRENT_API_VERSION) + } + legacy::Request::SetConfig(config) => { span_mode = config.span_mode; - msg::Response::SetConfig(config) + legacy::Response::SetConfig(config) } }; write_response(res)? @@ -468,25 +455,17 @@ fn run_() -> io::Result<()> { Ok(()) } -fn from_srv_req( - value: proc_macro_srv::SubRequest, -) -> proc_macro_api::bidirectional_protocol::msg::SubRequest { +fn from_srv_req(value: proc_macro_srv::SubRequest) -> bidirectional::SubRequest { match value { proc_macro_srv::SubRequest::SourceText { file_id, start, end } => { - proc_macro_api::bidirectional_protocol::msg::SubRequest::SourceText { - file_id: file_id.file_id().index(), - start, - end, - } + bidirectional::SubRequest::SourceText { file_id: file_id.file_id().index(), start, end } } } } -fn from_client_res( - value: proc_macro_api::bidirectional_protocol::msg::SubResponse, -) -> proc_macro_srv::SubResponse { +fn from_client_res(value: bidirectional::SubResponse) -> proc_macro_srv::SubResponse { match value { - proc_macro_api::bidirectional_protocol::msg::SubResponse::SourceTextResult { text } => { + bidirectional::SubResponse::SourceTextResult { text } => { proc_macro_srv::SubResponse::SourceTextResult { text } } } @@ -495,17 +474,25 @@ fn from_client_res( fn send_response( stdout: &mut W, id: u64, - resp: proc_macro_api::bidirectional_protocol::msg::Response, + resp: bidirectional::Response, ) -> io::Result<()> { - let resp = Envelope { id, kind: Kind::Response, payload: Payload::Response(resp) }; + let resp = bidirectional::Envelope { + id, + kind: bidirectional::Kind::Response, + payload: bidirectional::Payload::Response(resp), + }; resp.write::(stdout) } fn send_subrequest( stdout: &mut W, id: u64, - resp: proc_macro_api::bidirectional_protocol::msg::SubRequest, + resp: bidirectional::SubRequest, ) -> io::Result<()> { - let resp = Envelope { id, kind: Kind::SubRequest, payload: Payload::SubRequest(resp) }; + let resp = bidirectional::Envelope { + id, + kind: bidirectional::Kind::SubRequest, + payload: bidirectional::Payload::SubRequest(resp), + }; resp.write::(stdout) } From b65078edce5cfb09a1870fb2df4fefd530f135b4 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 18 Dec 2025 14:34:57 +0530 Subject: [PATCH 09/77] change request id to u32 to make powerpc happy --- .../proc-macro-api/src/bidirectional_protocol/msg.rs | 2 +- .../crates/proc-macro-api/src/process.rs | 6 +++--- .../crates/proc-macro-srv-cli/src/main_loop.rs | 11 ++++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs index 7aed3ae1e6072..796573385c3c4 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs @@ -8,7 +8,7 @@ use crate::{ legacy_protocol::msg::{FlatTree, Message, PanicMessage, ServerConfig}, }; -pub type RequestId = u64; +pub type RequestId = u32; #[derive(Debug, Serialize, Deserialize)] pub struct Envelope { 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 723fc928ff66c..bb4599c53272f 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 @@ -6,7 +6,7 @@ use std::{ process::{Child, ChildStdin, ChildStdout, Command, Stdio}, sync::{ Arc, Mutex, OnceLock, - atomic::{AtomicU64, Ordering}, + atomic::{AtomicU32, Ordering}, }, }; @@ -36,7 +36,7 @@ pub(crate) struct ProcMacroServerProcess { protocol: Protocol, /// Populated when the server exits. exited: OnceLock>, - next_request_id: AtomicU64, + next_request_id: AtomicU32, } #[derive(Debug, Clone)] @@ -94,7 +94,7 @@ impl ProcMacroServerProcess { version: 0, protocol: protocol.clone(), exited: OnceLock::new(), - next_request_id: AtomicU64::new(1), + next_request_id: AtomicU32::new(1), }) }; let mut srv = create_srv()?; 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 1ad73d4daac29..dc828a87de13a 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 @@ -10,6 +10,7 @@ use proc_macro_api::{ version::CURRENT_API_VERSION, }; +use bidirectional::RequestId; use legacy::Message; use proc_macro_srv::{EnvSnapshot, SpanId}; @@ -128,7 +129,7 @@ fn handle_expand( stdin: &mut R, stdout: &mut W, buf: &mut C::Buf, - req_id: u64, + req_id: RequestId, span_mode: legacy::SpanMode, task: bidirectional::ExpandMacro, ) -> io::Result<()> { @@ -143,7 +144,7 @@ fn handle_expand( fn handle_expand_id( srv: &proc_macro_srv::ProcMacroSrv<'_>, stdout: &mut W, - req_id: u64, + req_id: RequestId, task: bidirectional::ExpandMacro, ) -> io::Result<()> { let bidirectional::ExpandMacro { lib, env, current_dir, data } = task; @@ -189,7 +190,7 @@ fn handle_expand_ra( stdin: &mut R, stdout: &mut W, buf: &mut C::Buf, - req_id: u64, + req_id: RequestId, task: bidirectional::ExpandMacro, ) -> io::Result<()> { let bidirectional::ExpandMacro { @@ -473,7 +474,7 @@ fn from_client_res(value: bidirectional::SubResponse) -> proc_macro_srv::SubResp fn send_response( stdout: &mut W, - id: u64, + id: u32, resp: bidirectional::Response, ) -> io::Result<()> { let resp = bidirectional::Envelope { @@ -486,7 +487,7 @@ fn send_response( fn send_subrequest( stdout: &mut W, - id: u64, + id: u32, resp: bidirectional::SubRequest, ) -> io::Result<()> { let resp = bidirectional::Envelope { From e337275d0667dc6d61af6d99d74b1df89198a521 Mon Sep 17 00:00:00 2001 From: Tsukasa OI Date: Sun, 14 Sep 2025 04:37:45 +0000 Subject: [PATCH 10/77] RISC-V: Add virtual target feature: `zkne_or_zknd` Because some AES key scheduling instructions require *either* Zkne or Zknd extension, we must have a target feature to represent `(Zkne || Zknd)`. This commit adds (perma-unstable) target feature to the RISC-V architecture: `zkne_or_zknd` for this purpose. Helped-by: sayantn --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 5 +++++ compiler/rustc_target/src/target_features.rs | 5 +++-- tests/ui/check-cfg/target_feature.stderr | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 682484595a894..14ef9ce68c9b4 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -262,6 +262,11 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option Some(LLVMFeature::new("crypto")), s => Some(LLVMFeature::new(s)), }, + Arch::RiscV32 | Arch::RiscV64 => match s { + // Filter out Rust-specific *virtual* target feature + "zkne_or_zknd" => None, + s => Some(LLVMFeature::new(s)), + }, Arch::Sparc | Arch::Sparc64 => match s { "leoncasa" => Some(LLVMFeature::new("hasleoncasa")), s => Some(LLVMFeature::new(s)), diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index e516a31d1e66e..2c2e3c7caad7c 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -690,8 +690,9 @@ static RISCV_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("zimop", Unstable(sym::riscv_target_feature), &[]), ("zk", Stable, &["zkn", "zkr", "zkt"]), ("zkn", Stable, &["zbkb", "zbkc", "zbkx", "zkne", "zknd", "zknh"]), - ("zknd", Stable, &[]), - ("zkne", Stable, &[]), + ("zknd", Stable, &["zkne_or_zknd"]), + ("zkne", Stable, &["zkne_or_zknd"]), + ("zkne_or_zknd", Unstable(sym::riscv_target_feature), &[]), // Not an extension ("zknh", Stable, &[]), ("zkr", Stable, &[]), ("zks", Stable, &["zbkb", "zbkc", "zbkx", "zksed", "zksh"]), diff --git a/tests/ui/check-cfg/target_feature.stderr b/tests/ui/check-cfg/target_feature.stderr index d25e7f094964e..ea7c2efddc6eb 100644 --- a/tests/ui/check-cfg/target_feature.stderr +++ b/tests/ui/check-cfg/target_feature.stderr @@ -423,6 +423,7 @@ LL | cfg!(target_feature = "_UNEXPECTED_VALUE"); `zkn` `zknd` `zkne` +`zkne_or_zknd` `zknh` `zkr` `zks` From 288c372b7a039cb05803ebc6ed04254fec535cf0 Mon Sep 17 00:00:00 2001 From: Tsukasa OI Date: Fri, 19 Sep 2025 21:36:50 +0000 Subject: [PATCH 11/77] RISC-V: Use inline assembly and `zkne_or_zknd` on (Zkne or Zknd) intrinsics Using the inline assembly and `zkne_or_zknd` target feature could avoid current issues regarding intrinsics available when either Zkne or Zknd is available. Before this commit, intrinsics `aes64ks1i` and `aes64ks2` required both Zkne and Zknd extensions, not either Zkne or Zknd. Closes: https://github.com/rust-lang/stdarch/issues/1765 --- .../crates/core_arch/src/riscv64/zk.rs | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/library/stdarch/crates/core_arch/src/riscv64/zk.rs b/library/stdarch/crates/core_arch/src/riscv64/zk.rs index a30653cbe0885..3aa1ca39b3c94 100644 --- a/library/stdarch/crates/core_arch/src/riscv64/zk.rs +++ b/library/stdarch/crates/core_arch/src/riscv64/zk.rs @@ -1,6 +1,8 @@ #[cfg(test)] use stdarch_test::assert_instr; +use crate::arch::asm; + unsafe extern "unadjusted" { #[link_name = "llvm.riscv.aes64es"] fn _aes64es(rs1: i64, rs2: i64) -> i64; @@ -14,12 +16,6 @@ unsafe extern "unadjusted" { #[link_name = "llvm.riscv.aes64dsm"] fn _aes64dsm(rs1: i64, rs2: i64) -> i64; - #[link_name = "llvm.riscv.aes64ks1i"] - fn _aes64ks1i(rs1: i64, rnum: i32) -> i64; - - #[link_name = "llvm.riscv.aes64ks2"] - fn _aes64ks2(rs1: i64, rs2: i64) -> i64; - #[link_name = "llvm.riscv.aes64im"] fn _aes64im(rs1: i64) -> i64; @@ -133,15 +129,26 @@ pub fn aes64dsm(rs1: u64, rs2: u64) -> u64 { /// # Note /// /// The `RNUM` parameter is expected to be a constant value inside the range of `0..=10`. -#[target_feature(enable = "zkne", enable = "zknd")] +#[target_feature(enable = "zkne_or_zknd")] #[rustc_legacy_const_generics(1)] -#[cfg_attr(test, assert_instr(aes64ks1i, RNUM = 0))] #[inline] #[unstable(feature = "riscv_ext_intrinsics", issue = "114544")] pub fn aes64ks1i(rs1: u64) -> u64 { static_assert!(RNUM <= 10); - - unsafe { _aes64ks1i(rs1 as i64, RNUM as i32) as u64 } + unsafe { + let rd: u64; + asm!( + ".option push", + ".option arch, +zkne", + "aes64ks1i {}, {}, {}", + ".option pop", + lateout(reg) rd, + in(reg) rs1, + const RNUM, + options(pure, nomem, nostack, preserves_flags) + ); + rd + } } /// This instruction implements part of the KeySchedule operation for the AES Block cipher. @@ -155,12 +162,24 @@ pub fn aes64ks1i(rs1: u64) -> u64 { /// Version: v1.0.1 /// /// Section: 3.11 -#[target_feature(enable = "zkne", enable = "zknd")] -#[cfg_attr(test, assert_instr(aes64ks2))] +#[target_feature(enable = "zkne_or_zknd")] #[inline] #[unstable(feature = "riscv_ext_intrinsics", issue = "114544")] pub fn aes64ks2(rs1: u64, rs2: u64) -> u64 { - unsafe { _aes64ks2(rs1 as i64, rs2 as i64) as u64 } + unsafe { + let rd: u64; + asm!( + ".option push", + ".option arch, +zkne", + "aes64ks2 {}, {}, {}", + ".option pop", + lateout(reg) rd, + in(reg) rs1, + in(reg) rs2, + options(pure, nomem, nostack, preserves_flags) + ); + rd + } } /// This instruction accelerates the inverse MixColumns step of the AES Block Cipher, and is used to aid creation of From ba9cc48c7a093a95554ab7c38b0f7f6db0fe49dd Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 22 Dec 2025 04:21:46 +0000 Subject: [PATCH 12/77] Prepare for merging from rust-lang/rust This updates the rust-version file to f41f40408d719aa9ae0c6bfa17619d8f3f9e5b99. --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index dcf82c94aaeed..95e1cd0db37c1 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -0208ee09be465f69005a7a12c28d5eccac7d5f34 +f41f40408d719aa9ae0c6bfa17619d8f3f9e5b99 From 23278232ac3748c80bac01642b0a3bbe056c498a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 22 Dec 2025 19:21:05 +0530 Subject: [PATCH 13/77] remove request_id, rename postcardNew to BidirectionalPostcardPrototype and remove JsonNew --- .../src/bidirectional_protocol.rs | 20 +++--------- .../src/bidirectional_protocol/msg.rs | 9 ------ .../crates/proc-macro-api/src/process.rs | 32 ++++++------------- .../crates/proc-macro-srv-cli/src/main.rs | 6 +--- .../proc-macro-srv-cli/src/main_loop.rs | 22 +++---------- 5 files changed, 20 insertions(+), 69 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs index 728f0f707d715..c7caccb96ff1f 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs @@ -12,7 +12,7 @@ use span::{FileId, Span}; use crate::{ Codec, ProcMacro, ProcMacroKind, ServerError, bidirectional_protocol::msg::{ - Envelope, ExpandMacro, ExpandMacroData, ExpnGlobals, Kind, Payload, Request, RequestId, + Envelope, ExpandMacro, ExpandMacroData, ExpnGlobals, Kind, Payload, Request, Response, SubRequest, SubResponse, }, legacy_protocol::{ @@ -37,11 +37,10 @@ pub fn run_conversation( writer: &mut dyn Write, reader: &mut dyn BufRead, buf: &mut C::Buf, - id: RequestId, initial: Payload, callbacks: &mut dyn ClientCallbacks, ) -> Result { - let msg = Envelope { id, kind: Kind::Request, payload: initial }; + let msg = Envelope { kind: Kind::Request, payload: initial }; let encoded = C::encode(&msg).map_err(wrap_encode)?; C::write(writer, &encoded).map_err(wrap_io("failed to write initial request"))?; @@ -56,18 +55,11 @@ pub fn run_conversation( let msg: Envelope = C::decode(b).map_err(wrap_decode)?; - if msg.id != id { - return Err(ServerError { - message: format!("unexpected message id {}, expected {}", msg.id, id), - io: None, - }); - } - match (msg.kind, msg.payload) { (Kind::SubRequest, Payload::SubRequest(sr)) => { let resp = callbacks.handle_sub_request(sr)?; let reply = - Envelope { id, kind: Kind::SubResponse, payload: Payload::SubResponse(resp) }; + Envelope { kind: Kind::SubResponse, payload: Payload::SubResponse(resp) }; let encoded = C::encode(&reply).map_err(wrap_encode)?; C::write(writer, &encoded).map_err(wrap_io("failed to write sub-response"))?; } @@ -268,11 +260,9 @@ fn run_request( return Err(server_error.clone()); } - let id = srv.request_id(); - if srv.use_postcard() { - srv.run_bidirectional::(id, msg, callbacks) + srv.run_bidirectional::(msg, callbacks) } else { - srv.run_bidirectional::(id, msg, callbacks) + srv.run_bidirectional::(msg, callbacks) } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs index 796573385c3c4..4f81ef4ae6bdb 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs @@ -8,21 +8,12 @@ use crate::{ legacy_protocol::msg::{FlatTree, Message, PanicMessage, ServerConfig}, }; -pub type RequestId = u32; - #[derive(Debug, Serialize, Deserialize)] pub struct Envelope { - pub id: RequestId, pub kind: Kind, pub payload: Payload, } -impl From<(RequestId, Kind, Payload)> for Envelope { - fn from(value: (RequestId, Kind, Payload)) -> Self { - Envelope { id: value.0, kind: value.1, payload: value.2 } - } -} - #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] pub enum Kind { Request, 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 bb4599c53272f..1c1709e5fa646 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 @@ -6,7 +6,6 @@ use std::{ process::{Child, ChildStdin, ChildStdout, Command, Stdio}, sync::{ Arc, Mutex, OnceLock, - atomic::{AtomicU32, Ordering}, }, }; @@ -20,7 +19,7 @@ use crate::{ Codec, ProcMacro, ProcMacroKind, ServerError, bidirectional_protocol::{ self, ClientCallbacks, - msg::{Payload, RequestId}, + msg::Payload, }, legacy_protocol::{self, SpanMode}, version, @@ -36,15 +35,13 @@ pub(crate) struct ProcMacroServerProcess { protocol: Protocol, /// Populated when the server exits. exited: OnceLock>, - next_request_id: AtomicU32, } #[derive(Debug, Clone)] pub(crate) enum Protocol { LegacyJson { mode: SpanMode }, LegacyPostcard { mode: SpanMode }, - NewPostcard { mode: SpanMode }, - NewJson { mode: SpanMode }, + BidirectionalPostcardPrototype { mode: SpanMode }, } /// Maintains the state of the proc-macro server process. @@ -74,8 +71,7 @@ impl ProcMacroServerProcess { && has_working_format_flag { &[ - (Some("postcard-new"), Protocol::NewPostcard { mode: SpanMode::Id }), - (Some("json-new"), Protocol::NewJson { mode: SpanMode::Id }), + (Some("postcard-new"), Protocol::BidirectionalPostcardPrototype { mode: SpanMode::Id }), (Some("postcard-legacy"), Protocol::LegacyPostcard { mode: SpanMode::Id }), (Some("json-legacy"), Protocol::LegacyJson { mode: SpanMode::Id }), ] @@ -94,7 +90,6 @@ impl ProcMacroServerProcess { version: 0, protocol: protocol.clone(), exited: OnceLock::new(), - next_request_id: AtomicU32::new(1), }) }; let mut srv = create_srv()?; @@ -122,8 +117,7 @@ impl ProcMacroServerProcess { match &mut srv.protocol { Protocol::LegacyJson { mode } | Protocol::LegacyPostcard { mode } - | Protocol::NewJson { mode } - | Protocol::NewPostcard { mode } => *mode = new_mode, + | Protocol::BidirectionalPostcardPrototype { mode } => *mode = new_mode, } } tracing::info!("Proc-macro server protocol: {:?}", srv.protocol); @@ -159,8 +153,7 @@ impl ProcMacroServerProcess { match self.protocol { Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer, Protocol::LegacyPostcard { mode } => mode == SpanMode::RustAnalyzer, - Protocol::NewJson { mode } => mode == SpanMode::RustAnalyzer, - Protocol::NewPostcard { mode } => mode == SpanMode::RustAnalyzer, + Protocol::BidirectionalPostcardPrototype { mode } => mode == SpanMode::RustAnalyzer, } } @@ -170,7 +163,7 @@ impl ProcMacroServerProcess { Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { legacy_protocol::version_check(self) } - Protocol::NewJson { .. } | Protocol::NewPostcard { .. } => { + Protocol::BidirectionalPostcardPrototype { .. } => { bidirectional_protocol::version_check(self) } } @@ -182,7 +175,7 @@ impl ProcMacroServerProcess { Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { legacy_protocol::enable_rust_analyzer_spans(self) } - Protocol::NewJson { .. } | Protocol::NewPostcard { .. } => { + Protocol::BidirectionalPostcardPrototype { .. } => { bidirectional_protocol::enable_rust_analyzer_spans(self) } } @@ -197,7 +190,7 @@ impl ProcMacroServerProcess { Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { legacy_protocol::find_proc_macros(self, dylib_path) } - Protocol::NewJson { .. } | Protocol::NewPostcard { .. } => { + Protocol::BidirectionalPostcardPrototype { .. } => { bidirectional_protocol::find_proc_macros(self, dylib_path) } } @@ -229,7 +222,7 @@ impl ProcMacroServerProcess { current_dir, ) } - Protocol::NewJson { .. } | Protocol::NewPostcard { .. } => { + Protocol::BidirectionalPostcardPrototype { .. } => { bidirectional_protocol::expand( proc_macro, db, @@ -307,20 +300,15 @@ impl ProcMacroServerProcess { pub(crate) fn run_bidirectional( &self, - id: RequestId, initial: Payload, callbacks: &mut dyn ClientCallbacks, ) -> Result { self.with_locked_io::(|writer, reader, buf| { bidirectional_protocol::run_conversation::( - writer, reader, buf, id, initial, callbacks, + writer, reader, buf, initial, callbacks, ) }) } - - pub(crate) fn request_id(&self) -> RequestId { - self.next_request_id.fetch_add(1, Ordering::Relaxed) - } } /// Manages the execution of the proc-macro server process. 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 c9134b402dfa8..53302dd5798c2 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-new") + .default_value("postcard-new") .value_parser(clap::builder::EnumValueParser::::new()), clap::Arg::new("version") .long("version") @@ -52,7 +52,6 @@ fn main() -> std::io::Result<()> { enum ProtocolFormat { JsonLegacy, PostcardLegacy, - JsonNew, PostcardNew, } @@ -61,7 +60,6 @@ impl ValueEnum for ProtocolFormat { &[ ProtocolFormat::JsonLegacy, ProtocolFormat::PostcardLegacy, - ProtocolFormat::JsonNew, ProtocolFormat::PostcardNew, ] } @@ -72,7 +70,6 @@ impl ValueEnum for ProtocolFormat { ProtocolFormat::PostcardLegacy => { Some(clap::builder::PossibleValue::new("postcard-legacy")) } - ProtocolFormat::JsonNew => Some(clap::builder::PossibleValue::new("json-new")), ProtocolFormat::PostcardNew => Some(clap::builder::PossibleValue::new("postcard-new")), } } @@ -81,7 +78,6 @@ impl ValueEnum for ProtocolFormat { "json-legacy" => Ok(ProtocolFormat::JsonLegacy), "postcard-legacy" => Ok(ProtocolFormat::PostcardLegacy), "postcard-new" => Ok(ProtocolFormat::PostcardNew), - "json-new" => Ok(ProtocolFormat::JsonNew), _ => 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 dc828a87de13a..ee9b2088117d6 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 @@ -10,7 +10,6 @@ use proc_macro_api::{ version::CURRENT_API_VERSION, }; -use bidirectional::RequestId; use legacy::Message; use proc_macro_srv::{EnvSnapshot, SpanId}; @@ -39,7 +38,6 @@ pub(crate) fn run(format: ProtocolFormat) -> io::Result<()> { match format { ProtocolFormat::JsonLegacy => run_::(), ProtocolFormat::PostcardLegacy => run_::(), - ProtocolFormat::JsonNew => run_new::(), ProtocolFormat::PostcardNew => run_new::(), } } @@ -83,7 +81,6 @@ fn run_new() -> io::Result<()> { send_response::<_, C>( &mut stdout, - req.id, bidirectional::Response::ListMacros(res), )?; } @@ -91,7 +88,6 @@ fn run_new() -> io::Result<()> { bidirectional::Request::ApiVersionCheck {} => { send_response::<_, C>( &mut stdout, - req.id, bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION), )?; } @@ -100,7 +96,6 @@ fn run_new() -> io::Result<()> { span_mode = config.span_mode; send_response::<_, C>( &mut stdout, - req.id, bidirectional::Response::SetConfig(config), )?; } @@ -110,7 +105,6 @@ fn run_new() -> io::Result<()> { &mut stdin, &mut stdout, &mut buf, - req.id, span_mode, *task, )?; @@ -129,14 +123,13 @@ fn handle_expand( stdin: &mut R, stdout: &mut W, buf: &mut C::Buf, - req_id: RequestId, span_mode: legacy::SpanMode, task: bidirectional::ExpandMacro, ) -> io::Result<()> { match span_mode { - legacy::SpanMode::Id => handle_expand_id::<_, C>(srv, stdout, req_id, task), + legacy::SpanMode::Id => handle_expand_id::<_, C>(srv, stdout, task), legacy::SpanMode::RustAnalyzer => { - handle_expand_ra::<_, _, C>(srv, stdin, stdout, buf, req_id, task) + handle_expand_ra::<_, _, C>(srv, stdin, stdout, buf, task) } } } @@ -144,7 +137,6 @@ fn handle_expand( fn handle_expand_id( srv: &proc_macro_srv::ProcMacroSrv<'_>, stdout: &mut W, - req_id: RequestId, task: bidirectional::ExpandMacro, ) -> io::Result<()> { let bidirectional::ExpandMacro { lib, env, current_dir, data } = task; @@ -182,7 +174,7 @@ fn handle_expand_id( }) .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); - send_response::<_, C>(stdout, req_id, bidirectional::Response::ExpandMacro(res)) + send_response::<_, C>(stdout, bidirectional::Response::ExpandMacro(res)) } fn handle_expand_ra( @@ -190,7 +182,6 @@ fn handle_expand_ra( stdin: &mut R, stdout: &mut W, buf: &mut C::Buf, - req_id: RequestId, task: bidirectional::ExpandMacro, ) -> io::Result<()> { let bidirectional::ExpandMacro { @@ -276,7 +267,6 @@ fn handle_expand_ra( if let Ok(res) = result_rx.try_recv() { send_response::<_, C>( stdout, - req_id, bidirectional::Response::ExpandMacroExtended(res), ) .unwrap(); @@ -290,7 +280,7 @@ fn handle_expand_ra( } }; - send_subrequest::<_, C>(stdout, req_id, from_srv_req(subreq)).unwrap(); + send_subrequest::<_, C>(stdout, from_srv_req(subreq)).unwrap(); let resp_opt = bidirectional::Envelope::read::<_, C>(stdin, buf).unwrap(); let resp = match resp_opt { @@ -474,11 +464,9 @@ fn from_client_res(value: bidirectional::SubResponse) -> proc_macro_srv::SubResp fn send_response( stdout: &mut W, - id: u32, resp: bidirectional::Response, ) -> io::Result<()> { let resp = bidirectional::Envelope { - id, kind: bidirectional::Kind::Response, payload: bidirectional::Payload::Response(resp), }; @@ -487,11 +475,9 @@ fn send_response( fn send_subrequest( stdout: &mut W, - id: u32, resp: bidirectional::SubRequest, ) -> io::Result<()> { let resp = bidirectional::Envelope { - id, kind: bidirectional::Kind::SubRequest, payload: bidirectional::Payload::SubRequest(resp), }; From 3db62cf30798a2c390c4b568495e150b19e661dd Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 22 Dec 2025 19:21:22 +0530 Subject: [PATCH 14/77] comment out disabled workflow --- src/tools/rust-analyzer/.github/workflows/ci.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index 623200aea0321..81ca0254982d5 100644 --- a/src/tools/rust-analyzer/.github/workflows/ci.yaml +++ b/src/tools/rust-analyzer/.github/workflows/ci.yaml @@ -70,6 +70,10 @@ jobs: - name: Test run: cargo test --features sysroot-abi -p proc-macro-srv -p proc-macro-srv-cli -p proc-macro-api -- --quiet + + # FIXME: This is temporarily disable, more info: https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Non-generic.20spans/with/564604549 + # - name: Check salsa dependency + # run: "! (cargo tree -p proc-macro-srv-cli | grep -q salsa)" rust: if: github.repository == 'rust-lang/rust-analyzer' From 8e21457edd104f9a82c57dc0cb59f0a283d01e3f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 22 Dec 2025 20:04:35 +0530 Subject: [PATCH 15/77] remove envelop and keep it simple --- .../src/bidirectional_protocol.rs | 61 ++++++----- .../src/bidirectional_protocol/msg.rs | 18 +--- .../crates/proc-macro-api/src/process.rs | 46 ++++---- .../crates/proc-macro-srv-cli/src/main.rs | 6 +- .../proc-macro-srv-cli/src/main_loop.rs | 102 +++++++----------- 5 files changed, 94 insertions(+), 139 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs index c7caccb96ff1f..4cb6a1d90f19d 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs @@ -12,8 +12,8 @@ use span::{FileId, Span}; use crate::{ Codec, ProcMacro, ProcMacroKind, ServerError, bidirectional_protocol::msg::{ - Envelope, ExpandMacro, ExpandMacroData, ExpnGlobals, Kind, Payload, Request, - Response, SubRequest, SubResponse, + BidirectionalMessage, ExpandMacro, ExpandMacroData, ExpnGlobals, Request, Response, + SubRequest, SubResponse, }, legacy_protocol::{ SpanMode, @@ -37,10 +37,9 @@ pub fn run_conversation( writer: &mut dyn Write, reader: &mut dyn BufRead, buf: &mut C::Buf, - initial: Payload, + msg: BidirectionalMessage, callbacks: &mut dyn ClientCallbacks, -) -> Result { - let msg = Envelope { kind: Kind::Request, payload: initial }; +) -> Result { let encoded = C::encode(&msg).map_err(wrap_encode)?; C::write(writer, &encoded).map_err(wrap_io("failed to write initial request"))?; @@ -53,25 +52,21 @@ pub fn run_conversation( }); }; - let msg: Envelope = C::decode(b).map_err(wrap_decode)?; + let msg: BidirectionalMessage = C::decode(b).map_err(wrap_decode)?; - match (msg.kind, msg.payload) { - (Kind::SubRequest, Payload::SubRequest(sr)) => { + match msg { + BidirectionalMessage::Response(response) => { + return Ok(BidirectionalMessage::Response(response)); + } + BidirectionalMessage::SubRequest(sr) => { let resp = callbacks.handle_sub_request(sr)?; - let reply = - Envelope { kind: Kind::SubResponse, payload: Payload::SubResponse(resp) }; + let reply = BidirectionalMessage::SubResponse(resp); let encoded = C::encode(&reply).map_err(wrap_encode)?; C::write(writer, &encoded).map_err(wrap_io("failed to write sub-response"))?; } - (Kind::Response, payload) => { - return Ok(payload); - } - (kind, payload) => { + _ => { return Err(ServerError { - message: format!( - "unexpected message kind {:?} with payload {:?}", - kind, payload - ), + message: format!("unexpected message {:?}", msg), io: None, }); } @@ -92,7 +87,7 @@ fn wrap_decode(err: io::Error) -> ServerError { } pub(crate) fn version_check(srv: &ProcMacroServerProcess) -> Result { - let request = Payload::Request(Request::ApiVersionCheck {}); + let request = BidirectionalMessage::Request(Request::ApiVersionCheck {}); struct NoCallbacks; impl ClientCallbacks for NoCallbacks { @@ -106,7 +101,7 @@ pub(crate) fn version_check(srv: &ProcMacroServerProcess) -> Result Ok(version), + BidirectionalMessage::Response(Response::ApiVersionCheck(version)) => Ok(version), other => { Err(ServerError { message: format!("unexpected response: {:?}", other), io: None }) } @@ -117,8 +112,9 @@ pub(crate) fn version_check(srv: &ProcMacroServerProcess) -> Result Result { - let request = - Payload::Request(Request::SetConfig(ServerConfig { span_mode: SpanMode::RustAnalyzer })); + let request = BidirectionalMessage::Request(Request::SetConfig(ServerConfig { + span_mode: SpanMode::RustAnalyzer, + })); struct NoCallbacks; impl ClientCallbacks for NoCallbacks { @@ -132,7 +128,9 @@ pub(crate) fn enable_rust_analyzer_spans( let response_payload = run_request(srv, request, &mut callbacks)?; match response_payload { - Payload::Response(Response::SetConfig(ServerConfig { span_mode })) => Ok(span_mode), + BidirectionalMessage::Response(Response::SetConfig(ServerConfig { span_mode })) => { + Ok(span_mode) + } _ => Err(ServerError { message: "unexpected response".to_owned(), io: None }), } } @@ -142,8 +140,9 @@ pub(crate) fn find_proc_macros( srv: &ProcMacroServerProcess, dylib_path: &AbsPath, ) -> Result, String>, ServerError> { - let request = - Payload::Request(Request::ListMacros { dylib_path: dylib_path.to_path_buf().into() }); + let request = BidirectionalMessage::Request(Request::ListMacros { + dylib_path: dylib_path.to_path_buf().into(), + }); struct NoCallbacks; impl ClientCallbacks for NoCallbacks { @@ -157,7 +156,7 @@ pub(crate) fn find_proc_macros( let response_payload = run_request(srv, request, &mut callbacks)?; match response_payload { - Payload::Response(Response::ListMacros(it)) => Ok(it), + BidirectionalMessage::Response(Response::ListMacros(it)) => Ok(it), _ => Err(ServerError { message: "unexpected response".to_owned(), io: None }), } } @@ -179,7 +178,7 @@ pub(crate) fn expand( let def_site = span_data_table.insert_full(def_site).0; let call_site = span_data_table.insert_full(call_site).0; let mixed_site = span_data_table.insert_full(mixed_site).0; - let task = Payload::Request(Request::ExpandMacro(Box::new(ExpandMacro { + let task = BidirectionalMessage::Request(Request::ExpandMacro(Box::new(ExpandMacro { data: ExpandMacroData { macro_body: FlatTree::from_subtree(subtree, version, &mut span_data_table), macro_name: proc_macro.name.to_string(), @@ -225,7 +224,7 @@ pub(crate) fn expand( let response_payload = run_request(&proc_macro.process, task, &mut callbacks)?; match response_payload { - Payload::Response(Response::ExpandMacro(it)) => Ok(it + BidirectionalMessage::Response(Response::ExpandMacro(it)) => Ok(it .map(|tree| { let mut expanded = FlatTree::to_subtree_resolved(tree, version, &span_data_table); if proc_macro.needs_fixup_change() { @@ -234,7 +233,7 @@ pub(crate) fn expand( expanded }) .map_err(|msg| msg.0)), - Payload::Response(Response::ExpandMacroExtended(it)) => Ok(it + BidirectionalMessage::Response(Response::ExpandMacroExtended(it)) => Ok(it .map(|resp| { let mut expanded = FlatTree::to_subtree_resolved( resp.tree, @@ -253,9 +252,9 @@ pub(crate) fn expand( fn run_request( srv: &ProcMacroServerProcess, - msg: Payload, + msg: BidirectionalMessage, callbacks: &mut dyn ClientCallbacks, -) -> Result { +) -> Result { if let Some(server_error) = srv.exited() { return Err(server_error.clone()); } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs index 4f81ef4ae6bdb..cf8becd922de2 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs @@ -8,20 +8,6 @@ use crate::{ legacy_protocol::msg::{FlatTree, Message, PanicMessage, ServerConfig}, }; -#[derive(Debug, Serialize, Deserialize)] -pub struct Envelope { - pub kind: Kind, - pub payload: Payload, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] -pub enum Kind { - Request, - Response, - SubRequest, - SubResponse, -} - #[derive(Debug, Serialize, Deserialize)] pub enum SubRequest { SourceText { file_id: u32, start: u32, end: u32 }, @@ -33,7 +19,7 @@ pub enum SubResponse { } #[derive(Debug, Serialize, Deserialize)] -pub enum Payload { +pub enum BidirectionalMessage { Request(Request), Response(Response), SubRequest(SubRequest), @@ -102,4 +88,4 @@ impl ExpnGlobals { } } -impl Message for Envelope {} +impl Message for BidirectionalMessage {} 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 1c1709e5fa646..6d40250012123 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 @@ -4,9 +4,7 @@ use std::{ io::{self, BufRead, BufReader, Read, Write}, panic::AssertUnwindSafe, process::{Child, ChildStdin, ChildStdout, Command, Stdio}, - sync::{ - Arc, Mutex, OnceLock, - }, + sync::{Arc, Mutex, OnceLock}, }; use base_db::SourceDatabase; @@ -17,10 +15,7 @@ use stdx::JodChild; use crate::{ Codec, ProcMacro, ProcMacroKind, ServerError, - bidirectional_protocol::{ - self, ClientCallbacks, - msg::Payload, - }, + bidirectional_protocol::{self, ClientCallbacks, msg::BidirectionalMessage}, legacy_protocol::{self, SpanMode}, version, }; @@ -71,7 +66,10 @@ impl ProcMacroServerProcess { && has_working_format_flag { &[ - (Some("postcard-new"), Protocol::BidirectionalPostcardPrototype { mode: SpanMode::Id }), + ( + Some("postcard-new"), + Protocol::BidirectionalPostcardPrototype { mode: SpanMode::Id }, + ), (Some("postcard-legacy"), Protocol::LegacyPostcard { mode: SpanMode::Id }), (Some("json-legacy"), Protocol::LegacyJson { mode: SpanMode::Id }), ] @@ -222,19 +220,17 @@ impl ProcMacroServerProcess { current_dir, ) } - Protocol::BidirectionalPostcardPrototype { .. } => { - bidirectional_protocol::expand( - proc_macro, - db, - subtree, - attr, - env, - def_site, - call_site, - mixed_site, - current_dir, - ) - } + Protocol::BidirectionalPostcardPrototype { .. } => bidirectional_protocol::expand( + proc_macro, + db, + subtree, + attr, + env, + def_site, + call_site, + mixed_site, + current_dir, + ), } } @@ -300,13 +296,11 @@ impl ProcMacroServerProcess { pub(crate) fn run_bidirectional( &self, - initial: Payload, + initial: BidirectionalMessage, callbacks: &mut dyn ClientCallbacks, - ) -> Result { + ) -> Result { self.with_locked_io::(|writer, reader, buf| { - bidirectional_protocol::run_conversation::( - writer, reader, buf, initial, callbacks, - ) + bidirectional_protocol::run_conversation::(writer, reader, buf, initial, callbacks) }) } } 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 53302dd5798c2..01b261dab7482 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 @@ -57,11 +57,7 @@ enum ProtocolFormat { impl ValueEnum for ProtocolFormat { fn value_variants<'a>() -> &'a [Self] { - &[ - ProtocolFormat::JsonLegacy, - ProtocolFormat::PostcardLegacy, - ProtocolFormat::PostcardNew, - ] + &[ProtocolFormat::JsonLegacy, ProtocolFormat::PostcardLegacy, ProtocolFormat::PostcardNew] } fn to_possible_value(&self) -> Option { 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 ee9b2088117d6..9c66a308b18e0 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 @@ -63,54 +63,46 @@ fn run_new() -> io::Result<()> { let mut span_mode = legacy::SpanMode::Id; 'outer: loop { - let req_opt = bidirectional::Envelope::read::<_, C>(&mut stdin, &mut buf)?; + let req_opt = bidirectional::BidirectionalMessage::read::<_, C>(&mut stdin, &mut buf)?; let Some(req) = req_opt else { break 'outer; }; - match (req.kind, req.payload) { - (bidirectional::Kind::Request, bidirectional::Payload::Request(request)) => { - match request { - bidirectional::Request::ListMacros { dylib_path } => { - let res = srv.list_macros(&dylib_path).map(|macros| { - macros - .into_iter() - .map(|(name, kind)| (name, macro_kind_to_api(kind))) - .collect() - }); + match req { + bidirectional::BidirectionalMessage::Request(request) => match request { + bidirectional::Request::ListMacros { dylib_path } => { + let res = srv.list_macros(&dylib_path).map(|macros| { + macros + .into_iter() + .map(|(name, kind)| (name, macro_kind_to_api(kind))) + .collect() + }); + + send_response::<_, C>(&mut stdout, bidirectional::Response::ListMacros(res))?; + } - send_response::<_, C>( - &mut stdout, - bidirectional::Response::ListMacros(res), - )?; - } - - bidirectional::Request::ApiVersionCheck {} => { - send_response::<_, C>( - &mut stdout, - bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION), - )?; - } - - bidirectional::Request::SetConfig(config) => { - span_mode = config.span_mode; - send_response::<_, C>( - &mut stdout, - bidirectional::Response::SetConfig(config), - )?; - } - bidirectional::Request::ExpandMacro(task) => { - handle_expand::<_, _, C>( - &srv, - &mut stdin, - &mut stdout, - &mut buf, - span_mode, - *task, - )?; - } + bidirectional::Request::ApiVersionCheck {} => { + send_response::<_, C>( + &mut stdout, + bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION), + )?; } - } + + bidirectional::Request::SetConfig(config) => { + span_mode = config.span_mode; + send_response::<_, C>(&mut stdout, bidirectional::Response::SetConfig(config))?; + } + bidirectional::Request::ExpandMacro(task) => { + handle_expand::<_, _, C>( + &srv, + &mut stdin, + &mut stdout, + &mut buf, + span_mode, + *task, + )?; + } + }, _ => continue, } } @@ -265,11 +257,8 @@ fn handle_expand_ra( loop { if let Ok(res) = result_rx.try_recv() { - send_response::<_, C>( - stdout, - bidirectional::Response::ExpandMacroExtended(res), - ) - .unwrap(); + send_response::<_, C>(stdout, bidirectional::Response::ExpandMacroExtended(res)) + .unwrap(); break; } @@ -282,7 +271,7 @@ fn handle_expand_ra( send_subrequest::<_, C>(stdout, from_srv_req(subreq)).unwrap(); - let resp_opt = bidirectional::Envelope::read::<_, C>(stdin, buf).unwrap(); + let resp_opt = bidirectional::BidirectionalMessage::read::<_, C>(stdin, buf).unwrap(); let resp = match resp_opt { Some(env) => env, None => { @@ -290,11 +279,8 @@ fn handle_expand_ra( } }; - match (resp.kind, resp.payload) { - ( - bidirectional::Kind::SubResponse, - bidirectional::Payload::SubResponse(subresp), - ) => { + match resp { + bidirectional::BidirectionalMessage::SubResponse(subresp) => { let _ = subresp_tx.send(from_client_res(subresp)); } _ => { @@ -466,10 +452,7 @@ fn send_response( stdout: &mut W, resp: bidirectional::Response, ) -> io::Result<()> { - let resp = bidirectional::Envelope { - kind: bidirectional::Kind::Response, - payload: bidirectional::Payload::Response(resp), - }; + let resp = bidirectional::BidirectionalMessage::Response(resp); resp.write::(stdout) } @@ -477,9 +460,6 @@ fn send_subrequest( stdout: &mut W, resp: bidirectional::SubRequest, ) -> io::Result<()> { - let resp = bidirectional::Envelope { - kind: bidirectional::Kind::SubRequest, - payload: bidirectional::Payload::SubRequest(resp), - }; + let resp = bidirectional::BidirectionalMessage::SubRequest(resp); resp.write::(stdout) } From 7c50ee61160b8a69581ca628864a7e73f7c95750 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 22 Dec 2025 20:56:16 +0530 Subject: [PATCH 16/77] rename PostcardNew to bidirectional-postcard-prototype --- .../crates/proc-macro-srv-cli/src/main.rs | 10 +++++----- .../crates/proc-macro-srv-cli/src/main_loop.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) 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 01b261dab7482..7e50888114baf 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs @@ -31,7 +31,7 @@ fn main() -> std::io::Result<()> { clap::Arg::new("format") .long("format") .action(clap::ArgAction::Set) - .default_value("postcard-new") + .default_value("bidirectional-postcard-prototype") .value_parser(clap::builder::EnumValueParser::::new()), clap::Arg::new("version") .long("version") @@ -52,12 +52,12 @@ fn main() -> std::io::Result<()> { enum ProtocolFormat { JsonLegacy, PostcardLegacy, - PostcardNew, + BidirectionalPostcardPrototype, } impl ValueEnum for ProtocolFormat { fn value_variants<'a>() -> &'a [Self] { - &[ProtocolFormat::JsonLegacy, ProtocolFormat::PostcardLegacy, ProtocolFormat::PostcardNew] + &[ProtocolFormat::JsonLegacy, ProtocolFormat::PostcardLegacy, ProtocolFormat::BidirectionalPostcardPrototype] } fn to_possible_value(&self) -> Option { @@ -66,14 +66,14 @@ impl ValueEnum for ProtocolFormat { ProtocolFormat::PostcardLegacy => { Some(clap::builder::PossibleValue::new("postcard-legacy")) } - ProtocolFormat::PostcardNew => Some(clap::builder::PossibleValue::new("postcard-new")), + ProtocolFormat::BidirectionalPostcardPrototype => Some(clap::builder::PossibleValue::new("postcard-new")), } } fn from_str(input: &str, _ignore_case: bool) -> Result { match input { "json-legacy" => Ok(ProtocolFormat::JsonLegacy), "postcard-legacy" => Ok(ProtocolFormat::PostcardLegacy), - "postcard-new" => Ok(ProtocolFormat::PostcardNew), + "bidirectional-postcard-prototype" => Ok(ProtocolFormat::BidirectionalPostcardPrototype), _ => 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 9c66a308b18e0..8666c1367717a 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 @@ -38,7 +38,7 @@ pub(crate) fn run(format: ProtocolFormat) -> io::Result<()> { match format { ProtocolFormat::JsonLegacy => run_::(), ProtocolFormat::PostcardLegacy => run_::(), - ProtocolFormat::PostcardNew => run_new::(), + ProtocolFormat::BidirectionalPostcardPrototype => run_new::(), } } From 6c525fa2d93e92261f5c5a3da0c227af3833b018 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 23 Dec 2025 06:03:06 +0530 Subject: [PATCH 17/77] remove internal callbacks, and move callback to rust-analyzer level --- .../rust-analyzer/.github/workflows/ci.yaml | 5 +- src/tools/rust-analyzer/Cargo.lock | 1 - .../crates/load-cargo/src/lib.rs | 22 ++++- .../crates/proc-macro-api/Cargo.toml | 1 - .../src/bidirectional_protocol.rs | 85 +++++-------------- .../proc-macro-api/src/legacy_protocol.rs | 2 - .../crates/proc-macro-api/src/lib.rs | 15 ++-- .../crates/proc-macro-api/src/process.rs | 36 ++++---- .../crates/proc-macro-srv-cli/src/main.rs | 14 ++- 9 files changed, 83 insertions(+), 98 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index 81ca0254982d5..e817f770a4170 100644 --- a/src/tools/rust-analyzer/.github/workflows/ci.yaml +++ b/src/tools/rust-analyzer/.github/workflows/ci.yaml @@ -71,9 +71,8 @@ jobs: - name: Test run: cargo test --features sysroot-abi -p proc-macro-srv -p proc-macro-srv-cli -p proc-macro-api -- --quiet - # FIXME: This is temporarily disable, more info: https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Non-generic.20spans/with/564604549 - # - name: Check salsa dependency - # run: "! (cargo tree -p proc-macro-srv-cli | grep -q salsa)" + - name: Check salsa dependency + run: "! (cargo tree -p proc-macro-srv-cli | grep -q salsa)" rust: if: github.repository == 'rust-lang/rust-analyzer' diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 060a62b112b7f..7d133f99498b6 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1836,7 +1836,6 @@ dependencies = [ name = "proc-macro-api" version = "0.0.0" dependencies = [ - "base-db", "indexmap", "intern", "paths", 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 e043e4ac76344..a7b22b0d6a042 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -23,11 +23,17 @@ use ide_db::{ prime_caches, }; use itertools::Itertools; -use proc_macro_api::{MacroDylib, ProcMacroClient}; +use proc_macro_api::{ + MacroDylib, ProcMacroClient, + bidirectional_protocol::{ + msg::{SubRequest, SubResponse}, + reject_subrequests, + }, +}; use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace}; use span::Span; use vfs::{ - AbsPath, AbsPathBuf, VfsPath, + AbsPath, AbsPathBuf, FileId, VfsPath, file_set::FileSetConfig, loader::{Handle, LoadingProgress}, }; @@ -427,7 +433,7 @@ pub fn load_proc_macro( ) -> ProcMacroLoadResult { let res: Result, _> = (|| { let dylib = MacroDylib::new(path.to_path_buf()); - let vec = server.load_dylib(dylib).map_err(|e| { + let vec = server.load_dylib(dylib, Some(&mut reject_subrequests)).map_err(|e| { ProcMacroLoadingError::ProcMacroSrvError(format!("{e}").into_boxed_str()) })?; if vec.is_empty() { @@ -533,8 +539,15 @@ impl ProcMacroExpander for Expander { mixed_site: Span, current_dir: String, ) -> Result, ProcMacroExpansionError> { + let mut cb = |req| match req { + SubRequest::SourceText { file_id, start, end } => { + let file = FileId::from_raw(file_id); + let text = db.file_text(file).text(db); + let slice = text.get(start as usize..end as usize).map(ToOwned::to_owned); + Ok(SubResponse::SourceTextResult { text: slice }) + } + }; match self.0.expand( - db, subtree.view(), attrs.map(|attrs| attrs.view()), env.clone().into(), @@ -542,6 +555,7 @@ impl ProcMacroExpander for Expander { call_site, mixed_site, current_dir, + Some(&mut cb), ) { Ok(Ok(subtree)) => Ok(subtree), Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err)), 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 7e56d68964ce5..4de1a3e5dd7d5 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml @@ -19,7 +19,6 @@ serde_json = { workspace = true, features = ["unbounded_depth"] } tracing.workspace = true rustc-hash.workspace = true indexmap.workspace = true -base-db.workspace = true # local deps paths = { workspace = true, features = ["serde1"] } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs index 4cb6a1d90f19d..bd74738bbdf54 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs @@ -5,9 +5,8 @@ use std::{ sync::Arc, }; -use base_db::SourceDatabase; use paths::AbsPath; -use span::{FileId, Span}; +use span::Span; use crate::{ Codec, ProcMacro, ProcMacroKind, ServerError, @@ -29,16 +28,14 @@ use crate::{ pub mod msg; -pub trait ClientCallbacks { - fn handle_sub_request(&mut self, req: SubRequest) -> Result; -} +pub type SubCallback<'a> = &'a mut dyn FnMut(SubRequest) -> Result; pub fn run_conversation( writer: &mut dyn Write, reader: &mut dyn BufRead, buf: &mut C::Buf, msg: BidirectionalMessage, - callbacks: &mut dyn ClientCallbacks, + callback: SubCallback<'_>, ) -> Result { let encoded = C::encode(&msg).map_err(wrap_encode)?; C::write(writer, &encoded).map_err(wrap_io("failed to write initial request"))?; @@ -59,7 +56,7 @@ pub fn run_conversation( return Ok(BidirectionalMessage::Response(response)); } BidirectionalMessage::SubRequest(sr) => { - let resp = callbacks.handle_sub_request(sr)?; + let resp = callback(sr)?; let reply = BidirectionalMessage::SubResponse(resp); let encoded = C::encode(&reply).map_err(wrap_encode)?; C::write(writer, &encoded).map_err(wrap_io("failed to write sub-response"))?; @@ -86,19 +83,13 @@ fn wrap_decode(err: io::Error) -> ServerError { ServerError { message: "failed to decode message".into(), io: Some(Arc::new(err)) } } -pub(crate) fn version_check(srv: &ProcMacroServerProcess) -> Result { +pub(crate) fn version_check( + srv: &ProcMacroServerProcess, + callback: SubCallback<'_>, +) -> Result { let request = BidirectionalMessage::Request(Request::ApiVersionCheck {}); - struct NoCallbacks; - impl ClientCallbacks for NoCallbacks { - fn handle_sub_request(&mut self, _req: SubRequest) -> Result { - Err(ServerError { message: "sub-request not supported here".into(), io: None }) - } - } - - let mut callbacks = NoCallbacks; - - let response_payload = run_request(srv, request, &mut callbacks)?; + let response_payload = run_request(srv, request, callback)?; match response_payload { BidirectionalMessage::Response(Response::ApiVersionCheck(version)) => Ok(version), @@ -111,21 +102,13 @@ pub(crate) fn version_check(srv: &ProcMacroServerProcess) -> Result, ) -> Result { let request = BidirectionalMessage::Request(Request::SetConfig(ServerConfig { span_mode: SpanMode::RustAnalyzer, })); - struct NoCallbacks; - impl ClientCallbacks for NoCallbacks { - fn handle_sub_request(&mut self, _req: SubRequest) -> Result { - Err(ServerError { message: "sub-request not supported here".into(), io: None }) - } - } - - let mut callbacks = NoCallbacks; - - let response_payload = run_request(srv, request, &mut callbacks)?; + let response_payload = run_request(srv, request, callback)?; match response_payload { BidirectionalMessage::Response(Response::SetConfig(ServerConfig { span_mode })) => { @@ -139,21 +122,13 @@ pub(crate) fn enable_rust_analyzer_spans( pub(crate) fn find_proc_macros( srv: &ProcMacroServerProcess, dylib_path: &AbsPath, + callback: SubCallback<'_>, ) -> Result, String>, ServerError> { let request = BidirectionalMessage::Request(Request::ListMacros { dylib_path: dylib_path.to_path_buf().into(), }); - struct NoCallbacks; - impl ClientCallbacks for NoCallbacks { - fn handle_sub_request(&mut self, _req: SubRequest) -> Result { - Err(ServerError { message: "sub-request not supported here".into(), io: None }) - } - } - - let mut callbacks = NoCallbacks; - - let response_payload = run_request(srv, request, &mut callbacks)?; + let response_payload = run_request(srv, request, callback)?; match response_payload { BidirectionalMessage::Response(Response::ListMacros(it)) => Ok(it), @@ -163,7 +138,6 @@ pub(crate) fn find_proc_macros( pub(crate) fn expand( proc_macro: &ProcMacro, - db: &dyn SourceDatabase, subtree: tt::SubtreeView<'_, Span>, attr: Option>, env: Vec<(String, String)>, @@ -171,6 +145,7 @@ pub(crate) fn expand( call_site: Span, mixed_site: Span, current_dir: String, + callback: SubCallback<'_>, ) -> Result>, String>, crate::ServerError> { let version = proc_macro.process.version(); @@ -201,27 +176,7 @@ pub(crate) fn expand( current_dir: Some(current_dir), }))); - struct Callbacks<'de> { - db: &'de dyn SourceDatabase, - } - impl<'db> ClientCallbacks for Callbacks<'db> { - fn handle_sub_request(&mut self, req: SubRequest) -> Result { - match req { - SubRequest::SourceText { file_id, start, end } => { - let file = FileId::from_raw(file_id); - let text = self.db.file_text(file).text(self.db); - - let slice = text.get(start as usize..end as usize).map(|s| s.to_owned()); - - Ok(SubResponse::SourceTextResult { text: slice }) - } - } - } - } - - let mut callbacks = Callbacks { db }; - - let response_payload = run_request(&proc_macro.process, task, &mut callbacks)?; + let response_payload = run_request(&proc_macro.process, task, callback)?; match response_payload { BidirectionalMessage::Response(Response::ExpandMacro(it)) => Ok(it @@ -253,15 +208,19 @@ pub(crate) fn expand( fn run_request( srv: &ProcMacroServerProcess, msg: BidirectionalMessage, - callbacks: &mut dyn ClientCallbacks, + callback: SubCallback<'_>, ) -> Result { if let Some(server_error) = srv.exited() { return Err(server_error.clone()); } if srv.use_postcard() { - srv.run_bidirectional::(msg, callbacks) + srv.run_bidirectional::(msg, callback) } else { - srv.run_bidirectional::(msg, callbacks) + srv.run_bidirectional::(msg, callback) } } + +pub fn reject_subrequests(req: SubRequest) -> Result { + Err(ServerError { message: format!("{req:?} sub-request not supported here"), io: None }) +} 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 81a9f391812c6..0d16b60025d2f 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 @@ -7,7 +7,6 @@ use std::{ sync::Arc, }; -use base_db::SourceDatabase; use paths::AbsPath; use span::Span; @@ -78,7 +77,6 @@ pub(crate) fn find_proc_macros( pub(crate) fn expand( proc_macro: &ProcMacro, - _db: &dyn SourceDatabase, subtree: tt::SubtreeView<'_, Span>, attr: Option>, env: Vec<(String, String)>, 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 7b9b5b39ab1c8..0ee0c3afb584a 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 @@ -21,14 +21,13 @@ pub mod legacy_protocol; mod process; pub mod transport; -use base_db::SourceDatabase; 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}; -use crate::process::ProcMacroServerProcess; pub use crate::transport::codec::Codec; +use crate::{bidirectional_protocol::SubCallback, process::ProcMacroServerProcess}; /// The versions of the server protocol pub mod version { @@ -143,9 +142,13 @@ impl ProcMacroClient { } /// Loads a proc-macro dylib into the server process returning a list of `ProcMacro`s loaded. - pub fn load_dylib(&self, dylib: MacroDylib) -> Result, ServerError> { + pub fn load_dylib( + &self, + dylib: MacroDylib, + callback: Option>, + ) -> Result, ServerError> { let _p = tracing::info_span!("ProcMacroServer::load_dylib").entered(); - let macros = self.process.find_proc_macros(&dylib.path)?; + let macros = self.process.find_proc_macros(&dylib.path, callback)?; let dylib_path = Arc::new(dylib.path); let dylib_last_modified = std::fs::metadata(dylib_path.as_path()) @@ -219,7 +222,6 @@ impl ProcMacro { /// This includes span information and environmental context. pub fn expand( &self, - db: &dyn SourceDatabase, subtree: tt::SubtreeView<'_, Span>, attr: Option>, env: Vec<(String, String)>, @@ -227,6 +229,7 @@ impl ProcMacro { call_site: Span, mixed_site: Span, current_dir: String, + callback: Option>, ) -> Result, String>, ServerError> { let (mut subtree, mut attr) = (subtree, attr); let (mut subtree_changed, mut attr_changed); @@ -243,7 +246,6 @@ impl ProcMacro { } self.process.expand( - db, self, subtree, attr, @@ -252,6 +254,7 @@ impl ProcMacro { call_site, mixed_site, current_dir, + callback, ) } } 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 6d40250012123..01de8e98ff56b 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 @@ -7,7 +7,6 @@ use std::{ sync::{Arc, Mutex, OnceLock}, }; -use base_db::SourceDatabase; use paths::AbsPath; use semver::Version; use span::Span; @@ -15,7 +14,7 @@ use stdx::JodChild; use crate::{ Codec, ProcMacro, ProcMacroKind, ServerError, - bidirectional_protocol::{self, ClientCallbacks, msg::BidirectionalMessage}, + bidirectional_protocol::{self, SubCallback, msg::BidirectionalMessage, reject_subrequests}, legacy_protocol::{self, SpanMode}, version, }; @@ -67,7 +66,7 @@ impl ProcMacroServerProcess { { &[ ( - Some("postcard-new"), + Some("bidirectional-postcard-prototype"), Protocol::BidirectionalPostcardPrototype { mode: SpanMode::Id }, ), (Some("postcard-legacy"), Protocol::LegacyPostcard { mode: SpanMode::Id }), @@ -92,7 +91,7 @@ impl ProcMacroServerProcess { }; let mut srv = create_srv()?; tracing::info!("sending proc-macro server version check"); - match srv.version_check() { + match srv.version_check(Some(&mut reject_subrequests)) { Ok(v) if v > version::CURRENT_API_VERSION => { #[allow(clippy::disallowed_methods)] let process_version = Command::new(process_path) @@ -110,7 +109,8 @@ impl ProcMacroServerProcess { 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() + && let Ok(new_mode) = + srv.enable_rust_analyzer_spans(Some(&mut reject_subrequests)) { match &mut srv.protocol { Protocol::LegacyJson { mode } @@ -156,25 +156,30 @@ impl ProcMacroServerProcess { } /// Checks the API version of the running proc-macro server. - fn version_check(&self) -> Result { + fn version_check(&self, callback: Option>) -> Result { match self.protocol { Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { legacy_protocol::version_check(self) } Protocol::BidirectionalPostcardPrototype { .. } => { - bidirectional_protocol::version_check(self) + let cb = callback.expect("callback required for bidirectional protocol"); + bidirectional_protocol::version_check(self, cb) } } } /// Enable support for rust-analyzer span mode if the server supports it. - fn enable_rust_analyzer_spans(&self) -> Result { + fn enable_rust_analyzer_spans( + &self, + callback: Option>, + ) -> Result { match self.protocol { Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { legacy_protocol::enable_rust_analyzer_spans(self) } Protocol::BidirectionalPostcardPrototype { .. } => { - bidirectional_protocol::enable_rust_analyzer_spans(self) + let cb = callback.expect("callback required for bidirectional protocol"); + bidirectional_protocol::enable_rust_analyzer_spans(self, cb) } } } @@ -183,20 +188,21 @@ impl ProcMacroServerProcess { pub(crate) fn find_proc_macros( &self, dylib_path: &AbsPath, + callback: Option>, ) -> Result, String>, ServerError> { match self.protocol { Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { legacy_protocol::find_proc_macros(self, dylib_path) } Protocol::BidirectionalPostcardPrototype { .. } => { - bidirectional_protocol::find_proc_macros(self, dylib_path) + let cb = callback.expect("callback required for bidirectional protocol"); + bidirectional_protocol::find_proc_macros(self, dylib_path, cb) } } } pub(crate) fn expand( &self, - db: &dyn SourceDatabase, proc_macro: &ProcMacro, subtree: tt::SubtreeView<'_, Span>, attr: Option>, @@ -205,12 +211,12 @@ impl ProcMacroServerProcess { call_site: Span, mixed_site: Span, current_dir: String, + callback: Option>, ) -> Result, String>, ServerError> { match self.protocol { Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { legacy_protocol::expand( proc_macro, - db, subtree, attr, env, @@ -222,7 +228,6 @@ impl ProcMacroServerProcess { } Protocol::BidirectionalPostcardPrototype { .. } => bidirectional_protocol::expand( proc_macro, - db, subtree, attr, env, @@ -230,6 +235,7 @@ impl ProcMacroServerProcess { call_site, mixed_site, current_dir, + callback.expect("callback required for bidirectional protocol"), ), } } @@ -297,10 +303,10 @@ impl ProcMacroServerProcess { pub(crate) fn run_bidirectional( &self, initial: BidirectionalMessage, - callbacks: &mut dyn ClientCallbacks, + callback: SubCallback<'_>, ) -> Result { self.with_locked_io::(|writer, reader, buf| { - bidirectional_protocol::run_conversation::(writer, reader, buf, initial, callbacks) + bidirectional_protocol::run_conversation::(writer, reader, buf, initial, callback) }) } } 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 7e50888114baf..d73d20b584224 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 @@ -57,7 +57,11 @@ enum ProtocolFormat { impl ValueEnum for ProtocolFormat { fn value_variants<'a>() -> &'a [Self] { - &[ProtocolFormat::JsonLegacy, ProtocolFormat::PostcardLegacy, ProtocolFormat::BidirectionalPostcardPrototype] + &[ + ProtocolFormat::JsonLegacy, + ProtocolFormat::PostcardLegacy, + ProtocolFormat::BidirectionalPostcardPrototype, + ] } fn to_possible_value(&self) -> Option { @@ -66,14 +70,18 @@ impl ValueEnum for ProtocolFormat { ProtocolFormat::PostcardLegacy => { Some(clap::builder::PossibleValue::new("postcard-legacy")) } - ProtocolFormat::BidirectionalPostcardPrototype => Some(clap::builder::PossibleValue::new("postcard-new")), + ProtocolFormat::BidirectionalPostcardPrototype => { + Some(clap::builder::PossibleValue::new("postcard-new")) + } } } fn from_str(input: &str, _ignore_case: bool) -> Result { match input { "json-legacy" => Ok(ProtocolFormat::JsonLegacy), "postcard-legacy" => Ok(ProtocolFormat::PostcardLegacy), - "bidirectional-postcard-prototype" => Ok(ProtocolFormat::BidirectionalPostcardPrototype), + "bidirectional-postcard-prototype" => { + Ok(ProtocolFormat::BidirectionalPostcardPrototype) + } _ => Err(format!("unknown protocol format: {input}")), } } From cf02cff2cf3865d78013e58f9749bc112a5e33c4 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 23 Dec 2025 11:59:56 +0530 Subject: [PATCH 18/77] remove channels with callbacks in proc-macro-srv --- .../proc-macro-srv-cli/src/main_loop.rs | 135 ++++++++---------- .../crates/proc-macro-srv/src/dylib.rs | 31 +--- .../proc-macro-srv/src/dylib/proc_macros.rs | 80 +---------- .../crates/proc-macro-srv/src/lib.rs | 69 ++------- .../src/server_impl/rust_analyzer_span.rs | 31 ++-- .../src/server_impl/token_id.rs | 5 +- .../crates/proc-macro-srv/src/tests/utils.rs | 8 +- 7 files changed, 99 insertions(+), 260 deletions(-) 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 8666c1367717a..99e3d79ef29a9 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -1,7 +1,6 @@ //! The main loop of the proc-macro server. use std::io; -use crossbeam_channel::unbounded; use proc_macro_api::{ Codec, bidirectional_protocol::msg as bidirectional, @@ -82,6 +81,7 @@ fn run_new() -> io::Result<()> { } bidirectional::Request::ApiVersionCheck {} => { + // bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION).write::<_, C>(stdout) send_response::<_, C>( &mut stdout, bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION), @@ -160,6 +160,7 @@ fn handle_expand_id( def_site, call_site, mixed_site, + None, ) .map(|it| { legacy::FlatTree::from_tokenstream_raw::(it, call_site, CURRENT_API_VERSION) @@ -169,7 +170,7 @@ fn handle_expand_id( send_response::<_, C>(stdout, bidirectional::Response::ExpandMacro(res)) } -fn handle_expand_ra( +fn handle_expand_ra( srv: &proc_macro_srv::ProcMacroSrv<'_>, stdin: &mut R, stdout: &mut W, @@ -185,74 +186,69 @@ fn handle_expand_ra( macro_body, macro_name, attributes, - has_global_spans: - bidirectional::ExpnGlobals { serialize: _, def_site, call_site, mixed_site }, + has_global_spans: bidirectional::ExpnGlobals { def_site, call_site, mixed_site, .. }, span_data_table, }, } = task; let mut span_data_table = legacy::deserialize_span_data_index_map(&span_data_table); - let def_site_span = span_data_table[def_site]; - let call_site_span = span_data_table[call_site]; - let mixed_site_span = span_data_table[mixed_site]; + let def_site = span_data_table[def_site]; + let call_site = span_data_table[call_site]; + let mixed_site = span_data_table[mixed_site]; - let macro_body_ts = + 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_ts = attributes.map(|it| { + let attributes = attributes.map(|it| { it.to_tokenstream_resolved(CURRENT_API_VERSION, &span_data_table, |a, b| { srv.join_spans(a, b).unwrap_or(b) }) }); - let (subreq_tx, subreq_rx) = unbounded::(); - let (subresp_tx, subresp_rx) = unbounded::(); + let (subreq_tx, subreq_rx) = crossbeam_channel::unbounded(); + let (subresp_tx, subresp_rx) = crossbeam_channel::unbounded(); let (result_tx, result_rx) = crossbeam_channel::bounded(1); std::thread::scope(|scope| { - let srv_ref = &srv; - - scope.spawn({ - let lib = lib.clone(); - let env = env.clone(); - let current_dir = current_dir.clone(); - let macro_name = macro_name.clone(); - move || { - let res = srv_ref - .expand_with_channels( - lib, - &env, - current_dir, - ¯o_name, - macro_body_ts, - attributes_ts, - def_site_span, - call_site_span, - mixed_site_span, - subresp_rx, - subreq_tx, + scope.spawn(|| { + let callback = Box::new(move |req: proc_macro_srv::SubRequest| { + subreq_tx.send(req).unwrap(); + subresp_rx.recv().unwrap() + }); + + let res = srv + .expand( + lib, + &env, + current_dir, + ¯o_name, + macro_body, + attributes, + def_site, + call_site, + mixed_site, + Some(callback), + ) + .map(|it| { + ( + legacy::FlatTree::from_tokenstream( + it, + CURRENT_API_VERSION, + call_site, + &mut span_data_table, + ), + legacy::serialize_span_data_index_map(&span_data_table), ) - .map(|it| { - ( - legacy::FlatTree::from_tokenstream( - it, - CURRENT_API_VERSION, - call_site_span, - &mut span_data_table, - ), - legacy::serialize_span_data_index_map(&span_data_table), - ) - }) - .map(|(tree, span_data_table)| bidirectional::ExpandMacroExtended { - tree, - span_data_table, - }) - .map_err(|e| e.into_string().unwrap_or_default()) - .map_err(legacy::PanicMessage); - let _ = result_tx.send(res); - } + }) + .map(|(tree, span_data_table)| bidirectional::ExpandMacroExtended { + tree, + span_data_table, + }) + .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); + + let _ = result_tx.send(res); }); loop { @@ -264,31 +260,26 @@ fn handle_expand_ra( let subreq = match subreq_rx.recv() { Ok(r) => r, - Err(_) => { - break; - } + Err(_) => break, }; - send_subrequest::<_, C>(stdout, from_srv_req(subreq)).unwrap(); + let api_req = from_srv_req(subreq); + bidirectional::BidirectionalMessage::SubRequest(api_req).write::<_, C>(stdout).unwrap(); - let resp_opt = bidirectional::BidirectionalMessage::read::<_, C>(stdin, buf).unwrap(); - let resp = match resp_opt { - Some(env) => env, - None => { - break; - } - }; + let resp = bidirectional::BidirectionalMessage::read::<_, C>(stdin, buf) + .unwrap() + .expect("client closed connection"); match resp { - bidirectional::BidirectionalMessage::SubResponse(subresp) => { - let _ = subresp_tx.send(from_client_res(subresp)); - } - _ => { - break; + bidirectional::BidirectionalMessage::SubResponse(api_resp) => { + let srv_resp = from_client_res(api_resp); + subresp_tx.send(srv_resp).unwrap(); } + other => panic!("expected SubResponse, got {other:?}"), } } }); + Ok(()) } @@ -356,6 +347,7 @@ fn run_() -> io::Result<()> { def_site, call_site, mixed_site, + None, ) .map(|it| { legacy::FlatTree::from_tokenstream_raw::( @@ -397,6 +389,7 @@ fn run_() -> io::Result<()> { def_site, call_site, mixed_site, + None, ) .map(|it| { ( @@ -455,11 +448,3 @@ fn send_response( let resp = bidirectional::BidirectionalMessage::Response(resp); resp.write::(stdout) } - -fn send_subrequest( - stdout: &mut W, - resp: bidirectional::SubRequest, -) -> io::Result<()> { - let resp = bidirectional::BidirectionalMessage::SubRequest(resp); - resp.write::(stdout) -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs index ba089c9549d3a..082a1d77b5aa2 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs @@ -12,7 +12,7 @@ use object::Object; use paths::{Utf8Path, Utf8PathBuf}; use crate::{ - PanicMessage, ProcMacroKind, ProcMacroSrvSpan, dylib::proc_macros::ProcMacros, + PanicMessage, ProcMacroKind, ProcMacroSrvSpan, SubCallback, dylib::proc_macros::ProcMacros, token_stream::TokenStream, }; @@ -45,39 +45,14 @@ impl Expander { def_site: S, call_site: S, mixed_site: S, + callback: Option, ) -> Result, PanicMessage> where ::TokenStream: Default, { self.inner .proc_macros - .expand(macro_name, macro_body, attribute, def_site, call_site, mixed_site) - } - - pub(crate) fn expand_with_channels( - &self, - macro_name: &str, - macro_body: crate::token_stream::TokenStream, - attribute: Option>, - def_site: S, - call_site: S, - mixed_site: S, - cli_to_server: crossbeam_channel::Receiver, - server_to_cli: crossbeam_channel::Sender, - ) -> Result, crate::PanicMessage> - where - ::TokenStream: Default, - { - self.inner.proc_macros.expand_with_channels( - macro_name, - macro_body, - attribute, - def_site, - call_site, - mixed_site, - cli_to_server, - server_to_cli, - ) + .expand(macro_name, macro_body, attribute, def_site, call_site, mixed_site, callback) } pub(crate) fn list_macros(&self) -> impl Iterator { diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs index 5b6f1cf2f3328..6f6bd086de64a 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs @@ -1,8 +1,7 @@ //! Proc macro ABI +use crate::{ProcMacroKind, ProcMacroSrvSpan, SubCallback, token_stream::TokenStream}; use proc_macro::bridge; -use crate::{ProcMacroKind, ProcMacroSrvSpan, token_stream::TokenStream}; - #[repr(transparent)] pub(crate) struct ProcMacros([bridge::client::ProcMacro]); @@ -21,6 +20,7 @@ impl ProcMacros { def_site: S, call_site: S, mixed_site: S, + callback: Option, ) -> Result, crate::PanicMessage> { let parsed_attributes = attribute.unwrap_or_default(); @@ -31,65 +31,7 @@ impl ProcMacros { { let res = client.run( &bridge::server::SameThread, - S::make_server(call_site, def_site, mixed_site, None, None), - macro_body, - cfg!(debug_assertions), - ); - return res.map_err(crate::PanicMessage::from); - } - bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => { - let res = client.run( - &bridge::server::SameThread, - S::make_server(call_site, def_site, mixed_site, None, None), - macro_body, - cfg!(debug_assertions), - ); - return res.map_err(crate::PanicMessage::from); - } - bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => { - let res = client.run( - &bridge::server::SameThread, - S::make_server(call_site, def_site, mixed_site, None, None), - parsed_attributes, - macro_body, - cfg!(debug_assertions), - ); - return res.map_err(crate::PanicMessage::from); - } - _ => continue, - } - } - - Err(bridge::PanicMessage::String(format!("proc-macro `{macro_name}` is missing")).into()) - } - - pub(crate) fn expand_with_channels( - &self, - macro_name: &str, - macro_body: TokenStream, - attribute: Option>, - def_site: S, - call_site: S, - mixed_site: S, - cli_to_server: crossbeam_channel::Receiver, - server_to_cli: crossbeam_channel::Sender, - ) -> Result, crate::PanicMessage> { - let parsed_attributes = attribute.unwrap_or_default(); - - for proc_macro in &self.0 { - match proc_macro { - bridge::client::ProcMacro::CustomDerive { trait_name, client, .. } - if *trait_name == macro_name => - { - let res = client.run( - &bridge::server::SameThread, - S::make_server( - call_site, - def_site, - mixed_site, - Some(cli_to_server), - Some(server_to_cli), - ), + S::make_server(call_site, def_site, mixed_site, callback), macro_body, cfg!(debug_assertions), ); @@ -98,13 +40,7 @@ impl ProcMacros { bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => { let res = client.run( &bridge::server::SameThread, - S::make_server( - call_site, - def_site, - mixed_site, - Some(cli_to_server), - Some(server_to_cli), - ), + S::make_server(call_site, def_site, mixed_site, callback), macro_body, cfg!(debug_assertions), ); @@ -113,13 +49,7 @@ impl ProcMacros { bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => { let res = client.run( &bridge::server::SameThread, - S::make_server( - call_site, - def_site, - mixed_site, - Some(cli_to_server), - Some(server_to_cli), - ), + S::make_server(call_site, def_site, mixed_site, callback), parsed_attributes, macro_body, cfg!(debug_assertions), 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 f369ab93a2a3c..705ac930ed9c0 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 @@ -91,6 +91,8 @@ impl<'env> ProcMacroSrv<'env> { } } +pub type SubCallback = Box SubResponse + Send + Sync + 'static>; + pub enum SubRequest { SourceText { file_id: EditionedFileId, start: u32, end: u32 }, } @@ -113,6 +115,7 @@ impl ProcMacroSrv<'_> { def_site: S, call_site: S, mixed_site: S, + callback: Option, ) -> Result, PanicMessage> { let snapped_env = self.env; let expander = self.expander(lib.as_ref()).map_err(|err| PanicMessage { @@ -128,54 +131,9 @@ impl ProcMacroSrv<'_> { .stack_size(EXPANDER_STACK_SIZE) .name(macro_name.to_owned()) .spawn_scoped(s, move || { - expander - .expand(macro_name, macro_body, attribute, def_site, call_site, mixed_site) - }); - match thread.unwrap().join() { - Ok(res) => res, - Err(e) => std::panic::resume_unwind(e), - } - }); - prev_env.rollback(); - - result - } - - pub fn expand_with_channels( - &self, - lib: impl AsRef, - env: &[(String, String)], - current_dir: Option>, - macro_name: &str, - macro_body: token_stream::TokenStream, - attribute: Option>, - def_site: S, - call_site: S, - mixed_site: S, - cli_to_server: crossbeam_channel::Receiver, - server_to_cli: crossbeam_channel::Sender, - ) -> Result, PanicMessage> { - let snapped_env = self.env; - let expander = self.expander(lib.as_ref()).map_err(|err| PanicMessage { - message: Some(format!("failed to load macro: {err}")), - })?; - - let prev_env = EnvChange::apply(snapped_env, env, current_dir.as_ref().map(<_>::as_ref)); - - let result = thread::scope(|s| { - let thread = thread::Builder::new() - .stack_size(EXPANDER_STACK_SIZE) - .name(macro_name.to_owned()) - .spawn_scoped(s, move || { - expander.expand_with_channels( - macro_name, - macro_body, - attribute, - def_site, - call_site, - mixed_site, - cli_to_server, - server_to_cli, + expander.expand( + macro_name, macro_body, attribute, def_site, call_site, mixed_site, + callback, ) }); match thread.unwrap().join() { @@ -229,8 +187,7 @@ pub trait ProcMacroSrvSpan: Copy + Send + Sync { call_site: Self, def_site: Self, mixed_site: Self, - cli_to_server: Option>, - server_to_cli: Option>, + callback: Option, ) -> Self::Server; } @@ -241,15 +198,13 @@ impl ProcMacroSrvSpan for SpanId { call_site: Self, def_site: Self, mixed_site: Self, - cli_to_server: Option>, - server_to_cli: Option>, + callback: Option, ) -> Self::Server { Self::Server { call_site, def_site, mixed_site, - cli_to_server, - server_to_cli, + callback, tracked_env_vars: Default::default(), tracked_paths: Default::default(), } @@ -262,17 +217,15 @@ impl ProcMacroSrvSpan for Span { call_site: Self, def_site: Self, mixed_site: Self, - cli_to_server: Option>, - server_to_cli: Option>, + callback: Option, ) -> Self::Server { Self::Server { call_site, def_site, mixed_site, + callback, tracked_env_vars: Default::default(), tracked_paths: Default::default(), - cli_to_server, - server_to_cli, } } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index cccb74429dd61..0bce67fcd9fec 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -14,7 +14,7 @@ use proc_macro::bridge::server; use span::{FIXUP_ERASED_FILE_AST_ID_MARKER, Span, TextRange, TextSize}; use crate::{ - SubRequest, SubResponse, + SubCallback, SubRequest, SubResponse, bridge::{Diagnostic, ExpnGlobals, Literal, TokenTree}, server_impl::literal_from_str, }; @@ -29,8 +29,7 @@ pub struct RaSpanServer { pub call_site: Span, pub def_site: Span, pub mixed_site: Span, - pub cli_to_server: Option>, - pub server_to_cli: Option>, + pub callback: Option, } impl server::Types for RaSpanServer { @@ -153,21 +152,17 @@ impl server::Span for RaSpanServer { /// See PR: /// https://github.com/rust-lang/rust/pull/55780 fn source_text(&mut self, span: Self::Span) -> Option { - // FIXME requires db, needs special handling wrt fixup spans - if self.server_to_cli.is_some() && self.cli_to_server.is_some() { - let file_id = span.anchor.file_id; - let start: u32 = span.range.start().into(); - let end: u32 = span.range.end().into(); - let _ = self.server_to_cli.clone().unwrap().send(SubRequest::SourceText { - file_id, - start, - end, - }); - match self.cli_to_server.as_ref()?.recv().ok()? { - SubResponse::SourceTextResult { text } => text, - } - } else { - None + let file_id = span.anchor.file_id; + let start: u32 = span.range.start().into(); + let end: u32 = span.range.end().into(); + + let req = SubRequest::SourceText { file_id, start, end }; + + let cb = self.callback.as_mut()?; + let response = cb(req); + + match response { + SubResponse::SourceTextResult { text } => text, } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs index 268042b3bc076..3b12644ec34a9 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs @@ -9,7 +9,7 @@ use intern::Symbol; use proc_macro::bridge::server; use crate::{ - SubRequest, SubResponse, + SubCallback, bridge::{Diagnostic, ExpnGlobals, Literal, TokenTree}, server_impl::literal_from_str, }; @@ -35,8 +35,7 @@ pub struct SpanIdServer { pub call_site: Span, pub def_site: Span, pub mixed_site: Span, - pub cli_to_server: Option>, - pub server_to_cli: Option>, + pub callback: Option, } impl server::Types for SpanIdServer { diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs index 1b12308ad6c5b..61fcd810b1d9d 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs @@ -59,8 +59,9 @@ fn assert_expand_impl( let input_ts_string = format!("{input_ts:?}"); let attr_ts_string = attr_ts.as_ref().map(|it| format!("{it:?}")); - let res = - expander.expand(macro_name, input_ts, attr_ts, def_site, call_site, mixed_site).unwrap(); + let res = expander + .expand(macro_name, input_ts, attr_ts, def_site, call_site, mixed_site, None) + .unwrap(); expect.assert_eq(&format!( "{input_ts_string}{}{}{}", if attr_ts_string.is_some() { "\n\n" } else { "" }, @@ -91,7 +92,8 @@ fn assert_expand_impl( let fixture_string = format!("{fixture:?}"); let attr_string = attr.as_ref().map(|it| format!("{it:?}")); - let res = expander.expand(macro_name, fixture, attr, def_site, call_site, mixed_site).unwrap(); + let res = + expander.expand(macro_name, fixture, attr, def_site, call_site, mixed_site, None).unwrap(); expect_spanned.assert_eq(&format!( "{fixture_string}{}{}{}", if attr_string.is_some() { "\n\n" } else { "" }, From d0e14f1885eb13ae9ea453817575428f0731b456 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 23 Dec 2025 13:11:53 +0100 Subject: [PATCH 19/77] fix: Fix flycheck generations not being synced for multiple workspaces The diagnostics collection globally tracks the generation for all loaded workspaces as its shared between them, yet the flycheck actors track their own separate generations per workspace. This mismatch could cause flycheck to not work correctly when multiple workspaces were loaded. --- .../rust-analyzer/crates/rust-analyzer/src/flycheck.rs | 8 ++++---- .../rust-analyzer/crates/rust-analyzer/src/reload.rs | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs index 14a4a1752f414..b062641691885 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -147,13 +147,13 @@ pub(crate) struct FlycheckHandle { sender: Sender, _thread: stdx::thread::JoinHandle, id: usize, - generation: AtomicUsize, + generation: Arc, } impl FlycheckHandle { pub(crate) fn spawn( id: usize, - generation: DiagnosticsGeneration, + generation: Arc, sender: Sender, config: FlycheckConfig, sysroot_root: Option, @@ -163,7 +163,7 @@ impl FlycheckHandle { ) -> FlycheckHandle { let actor = FlycheckActor::new( id, - generation, + generation.load(Ordering::Relaxed), sender, config, sysroot_root, @@ -176,7 +176,7 @@ impl FlycheckHandle { stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker, format!("Flycheck{id}")) .spawn(move || actor.run(receiver)) .expect("failed to spawn thread"); - FlycheckHandle { id, generation: generation.into(), sender, _thread: thread } + FlycheckHandle { id, generation, sender, _thread: thread } } /// Schedule a re-start of the cargo check worker to do a workspace wide check. 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 317c1123659e9..e3a5ee221973b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -13,7 +13,7 @@ //! project is currently loading and we don't have a full project model, we //! still want to respond to various requests. // FIXME: This is a mess that needs some untangling work -use std::{iter, mem}; +use std::{iter, mem, sync::atomic::AtomicUsize}; use hir::{ChangeWithProcMacros, ProcMacrosBuilder, db::DefDatabase}; use ide_db::{ @@ -866,12 +866,13 @@ impl GlobalState { let invocation_strategy = config.invocation_strategy(); let next_gen = self.flycheck.iter().map(FlycheckHandle::generation).max().unwrap_or_default() + 1; + let generation = Arc::new(AtomicUsize::new(next_gen)); self.flycheck = match invocation_strategy { crate::flycheck::InvocationStrategy::Once => { vec![FlycheckHandle::spawn( 0, - next_gen, + generation.clone(), sender.clone(), config, None, @@ -915,7 +916,7 @@ impl GlobalState { .map(|(id, (root, manifest_path, target_dir), sysroot_root)| { FlycheckHandle::spawn( id, - next_gen, + generation.clone(), sender.clone(), config.clone(), sysroot_root, From 184e06223f3ada29d140d1e2559142ead18cfff2 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 23 Dec 2025 18:25:21 +0530 Subject: [PATCH 20/77] chore: remove unwanted comments, extra lines and putting cli srv on default --- src/tools/rust-analyzer/.github/workflows/ci.yaml | 2 +- src/tools/rust-analyzer/Cargo.lock | 1 - src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main.rs | 4 ++-- src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml | 1 - .../crates/proc-macro-srv/src/server_impl/token_id.rs | 1 - 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index e817f770a4170..5975272d871a7 100644 --- a/src/tools/rust-analyzer/.github/workflows/ci.yaml +++ b/src/tools/rust-analyzer/.github/workflows/ci.yaml @@ -70,7 +70,7 @@ jobs: - name: Test run: cargo test --features sysroot-abi -p proc-macro-srv -p proc-macro-srv-cli -p proc-macro-api -- --quiet - + - name: Check salsa dependency run: "! (cargo tree -p proc-macro-srv-cli | grep -q salsa)" diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 7d133f99498b6..10cd6cd43c200 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1856,7 +1856,6 @@ dependencies = [ name = "proc-macro-srv" version = "0.0.0" dependencies = [ - "crossbeam-channel", "expect-test", "intern", "libc", 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 d73d20b584224..bdfdb50002e12 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("bidirectional-postcard-prototype") + .default_value("json-legacy") .value_parser(clap::builder::EnumValueParser::::new()), clap::Arg::new("version") .long("version") @@ -71,7 +71,7 @@ impl ValueEnum for ProtocolFormat { Some(clap::builder::PossibleValue::new("postcard-legacy")) } ProtocolFormat::BidirectionalPostcardPrototype => { - Some(clap::builder::PossibleValue::new("postcard-new")) + Some(clap::builder::PossibleValue::new("bidirectional-postcard-prototype")) } } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml index b2abda0bfd7f4..361017178409b 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml @@ -22,7 +22,6 @@ paths.workspace = true # span = {workspace = true, default-features = false} does not work span = { path = "../span", version = "0.0.0", default-features = false} intern.workspace = true -crossbeam-channel.workspace = true ra-ap-rustc_lexer.workspace = true diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs index 3b12644ec34a9..9db7597d849f4 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs @@ -141,7 +141,6 @@ impl server::Span for SpanIdServer { /// See PR: /// https://github.com/rust-lang/rust/pull/55780 fn source_text(&mut self, _span: Self::Span) -> Option { - // FIXME requires db, needs special handling wrt fixup spans None } From d6a2a52883d5b47cb7232466a3e1911a1d46680e Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 23 Dec 2025 22:30:00 +0530 Subject: [PATCH 21/77] return error on combination of bidirectional message and json framing --- .../proc-macro-api/src/bidirectional_protocol.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs index bd74738bbdf54..e919ff48fe4cb 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs @@ -22,7 +22,7 @@ use crate::{ }, }, process::ProcMacroServerProcess, - transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, + transport::codec::postcard::PostcardProtocol, version, }; @@ -210,14 +210,16 @@ fn run_request( msg: BidirectionalMessage, callback: SubCallback<'_>, ) -> Result { - if let Some(server_error) = srv.exited() { - return Err(server_error.clone()); + if let Some(err) = srv.exited() { + return Err(err.clone()); } - if srv.use_postcard() { - srv.run_bidirectional::(msg, callback) - } else { - srv.run_bidirectional::(msg, callback) + match srv.use_postcard() { + true => srv.run_bidirectional::(msg, callback), + false => Err(ServerError { + message: "bidirectional messaging does not support JSON".to_owned(), + io: None, + }), } } From df2833d5d34aafcd6adc3a91ca297795a0b830ac Mon Sep 17 00:00:00 2001 From: Ada Alakbarova <58857108+ada4a@users.noreply.github.com> Date: Tue, 23 Dec 2025 21:20:11 +0100 Subject: [PATCH 22/77] Fix typo in a URL --- .../rust-analyzer/docs/book/src/contributing/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/docs/book/src/contributing/architecture.md b/src/tools/rust-analyzer/docs/book/src/contributing/architecture.md index 67653ebd74370..50f60bcdccc1b 100644 --- a/src/tools/rust-analyzer/docs/book/src/contributing/architecture.md +++ b/src/tools/rust-analyzer/docs/book/src/contributing/architecture.md @@ -10,7 +10,7 @@ See also these implementation-related blog posts: * * -* +* * * From 5c99ebf42c004696e8dbae8c24dd481d00f180f0 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 24 Dec 2025 21:46:57 +0800 Subject: [PATCH 23/77] Fix indent for convert_to_guarded_return --- .../src/handlers/convert_to_guarded_return.rs | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index 05ccd5b9bff69..08b114072fd9f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -218,7 +218,7 @@ fn let_stmt_to_guarded_return( let let_else_stmt = make::let_else_stmt( happy_pattern, let_stmt.ty(), - expr, + expr.reset_indent(), ast::make::tail_only_block_expr(early_expression), ); let let_else_stmt = let_else_stmt.indent(let_indent_level); @@ -275,11 +275,11 @@ fn flat_let_chain(mut expr: ast::Expr) -> Vec { && bin_expr.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And)) && let (Some(lhs), Some(rhs)) = (bin_expr.lhs(), bin_expr.rhs()) { - reduce_cond(rhs); + reduce_cond(rhs.reset_indent()); expr = lhs; } - reduce_cond(expr); + reduce_cond(expr.reset_indent()); chains.reverse(); chains } @@ -1019,6 +1019,63 @@ fn main() { ); } + #[test] + fn indentations() { + check_assist( + convert_to_guarded_return, + r#" +mod indent { + fn main() { + $0if let None = Some( + 92 + ) { + foo( + 93 + ); + } + } +} +"#, + r#" +mod indent { + fn main() { + let None = Some( + 92 + ) else { return }; + foo( + 93 + ); + } +} +"#, + ); + + check_assist( + convert_to_guarded_return, + r#" +//- minicore: option +mod indent { + fn foo(_: i32) -> Option { None } + fn main() { + $0let x = foo( + 2 + ); + } +} +"#, + r#" +mod indent { + fn foo(_: i32) -> Option { None } + fn main() { + let Some(x) = foo( + 2 + ) else { return }; + } +} +"#, + ); + } + #[test] fn ignore_already_converted_if() { check_assist_not_applicable( From faac1b517f59e92b51b176fb0bbec26b82a2a0d6 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Thu, 25 Dec 2025 04:19:22 +0000 Subject: [PATCH 24/77] Prepare for merging from rust-lang/rust This updates the rust-version file to e7d44143a12a526488e4f0c0d7ea8e62a4fe9354. --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index 95e1cd0db37c1..5ffe95a0b54fa 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -f41f40408d719aa9ae0c6bfa17619d8f3f9e5b99 +e7d44143a12a526488e4f0c0d7ea8e62a4fe9354 From 38ff5a7a5e45457d99f0dde0ffeeecfcdf703f1e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 25 Dec 2025 10:38:05 +0100 Subject: [PATCH 25/77] Introduce cargo-machete ci step --- src/tools/rust-analyzer/.github/workflows/ci.yaml | 6 ++++++ src/tools/rust-analyzer/Cargo.lock | 4 ---- src/tools/rust-analyzer/crates/hir-def/Cargo.toml | 1 - src/tools/rust-analyzer/crates/intern/Cargo.toml | 1 - .../rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml | 1 - src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml | 1 - src/tools/rust-analyzer/crates/test-fixture/Cargo.toml | 1 - 7 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index 5975272d871a7..1a0deee564aeb 100644 --- a/src/tools/rust-analyzer/.github/workflows/ci.yaml +++ b/src/tools/rust-analyzer/.github/workflows/ci.yaml @@ -122,6 +122,12 @@ jobs: - name: Run tests run: cargo nextest run --no-fail-fast --hide-progress-bar --status-level fail + - name: Install cargo-machete + uses: taiki-e/install-action@cargo-machete + + - name: Run cargo-machete + run: cargo machete + - name: Run Clippy if: matrix.os == 'macos-latest' run: cargo clippy --all-targets -- -D clippy::disallowed_macros -D clippy::dbg_macro -D clippy::todo -D clippy::print_stdout -D clippy::print_stderr diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 1e924d92f4244..a2eb8b1397b85 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -821,7 +821,6 @@ dependencies = [ "intern", "itertools 0.14.0", "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mbe", "query-group-macro", "ra-ap-rustc_abi", "ra-ap-rustc_parse_format", @@ -1219,7 +1218,6 @@ dependencies = [ "hashbrown 0.14.5", "rayon", "rustc-hash 2.1.1", - "smallvec", "triomphe", ] @@ -1882,7 +1880,6 @@ dependencies = [ "postcard", "proc-macro-api", "proc-macro-srv", - "tt", ] [[package]] @@ -2782,7 +2779,6 @@ dependencies = [ "hir-expand", "intern", "paths", - "rustc-hash 2.1.1", "span", "stdx", "test-utils", diff --git a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml index a9b51e347d609..46acf3de62644 100644 --- a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml @@ -40,7 +40,6 @@ intern.workspace = true base-db.workspace = true syntax.workspace = true hir-expand.workspace = true -mbe.workspace = true cfg.workspace = true tt.workspace = true span.workspace = true diff --git a/src/tools/rust-analyzer/crates/intern/Cargo.toml b/src/tools/rust-analyzer/crates/intern/Cargo.toml index 6414f091783c8..ad73c191c0471 100644 --- a/src/tools/rust-analyzer/crates/intern/Cargo.toml +++ b/src/tools/rust-analyzer/crates/intern/Cargo.toml @@ -18,7 +18,6 @@ dashmap.workspace = true hashbrown.workspace = true rustc-hash.workspace = true triomphe.workspace = true -smallvec.workspace = true rayon.workspace = true [lints] 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 aa153897fa964..2c6e5a16ee06f 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 @@ -13,7 +13,6 @@ publish = false [dependencies] 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"]} diff --git a/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml b/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml index 8910911ff0259..b2f238efc0255 100644 --- a/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml +++ b/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml @@ -10,7 +10,6 @@ cargo-fuzz = true [dependencies] syntax = { path = "..", version = "0.0.0" } -text-edit = { path = "../../text-edit", version = "0.0.0" } libfuzzer-sys = "0.4.5" # Prevent this from interfering with workspaces diff --git a/src/tools/rust-analyzer/crates/test-fixture/Cargo.toml b/src/tools/rust-analyzer/crates/test-fixture/Cargo.toml index 7760ae7aa0450..74a4f8363248b 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/Cargo.toml +++ b/src/tools/rust-analyzer/crates/test-fixture/Cargo.toml @@ -14,7 +14,6 @@ test-utils.workspace = true tt.workspace = true cfg.workspace = true base-db.workspace = true -rustc-hash.workspace = true span.workspace = true stdx.workspace = true intern.workspace = true From 2c7bd6d46206b5477a26c1b98b2ac09cfa9cb07b Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 25 Dec 2025 15:44:56 +0200 Subject: [PATCH 26/77] Store closures with "tupled" inputs And remove it when needed, the opposite of what was previously, where we stored without a tuple and tupled for the solver (because it requires that). Because this is what rustc does, and generally, the closer we follow rustc, the easier our lives become. The weird name `signature_unclosure()` also comes from rustc. --- .../crates/hir-ty/src/display.rs | 53 ++++++++--------- .../crates/hir-ty/src/infer/closure.rs | 11 +++- .../hir-ty/src/infer/closure/analysis.rs | 4 +- .../crates/hir-ty/src/infer/coerce.rs | 38 +++++++----- .../rust-analyzer/crates/hir-ty/src/layout.rs | 10 ++-- .../crates/hir-ty/src/mir/borrowck.rs | 3 +- .../crates/hir-ty/src/mir/eval.rs | 6 +- .../crates/hir-ty/src/mir/lower.rs | 9 +-- .../hir-ty/src/next_solver/generic_arg.rs | 58 ++----------------- .../crates/hir-ty/src/next_solver/ty.rs | 27 +++++++-- 10 files changed, 99 insertions(+), 120 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index b9e23464e9803..0f989d6c5851f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -1383,37 +1383,30 @@ impl<'db> HirDisplay<'db> for Ty<'db> { } _ => (), } - let sig = substs - .split_closure_args_untupled() - .closure_sig_as_fn_ptr_ty - .callable_sig(interner); - if let Some(sig) = sig { - let sig = sig.skip_binder(); - let InternedClosure(def, _) = db.lookup_intern_closure(id); - let infer = InferenceResult::for_body(db, def); - let (_, kind) = infer.closure_info(id); - match f.closure_style { - ClosureStyle::ImplFn => write!(f, "impl {kind:?}(")?, - ClosureStyle::RANotation => write!(f, "|")?, - _ => unreachable!(), - } - if sig.inputs().is_empty() { - } else if f.should_truncate() { - write!(f, "{TYPE_HINT_TRUNCATION}")?; - } else { - f.write_joined(sig.inputs(), ", ")?; - }; - match f.closure_style { - ClosureStyle::ImplFn => write!(f, ")")?, - ClosureStyle::RANotation => write!(f, "|")?, - _ => unreachable!(), - } - if f.closure_style == ClosureStyle::RANotation || !sig.output().is_unit() { - write!(f, " -> ")?; - sig.output().hir_fmt(f)?; - } + let sig = interner.signature_unclosure(substs.as_closure().sig(), Safety::Safe); + let sig = sig.skip_binder(); + let InternedClosure(def, _) = db.lookup_intern_closure(id); + let infer = InferenceResult::for_body(db, def); + let (_, kind) = infer.closure_info(id); + match f.closure_style { + ClosureStyle::ImplFn => write!(f, "impl {kind:?}(")?, + ClosureStyle::RANotation => write!(f, "|")?, + _ => unreachable!(), + } + if sig.inputs().is_empty() { + } else if f.should_truncate() { + write!(f, "{TYPE_HINT_TRUNCATION}")?; } else { - write!(f, "{{closure}}")?; + f.write_joined(sig.inputs(), ", ")?; + }; + match f.closure_style { + ClosureStyle::ImplFn => write!(f, ")")?, + ClosureStyle::RANotation => write!(f, "|")?, + _ => unreachable!(), + } + if f.closure_style == ClosureStyle::RANotation || !sig.output().is_unit() { + write!(f, " -> ")?; + sig.output().hir_fmt(f)?; } } TyKind::CoroutineClosure(id, args) => { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs index 19ffa3a9398a4..d1391ad24e4df 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs @@ -68,7 +68,6 @@ impl<'db> InferenceContext<'_, 'db> { let ClosureSignatures { bound_sig, liberated_sig } = self.sig_of_closure(arg_types, ret_type, expected_sig); let body_ret_ty = bound_sig.output().skip_binder(); - let sig_ty = Ty::new_fn_ptr(interner, bound_sig); let parent_args = GenericArgs::identity_for_item(interner, self.generic_def.into()); // FIXME: Make this an infer var and infer it later. @@ -117,6 +116,16 @@ impl<'db> InferenceContext<'_, 'db> { } None => {} }; + let sig = bound_sig.map_bound(|sig| { + interner.mk_fn_sig( + [Ty::new_tup(interner, sig.inputs())], + sig.output(), + sig.c_variadic, + sig.safety, + sig.abi, + ) + }); + let sig_ty = Ty::new_fn_ptr(interner, sig); // FIXME: Infer the kind later if needed. let parts = ClosureArgsParts { parent_args: parent_args.as_slice(), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs index 5b0360071d9da..6ae6f75525028 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs @@ -15,7 +15,7 @@ use hir_def::{ }; use rustc_ast_ir::Mutability; use rustc_hash::{FxHashMap, FxHashSet}; -use rustc_type_ir::inherent::{IntoKind, Ty as _}; +use rustc_type_ir::inherent::{GenericArgs as _, IntoKind, Ty as _}; use smallvec::{SmallVec, smallvec}; use stdx::{format_to, never}; use syntax::utils::is_raw_identifier; @@ -103,7 +103,7 @@ impl CapturedItem { pub fn ty<'db>(&self, db: &'db dyn HirDatabase, subst: GenericArgs<'db>) -> Ty<'db> { let interner = DbInterner::new_no_crate(db); - self.ty.get().instantiate(interner, subst.split_closure_args_untupled().parent_args) + self.ty.get().instantiate(interner, subst.as_closure().parent_args()) } pub fn kind(&self) -> CaptureKind { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs index bb9cb1c1ca098..ec7dad0fa08ca 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs @@ -46,7 +46,9 @@ use rustc_type_ir::{ BoundVar, DebruijnIndex, TyVid, TypeAndMut, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, error::TypeError, - inherent::{Const as _, GenericArg as _, IntoKind, Safety, SliceLike, Ty as _}, + inherent::{ + Const as _, GenericArg as _, GenericArgs as _, IntoKind, Safety as _, SliceLike, Ty as _, + }, }; use smallvec::{SmallVec, smallvec}; use tracing::{debug, instrument}; @@ -63,6 +65,7 @@ use crate::{ Canonical, ClauseKind, CoercePredicate, Const, ConstKind, DbInterner, ErrorGuaranteed, GenericArgs, ParamEnv, PolyFnSig, PredicateKind, Region, RegionKind, TraitRef, Ty, TyKind, TypingMode, + abi::Safety, infer::{ DbInternerInferExt, InferCtxt, InferOk, InferResult, relate::RelateResult, @@ -921,10 +924,8 @@ where // or // `unsafe fn(arg0,arg1,...) -> _` let safety = hdr.safety; - let closure_sig = args_a.closure_sig_untupled().map_bound(|mut sig| { - sig.safety = hdr.safety; - sig - }); + let closure_sig = + self.interner().signature_unclosure(args_a.as_closure().sig(), safety); let pointer_ty = Ty::new_fn_ptr(self.interner(), closure_sig); debug!("coerce_closure_to_fn(a={:?}, b={:?}, pty={:?})", a, b, pointer_ty); self.unify_and( @@ -1125,23 +1126,28 @@ impl<'db> InferenceContext<'_, 'db> { } (TyKind::Closure(_, args), TyKind::FnDef(..)) => { let b_sig = new_ty.fn_sig(self.table.interner()); - let a_sig = args.closure_sig_untupled().map_bound(|mut sig| { - sig.safety = b_sig.safety(); - sig - }); + let a_sig = self + .interner() + .signature_unclosure(args.as_closure().sig(), b_sig.safety()); (Some(a_sig), Some(b_sig)) } (TyKind::FnDef(..), TyKind::Closure(_, args)) => { let a_sig = prev_ty.fn_sig(self.table.interner()); - let b_sig = args.closure_sig_untupled().map_bound(|mut sig| { - sig.safety = a_sig.safety(); - sig - }); + let b_sig = self + .interner() + .signature_unclosure(args.as_closure().sig(), a_sig.safety()); (Some(a_sig), Some(b_sig)) } - (TyKind::Closure(_, args_a), TyKind::Closure(_, args_b)) => { - (Some(args_a.closure_sig_untupled()), Some(args_b.closure_sig_untupled())) - } + (TyKind::Closure(_, args_a), TyKind::Closure(_, args_b)) => ( + Some( + self.interner() + .signature_unclosure(args_a.as_closure().sig(), Safety::Safe), + ), + Some( + self.interner() + .signature_unclosure(args_b.as_closure().sig(), Safety::Safe), + ), + ), _ => (None, None), } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs index b6ad3624ae28b..525100439f5b8 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs @@ -14,7 +14,10 @@ use rustc_abi::{ TargetDataLayout, WrappingRange, }; use rustc_index::IndexVec; -use rustc_type_ir::{FloatTy, IntTy, UintTy, inherent::IntoKind}; +use rustc_type_ir::{ + FloatTy, IntTy, UintTy, + inherent::{GenericArgs as _, IntoKind}, +}; use triomphe::Arc; use crate::{ @@ -335,10 +338,7 @@ pub fn layout_of_ty_query( let fields = captures .iter() .map(|it| { - let ty = it - .ty - .get() - .instantiate(interner, args.split_closure_args_untupled().parent_args); + let ty = it.ty.get().instantiate(interner, args.as_closure().parent_args()); db.layout_of_ty(ty.store(), trait_env.clone()) }) .collect::, _>>()?; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs index 941b6c75bfe74..dece61a57df55 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs @@ -8,6 +8,7 @@ use std::iter; use hir_def::{DefWithBodyId, HasModule}; use la_arena::ArenaMap; use rustc_hash::FxHashMap; +use rustc_type_ir::inherent::GenericArgs as _; use stdx::never; use triomphe::Arc; @@ -123,7 +124,7 @@ fn make_fetch_closure_field<'db>( let InternedClosure(def, _) = db.lookup_intern_closure(c); let infer = InferenceResult::for_body(db, def); let (captures, _) = infer.closure_info(c); - let parent_subst = subst.split_closure_args_untupled().parent_args; + let parent_subst = subst.as_closure().parent_args(); let interner = DbInterner::new_no_crate(db); captures.get(f).expect("broken closure field").ty.get().instantiate(interner, parent_subst) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index c7156bb11ed63..2d1368858bcfd 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -27,7 +27,7 @@ use rustc_ast_ir::Mutability; use rustc_hash::{FxHashMap, FxHashSet}; use rustc_type_ir::{ AliasTyKind, - inherent::{AdtDef, IntoKind, Region as _, SliceLike, Ty as _}, + inherent::{AdtDef, GenericArgs as _, IntoKind, Region as _, SliceLike, Ty as _}, }; use span::FileId; use stdx::never; @@ -731,7 +731,7 @@ impl<'db> Evaluator<'db> { let InternedClosure(def, _) = self.db.lookup_intern_closure(c); let infer = InferenceResult::for_body(self.db, def); let (captures, _) = infer.closure_info(c); - let parent_subst = subst.split_closure_args_untupled().parent_args; + let parent_subst = subst.as_closure().parent_args(); captures .get(f) .expect("broken closure field") @@ -2771,7 +2771,7 @@ impl<'db> Evaluator<'db> { TyKind::Closure(closure, subst) => self.exec_closure( closure.0, func_data, - GenericArgs::new_from_slice(subst.split_closure_args_untupled().parent_args), + GenericArgs::new_from_slice(subst.as_closure().parent_args()), destination, &args[1..], locals, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index e8d42bed9fa7a..762f91fb0d997 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -19,7 +19,7 @@ use hir_expand::name::Name; use la_arena::ArenaMap; use rustc_apfloat::Float; use rustc_hash::FxHashMap; -use rustc_type_ir::inherent::{Const as _, IntoKind, Ty as _}; +use rustc_type_ir::inherent::{Const as _, GenericArgs as _, IntoKind, Ty as _}; use span::{Edition, FileId}; use syntax::TextRange; use triomphe::Arc; @@ -44,6 +44,7 @@ use crate::{ next_solver::{ Const, DbInterner, ParamConst, ParamEnv, Region, StoredGenericArgs, StoredTy, TyKind, TypingMode, UnevaluatedConst, + abi::Safety, infer::{DbInternerInferExt, InferCtxt}, }, traits::FnTrait, @@ -2138,11 +2139,7 @@ pub fn mir_body_for_closure_query<'db>( .store(), }); ctx.result.param_locals.push(closure_local); - let Some(sig) = - substs.split_closure_args_untupled().closure_sig_as_fn_ptr_ty.callable_sig(ctx.interner()) - else { - implementation_error!("closure has not callable sig"); - }; + let sig = ctx.interner().signature_unclosure(substs.as_closure().sig(), Safety::Safe); let resolver_guard = ctx.resolver.update_to_inner_scope(db, owner, expr); let current = ctx.lower_params_and_bindings( args.iter().zip(sig.skip_binder().inputs().iter()).map(|(it, y)| (*it, *y)), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generic_arg.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generic_arg.rs index 9936e443210aa..72cf2f9f07f20 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generic_arg.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generic_arg.rs @@ -11,9 +11,9 @@ use std::{hint::unreachable_unchecked, marker::PhantomData, ptr::NonNull}; use hir_def::{GenericDefId, GenericParamId}; use intern::InternedRef; use rustc_type_ir::{ - ClosureArgs, ConstVid, CoroutineArgs, CoroutineClosureArgs, FallibleTypeFolder, FnSigTys, - GenericTypeVisitable, Interner, TyKind, TyVid, TypeFoldable, TypeFolder, TypeVisitable, - TypeVisitor, Variance, + ClosureArgs, ConstVid, CoroutineArgs, CoroutineClosureArgs, FallibleTypeFolder, + GenericTypeVisitable, Interner, TyVid, TypeFoldable, TypeFolder, TypeVisitable, TypeVisitor, + Variance, inherent::{GenericArg as _, GenericsOf, IntoKind, SliceLike, Term as _, Ty as _}, relate::{Relate, VarianceDiagInfo}, walk::TypeWalker, @@ -21,12 +21,11 @@ use rustc_type_ir::{ use smallvec::SmallVec; use crate::next_solver::{ - ConstInterned, PolyFnSig, RegionInterned, TyInterned, impl_foldable_for_interned_slice, - interned_slice, + ConstInterned, RegionInterned, TyInterned, impl_foldable_for_interned_slice, interned_slice, }; use super::{ - Const, DbInterner, EarlyParamRegion, ErrorGuaranteed, ParamConst, Region, SolverDefId, Ty, Tys, + Const, DbInterner, EarlyParamRegion, ErrorGuaranteed, ParamConst, Region, SolverDefId, Ty, generics::Generics, }; @@ -566,33 +565,6 @@ impl<'db> GenericArgs<'db> { } } - pub fn closure_sig_untupled(self) -> PolyFnSig<'db> { - let TyKind::FnPtr(inputs_and_output, hdr) = - self.split_closure_args_untupled().closure_sig_as_fn_ptr_ty.kind() - else { - unreachable!("not a function pointer") - }; - inputs_and_output.with(hdr) - } - - /// A "sensible" `.split_closure_args()`, where the arguments are not in a tuple. - pub fn split_closure_args_untupled(self) -> rustc_type_ir::ClosureArgsParts> { - // FIXME: should use `ClosureSubst` when possible - match self.as_slice() { - [parent_args @ .., closure_kind_ty, sig_ty, tupled_upvars_ty] => { - rustc_type_ir::ClosureArgsParts { - parent_args, - closure_sig_as_fn_ptr_ty: sig_ty.expect_ty(), - closure_kind_ty: closure_kind_ty.expect_ty(), - tupled_upvars_ty: tupled_upvars_ty.expect_ty(), - } - } - _ => { - unreachable!("unexpected closure sig"); - } - } - } - pub fn types(self) -> impl Iterator> { self.iter().filter_map(|it| it.as_type()) } @@ -688,27 +660,9 @@ impl<'db> rustc_type_ir::inherent::GenericArgs> for GenericArgs< // FIXME: should use `ClosureSubst` when possible match self.as_slice() { [parent_args @ .., closure_kind_ty, sig_ty, tupled_upvars_ty] => { - let interner = DbInterner::conjure(); - // This is stupid, but the next solver expects the first input to actually be a tuple - let sig_ty = match sig_ty.expect_ty().kind() { - TyKind::FnPtr(sig_tys, header) => Ty::new( - interner, - TyKind::FnPtr( - sig_tys.map_bound(|s| { - let inputs = Ty::new_tup(interner, s.inputs()); - let output = s.output(); - FnSigTys { - inputs_and_output: Tys::new_from_slice(&[inputs, output]), - } - }), - header, - ), - ), - _ => unreachable!("sig_ty should be last"), - }; rustc_type_ir::ClosureArgsParts { parent_args, - closure_sig_as_fn_ptr_ty: sig_ty, + closure_sig_as_fn_ptr_ty: sig_ty.expect_ty(), closure_kind_ty: closure_kind_ty.expect_ty(), tupled_upvars_ty: tupled_upvars_ty.expect_ty(), } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs index c89831bd40779..030ba37015f2e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs @@ -26,6 +26,7 @@ use rustc_type_ir::{ }; use crate::{ + FnAbi, db::{HirDatabase, InternedCoroutine}, lower::GenericPredicates, next_solver::{ @@ -495,10 +496,9 @@ impl<'db> Ty<'db> { Some(interner.fn_sig(callable).instantiate(interner, args)) } TyKind::FnPtr(sig, hdr) => Some(sig.with(hdr)), - TyKind::Closure(_, closure_args) => closure_args - .split_closure_args_untupled() - .closure_sig_as_fn_ptr_ty - .callable_sig(interner), + TyKind::Closure(_, closure_args) => { + Some(interner.signature_unclosure(closure_args.as_closure().sig(), Safety::Safe)) + } TyKind::CoroutineClosure(coroutine_id, args) => { Some(args.as_coroutine_closure().coroutine_closure_sig().map_bound(|sig| { let unit_ty = Ty::new_unit(interner); @@ -1426,3 +1426,22 @@ impl<'db> PlaceholderLike> for PlaceholderTy { Placeholder { universe: ui, bound: BoundTy { var, kind: BoundTyKind::Anon } } } } + +impl<'db> DbInterner<'db> { + /// Given a closure signature, returns an equivalent fn signature. Detuples + /// and so forth -- so e.g., if we have a sig with `Fn<(u32, i32)>` then + /// you would get a `fn(u32, i32)`. + /// `unsafety` determines the unsafety of the fn signature. If you pass + /// `Safety::Unsafe` in the previous example, then you would get + /// an `unsafe fn (u32, i32)`. + /// It cannot convert a closure that requires unsafe. + pub fn signature_unclosure(self, sig: PolyFnSig<'db>, safety: Safety) -> PolyFnSig<'db> { + sig.map_bound(|s| { + let params = match s.inputs()[0].kind() { + TyKind::Tuple(params) => params, + _ => panic!(), + }; + self.mk_fn_sig(params, s.output(), s.c_variadic, safety, FnAbi::Rust) + }) + } +} From ebb0d0b7bd58510f6dd2bc21d411f5aec3116040 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 2 Dec 2025 13:54:28 +0200 Subject: [PATCH 27/77] Make builtin derives cheaper, by not really expanding them, instead store them unexpanded --- .../rust-analyzer/crates/hir-def/src/attrs.rs | 2 + .../crates/hir-def/src/builtin_derive.rs | 61 ++ .../crates/hir-def/src/item_scope.rs | 15 +- .../crates/hir-def/src/lang_item.rs | 51 +- .../rust-analyzer/crates/hir-def/src/lib.rs | 29 + .../crates/hir-def/src/nameres/collector.rs | 213 +++++-- .../hir-def/src/nameres/tests/macros.rs | 8 +- .../hir-expand/src/builtin/derive_macro.rs | 5 +- .../crates/hir-ty/src/builtin_derive.rs | 575 ++++++++++++++++++ .../rust-analyzer/crates/hir-ty/src/drop.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/lib.rs | 1 + .../rust-analyzer/crates/hir-ty/src/lower.rs | 7 + .../crates/hir-ty/src/method_resolution.rs | 54 +- .../hir-ty/src/method_resolution/probe.rs | 6 +- .../crates/hir-ty/src/next_solver/def_id.rs | 74 ++- .../crates/hir-ty/src/next_solver/generics.rs | 18 +- .../hir-ty/src/next_solver/infer/select.rs | 8 +- .../crates/hir-ty/src/next_solver/interner.rs | 113 ++-- .../crates/hir-ty/src/next_solver/solver.rs | 10 +- .../crates/hir-ty/src/tests/incremental.rs | 40 +- .../crates/intern/src/symbol/symbols.rs | 2 + 21 files changed, 1143 insertions(+), 151 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs create mode 100644 src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index 34a9230794d18..e91d72a701cff 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -188,6 +188,7 @@ fn match_attr_flags(attr_flags: &mut AttrFlags, attr: Meta) -> ControlFlow attr_flags.insert(AttrFlags::IS_DEPRECATED), "macro_export" => attr_flags.insert(AttrFlags::IS_MACRO_EXPORT), "no_mangle" => attr_flags.insert(AttrFlags::NO_MANGLE), + "pointee" => attr_flags.insert(AttrFlags::IS_POINTEE), "non_exhaustive" => attr_flags.insert(AttrFlags::NON_EXHAUSTIVE), "ignore" => attr_flags.insert(AttrFlags::IS_IGNORE), "bench" => attr_flags.insert(AttrFlags::IS_BENCH), @@ -289,6 +290,7 @@ bitflags::bitflags! { const RUSTC_PAREN_SUGAR = 1 << 42; const RUSTC_COINDUCTIVE = 1 << 43; const RUSTC_FORCE_INLINE = 1 << 44; + const IS_POINTEE = 1 << 45; } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs b/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs new file mode 100644 index 0000000000000..e0e163783f436 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs @@ -0,0 +1,61 @@ +//! Definition of builtin derive impls. +//! +//! To save time and memory, builtin derives are not really expanded. Instead, we record them +//! and create their impls based on lowered data, see crates/hir-ty/src/builtin_derive.rs. + +use hir_expand::builtin::BuiltinDeriveExpander; + +macro_rules! declare_enum { + ( $( $trait:ident ),* $(,)? ) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum BuiltinDeriveImplTrait { + $( $trait, )* + } + + impl BuiltinDeriveImplTrait { + #[inline] + pub fn get_id(self, lang_items: &crate::lang_item::LangItems) -> Option { + match self { + $( Self::$trait => lang_items.$trait, )* + } + } + } + }; +} + +declare_enum!( + Copy, + Clone, + Default, + Debug, + Hash, + Ord, + PartialOrd, + Eq, + PartialEq, + CoerceUnsized, + DispatchFromDyn, +); + +pub(crate) fn with_derive_traits( + derive: BuiltinDeriveExpander, + mut f: impl FnMut(BuiltinDeriveImplTrait), +) { + let trait_ = match derive { + BuiltinDeriveExpander::Copy => BuiltinDeriveImplTrait::Copy, + BuiltinDeriveExpander::Clone => BuiltinDeriveImplTrait::Clone, + BuiltinDeriveExpander::Default => BuiltinDeriveImplTrait::Default, + BuiltinDeriveExpander::Debug => BuiltinDeriveImplTrait::Debug, + BuiltinDeriveExpander::Hash => BuiltinDeriveImplTrait::Hash, + BuiltinDeriveExpander::Ord => BuiltinDeriveImplTrait::Ord, + BuiltinDeriveExpander::PartialOrd => BuiltinDeriveImplTrait::PartialOrd, + BuiltinDeriveExpander::Eq => BuiltinDeriveImplTrait::Eq, + BuiltinDeriveExpander::PartialEq => BuiltinDeriveImplTrait::PartialEq, + BuiltinDeriveExpander::CoercePointee => { + f(BuiltinDeriveImplTrait::CoerceUnsized); + f(BuiltinDeriveImplTrait::DispatchFromDyn); + return; + } + }; + f(trait_); +} 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 3ffeebfaf2c98..4f3700e8e41c7 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 @@ -16,8 +16,8 @@ use syntax::ast; use thin_vec::ThinVec; use crate::{ - AdtId, BuiltinType, ConstId, ExternBlockId, ExternCrateId, FxIndexMap, HasModule, ImplId, - Lookup, MacroCallStyles, MacroId, ModuleDefId, ModuleId, TraitId, UseId, + AdtId, BuiltinDeriveImplId, BuiltinType, ConstId, ExternBlockId, ExternCrateId, FxIndexMap, + HasModule, ImplId, Lookup, MacroCallStyles, MacroId, ModuleDefId, ModuleId, TraitId, UseId, db::DefDatabase, per_ns::{Item, MacrosItem, PerNs, TypesItem, ValuesItem}, visibility::Visibility, @@ -159,6 +159,7 @@ pub struct ItemScope { declarations: ThinVec, impls: ThinVec, + builtin_derive_impls: ThinVec, extern_blocks: ThinVec, unnamed_consts: ThinVec, /// Traits imported via `use Trait as _;`. @@ -329,6 +330,10 @@ impl ItemScope { self.impls.iter().copied() } + pub fn builtin_derive_impls(&self) -> impl ExactSizeIterator + '_ { + self.builtin_derive_impls.iter().copied() + } + pub fn all_macro_calls(&self) -> impl Iterator + '_ { self.macro_invocations.values().copied().chain(self.attr_macros.values().copied()).chain( self.derive_macros.values().flat_map(|it| { @@ -471,6 +476,10 @@ impl ItemScope { self.impls.push(imp); } + pub(crate) fn define_builtin_derive_impl(&mut self, imp: BuiltinDeriveImplId) { + self.builtin_derive_impls.push(imp); + } + pub(crate) fn define_extern_block(&mut self, extern_block: ExternBlockId) { self.extern_blocks.push(extern_block); } @@ -811,6 +820,7 @@ impl ItemScope { unresolved, declarations, impls, + builtin_derive_impls, unnamed_consts, unnamed_trait_imports, legacy_macros, @@ -834,6 +844,7 @@ impl ItemScope { unresolved.shrink_to_fit(); declarations.shrink_to_fit(); impls.shrink_to_fit(); + builtin_derive_impls.shrink_to_fit(); unnamed_consts.shrink_to_fit(); unnamed_trait_imports.shrink_to_fit(); legacy_macros.shrink_to_fit(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs index fd693477a4b4b..41d69c1fd6243 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs @@ -2,6 +2,7 @@ //! //! This attribute to tell the compiler about semi built-in std library //! features, such as Fn family of traits. +use hir_expand::name::Name; use intern::{Symbol, sym}; use stdx::impl_from; @@ -10,7 +11,7 @@ use crate::{ StaticId, StructId, TraitId, TypeAliasId, UnionId, attrs::AttrFlags, db::DefDatabase, - nameres::{assoc::TraitItems, crate_def_map, crate_local_def_map}, + nameres::{DefMap, assoc::TraitItems, crate_def_map, crate_local_def_map}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -93,6 +94,10 @@ pub fn crate_lang_items(db: &dyn DefDatabase, krate: Crate) -> Option Option { + let mut current = &core_def_map[core_def_map.root]; + for module in modules { + let Some((ModuleDefId::ModuleId(cur), _)) = + current.scope.type_(&Name::new_symbol_root(module.clone())) + else { + return None; + }; + if cur.krate(db) != core_def_map.krate() || cur.block(db) != core_def_map.block_id() { + return None; + } + current = &core_def_map[cur]; + } + let Some((ModuleDefId::TraitId(trait_), _)) = current.scope.type_(&Name::new_symbol_root(name)) + else { + return None; + }; + Some(trait_) +} + #[salsa::tracked(returns(as_deref))] pub(crate) fn crate_notable_traits(db: &dyn DefDatabase, krate: Crate) -> Option> { let mut traits = Vec::new(); @@ -158,6 +188,10 @@ macro_rules! language_item_table { ( $LangItems:ident => $( $(#[$attr:meta])* $lang_item:ident, $module:ident :: $name:ident, $target:ident; )* + + @non_lang_core_traits: + + $( core::$($non_lang_module:ident)::*, $non_lang_trait:ident; )* ) => { #[allow(non_snake_case)] // FIXME: Should we remove this? #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] @@ -166,6 +200,9 @@ macro_rules! language_item_table { $(#[$attr])* pub $lang_item: Option<$target>, )* + $( + pub $non_lang_trait: Option, + )* } impl LangItems { @@ -176,6 +213,7 @@ macro_rules! language_item_table { /// Merges `self` with `other`, with preference to `self` items. fn merge_prefer_self(&mut self, other: &Self) { $( self.$lang_item = self.$lang_item.or(other.$lang_item); )* + $( self.$non_lang_trait = self.$non_lang_trait.or(other.$non_lang_trait); )* } fn assign_lang_item(&mut self, name: Symbol, target: LangItemTarget) { @@ -190,6 +228,10 @@ macro_rules! language_item_table { _ => {} } } + + fn fill_non_lang_core_traits(&mut self, db: &dyn DefDatabase, core_def_map: &DefMap) { + $( self.$non_lang_trait = resolve_core_trait(db, core_def_map, &[ $(sym::$non_lang_module),* ], sym::$non_lang_trait); )* + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -426,4 +468,11 @@ language_item_table! { LangItems => String, sym::String, StructId; CStr, sym::CStr, StructId; Ordering, sym::Ordering, EnumId; + + @non_lang_core_traits: + core::default, Default; + core::fmt, Debug; + core::hash, Hash; + core::cmp, Ord; + core::cmp, Eq; } 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 97af8ad93def3..fde1e6ca17ee3 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -30,6 +30,7 @@ pub mod dyn_map; pub mod item_tree; +pub mod builtin_derive; pub mod lang_item; pub mod hir; @@ -80,6 +81,7 @@ pub use hir_expand::{Intern, Lookup, tt}; use crate::{ attrs::AttrFlags, + builtin_derive::BuiltinDeriveImplTrait, builtin_type::BuiltinType, db::DefDatabase, expr_store::ExpressionStoreSourceMap, @@ -331,6 +333,19 @@ impl ImplId { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct BuiltinDeriveImplLoc { + pub adt: AdtId, + pub trait_: BuiltinDeriveImplTrait, +} + +#[salsa::interned(debug, no_lifetime)] +#[derive(PartialOrd, Ord)] +pub struct BuiltinDeriveImplId { + #[returns(ref)] + pub loc: BuiltinDeriveImplLoc, +} + type UseLoc = ItemLoc; impl_intern!(UseId, UseLoc, intern_use, lookup_intern_use); @@ -1009,6 +1024,20 @@ fn module_for_assoc_item_loc<'db>( id.lookup(db).container.module(db) } +impl HasModule for BuiltinDeriveImplLoc { + #[inline] + fn module(&self, db: &dyn DefDatabase) -> ModuleId { + self.adt.module(db) + } +} + +impl HasModule for BuiltinDeriveImplId { + #[inline] + fn module(&self, db: &dyn DefDatabase) -> ModuleId { + self.loc(db).module(db) + } +} + impl HasModule for FunctionId { #[inline] fn module(&self, db: &dyn DefDatabase) -> ModuleId { 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 7e1ec526a7bc7..2fac0837de22c 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 @@ -12,7 +12,7 @@ use hir_expand::{ AttrMacroAttrIds, EditionedFileId, ErasedAstId, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, attrs::{Attr, AttrId}, - builtin::{find_builtin_attr, find_builtin_derive, find_builtin_macro}, + builtin::{BuiltinDeriveExpander, find_builtin_attr, find_builtin_derive, find_builtin_macro}, mod_path::{ModPath, PathKind}, name::{AsName, Name}, proc_macro::CustomProcMacroExpander, @@ -23,15 +23,17 @@ use la_arena::Idx; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::SmallVec; use span::{Edition, FileAstId, SyntaxContext}; +use stdx::always; use syntax::ast; use triomphe::Arc; use crate::{ - AdtId, AssocItemId, AstId, AstIdWithPath, ConstLoc, EnumLoc, ExternBlockLoc, ExternCrateId, - ExternCrateLoc, FunctionId, FunctionLoc, FxIndexMap, ImplLoc, Intern, ItemContainerId, Lookup, - Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, - ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, - UnionLoc, UnresolvedMacro, UseId, UseLoc, + AdtId, AssocItemId, AstId, AstIdWithPath, BuiltinDeriveImplId, BuiltinDeriveImplLoc, ConstLoc, + EnumLoc, ExternBlockLoc, ExternCrateId, ExternCrateLoc, FunctionId, FunctionLoc, FxIndexMap, + ImplLoc, Intern, ItemContainerId, Lookup, Macro2Id, Macro2Loc, MacroExpander, MacroId, + MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, ModuleDefId, ModuleId, ProcMacroId, + ProcMacroLoc, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, UseId, + UseLoc, db::DefDatabase, item_scope::{GlobId, ImportId, ImportOrExternCrate, PerNsGlobImports}, item_tree::{ @@ -104,6 +106,7 @@ pub(super) fn collect_defs( prev_active_attrs: Default::default(), unresolved_extern_crates: Default::default(), is_proc_macro: krate.is_proc_macro, + deferred_builtin_derives: Default::default(), }; if tree_id.is_block() { collector.seed_with_inner(tree_id); @@ -214,6 +217,15 @@ enum MacroDirectiveKind<'db> { }, } +#[derive(Debug)] +struct DeferredBuiltinDerive { + call_id: MacroCallId, + derive: BuiltinDeriveExpander, + module_id: ModuleId, + depth: usize, + container: ItemContainerId, +} + /// Walks the tree of module recursively struct DefCollector<'db> { db: &'db dyn DefDatabase, @@ -252,6 +264,11 @@ struct DefCollector<'db> { /// on the same item. Therefore, this holds all active attributes that we already /// expanded. prev_active_attrs: FxHashMap, SmallVec<[AttrId; 1]>>, + /// To save memory, we do not really expand builtin derives. Instead, we save them as a `BuiltinDeriveImplId`. + /// + /// However, we can only do that when the derive is directly above the item, and there is no attribute in between. + /// Otherwise, all sorts of weird things can happen, like the item name resolving to something else. + deferred_builtin_derives: FxHashMap, Vec>, } impl<'db> DefCollector<'db> { @@ -1241,7 +1258,7 @@ impl<'db> DefCollector<'db> { fn resolve_macros(&mut self) -> ReachedFixedPoint { let mut macros = mem::take(&mut self.unresolved_macros); let mut resolved = Vec::new(); - let mut push_resolved = |directive: &MacroDirective<'_>, call_id| { + let push_resolved = |resolved: &mut Vec<_>, directive: &MacroDirective<'_>, call_id| { let attr_macro_item = match &directive.kind { MacroDirectiveKind::Attr { ast_id, .. } => Some(ast_id.ast_id), MacroDirectiveKind::FnLike { .. } | MacroDirectiveKind::Derive { .. } => None, @@ -1271,8 +1288,8 @@ impl<'db> DefCollector<'db> { MacroSubNs::Attr } }; - let resolver = |path: &_| { - let resolved_res = self.def_map.resolve_path_fp_with_macro( + let resolver = |def_map: &DefMap, path: &_| { + let resolved_res = def_map.resolve_path_fp_with_macro( self.crate_local_def_map.unwrap_or(&self.local_def_map), self.db, ResolveMode::Other, @@ -1283,7 +1300,7 @@ impl<'db> DefCollector<'db> { ); resolved_res.resolved_def.take_macros().map(|it| (it, self.db.macro_def(it))) }; - let resolver_def_id = |path: &_| resolver(path).map(|(_, it)| it); + let resolver_def_id = |path: &_| resolver(&self.def_map, path).map(|(_, it)| it); match &directive.kind { MacroDirectiveKind::FnLike { ast_id, expand_to, ctxt: call_site } => { @@ -1306,7 +1323,7 @@ impl<'db> DefCollector<'db> { .scope .add_macro_invoc(ast_id.ast_id, call_id); - push_resolved(directive, call_id); + push_resolved(&mut resolved, directive, call_id); res = ReachedFixedPoint::No; return Resolved::Yes; @@ -1320,6 +1337,7 @@ impl<'db> DefCollector<'db> { ctxt: call_site, derive_macro_id, } => { + // FIXME: This code is almost duplicate below. let id = derive_macro_as_call_id( self.db, ast_id, @@ -1327,7 +1345,7 @@ impl<'db> DefCollector<'db> { *derive_pos as u32, *call_site, self.def_map.krate, - resolver, + |path| resolver(&self.def_map, path), *derive_macro_id, ); @@ -1354,7 +1372,8 @@ impl<'db> DefCollector<'db> { } } - push_resolved(directive, call_id); + push_resolved(&mut resolved, directive, call_id); + res = ReachedFixedPoint::No; return Resolved::Yes; } @@ -1470,18 +1489,78 @@ impl<'db> DefCollector<'db> { ast_id.value, Interned::new(path), ); - self.unresolved_macros.push(MacroDirective { - module_id: directive.module_id, - depth: directive.depth + 1, - kind: MacroDirectiveKind::Derive { - ast_id, - derive_attr: *attr_id, - derive_pos: idx, - ctxt: call_site.ctx, - derive_macro_id: call_id, - }, - container: directive.container, - }); + + // Try to resolve the derive immediately. If we succeed, we can also use the fast path + // for builtin derives. If not, we cannot use it, as it can cause the ADT to become + // interned while the derive is still unresolved, which will cause it to get forgotten. + let id = derive_macro_as_call_id( + self.db, + &ast_id, + *attr_id, + idx as u32, + call_site.ctx, + self.def_map.krate, + |path| resolver(&self.def_map, path), + call_id, + ); + + if let Ok((macro_id, def_id, call_id)) = id { + self.def_map.modules[directive.module_id] + .scope + .set_derive_macro_invoc( + ast_id.ast_id, + call_id, + *attr_id, + idx, + ); + // Record its helper attributes. + if def_id.krate != self.def_map.krate { + let def_map = crate_def_map(self.db, def_id.krate); + if let Some(helpers) = + def_map.data.exported_derives.get(¯o_id) + { + self.def_map + .derive_helpers_in_scope + .entry(ast_id.ast_id.map(|it| it.upcast())) + .or_default() + .extend(izip!( + helpers.iter().cloned(), + iter::repeat(macro_id), + iter::repeat(call_id), + )); + } + } + + if let MacroDefKind::BuiltInDerive(_, builtin_derive) = + def_id.kind + { + self.deferred_builtin_derives + .entry(ast_id.ast_id.upcast()) + .or_default() + .push(DeferredBuiltinDerive { + call_id, + derive: builtin_derive, + module_id: directive.module_id, + container: directive.container, + depth: directive.depth, + }); + } else { + push_resolved(&mut resolved, directive, call_id); + } + } else { + self.unresolved_macros.push(MacroDirective { + module_id: directive.module_id, + depth: directive.depth + 1, + kind: MacroDirectiveKind::Derive { + ast_id, + derive_attr: *attr_id, + derive_pos: idx, + ctxt: call_site.ctx, + derive_macro_id: call_id, + }, + container: directive.container, + }); + } len = idx; } @@ -1522,12 +1601,25 @@ impl<'db> DefCollector<'db> { } } + // Clear deferred derives for this item, unfortunately we cannot use them due to the attribute. + if let Some(deferred_derives) = self.deferred_builtin_derives.remove(&ast_id) { + resolved.extend(deferred_derives.into_iter().map(|derive| { + ( + derive.module_id, + derive.depth, + derive.container, + derive.call_id, + Some(ast_id), + ) + })); + } + let call_id = call_id(); self.def_map.modules[directive.module_id] .scope .add_attr_macro_invoc(ast_id, call_id); - push_resolved(directive, call_id); + push_resolved(&mut resolved, directive, call_id); res = ReachedFixedPoint::No; return Resolved::Yes; } @@ -1709,6 +1801,12 @@ impl<'db> DefCollector<'db> { )); } + always!( + self.deferred_builtin_derives.is_empty(), + "self.deferred_builtin_derives={:#?}", + self.deferred_builtin_derives, + ); + (self.def_map, self.local_def_map) } } @@ -1751,6 +1849,26 @@ impl ModCollector<'_, '_> { } let db = self.def_collector.db; let module_id = self.module_id; + let consider_deferred_derives = + |file_id: HirFileId, + deferred_derives: &mut FxHashMap<_, Vec>, + ast_id: FileAstId, + id: AdtId, + def_map: &mut DefMap| { + let Some(deferred_derives) = + deferred_derives.remove(&InFile::new(file_id, ast_id.upcast())) + else { + return; + }; + let module = &mut def_map.modules[module_id]; + for deferred_derive in deferred_derives { + crate::builtin_derive::with_derive_traits(deferred_derive.derive, |trait_| { + let impl_id = + BuiltinDeriveImplId::new(db, BuiltinDeriveImplLoc { adt: id, trait_ }); + module.scope.define_builtin_derive_impl(impl_id); + }); + } + }; let update_def = |def_collector: &mut DefCollector<'_>, id, name: &Name, vis, has_constructor| { def_collector.def_map.modules[module_id].scope.declare(id); @@ -1928,11 +2046,21 @@ impl ModCollector<'_, '_> { let it = &self.item_tree[id]; let vis = resolve_vis(def_map, local_def_map, &self.item_tree[it.visibility]); + let interned = StructLoc { + container: module_id, + id: InFile::new(self.tree_id.file_id(), id), + } + .intern(db); + consider_deferred_derives( + self.tree_id.file_id(), + &mut self.def_collector.deferred_builtin_derives, + id.upcast(), + interned.into(), + def_map, + ); update_def( self.def_collector, - StructLoc { container: module_id, id: InFile::new(self.file_id(), id) } - .intern(db) - .into(), + interned.into(), &it.name, vis, !matches!(it.shape, FieldsShape::Record), @@ -1942,15 +2070,19 @@ impl ModCollector<'_, '_> { let it = &self.item_tree[id]; let vis = resolve_vis(def_map, local_def_map, &self.item_tree[it.visibility]); - update_def( - self.def_collector, - UnionLoc { container: module_id, id: InFile::new(self.file_id(), id) } - .intern(db) - .into(), - &it.name, - vis, - false, + let interned = UnionLoc { + container: module_id, + id: InFile::new(self.tree_id.file_id(), id), + } + .intern(db); + consider_deferred_derives( + self.tree_id.file_id(), + &mut self.def_collector.deferred_builtin_derives, + id.upcast(), + interned.into(), + def_map, ); + update_def(self.def_collector, interned.into(), &it.name, vis, false); } ModItemId::Enum(id) => { let it = &self.item_tree[id]; @@ -1960,6 +2092,13 @@ impl ModCollector<'_, '_> { } .intern(db); + consider_deferred_derives( + self.tree_id.file_id(), + &mut self.def_collector.deferred_builtin_derives, + id.upcast(), + enum_.into(), + def_map, + ); let vis = resolve_vis(def_map, local_def_map, &self.item_tree[it.visibility]); update_def(self.def_collector, enum_.into(), &it.name, vis, false); } 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 c8eb968b35876..a943f6f0ac01e 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 @@ -784,7 +784,7 @@ macro_rules! foo { pub use core::clone::Clone; "#, - |map| assert_eq!(map.modules[map.root].scope.impls().len(), 1), + |map| assert_eq!(map.modules[map.root].scope.builtin_derive_impls().len(), 1), ); } @@ -806,7 +806,7 @@ pub macro Copy {} #[rustc_builtin_macro] pub macro Clone {} "#, - |map| assert_eq!(map.modules[map.root].scope.impls().len(), 2), + |map| assert_eq!(map.modules[map.root].scope.builtin_derive_impls().len(), 2), ); } @@ -849,7 +849,7 @@ pub macro derive($item:item) {} #[rustc_builtin_macro] pub macro Clone {} "#, - |map| assert_eq!(map.modules[map.root].scope.impls().len(), 1), + |map| assert_eq!(map.modules[map.root].scope.builtin_derive_impls().len(), 1), ); } @@ -1609,7 +1609,7 @@ macro_rules! derive { () => {} } #[derive(Clone)] struct S; "#, - |map| assert_eq!(map.modules[map.root].scope.impls().len(), 1), + |map| assert_eq!(map.modules[map.root].scope.builtin_derive_impls().len(), 1), ); } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs index 6582f4b07574c..c805197425856 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs @@ -28,7 +28,7 @@ use syntax::{ }; macro_rules! register_builtin { - ( $($trait:ident => $expand:ident),* ) => { + ( $($trait:ident => $expand:ident),* $(,)? ) => { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BuiltinDeriveExpander { $($trait),* @@ -48,7 +48,6 @@ macro_rules! register_builtin { } } } - }; } @@ -75,7 +74,7 @@ register_builtin! { PartialOrd => partial_ord_expand, Eq => eq_expand, PartialEq => partial_eq_expand, - CoercePointee => coerce_pointee_expand + CoercePointee => coerce_pointee_expand, } pub fn find_builtin_derive(ident: &name::Name) -> Option { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs new file mode 100644 index 0000000000000..15d9634cfab6c --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs @@ -0,0 +1,575 @@ +//! Implementation of builtin derive impls. + +use std::ops::ControlFlow; + +use hir_def::{ + AdtId, BuiltinDeriveImplId, BuiltinDeriveImplLoc, HasModule, LocalFieldId, TraitId, + TypeOrConstParamId, TypeParamId, + attrs::AttrFlags, + builtin_derive::BuiltinDeriveImplTrait, + hir::generics::{GenericParams, TypeOrConstParamData}, +}; +use itertools::Itertools; +use la_arena::ArenaMap; +use rustc_type_ir::{ + AliasTyKind, Interner, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor, Upcast, + inherent::{GenericArgs as _, IntoKind}, +}; + +use crate::{ + GenericPredicates, + db::HirDatabase, + next_solver::{ + Clause, Clauses, DbInterner, EarlyBinder, GenericArgs, StoredEarlyBinder, StoredTy, + TraitRef, Ty, TyKind, fold::fold_tys, generics::Generics, + }, +}; + +fn fake_type_param(adt: AdtId) -> TypeParamId { + // HACK: Fake the param. + TypeParamId::from_unchecked(TypeOrConstParamId { + parent: adt.into(), + local_id: la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(u32::MAX)), + }) +} + +pub(crate) fn generics_of<'db>(interner: DbInterner<'db>, id: BuiltinDeriveImplId) -> Generics { + let db = interner.db; + let loc = id.loc(db); + match loc.trait_ { + BuiltinDeriveImplTrait::Copy + | BuiltinDeriveImplTrait::Clone + | BuiltinDeriveImplTrait::Default + | BuiltinDeriveImplTrait::Debug + | BuiltinDeriveImplTrait::Hash + | BuiltinDeriveImplTrait::Ord + | BuiltinDeriveImplTrait::PartialOrd + | BuiltinDeriveImplTrait::Eq + | BuiltinDeriveImplTrait::PartialEq => interner.generics_of(loc.adt.into()), + BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { + let mut generics = interner.generics_of(loc.adt.into()); + generics.push_param(fake_type_param(loc.adt).into()); + generics + } + } +} + +pub(crate) fn impl_trait<'db>( + interner: DbInterner<'db>, + id: BuiltinDeriveImplId, +) -> EarlyBinder<'db, TraitRef<'db>> { + let db = interner.db; + let loc = id.loc(db); + let trait_id = loc + .trait_ + .get_id(interner.lang_items()) + .expect("we don't pass the impl to the solver if we can't resolve the trait"); + match loc.trait_ { + BuiltinDeriveImplTrait::Copy + | BuiltinDeriveImplTrait::Clone + | BuiltinDeriveImplTrait::Default + | BuiltinDeriveImplTrait::Debug + | BuiltinDeriveImplTrait::Hash + | BuiltinDeriveImplTrait::Ord + | BuiltinDeriveImplTrait::Eq => { + let self_ty = Ty::new_adt( + interner, + loc.adt, + GenericArgs::identity_for_item(interner, loc.adt.into()), + ); + EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty])) + } + BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => { + let self_ty = Ty::new_adt( + interner, + loc.adt, + GenericArgs::identity_for_item(interner, loc.adt.into()), + ); + EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty, self_ty])) + } + BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { + let generic_params = GenericParams::new(db, loc.adt.into()); + let interner = DbInterner::new_no_crate(db); + let args = GenericArgs::identity_for_item(interner, loc.adt.into()); + let self_ty = Ty::new_adt(interner, loc.adt, args); + let Some((pointee_param_idx, _, new_param_ty)) = + coerce_pointee_params(interner, loc, &generic_params) + else { + // Malformed derive. + return EarlyBinder::bind(TraitRef::new( + interner, + trait_id.into(), + [self_ty, self_ty], + )); + }; + let changed_args = replace_pointee(interner, pointee_param_idx, new_param_ty, args); + let changed_self_ty = Ty::new_adt(interner, loc.adt, changed_args); + EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty, changed_self_ty])) + } + } +} + +#[salsa::tracked(returns(ref), unsafe(non_update_types))] +pub(crate) fn builtin_derive_predicates<'db>( + db: &'db dyn HirDatabase, + impl_: BuiltinDeriveImplId, +) -> GenericPredicates { + let loc = impl_.loc(db); + let generic_params = GenericParams::new(db, loc.adt.into()); + let interner = DbInterner::new_with(db, loc.module(db).krate(db)); + let adt_predicates = GenericPredicates::query(db, loc.adt.into()); + match loc.trait_ { + BuiltinDeriveImplTrait::Copy + | BuiltinDeriveImplTrait::Clone + | BuiltinDeriveImplTrait::Debug + | BuiltinDeriveImplTrait::Hash + | BuiltinDeriveImplTrait::Ord + | BuiltinDeriveImplTrait::PartialOrd + | BuiltinDeriveImplTrait::Eq + | BuiltinDeriveImplTrait::PartialEq => { + simple_trait_predicates(interner, loc, &generic_params, adt_predicates) + } + BuiltinDeriveImplTrait::Default => { + if matches!(loc.adt, AdtId::EnumId(_)) { + // Enums don't have extra bounds. + GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind( + Clauses::new_from_slice(adt_predicates.explicit_predicates().skip_binder()) + .store(), + )) + } else { + simple_trait_predicates(interner, loc, &generic_params, adt_predicates) + } + } + BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { + let Some((pointee_param_idx, pointee_param_id, new_param_ty)) = + coerce_pointee_params(interner, loc, &generic_params) + else { + // Malformed derive. + return GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind( + Clauses::default().store(), + )); + }; + let duplicated_bounds = + adt_predicates.explicit_predicates().iter_identity_copied().filter_map(|pred| { + let mentions_pointee = + pred.visit_with(&mut MentionsPointee { pointee_param_idx }).is_break(); + if !mentions_pointee { + return None; + } + let transformed = + replace_pointee(interner, pointee_param_idx, new_param_ty, pred); + Some(transformed) + }); + let unsize_trait = interner.lang_items().Unsize; + let unsize_bound = unsize_trait.map(|unsize_trait| { + let pointee_param_ty = Ty::new_param(interner, pointee_param_id, pointee_param_idx); + TraitRef::new(interner, unsize_trait.into(), [pointee_param_ty, new_param_ty]) + .upcast(interner) + }); + GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind( + Clauses::new_from_iter( + interner, + adt_predicates + .explicit_predicates() + .iter_identity_copied() + .chain(duplicated_bounds) + .chain(unsize_bound), + ) + .store(), + )) + } + } +} + +struct MentionsPointee { + pointee_param_idx: u32, +} + +impl<'db> TypeVisitor> for MentionsPointee { + type Result = ControlFlow<()>; + + fn visit_ty(&mut self, t: Ty<'db>) -> Self::Result { + if let TyKind::Param(param) = t.kind() + && param.index == self.pointee_param_idx + { + ControlFlow::Break(()) + } else { + t.super_visit_with(self) + } + } +} + +fn replace_pointee<'db, T: TypeFoldable>>( + interner: DbInterner<'db>, + pointee_param_idx: u32, + new_param_ty: Ty<'db>, + t: T, +) -> T { + fold_tys(interner, t, |ty| match ty.kind() { + TyKind::Param(param) if param.index == pointee_param_idx => new_param_ty, + _ => ty, + }) +} + +fn simple_trait_predicates<'db>( + interner: DbInterner<'db>, + loc: &BuiltinDeriveImplLoc, + generic_params: &GenericParams, + adt_predicates: &GenericPredicates, +) -> GenericPredicates { + let trait_id = loc + .trait_ + .get_id(interner.lang_items()) + .expect("we don't pass the impl to the solver if we can't resolve the trait"); + let extra_predicates = generic_params + .iter_type_or_consts() + .filter(|(_, data)| matches!(data, TypeOrConstParamData::TypeParamData(_))) + .map(|(param_idx, _)| { + let param_id = TypeParamId::from_unchecked(TypeOrConstParamId { + parent: loc.adt.into(), + local_id: param_idx, + }); + let param_idx = + param_idx.into_raw().into_u32() + (generic_params.len_lifetimes() as u32); + let param_ty = Ty::new_param(interner, param_id, param_idx); + let trait_ref = TraitRef::new(interner, trait_id.into(), [param_ty]); + trait_ref.upcast(interner) + }); + let mut assoc_type_bounds = Vec::new(); + match loc.adt { + AdtId::StructId(id) => extend_assoc_type_bounds( + interner, + &mut assoc_type_bounds, + interner.db.field_types(id.into()), + trait_id, + ), + AdtId::UnionId(id) => extend_assoc_type_bounds( + interner, + &mut assoc_type_bounds, + interner.db.field_types(id.into()), + trait_id, + ), + AdtId::EnumId(id) => { + for &(variant_id, _, _) in &id.enum_variants(interner.db).variants { + extend_assoc_type_bounds( + interner, + &mut assoc_type_bounds, + interner.db.field_types(variant_id.into()), + trait_id, + ) + } + } + } + GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind( + Clauses::new_from_iter( + interner, + adt_predicates + .explicit_predicates() + .iter_identity_copied() + .chain(extra_predicates) + .chain(assoc_type_bounds), + ) + .store(), + )) +} + +fn extend_assoc_type_bounds<'db>( + interner: DbInterner<'db>, + assoc_type_bounds: &mut Vec>, + fields: &ArenaMap>, + trait_: TraitId, +) { + struct ProjectionFinder<'a, 'db> { + interner: DbInterner<'db>, + assoc_type_bounds: &'a mut Vec>, + trait_: TraitId, + } + + impl<'db> TypeVisitor> for ProjectionFinder<'_, 'db> { + type Result = (); + + fn visit_ty(&mut self, t: Ty<'db>) -> Self::Result { + if let TyKind::Alias(AliasTyKind::Projection, _) = t.kind() { + self.assoc_type_bounds.push( + TraitRef::new(self.interner, self.trait_.into(), [t]).upcast(self.interner), + ); + } + + t.super_visit_with(self) + } + } + + let mut visitor = ProjectionFinder { interner, assoc_type_bounds, trait_ }; + for (_, field) in fields.iter() { + field.get().instantiate_identity().visit_with(&mut visitor); + } +} + +fn coerce_pointee_params<'db>( + interner: DbInterner<'db>, + loc: &BuiltinDeriveImplLoc, + generic_params: &GenericParams, +) -> Option<(u32, TypeParamId, Ty<'db>)> { + let pointee_param = { + if let Ok((pointee_param, _)) = generic_params + .iter_type_or_consts() + .filter(|param| matches!(param.1, TypeOrConstParamData::TypeParamData(_))) + .exactly_one() + { + pointee_param + } else { + let (_, generic_param_attrs) = + AttrFlags::query_generic_params(interner.db, loc.adt.into()); + generic_param_attrs + .iter() + .find(|param| param.1.contains(AttrFlags::IS_POINTEE)) + .map(|(param, _)| param) + .or_else(|| { + generic_params + .iter_type_or_consts() + .find(|param| matches!(param.1, TypeOrConstParamData::TypeParamData(_))) + .map(|(idx, _)| idx) + })? + } + }; + let pointee_param_id = TypeParamId::from_unchecked(TypeOrConstParamId { + parent: loc.adt.into(), + local_id: pointee_param, + }); + let pointee_param_idx = + pointee_param.into_raw().into_u32() + (generic_params.len_lifetimes() as u32); + let new_param_idx = generic_params.len() as u32; + let new_param_id = fake_type_param(loc.adt); + let new_param_ty = Ty::new_param(interner, new_param_id, new_param_idx); + Some((pointee_param_idx, pointee_param_id, new_param_ty)) +} + +#[cfg(test)] +mod tests { + use expect_test::{Expect, expect}; + use hir_def::nameres::crate_def_map; + use itertools::Itertools; + use stdx::format_to; + use test_fixture::WithFixture; + + use crate::{ + builtin_derive::{builtin_derive_predicates, impl_trait}, + next_solver::DbInterner, + test_db::TestDB, + }; + + fn check_trait_refs(#[rust_analyzer::rust_fixture] ra_fixture: &str, expectation: Expect) { + let db = TestDB::with_files(ra_fixture); + let def_map = crate_def_map(&db, db.test_crate()); + + let interner = DbInterner::new_with(&db, db.test_crate()); + crate::attach_db(&db, || { + let mut trait_refs = Vec::new(); + for (_, module) in def_map.modules() { + for derive in module.scope.builtin_derive_impls() { + let trait_ref = impl_trait(interner, derive).skip_binder(); + trait_refs.push(format!("{trait_ref:?}")); + } + } + + expectation.assert_eq(&trait_refs.join("\n")); + }); + } + + fn check_predicates(#[rust_analyzer::rust_fixture] ra_fixture: &str, expectation: Expect) { + let db = TestDB::with_files(ra_fixture); + let def_map = crate_def_map(&db, db.test_crate()); + + crate::attach_db(&db, || { + let mut predicates = String::new(); + for (_, module) in def_map.modules() { + for derive in module.scope.builtin_derive_impls() { + let preds = + builtin_derive_predicates(&db, derive).all_predicates().skip_binder(); + format_to!( + predicates, + "{}\n\n", + preds.iter().format_with("\n", |pred, formatter| formatter(&format_args!( + "{pred:?}" + ))), + ); + } + } + + expectation.assert_eq(&predicates); + }); + } + + #[test] + fn simple_macros_trait_ref() { + check_trait_refs( + r#" +//- minicore: derive, clone, copy, eq, ord, hash, fmt + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Simple; + +trait Trait {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]); + "#, + expect![[r#" + Simple: Debug + Simple: Clone + Simple: Copy + Simple: PartialEq<[Simple]> + Simple: Eq + Simple: PartialOrd<[Simple]> + Simple: Ord + Simple: Hash + WithGenerics<#0, #1, #2>: Debug + WithGenerics<#0, #1, #2>: Clone + WithGenerics<#0, #1, #2>: Copy + WithGenerics<#0, #1, #2>: PartialEq<[WithGenerics<#0, #1, #2>]> + WithGenerics<#0, #1, #2>: Eq + WithGenerics<#0, #1, #2>: PartialOrd<[WithGenerics<#0, #1, #2>]> + WithGenerics<#0, #1, #2>: Ord + WithGenerics<#0, #1, #2>: Hash"#]], + ); + } + + #[test] + fn coerce_pointee_trait_ref() { + check_trait_refs( + r#" +//- minicore: derive, coerce_pointee +use core::marker::CoercePointee; + +#[derive(CoercePointee)] +struct Simple(*const T); + +#[derive(CoercePointee)] +struct MultiGenericParams<'a, T, #[pointee] U: ?Sized, const N: usize>(*const U); + "#, + expect![[r#" + Simple<#0>: CoerceUnsized<[Simple<#1>]> + Simple<#0>: DispatchFromDyn<[Simple<#1>]> + MultiGenericParams<#0, #1, #2, #3>: CoerceUnsized<[MultiGenericParams<#0, #1, #4, #3>]> + MultiGenericParams<#0, #1, #2, #3>: DispatchFromDyn<[MultiGenericParams<#0, #1, #4, #3>]>"#]], + ); + } + + #[test] + fn simple_macros_predicates() { + check_predicates( + r#" +//- minicore: derive, clone, copy, eq, ord, hash, fmt + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Simple; + +trait Trait {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct WithGenerics<'a, T: Trait, const N: usize>(&'a [T; N]); + "#, + expect![[r#" + + + + + + + + + + + + + + + + + Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Debug, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Clone, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Copy, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: PartialEq, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Eq, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: PartialOrd, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Ord, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#1: Trait, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#2, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Hash, polarity:Positive), bound_vars: [] }) + + "#]], + ); + } + + #[test] + fn coerce_pointee_predicates() { + check_predicates( + r#" +//- minicore: derive, coerce_pointee +use core::marker::CoercePointee; + +#[derive(CoercePointee)] +struct Simple(*const T); + +trait Trait {} + +#[derive(CoercePointee)] +struct MultiGenericParams<'a, T, #[pointee] U: ?Sized, const N: usize>(*const U) +where + T: Trait, + U: Trait; + "#, + expect![[r#" + Clause(Binder { value: TraitPredicate(#0: Unsize<[#1]>, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#0: Unsize<[#1]>, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#1: Trait<[#2]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#2: Trait<[#2]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#3, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Trait<[#4]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#4: Trait<[#4]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#2: Unsize<[#4]>, polarity:Positive), bound_vars: [] }) + + Clause(Binder { value: TraitPredicate(#1: Trait<[#2]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#2: Trait<[#2]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: ConstArgHasType(#3, usize), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Sized, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#1: Trait<[#4]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#4: Trait<[#4]>, polarity:Positive), bound_vars: [] }) + Clause(Binder { value: TraitPredicate(#2: Unsize<[#4]>, polarity:Positive), bound_vars: [] }) + + "#]], + ); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/drop.rs b/src/tools/rust-analyzer/crates/hir-ty/src/drop.rs index 66692143bc1aa..9d6869eee9b84 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/drop.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/drop.rs @@ -32,7 +32,7 @@ fn has_destructor(interner: DbInterner<'_>, adt: AdtId) -> bool { }, None => TraitImpls::for_crate(db, module.krate(db)), }; - !impls.for_trait_and_self_ty(drop_trait, &SimplifiedType::Adt(adt.into())).is_empty() + !impls.for_trait_and_self_ty(drop_trait, &SimplifiedType::Adt(adt.into())).0.is_empty() } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 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 1674771413f25..7b414cd551749 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -25,6 +25,7 @@ extern crate ra_ap_rustc_next_trait_solver as rustc_next_trait_solver; extern crate self as hir_ty; +mod builtin_derive; mod infer; mod inhabitedness; mod lower; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 62a5837f349df..ebbf29e6ff465 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -1790,6 +1790,13 @@ impl<'db> GenericPredicates { } impl GenericPredicates { + #[inline] + pub(crate) fn from_explicit_own_predicates( + predicates: StoredEarlyBinder, + ) -> Self { + Self { predicates, own_predicates_start: 0, is_trait: false, parent_is_trait: false } + } + #[inline] pub fn query(db: &dyn HirDatabase, def: GenericDefId) -> &GenericPredicates { &Self::query_with_diagnostics(db, def).0 diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs index c370330a87172..88f48fdbc67a7 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs @@ -13,11 +13,12 @@ use tracing::{debug, instrument}; use base_db::Crate; use hir_def::{ - AssocItemId, BlockId, ConstId, FunctionId, GenericParamId, HasModule, ImplId, ItemContainerId, - ModuleId, TraitId, + AssocItemId, BlockId, BuiltinDeriveImplId, ConstId, FunctionId, GenericParamId, HasModule, + ImplId, ItemContainerId, ModuleId, TraitId, attrs::AttrFlags, expr_store::path::GenericArgs as HirGenericArgs, hir::ExprId, + lang_item::LangItems, nameres::{DefMap, block_def_map, crate_def_map}, resolver::Resolver, }; @@ -37,7 +38,7 @@ use crate::{ infer::{InferenceContext, unify::InferenceTable}, lower::GenericPredicates, next_solver::{ - Binder, ClauseKind, DbInterner, FnSig, GenericArgs, ParamEnv, PredicateKind, + AnyImplId, Binder, ClauseKind, DbInterner, FnSig, GenericArgs, ParamEnv, PredicateKind, SimplifiedType, SolverDefId, TraitRef, Ty, TyKind, TypingMode, infer::{ BoundRegionConversionTime, DbInternerInferExt, InferCtxt, InferOk, @@ -132,7 +133,7 @@ pub enum MethodError<'db> { // candidate can arise. Used for error reporting only. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum CandidateSource { - Impl(ImplId), + Impl(AnyImplId), Trait(TraitId), } @@ -462,6 +463,10 @@ fn lookup_impl_assoc_item_for_trait_ref<'db>( name: &Name, ) -> Option<(AssocItemId, GenericArgs<'db>)> { let (impl_id, impl_subst) = find_matching_impl(infcx, env, trait_ref)?; + let AnyImplId::ImplId(impl_id) = impl_id else { + // FIXME: Handle resolution to builtin derive. + return None; + }; let item = impl_id.impl_items(infcx.interner.db).items.iter().find_map(|(n, it)| match *it { AssocItemId::FunctionId(f) => (n == name).then_some(AssocItemId::FunctionId(f)), @@ -475,7 +480,7 @@ pub(crate) fn find_matching_impl<'db>( infcx: &InferCtxt<'db>, env: ParamEnv<'db>, trait_ref: TraitRef<'db>, -) -> Option<(ImplId, GenericArgs<'db>)> { +) -> Option<(AnyImplId, GenericArgs<'db>)> { let trait_ref = infcx.at(&ObligationCause::dummy(), env).deeply_normalize(trait_ref).ok()?; let obligation = Obligation::new(infcx.interner, ObligationCause::dummy(), env, trait_ref); @@ -635,13 +640,13 @@ impl InherentImpls { #[derive(Debug, PartialEq)] struct OneTraitImpls { - non_blanket_impls: FxHashMap>, + non_blanket_impls: FxHashMap, Box<[BuiltinDeriveImplId]>)>, blanket_impls: Box<[ImplId]>, } #[derive(Default)] struct OneTraitImplsBuilder { - non_blanket_impls: FxHashMap>, + non_blanket_impls: FxHashMap, Vec)>, blanket_impls: Vec, } @@ -650,7 +655,9 @@ impl OneTraitImplsBuilder { let mut non_blanket_impls = self .non_blanket_impls .into_iter() - .map(|(self_ty, impls)| (self_ty, impls.into_boxed_slice())) + .map(|(self_ty, (impls, builtin_derive_impls))| { + (self_ty, (impls.into_boxed_slice(), builtin_derive_impls.into_boxed_slice())) + }) .collect::>(); non_blanket_impls.shrink_to_fit(); let blanket_impls = self.blanket_impls.into_boxed_slice(); @@ -691,8 +698,9 @@ impl TraitImpls { impl TraitImpls { fn collect_def_map(db: &dyn HirDatabase, def_map: &DefMap) -> Self { + let lang_items = hir_def::lang_item::lang_items(db, def_map.krate()); let mut map = FxHashMap::default(); - collect(db, def_map, &mut map); + collect(db, def_map, lang_items, &mut map); let mut map = map .into_iter() .map(|(trait_id, trait_map)| (trait_id, trait_map.finish())) @@ -703,6 +711,7 @@ impl TraitImpls { fn collect( db: &dyn HirDatabase, def_map: &DefMap, + lang_items: &LangItems, map: &mut FxHashMap, ) { for (_module_id, module_data) in def_map.modules() { @@ -727,18 +736,29 @@ impl TraitImpls { let entry = map.entry(trait_ref.def_id.0).or_default(); match simplify_type(interner, self_ty, TreatParams::InstantiateWithInfer) { Some(self_ty) => { - entry.non_blanket_impls.entry(self_ty).or_default().push(impl_id) + entry.non_blanket_impls.entry(self_ty).or_default().0.push(impl_id) } None => entry.blanket_impls.push(impl_id), } } + for impl_id in module_data.scope.builtin_derive_impls() { + let loc = impl_id.loc(db); + let Some(trait_id) = loc.trait_.get_id(lang_items) else { continue }; + let entry = map.entry(trait_id).or_default(); + let entry = entry + .non_blanket_impls + .entry(SimplifiedType::Adt(loc.adt.into())) + .or_default(); + entry.1.push(impl_id); + } + // To better support custom derives, collect impls in all unnamed const items. // const _: () = { ... }; for konst in module_data.scope.unnamed_consts() { let body = db.body(konst.into()); for (_, block_def_map) in body.blocks(db) { - collect(db, block_def_map, map); + collect(db, block_def_map, lang_items, map); } } } @@ -761,11 +781,15 @@ impl TraitImpls { }) } - pub fn for_trait_and_self_ty(&self, trait_: TraitId, self_ty: &SimplifiedType) -> &[ImplId] { + pub fn for_trait_and_self_ty( + &self, + trait_: TraitId, + self_ty: &SimplifiedType, + ) -> (&[ImplId], &[BuiltinDeriveImplId]) { self.map .get(&trait_) .and_then(|map| map.non_blanket_impls.get(self_ty)) - .map(|it| &**it) + .map(|it| (&*it.0, &*it.1)) .unwrap_or_default() } @@ -773,7 +797,7 @@ impl TraitImpls { if let Some(impls) = self.map.get(&trait_) { callback(&impls.blanket_impls); for impls in impls.non_blanket_impls.values() { - callback(impls); + callback(&impls.0); } } } @@ -781,7 +805,7 @@ impl TraitImpls { pub fn for_self_ty(&self, self_ty: &SimplifiedType, mut callback: impl FnMut(&[ImplId])) { for for_trait in self.map.values() { if let Some(for_ty) = for_trait.non_blanket_impls.get(self_ty) { - callback(for_ty); + callback(&for_ty.0); } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs index cb9b810686e34..4a7c7d93539e6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs @@ -1001,7 +1001,7 @@ impl<'a, 'db, Choice: ProbeChoice<'db>> ProbeContext<'a, 'db, Choice> { self.with_impl_item(impl_def_id, |this, item| { if !this.has_applicable_self(item) { // No receiver declared. Not a candidate. - this.record_static_candidate(CandidateSource::Impl(impl_def_id)); + this.record_static_candidate(CandidateSource::Impl(impl_def_id.into())); return; } this.push_candidate( @@ -1490,7 +1490,7 @@ impl<'a, 'db, Choice: ProbeChoice<'db>> ProbeContext<'a, 'db, Choice> { /// so do not use to make a decision that may lead to a successful compilation. fn candidate_source(&self, candidate: &Candidate<'db>, self_ty: Ty<'db>) -> CandidateSource { match candidate.kind { - InherentImplCandidate { impl_def_id, .. } => CandidateSource::Impl(impl_def_id), + InherentImplCandidate { impl_def_id, .. } => CandidateSource::Impl(impl_def_id.into()), ObjectCandidate(trait_ref) | WhereClauseCandidate(trait_ref) => { CandidateSource::Trait(trait_ref.def_id().0) } @@ -1524,7 +1524,7 @@ impl<'a, 'db, Choice: ProbeChoice<'db>> ProbeContext<'a, 'db, Choice> { fn candidate_source_from_pick(&self, pick: &Pick<'db>) -> CandidateSource { match pick.kind { - InherentImplPick(impl_) => CandidateSource::Impl(impl_), + InherentImplPick(impl_) => CandidateSource::Impl(impl_.into()), ObjectPick(trait_) | TraitPick(trait_) => CandidateSource::Trait(trait_), WhereClausePick(trait_ref) => CandidateSource::Trait(trait_ref.skip_binder().def_id.0), } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs index b6167b4a097c1..aa6caefc4a069 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs @@ -1,9 +1,9 @@ //! Definition of `SolverDefId` use hir_def::{ - AdtId, AttrDefId, CallableDefId, ConstId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, - GeneralConstId, GenericDefId, HasModule, ImplId, ModuleId, StaticId, StructId, TraitId, - TypeAliasId, UnionId, db::DefDatabase, + AdtId, AttrDefId, BuiltinDeriveImplId, CallableDefId, ConstId, DefWithBodyId, EnumId, + EnumVariantId, FunctionId, GeneralConstId, GenericDefId, ImplId, StaticId, StructId, TraitId, + TypeAliasId, UnionId, }; use rustc_type_ir::inherent; use stdx::impl_from; @@ -24,6 +24,7 @@ pub enum SolverDefId { ConstId(ConstId), FunctionId(FunctionId), ImplId(ImplId), + BuiltinDeriveImplId(BuiltinDeriveImplId), StaticId(StaticId), TraitId(TraitId), TypeAliasId(TypeAliasId), @@ -57,6 +58,7 @@ impl std::fmt::Debug for SolverDefId { f.debug_tuple("FunctionId").field(&db.function_signature(id).name.as_str()).finish() } SolverDefId::ImplId(id) => f.debug_tuple("ImplId").field(&id).finish(), + SolverDefId::BuiltinDeriveImplId(id) => f.debug_tuple("ImplId").field(&id).finish(), SolverDefId::StaticId(id) => { f.debug_tuple("StaticId").field(&db.static_signature(id).name.as_str()).finish() } @@ -108,6 +110,7 @@ impl_from!( ConstId, FunctionId, ImplId, + BuiltinDeriveImplId, StaticId, TraitId, TypeAliasId, @@ -170,7 +173,8 @@ impl TryFrom for AttrDefId { SolverDefId::EnumVariantId(it) => Ok(it.into()), SolverDefId::Ctor(Ctor::Struct(it)) => Ok(it.into()), SolverDefId::Ctor(Ctor::Enum(it)) => Ok(it.into()), - SolverDefId::InternedClosureId(_) + SolverDefId::BuiltinDeriveImplId(_) + | SolverDefId::InternedClosureId(_) | SolverDefId::InternedCoroutineId(_) | SolverDefId::InternedOpaqueTyId(_) => Err(()), } @@ -191,6 +195,7 @@ impl TryFrom for DefWithBodyId { | SolverDefId::TraitId(_) | SolverDefId::TypeAliasId(_) | SolverDefId::ImplId(_) + | SolverDefId::BuiltinDeriveImplId(_) | SolverDefId::InternedClosureId(_) | SolverDefId::InternedCoroutineId(_) | SolverDefId::Ctor(Ctor::Struct(_)) @@ -216,6 +221,7 @@ impl TryFrom for GenericDefId { | SolverDefId::InternedCoroutineId(_) | SolverDefId::InternedOpaqueTyId(_) | SolverDefId::EnumVariantId(_) + | SolverDefId::BuiltinDeriveImplId(_) | SolverDefId::Ctor(_) => return Err(()), }) } @@ -241,28 +247,6 @@ impl SolverDefId { } } -impl HasModule for SolverDefId { - fn module(&self, db: &dyn DefDatabase) -> ModuleId { - match *self { - SolverDefId::AdtId(id) => id.module(db), - SolverDefId::ConstId(id) => id.module(db), - SolverDefId::FunctionId(id) => id.module(db), - SolverDefId::ImplId(id) => id.module(db), - SolverDefId::StaticId(id) => id.module(db), - SolverDefId::TraitId(id) => id.module(db), - SolverDefId::TypeAliasId(id) => id.module(db), - SolverDefId::InternedClosureId(id) => id.loc(db).0.module(db), - SolverDefId::InternedCoroutineId(id) => id.loc(db).0.module(db), - SolverDefId::InternedOpaqueTyId(id) => match id.loc(db) { - crate::ImplTraitId::ReturnTypeImplTrait(owner, _) => owner.module(db), - crate::ImplTraitId::TypeAliasImplTrait(owner, _) => owner.module(db), - }, - SolverDefId::Ctor(Ctor::Enum(id)) | SolverDefId::EnumVariantId(id) => id.module(db), - SolverDefId::Ctor(Ctor::Struct(id)) => id.module(db), - } - } -} - impl<'db> inherent::DefId> for SolverDefId { fn as_local(self) -> Option { Some(self) @@ -332,7 +316,6 @@ declare_id_wrapper!(TypeAliasIdWrapper, TypeAliasId); declare_id_wrapper!(ClosureIdWrapper, InternedClosureId); declare_id_wrapper!(CoroutineIdWrapper, InternedCoroutineId); declare_id_wrapper!(AdtIdWrapper, AdtId); -declare_id_wrapper!(ImplIdWrapper, ImplId); #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct GeneralConstIdWrapper(pub GeneralConstId); @@ -433,3 +416,40 @@ impl<'db> inherent::DefId> for CallableIdWrapper { true } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AnyImplId { + ImplId(ImplId), + BuiltinDeriveImplId(BuiltinDeriveImplId), +} + +impl_from!(ImplId, BuiltinDeriveImplId for AnyImplId); + +impl From for SolverDefId { + #[inline] + fn from(value: AnyImplId) -> SolverDefId { + match value { + AnyImplId::ImplId(it) => it.into(), + AnyImplId::BuiltinDeriveImplId(it) => it.into(), + } + } +} +impl TryFrom for AnyImplId { + type Error = (); + #[inline] + fn try_from(value: SolverDefId) -> Result { + match value { + SolverDefId::ImplId(it) => Ok(it.into()), + SolverDefId::BuiltinDeriveImplId(it) => Ok(it.into()), + _ => Err(()), + } + } +} +impl<'db> inherent::DefId> for AnyImplId { + fn as_local(self) -> Option { + Some(self.into()) + } + fn is_local(self) -> bool { + true + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generics.rs index 4d164a7e3bc5e..a8288b4e82590 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generics.rs @@ -4,14 +4,15 @@ use hir_def::{ ConstParamId, GenericDefId, GenericParamId, LifetimeParamId, TypeOrConstParamId, TypeParamId, hir::generics::{GenericParams, TypeOrConstParamData}, }; +use rustc_type_ir::inherent::GenericsOf; -use crate::{db::HirDatabase, generics::parent_generic_def}; +use crate::generics::parent_generic_def; use super::SolverDefId; use super::DbInterner; -pub(crate) fn generics(db: &dyn HirDatabase, def: SolverDefId) -> Generics { +pub(crate) fn generics(interner: DbInterner<'_>, def: SolverDefId) -> Generics { let mk_lt = |parent, index, local_id| { let id = GenericParamId::LifetimeParamId(LifetimeParamId { parent, local_id }); GenericParamDef { index, id } @@ -50,6 +51,7 @@ pub(crate) fn generics(db: &dyn HirDatabase, def: SolverDefId) -> Generics { result }; + let db = interner.db; let (parent, own_params) = match (def.try_into(), def) { (Ok(def), _) => ( parent_generic_def(db, def), @@ -66,9 +68,12 @@ pub(crate) fn generics(db: &dyn HirDatabase, def: SolverDefId) -> Generics { } } } + (_, SolverDefId::BuiltinDeriveImplId(id)) => { + return crate::builtin_derive::generics_of(interner, id); + } _ => panic!("No generics for {def:?}"), }; - let parent_generics = parent.map(|def| Box::new(generics(db, def.into()))); + let parent_generics = parent.map(|def| Box::new(generics(interner, def.into()))); Generics { parent, @@ -84,6 +89,13 @@ pub struct Generics { pub own_params: Vec, } +impl Generics { + pub(crate) fn push_param(&mut self, id: GenericParamId) { + let index = self.count() as u32; + self.own_params.push(GenericParamDef { index, id }); + } +} + #[derive(Debug)] pub struct GenericParamDef { index: u32, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/select.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/select.rs index 52ad410df6be7..bd407fd15718a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/select.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/select.rs @@ -2,7 +2,7 @@ use std::ops::ControlFlow; -use hir_def::{ImplId, TraitId}; +use hir_def::TraitId; use macros::{TypeFoldable, TypeVisitable}; use rustc_type_ir::{ Interner, @@ -12,7 +12,7 @@ use rustc_type_ir::{ use crate::{ db::InternedOpaqueTyId, next_solver::{ - Const, ErrorGuaranteed, GenericArgs, Goal, TraitRef, Ty, TypeError, + AnyImplId, Const, ErrorGuaranteed, GenericArgs, Goal, TraitRef, Ty, TypeError, infer::{ InferCtxt, select::EvaluationResult::*, @@ -249,7 +249,7 @@ impl<'db, N> ImplSource<'db, N> { pub(crate) struct ImplSourceUserDefinedData<'db, N> { #[type_visitable(ignore)] #[type_foldable(identity)] - pub(crate) impl_def_id: ImplId, + pub(crate) impl_def_id: AnyImplId, pub(crate) args: GenericArgs<'db>, pub(crate) nested: Vec, } @@ -395,7 +395,7 @@ fn to_selection<'db>(cand: InspectCandidate<'_, 'db>) -> Option> // FIXME: Remove this in favor of storing this in the tree // For impl candidates, we do the rematch manually to compute the args. ImplSource::UserDefined(ImplSourceUserDefinedData { - impl_def_id: impl_def_id.0, + impl_def_id, args: cand.instantiate_impl_args(), nested, }) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs index 2ebc5b81ba5c1..269474e0153e3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs @@ -38,10 +38,10 @@ use crate::{ lower::GenericPredicates, method_resolution::TraitImpls, next_solver::{ - AdtIdWrapper, BoundConst, CallableIdWrapper, CanonicalVarKind, ClosureIdWrapper, - CoroutineIdWrapper, Ctor, FnSig, FxIndexMap, GeneralConstIdWrapper, ImplIdWrapper, - OpaqueTypeKey, RegionAssumptions, SimplifiedType, SolverContext, SolverDefIds, - TraitIdWrapper, TypeAliasIdWrapper, UnevaluatedConst, util::explicit_item_bounds, + AdtIdWrapper, AnyImplId, BoundConst, CallableIdWrapper, CanonicalVarKind, ClosureIdWrapper, + CoroutineIdWrapper, Ctor, FnSig, FxIndexMap, GeneralConstIdWrapper, OpaqueTypeKey, + RegionAssumptions, SimplifiedType, SolverContext, SolverDefIds, TraitIdWrapper, + TypeAliasIdWrapper, UnevaluatedConst, util::explicit_item_bounds, }, }; @@ -1020,7 +1020,7 @@ impl<'db> Interner for DbInterner<'db> { type CoroutineClosureId = CoroutineIdWrapper; type CoroutineId = CoroutineIdWrapper; type AdtId = AdtIdWrapper; - type ImplId = ImplIdWrapper; + type ImplId = AnyImplId; type UnevaluatedConstId = GeneralConstIdWrapper; type Span = Span; @@ -1164,7 +1164,7 @@ impl<'db> Interner for DbInterner<'db> { } fn generics_of(self, def_id: Self::DefId) -> Self::GenericsOf { - generics(self.db(), def_id) + generics(self, def_id) } fn variances_of(self, def_id: Self::DefId) -> Self::VariancesOf { @@ -1190,6 +1190,7 @@ impl<'db> Interner for DbInterner<'db> { | SolverDefId::TraitId(_) | SolverDefId::TypeAliasId(_) | SolverDefId::ImplId(_) + | SolverDefId::BuiltinDeriveImplId(_) | SolverDefId::InternedClosureId(_) | SolverDefId::InternedCoroutineId(_) => { return VariancesOf::empty(self); @@ -1327,6 +1328,7 @@ impl<'db> Interner for DbInterner<'db> { | SolverDefId::AdtId(_) | SolverDefId::TraitId(_) | SolverDefId::ImplId(_) + | SolverDefId::BuiltinDeriveImplId(_) | SolverDefId::EnumVariantId(..) | SolverDefId::Ctor(..) | SolverDefId::InternedOpaqueTyId(..) => panic!(), @@ -1445,8 +1447,7 @@ impl<'db> Interner for DbInterner<'db> { self, def_id: Self::DefId, ) -> EarlyBinder> { - GenericPredicates::query_all(self.db, def_id.try_into().unwrap()) - .map_bound(|it| it.iter().copied()) + predicates_of(self.db, def_id).all_predicates().map_bound(|it| it.iter().copied()) } #[tracing::instrument(level = "debug", skip(self), ret)] @@ -1454,8 +1455,7 @@ impl<'db> Interner for DbInterner<'db> { self, def_id: Self::DefId, ) -> EarlyBinder> { - GenericPredicates::query_own(self.db, def_id.try_into().unwrap()) - .map_bound(|it| it.iter().copied()) + predicates_of(self.db, def_id).own_predicates().map_bound(|it| it.iter().copied()) } #[tracing::instrument(skip(self), ret)] @@ -1500,32 +1500,30 @@ impl<'db> Interner for DbInterner<'db> { } } - GenericPredicates::query_explicit(self.db, def_id.try_into().unwrap()).map_bound( - |predicates| { - predicates - .iter() - .copied() - .filter(|p| match p.kind().skip_binder() { - ClauseKind::Trait(it) => is_self_or_assoc(it.self_ty()), - ClauseKind::TypeOutlives(it) => is_self_or_assoc(it.0), - ClauseKind::Projection(it) => is_self_or_assoc(it.self_ty()), - ClauseKind::HostEffect(it) => is_self_or_assoc(it.self_ty()), - // FIXME: Not sure is this correct to allow other clauses but we might replace - // `generic_predicates_ns` query here with something closer to rustc's - // `implied_bounds_with_filter`, which is more granular lowering than this - // "lower at once and then filter" implementation. - _ => true, - }) - .map(|p| (p, Span::dummy())) - }, - ) + predicates_of(self.db, def_id).explicit_predicates().map_bound(|predicates| { + predicates + .iter() + .copied() + .filter(|p| match p.kind().skip_binder() { + ClauseKind::Trait(it) => is_self_or_assoc(it.self_ty()), + ClauseKind::TypeOutlives(it) => is_self_or_assoc(it.0), + ClauseKind::Projection(it) => is_self_or_assoc(it.self_ty()), + ClauseKind::HostEffect(it) => is_self_or_assoc(it.self_ty()), + // FIXME: Not sure is this correct to allow other clauses but we might replace + // `generic_predicates_ns` query here with something closer to rustc's + // `implied_bounds_with_filter`, which is more granular lowering than this + // "lower at once and then filter" implementation. + _ => true, + }) + .map(|p| (p, Span::dummy())) + }) } fn impl_super_outlives( self, impl_id: Self::ImplId, ) -> EarlyBinder> { - let trait_ref = self.db().impl_trait(impl_id.0).expect("expected an impl of trait"); + let trait_ref = self.impl_trait_ref(impl_id); trait_ref.map_bound(|trait_ref| { let clause: Clause<'_> = trait_ref.upcast(self); elaborate(self, [clause]).filter(|clause| { @@ -1790,6 +1788,7 @@ impl<'db> Interner for DbInterner<'db> { SolverDefId::ConstId(_) | SolverDefId::FunctionId(_) | SolverDefId::ImplId(_) + | SolverDefId::BuiltinDeriveImplId(_) | SolverDefId::StaticId(_) | SolverDefId::InternedClosureId(_) | SolverDefId::InternedCoroutineId(_) @@ -1805,7 +1804,12 @@ impl<'db> Interner for DbInterner<'db> { type_block, trait_block, &mut |impls| { - for &impl_ in impls.for_trait_and_self_ty(trait_def_id.0, &simp) { + let (regular_impls, builtin_derive_impls) = + impls.for_trait_and_self_ty(trait_def_id.0, &simp); + for &impl_ in regular_impls { + f(impl_.into()); + } + for &impl_ in builtin_derive_impls { f(impl_.into()); } }, @@ -1927,7 +1931,10 @@ impl<'db> Interner for DbInterner<'db> { } fn impl_is_default(self, impl_def_id: Self::ImplId) -> bool { - self.db.impl_signature(impl_def_id.0).is_default() + match impl_def_id { + AnyImplId::ImplId(impl_id) => self.db.impl_signature(impl_id).is_default(), + AnyImplId::BuiltinDeriveImplId(_) => false, + } } #[tracing::instrument(skip(self), ret)] @@ -1935,14 +1942,24 @@ impl<'db> Interner for DbInterner<'db> { self, impl_id: Self::ImplId, ) -> EarlyBinder> { - let db = self.db(); - db.impl_trait(impl_id.0) - // ImplIds for impls where the trait ref can't be resolved should never reach trait solving - .expect("invalid impl passed to trait solver") + match impl_id { + AnyImplId::ImplId(impl_id) => { + let db = self.db(); + db.impl_trait(impl_id) + // ImplIds for impls where the trait ref can't be resolved should never reach trait solving + .expect("invalid impl passed to trait solver") + } + AnyImplId::BuiltinDeriveImplId(impl_id) => { + crate::builtin_derive::impl_trait(self, impl_id) + } + } } fn impl_polarity(self, impl_id: Self::ImplId) -> rustc_type_ir::ImplPolarity { - let impl_data = self.db().impl_signature(impl_id.0); + let AnyImplId::ImplId(impl_id) = impl_id else { + return ImplPolarity::Positive; + }; + let impl_data = self.db().impl_signature(impl_id); if impl_data.flags.contains(ImplFlags::NEGATIVE) { ImplPolarity::Negative } else { @@ -2230,11 +2247,13 @@ impl<'db> Interner for DbInterner<'db> { specializing_impl_def_id: Self::ImplId, parent_impl_def_id: Self::ImplId, ) -> bool { - crate::specialization::specializes( - self.db, - specializing_impl_def_id.0, - parent_impl_def_id.0, - ) + let (AnyImplId::ImplId(specializing_impl_def_id), AnyImplId::ImplId(parent_impl_def_id)) = + (specializing_impl_def_id, parent_impl_def_id) + else { + // No builtin derive allow specialization currently. + return false; + }; + crate::specialization::specializes(self.db, specializing_impl_def_id, parent_impl_def_id) } fn next_trait_solver_globally(self) -> bool { @@ -2349,6 +2368,14 @@ impl<'db> DbInterner<'db> { } } +fn predicates_of(db: &dyn HirDatabase, def_id: SolverDefId) -> &GenericPredicates { + if let SolverDefId::BuiltinDeriveImplId(impl_) = def_id { + crate::builtin_derive::builtin_derive_predicates(db, impl_) + } else { + GenericPredicates::query(db, def_id.try_into().unwrap()) + } +} + macro_rules! TrivialTypeTraversalImpls { ($($ty:ty,)+) => { $( @@ -2396,7 +2423,7 @@ TrivialTypeTraversalImpls! { ClosureIdWrapper, CoroutineIdWrapper, AdtIdWrapper, - ImplIdWrapper, + AnyImplId, GeneralConstIdWrapper, Safety, FnAbi, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs index d800925ba4e9a..21fbd64dd066e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs @@ -12,7 +12,7 @@ use rustc_type_ir::{ use tracing::debug; use crate::next_solver::{ - AliasTy, CanonicalVarKind, Clause, ClauseKind, CoercePredicate, GenericArgs, ImplIdWrapper, + AliasTy, AnyImplId, CanonicalVarKind, Clause, ClauseKind, CoercePredicate, GenericArgs, ParamEnv, Predicate, PredicateKind, SubtypePredicate, Ty, TyKind, fold::fold_tys, util::sizedness_fast_path, }; @@ -174,9 +174,13 @@ impl<'db> SolverDelegate for SolverContext<'db> { &self, _goal_trait_ref: rustc_type_ir::TraitRef, trait_assoc_def_id: SolverDefId, - impl_id: ImplIdWrapper, + impl_id: AnyImplId, ) -> Result, ErrorGuaranteed> { - let impl_items = impl_id.0.impl_items(self.0.interner.db()); + let AnyImplId::ImplId(impl_id) = impl_id else { + // Builtin derive traits don't have type/consts assoc items. + return Ok(None); + }; + let impl_items = impl_id.impl_items(self.0.interner.db()); let id = match trait_assoc_def_id { SolverDefId::TypeAliasId(trait_assoc_id) => { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs index 6558d2179fba6..ea33c9bcd4f1c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs @@ -243,6 +243,10 @@ $0", "parse_shim", "real_span_map_shim", "TraitImpls::for_crate_", + "lang_items", + "crate_lang_items", + "AttrFlags::query_", + "AttrFlags::query_", ] "#]], ); @@ -279,6 +283,10 @@ pub struct NewStruct { "real_span_map_shim", "crate_local_def_map", "TraitImpls::for_crate_", + "crate_lang_items", + "AttrFlags::query_", + "AttrFlags::query_", + "AttrFlags::query_", ] "#]], ); @@ -314,6 +322,10 @@ $0", "parse_shim", "real_span_map_shim", "TraitImpls::for_crate_", + "lang_items", + "crate_lang_items", + "AttrFlags::query_", + "AttrFlags::query_", ] "#]], ); @@ -351,6 +363,13 @@ pub enum SomeEnum { "real_span_map_shim", "crate_local_def_map", "TraitImpls::for_crate_", + "crate_lang_items", + "AttrFlags::query_", + "AttrFlags::query_", + "AttrFlags::query_", + "EnumVariants::of_", + "AttrFlags::query_", + "AttrFlags::query_", ] "#]], ); @@ -386,6 +405,10 @@ $0", "parse_shim", "real_span_map_shim", "TraitImpls::for_crate_", + "lang_items", + "crate_lang_items", + "AttrFlags::query_", + "AttrFlags::query_", ] "#]], ); @@ -420,6 +443,9 @@ fn bar() -> f32 { "real_span_map_shim", "crate_local_def_map", "TraitImpls::for_crate_", + "crate_lang_items", + "AttrFlags::query_", + "AttrFlags::query_", ] "#]], ); @@ -459,6 +485,11 @@ $0", "parse_shim", "real_span_map_shim", "TraitImpls::for_crate_", + "lang_items", + "crate_lang_items", + "AttrFlags::query_", + "AttrFlags::query_", + "AttrFlags::query_", ] "#]], ); @@ -501,17 +532,16 @@ impl SomeStruct { "real_span_map_shim", "crate_local_def_map", "TraitImpls::for_crate_", - "AttrFlags::query_", - "impl_trait_with_diagnostics_query", - "impl_signature_shim", - "impl_signature_with_source_map_shim", - "lang_items", "crate_lang_items", + "AttrFlags::query_", "ImplItems::of_", "AttrFlags::query_", "AttrFlags::query_", "AttrFlags::query_", "AttrFlags::query_", + "impl_trait_with_diagnostics_shim", + "impl_signature_shim", + "impl_signature_with_source_map_shim", "impl_self_ty_with_diagnostics_query", "struct_signature_shim", "struct_signature_with_source_map_shim", 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 6e9c6d26b5b1e..aa7e40c65848e 100644 --- a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs +++ b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs @@ -525,5 +525,7 @@ define_symbols! { arbitrary_self_types, arbitrary_self_types_pointers, supertrait_item_shadowing, + hash, + cmp, define_opaque, } From 2f3a23c7e9250d87b7d0013f841de3e9cf03d6f2 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 26 Dec 2025 09:35:55 +0200 Subject: [PATCH 28/77] =?UTF-8?q?Stabilize=20type=20mismatch=20diagnostic?= =?UTF-8?q?=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crates/ide-diagnostics/src/handlers/type_mismatch.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs index e6702ccf13999..f443dc08f5fd2 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -52,6 +52,7 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch< ), display_range, ) + .stable() .with_fixes(fixes(ctx, d)) } From 04529b2e8332b9d49c6b4b205458400c61dc7e11 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 3 Dec 2025 19:09:42 +0200 Subject: [PATCH 29/77] Allow IDE layer to "see" fake builtin derive impls It sees them as regular impls; the details are abstracted. It's beautiful for the IDE layer, and less beautiful for `hir`, so this is a big change. Some small differences still exist: - We show builtin derives impl (to the IDE layer) as if they have had no generic parameters. It is possible to show the parameters, but that means also having to handle fake impls in `TypeParam` etc., and the benefit is questionable. - Getting the fn *def* type of a method of a builtin derive impl is not supported, as there is no real `FunctionId`, therefore no `CallableDefId`. The trait method is returned instead. Note: getting the fn *ptr* type of the method is supported well. - Builtin derive impls and their methods do not fully support `HasSource`, because, well, they have no source (at least, not in the form of `ast::Impl` and `ast::Fn`). To support them, we use the derive's `TextRange` where possible, and the trait method's source when not. It's important to note that the def map still records the `MacroCallId`. I have doubts over this, as this means it's very easy to create the queries we don't want to create, but it does make things more convenient. In particular, a nicety of this setup is that even "Expand macro recursively" works (it creates the macro input/output query, but given that they will only be created when the user invokes the command, that does not seem to be a problem). --- .../crates/hir-def/src/builtin_derive.rs | 114 +- .../crates/hir-def/src/item_scope.rs | 7 +- .../rust-analyzer/crates/hir-def/src/lib.rs | 15 + .../builtin_derive_macro.rs | 1657 +++++++++-------- .../src/macro_expansion_tests/proc_macros.rs | 8 +- .../crates/hir-def/src/nameres/collector.rs | 35 +- .../crates/hir-ty/src/builtin_derive.rs | 78 +- .../crates/hir-ty/src/consteval/tests.rs | 1 + .../rust-analyzer/crates/hir-ty/src/db.rs | 10 +- .../crates/hir-ty/src/display.rs | 49 +- .../rust-analyzer/crates/hir-ty/src/lib.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/lower.rs | 23 +- .../crates/hir-ty/src/method_resolution.rs | 75 +- .../crates/hir-ty/src/mir/eval.rs | 7 +- .../crates/hir-ty/src/mir/eval/shim.rs | 19 +- .../crates/hir-ty/src/mir/eval/shim/simd.rs | 15 - .../src/next_solver/format_proof_tree.rs | 31 +- .../crates/hir-ty/src/next_solver/interner.rs | 2 +- .../crates/hir-ty/src/tests/incremental.rs | 2 +- .../rust-analyzer/crates/hir/src/attrs.rs | 45 +- .../rust-analyzer/crates/hir/src/display.rs | 343 ++-- .../rust-analyzer/crates/hir/src/from_id.rs | 102 +- .../crates/hir/src/has_source.rs | 72 +- src/tools/rust-analyzer/crates/hir/src/lib.rs | 962 +++++++--- .../rust-analyzer/crates/hir/src/semantics.rs | 82 +- .../crates/hir/src/source_analyzer.rs | 54 +- .../test_symbol_index_collection.txt | 24 +- .../crates/ide/src/inlay_hints.rs | 28 +- .../ide/src/inlay_hints/implicit_drop.rs | 5 +- .../crates/ide/src/navigation_target.rs | 62 +- .../crates/ide/src/references.rs | 2 +- .../rust-analyzer/crates/ide/src/runnables.rs | 8 +- .../crates/intern/src/symbol/symbols.rs | 3 + .../rust-analyzer/src/cli/analysis_stats.rs | 32 +- 34 files changed, 2445 insertions(+), 1529 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs b/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs index e0e163783f436..32385516ab583 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/builtin_derive.rs @@ -3,40 +3,102 @@ //! To save time and memory, builtin derives are not really expanded. Instead, we record them //! and create their impls based on lowered data, see crates/hir-ty/src/builtin_derive.rs. -use hir_expand::builtin::BuiltinDeriveExpander; +use hir_expand::{InFile, builtin::BuiltinDeriveExpander, name::Name}; +use intern::{Symbol, sym}; +use tt::TextRange; + +use crate::{ + AdtId, BuiltinDeriveImplId, BuiltinDeriveImplLoc, FunctionId, HasModule, db::DefDatabase, +}; macro_rules! declare_enum { - ( $( $trait:ident ),* $(,)? ) => { + ( $( $trait:ident => [ $( $method:ident ),* ] ),* $(,)? ) => { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BuiltinDeriveImplTrait { $( $trait, )* } + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[allow(non_camel_case_types)] + pub enum BuiltinDeriveImplMethod { + $( $( $method, )* )* + } + impl BuiltinDeriveImplTrait { + #[inline] + pub fn name(self) -> Symbol { + match self { + $( Self::$trait => sym::$trait, )* + } + } + #[inline] pub fn get_id(self, lang_items: &crate::lang_item::LangItems) -> Option { match self { $( Self::$trait => lang_items.$trait, )* } } + + #[inline] + pub fn get_method(self, method_name: &Symbol) -> Option { + match self { + $( + Self::$trait => { + match method_name { + $( _ if *method_name == sym::$method => Some(BuiltinDeriveImplMethod::$method), )* + _ => None, + } + } + )* + } + } + + #[inline] + pub fn all_methods(self) -> &'static [BuiltinDeriveImplMethod] { + match self { + $( Self::$trait => &[ $(BuiltinDeriveImplMethod::$method),* ], )* + } + } + } + + impl BuiltinDeriveImplMethod { + #[inline] + pub fn name(self) -> Symbol { + match self { + $( $( BuiltinDeriveImplMethod::$method => sym::$method, )* )* + } + } } }; } declare_enum!( - Copy, - Clone, - Default, - Debug, - Hash, - Ord, - PartialOrd, - Eq, - PartialEq, - CoerceUnsized, - DispatchFromDyn, + Copy => [], + Clone => [clone], + Default => [default], + Debug => [fmt], + Hash => [hash], + Ord => [cmp], + PartialOrd => [partial_cmp], + Eq => [], + PartialEq => [eq], + CoerceUnsized => [], + DispatchFromDyn => [], ); +impl BuiltinDeriveImplMethod { + pub fn trait_method( + self, + db: &dyn DefDatabase, + impl_: BuiltinDeriveImplId, + ) -> Option { + let loc = impl_.loc(db); + let lang_items = crate::lang_item::lang_items(db, loc.krate(db)); + let trait_ = impl_.loc(db).trait_.get_id(lang_items)?; + trait_.trait_items(db).method_by_name(&Name::new_symbol_root(self.name())) + } +} + pub(crate) fn with_derive_traits( derive: BuiltinDeriveExpander, mut f: impl FnMut(BuiltinDeriveImplTrait), @@ -59,3 +121,29 @@ pub(crate) fn with_derive_traits( }; f(trait_); } + +impl BuiltinDeriveImplLoc { + pub fn source(&self, db: &dyn DefDatabase) -> InFile { + let (adt_ast_id, module) = match self.adt { + AdtId::StructId(adt) => { + let adt_loc = adt.loc(db); + (adt_loc.id.upcast(), adt_loc.container) + } + AdtId::UnionId(adt) => { + let adt_loc = adt.loc(db); + (adt_loc.id.upcast(), adt_loc.container) + } + AdtId::EnumId(adt) => { + let adt_loc = adt.loc(db); + (adt_loc.id.upcast(), adt_loc.container) + } + }; + let derive_range = self.derive_attr_id.find_derive_range( + db, + module.krate(db), + adt_ast_id, + self.derive_index, + ); + adt_ast_id.with_value(derive_range) + } +} 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 4f3700e8e41c7..9e7868b273efa 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 @@ -9,7 +9,7 @@ use indexmap::map::Entry; use itertools::Itertools; use la_arena::Idx; use rustc_hash::{FxHashMap, FxHashSet}; -use smallvec::{SmallVec, smallvec}; +use smallvec::SmallVec; use span::Edition; use stdx::format_to; use syntax::ast; @@ -531,12 +531,13 @@ impl ItemScope { adt: AstId, attr_id: AttrId, attr_call_id: MacroCallId, - len: usize, + mut derive_call_ids: SmallVec<[Option; 4]>, ) { + derive_call_ids.shrink_to_fit(); self.derive_macros.entry(adt).or_default().push(DeriveMacroInvocation { attr_id, attr_call_id, - derive_call_ids: smallvec![None; len], + derive_call_ids, }); } 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 fde1e6ca17ee3..8d6c418d75dc5 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -64,6 +64,7 @@ use base_db::{Crate, impl_intern_key}; use hir_expand::{ AstId, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallStyles, MacroDefId, MacroDefKind, + attrs::AttrId, builtin::{BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, eager::expand_eager_macro_input, @@ -337,6 +338,8 @@ impl ImplId { pub struct BuiltinDeriveImplLoc { pub adt: AdtId, pub trait_: BuiltinDeriveImplTrait, + pub derive_attr_id: AttrId, + pub derive_index: u32, } #[salsa::interned(debug, no_lifetime)] @@ -675,6 +678,18 @@ impl_from!( for ModuleDefId ); +impl From for ModuleDefId { + #[inline] + fn from(value: DefWithBodyId) -> Self { + match value { + DefWithBodyId::FunctionId(id) => id.into(), + DefWithBodyId::StaticId(id) => id.into(), + DefWithBodyId::ConstId(id) => id.into(), + DefWithBodyId::VariantId(id) => id.into(), + } + } +} + /// A constant, which might appears as a const item, an anonymous const block in expressions /// or patterns, or as a constant in types with const generics. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa_macros::Supertype)] diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs index 0013c2a25679a..814afdd16cf93 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs @@ -1,828 +1,833 @@ //! Tests for `builtin_derive_macro.rs` from `hir_expand`. -use expect_test::expect; - -use crate::macro_expansion_tests::{check, check_errors}; - -#[test] -fn test_copy_expand_simple() { - check( - r#" -//- minicore: derive, copy -#[derive(Copy)] -struct Foo; -"#, - expect![[r#" -#[derive(Copy)] -struct Foo; - -impl <> $crate::marker::Copy for Foo< > where {}"#]], - ); -} - -#[test] -fn test_copy_expand_in_core() { - check( - r#" -//- /lib.rs crate:core -#[rustc_builtin_macro] -macro derive {} -#[rustc_builtin_macro] -macro Copy {} -#[derive(Copy)] -struct Foo; -"#, - expect![[r#" -#[rustc_builtin_macro] -macro derive {} -#[rustc_builtin_macro] -macro Copy {} -#[derive(Copy)] -struct Foo; - -impl <> $crate::marker::Copy for Foo< > where {}"#]], - ); -} - -#[test] -fn test_copy_expand_with_type_params() { - check( - r#" -//- minicore: derive, copy -#[derive(Copy)] -struct Foo; -"#, - expect![[r#" -#[derive(Copy)] -struct Foo; - -impl $crate::marker::Copy for Foo where {}"#]], - ); -} - -#[test] -fn test_copy_expand_with_lifetimes() { - // We currently just ignore lifetimes - check( - r#" -//- minicore: derive, copy -#[derive(Copy)] -struct Foo; -"#, - expect![[r#" -#[derive(Copy)] -struct Foo; - -impl $crate::marker::Copy for Foo where {}"#]], - ); -} - -#[test] -fn test_clone_expand() { - check( - r#" -//- minicore: derive, clone -#[derive(Clone)] -enum Command { - Move { x: A, y: B }, - Do(&'static str), - Jump, -} -"#, - expect![[r#" -#[derive(Clone)] -enum Command { - Move { x: A, y: B }, - Do(&'static str), - Jump, -} - -impl $crate::clone::Clone for Command where { - fn clone(&self ) -> Self { - match self { - Command::Move { - x: x, y: y, - } - =>Command::Move { - x: x.clone(), y: y.clone(), - } - , Command::Do(f0, )=>Command::Do(f0.clone(), ), Command::Jump=>Command::Jump, - } - } -}"#]], - ); -} - -#[test] -fn test_clone_expand_with_associated_types() { - check( - r#" -//- minicore: derive, clone -trait Trait { - type InWc; - type InFieldQualified; - type InFieldShorthand; - type InGenericArg; -} -trait Marker {} -struct Vec(T); - -#[derive(Clone)] -struct Foo -where - ::InWc: Marker, -{ - qualified: ::InFieldQualified, - shorthand: T::InFieldShorthand, - generic: Vec, -} -"#, - expect![[r#" -trait Trait { - type InWc; - type InFieldQualified; - type InFieldShorthand; - type InGenericArg; -} -trait Marker {} -struct Vec(T); - -#[derive(Clone)] -struct Foo -where - ::InWc: Marker, -{ - qualified: ::InFieldQualified, - shorthand: T::InFieldShorthand, - generic: Vec, -} - -impl $crate::clone::Clone for Foo where ::InWc: Marker, T: Trait, T::InFieldShorthand: $crate::clone::Clone, T::InGenericArg: $crate::clone::Clone, { - fn clone(&self ) -> Self { - match self { - Foo { - qualified: qualified, shorthand: shorthand, generic: generic, - } - =>Foo { - qualified: qualified.clone(), shorthand: shorthand.clone(), generic: generic.clone(), - } - , - } - } -}"#]], - ); -} - -#[test] -fn test_clone_expand_with_const_generics() { - check( - r#" -//- minicore: derive, clone -#[derive(Clone)] -struct Foo(u32); -"#, - expect![[r#" -#[derive(Clone)] -struct Foo(u32); - -impl $crate::clone::Clone for Foo where { - fn clone(&self ) -> Self { - match self { - Foo(f0, )=>Foo(f0.clone(), ), - } - } -}"#]], - ); -} - -#[test] -fn test_default_expand() { - check( - r#" -//- minicore: derive, default -#[derive(Default)] -struct Foo { - field1: i32, - field2: (), -} -#[derive(Default)] -enum Bar { - Foo(u8), - #[default] - Bar, -} -"#, - expect![[r#" -#[derive(Default)] -struct Foo { - field1: i32, - field2: (), -} -#[derive(Default)] -enum Bar { - Foo(u8), - #[default] - Bar, -} - -impl <> $crate::default::Default for Foo< > where { - fn default() -> Self { - Foo { - field1: $crate::default::Default::default(), field2: $crate::default::Default::default(), - } - } -} -impl <> $crate::default::Default for Bar< > where { - fn default() -> Self { - Bar::Bar - } -}"#]], - ); -} - -#[test] -fn test_partial_eq_expand() { - check( - r#" -//- minicore: derive, eq -#[derive(PartialEq, Eq)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} -"#, - expect![[r#" -#[derive(PartialEq, Eq)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} - -impl <> $crate::cmp::PartialEq for Command< > where { - fn eq(&self , other: &Self ) -> bool { - match (self , other) { - (Command::Move { - x: x_self, y: y_self, - } - , Command::Move { - x: x_other, y: y_other, - } - )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false - } - } -} -impl <> $crate::cmp::Eq for Command< > where {}"#]], - ); -} - -#[test] -fn test_partial_eq_expand_with_derive_const() { - // FIXME: actually expand with const - check( - r#" -//- minicore: derive, eq -#[derive_const(PartialEq, Eq)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} -"#, - expect![[r#" -#[derive_const(PartialEq, Eq)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} - -impl <> $crate::cmp::PartialEq for Command< > where { - fn eq(&self , other: &Self ) -> bool { - match (self , other) { - (Command::Move { - x: x_self, y: y_self, - } - , Command::Move { - x: x_other, y: y_other, - } - )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false - } - } -} -impl <> $crate::cmp::Eq for Command< > where {}"#]], - ); -} - -#[test] -fn test_partial_ord_expand() { - check( - r#" -//- minicore: derive, ord -#[derive(PartialOrd, Ord)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} -"#, - expect![[r#" -#[derive(PartialOrd, Ord)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} - -impl <> $crate::cmp::PartialOrd for Command< > where { - fn partial_cmp(&self , other: &Self ) -> $crate::option::Option<$crate::cmp::Ordering> { - match $crate::intrinsics::discriminant_value(self ).partial_cmp(&$crate::intrinsics::discriminant_value(other)) { - $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { - match (self , other) { - (Command::Move { - x: x_self, y: y_self, - } - , Command::Move { - x: x_other, y: y_other, - } - )=>match x_self.partial_cmp(&x_other) { - $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { - match y_self.partial_cmp(&y_other) { - $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { - $crate::option::Option::Some($crate::cmp::Ordering::Equal) - } - c=>return c, - } - } - c=>return c, - } - , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.partial_cmp(&f0_other) { - $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { - $crate::option::Option::Some($crate::cmp::Ordering::Equal) - } - c=>return c, - } - , (Command::Jump, Command::Jump)=>$crate::option::Option::Some($crate::cmp::Ordering::Equal), _unused=>$crate::option::Option::Some($crate::cmp::Ordering::Equal) - } - } - c=>return c, - } - } -} -impl <> $crate::cmp::Ord for Command< > where { - fn cmp(&self , other: &Self ) -> $crate::cmp::Ordering { - match $crate::intrinsics::discriminant_value(self ).cmp(&$crate::intrinsics::discriminant_value(other)) { - $crate::cmp::Ordering::Equal=> { - match (self , other) { - (Command::Move { - x: x_self, y: y_self, - } - , Command::Move { - x: x_other, y: y_other, - } - )=>match x_self.cmp(&x_other) { - $crate::cmp::Ordering::Equal=> { - match y_self.cmp(&y_other) { - $crate::cmp::Ordering::Equal=> { - $crate::cmp::Ordering::Equal - } - c=>return c, - } - } - c=>return c, - } - , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.cmp(&f0_other) { - $crate::cmp::Ordering::Equal=> { - $crate::cmp::Ordering::Equal - } - c=>return c, - } - , (Command::Jump, Command::Jump)=>$crate::cmp::Ordering::Equal, _unused=>$crate::cmp::Ordering::Equal - } - } - c=>return c, - } - } -}"#]], - ); -} - -#[test] -fn test_hash_expand() { - check( - r#" -//- minicore: derive, hash -use core::hash::Hash; - -#[derive(Hash)] -struct Foo { - x: i32, - y: u64, - z: (i32, u64), -} -"#, - expect![[r#" -use core::hash::Hash; - -#[derive(Hash)] -struct Foo { - x: i32, - y: u64, - z: (i32, u64), -} - -impl <> $crate::hash::Hash for Foo< > where { - fn hash(&self , ra_expand_state: &mut H) { - match self { - Foo { - x: x, y: y, z: z, - } - => { - x.hash(ra_expand_state); - y.hash(ra_expand_state); - z.hash(ra_expand_state); - } - , - } - } -}"#]], - ); - check( - r#" -//- minicore: derive, hash -use core::hash::Hash; - -#[derive(Hash)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} -"#, - expect![[r#" -use core::hash::Hash; - -#[derive(Hash)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} - -impl <> $crate::hash::Hash for Command< > where { - fn hash(&self , ra_expand_state: &mut H) { - $crate::mem::discriminant(self ).hash(ra_expand_state); - match self { - Command::Move { - x: x, y: y, - } - => { - x.hash(ra_expand_state); - y.hash(ra_expand_state); - } - , Command::Do(f0, )=> { - f0.hash(ra_expand_state); - } - , Command::Jump=> {} - , - } - } -}"#]], - ); -} - -#[test] -fn test_debug_expand() { - check( - r#" -//- minicore: derive, fmt -use core::fmt::Debug; - -#[derive(Debug)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} -"#, - expect![[r#" -use core::fmt::Debug; - -#[derive(Debug)] -enum Command { - Move { x: i32, y: i32 }, - Do(&'static str), - Jump, -} - -impl <> $crate::fmt::Debug for Command< > where { - fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { - match self { - Command::Move { - x: x, y: y, - } - =>f.debug_struct("Move").field("x", &x).field("y", &y).finish(), Command::Do(f0, )=>f.debug_tuple("Do").field(&f0).finish(), Command::Jump=>f.write_str("Jump"), - } - } -}"#]], - ); -} -#[test] -fn test_debug_expand_with_cfg() { - check( - r#" - //- minicore: derive, fmt - use core::fmt::Debug; - - #[derive(Debug)] - struct HideAndShow { - #[cfg(never)] - always_hide: u32, - #[cfg(not(never))] - always_show: u32, - } - #[derive(Debug)] - enum HideAndShowEnum { - #[cfg(never)] - AlwaysHide, - #[cfg(not(never))] - AlwaysShow{ - #[cfg(never)] - always_hide: u32, - #[cfg(not(never))] - always_show: u32, - } - } - "#, - expect![[r#" -use core::fmt::Debug; - -#[derive(Debug)] -struct HideAndShow { - #[cfg(never)] - always_hide: u32, - #[cfg(not(never))] - always_show: u32, -} -#[derive(Debug)] -enum HideAndShowEnum { - #[cfg(never)] - AlwaysHide, - #[cfg(not(never))] - AlwaysShow{ - #[cfg(never)] - always_hide: u32, - #[cfg(not(never))] - always_show: u32, - } -} - -impl <> $crate::fmt::Debug for HideAndShow< > where { - fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { - match self { - HideAndShow { - always_show: always_show, - } - =>f.debug_struct("HideAndShow").field("always_show", &always_show).finish() - } - } -} -impl <> $crate::fmt::Debug for HideAndShowEnum< > where { - fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { - match self { - HideAndShowEnum::AlwaysShow { - always_show: always_show, - } - =>f.debug_struct("AlwaysShow").field("always_show", &always_show).finish(), - } - } -}"#]], - ); -} -#[test] -fn test_default_expand_with_cfg() { - check( - r#" -//- minicore: derive, default -#[derive(Default)] -struct Foo { - field1: i32, - #[cfg(never)] - field2: (), - #[cfg(feature = "never")] - field3: (), - #[cfg(not(feature = "never"))] - field4: (), -} -#[derive(Default)] -enum Bar { - Foo, - #[cfg_attr(not(never), default)] - Bar, -} -"#, - expect![[r##" -#[derive(Default)] -struct Foo { - field1: i32, - #[cfg(never)] - field2: (), - #[cfg(feature = "never")] - field3: (), - #[cfg(not(feature = "never"))] - field4: (), -} -#[derive(Default)] -enum Bar { - Foo, - #[cfg_attr(not(never), default)] - Bar, -} - -impl <> $crate::default::Default for Foo< > where { - fn default() -> Self { - Foo { - field1: $crate::default::Default::default(), field4: $crate::default::Default::default(), - } - } -} -impl <> $crate::default::Default for Bar< > where { - fn default() -> Self { - Bar::Bar - } -}"##]], - ); -} - -#[test] -fn coerce_pointee_expansion() { - check( - r#" -//- minicore: coerce_pointee - -use core::marker::CoercePointee; - -pub trait Trait {} - -#[derive(CoercePointee)] -#[repr(transparent)] -pub struct Foo<'a, T: ?Sized + Trait, #[pointee] U: ?Sized, const N: u32>(T) -where - U: Trait + ToString;"#, - expect![[r#" - -use core::marker::CoercePointee; - -pub trait Trait {} - -#[derive(CoercePointee)] -#[repr(transparent)] -pub struct Foo<'a, T: ?Sized + Trait, #[pointee] U: ?Sized, const N: u32>(T) -where - U: Trait + ToString; -impl $crate::ops::DispatchFromDyn> for Foo where U: Trait +ToString, T: Trait<__S>, __S: ?Sized, __S: Trait<__S> +ToString, U: ::core::marker::Unsize<__S>, T:?Sized+Trait, U:?Sized, {} -impl $crate::ops::CoerceUnsized> for Foo where U: Trait +ToString, T: Trait<__S>, __S: ?Sized, __S: Trait<__S> +ToString, U: ::core::marker::Unsize<__S>, T:?Sized+Trait, U:?Sized, {}"#]], - ); -} - -#[test] -fn coerce_pointee_errors() { - check_errors( - r#" -//- minicore: coerce_pointee - -use core::marker::CoercePointee; - -#[derive(CoercePointee)] -enum Enum {} - -#[derive(CoercePointee)] -struct Struct1; - -#[derive(CoercePointee)] -struct Struct2(); - -#[derive(CoercePointee)] -struct Struct3 {} - -#[derive(CoercePointee)] -struct Struct4(T); - -#[derive(CoercePointee)] -#[repr(transparent)] -struct Struct5(i32); - -#[derive(CoercePointee)] -#[repr(transparent)] -struct Struct6<#[pointee] T: ?Sized, #[pointee] U: ?Sized>(T, U); - -#[derive(CoercePointee)] -#[repr(transparent)] -struct Struct7(T, U); - -#[derive(CoercePointee)] -#[repr(transparent)] -struct Struct8<#[pointee] T, U: ?Sized>(T); - -#[derive(CoercePointee)] -#[repr(transparent)] -struct Struct9(T); - -#[derive(CoercePointee)] -#[repr(transparent)] -struct Struct9<#[pointee] T, U>(T) where T: ?Sized; -"#, - expect![[r#" - 35..72: `CoercePointee` can only be derived on `struct`s - 74..114: `CoercePointee` can only be derived on `struct`s with at least one field - 116..158: `CoercePointee` can only be derived on `struct`s with at least one field - 160..202: `CoercePointee` can only be derived on `struct`s with at least one field - 204..258: `CoercePointee` can only be derived on `struct`s with `#[repr(transparent)]` - 260..326: `CoercePointee` can only be derived on `struct`s that are generic over at least one type - 328..439: only one type parameter can be marked as `#[pointee]` when deriving `CoercePointee` traits - 441..530: exactly one generic type parameter must be marked as `#[pointee]` to derive `CoercePointee` traits - 532..621: `derive(CoercePointee)` requires `T` to be marked `?Sized` - 623..690: `derive(CoercePointee)` requires `T` to be marked `?Sized`"#]], - ); -} - -#[test] -fn union_derive() { - check_errors( - r#" -//- minicore: clone, copy, default, fmt, hash, ord, eq, derive - -#[derive(Copy)] -union Foo1 { _v: () } -#[derive(Clone)] -union Foo2 { _v: () } -#[derive(Default)] -union Foo3 { _v: () } -#[derive(Debug)] -union Foo4 { _v: () } -#[derive(Hash)] -union Foo5 { _v: () } -#[derive(Ord)] -union Foo6 { _v: () } -#[derive(PartialOrd)] -union Foo7 { _v: () } -#[derive(Eq)] -union Foo8 { _v: () } -#[derive(PartialEq)] -union Foo9 { _v: () } - "#, - expect![[r#" - 78..118: this trait cannot be derived for unions - 119..157: this trait cannot be derived for unions - 158..195: this trait cannot be derived for unions - 196..232: this trait cannot be derived for unions - 233..276: this trait cannot be derived for unions - 313..355: this trait cannot be derived for unions"#]], - ); -} - -#[test] -fn default_enum_without_default_attr() { - check_errors( - r#" -//- minicore: default, derive - -#[derive(Default)] -enum Foo { - Bar, -} - "#, - expect!["1..41: `#[derive(Default)]` on enum with no `#[default]`"], - ); -} - -#[test] -fn generic_enum_default() { - check( - r#" -//- minicore: default, derive - -#[derive(Default)] -enum Foo { - Bar(T), - #[default] - Baz, -} -"#, - expect![[r#" - -#[derive(Default)] -enum Foo { - Bar(T), - #[default] - Baz, -} - -impl $crate::default::Default for Foo where { - fn default() -> Self { - Foo::Baz - } -}"#]], - ); -} +// FIXME: This file is commented out because due to the fast path for builtin derives, +// the macros do not really get expanded, and we cannot check their expansion. +// It's not removed because we still need to find some way to do that nevertheless. +// Maybe going through the list of registered derive calls in the def map? + +// use expect_test::expect; + +// use crate::macro_expansion_tests::{check, check_errors}; + +// #[test] +// fn test_copy_expand_simple() { +// check( +// r#" +// //- minicore: derive, copy +// #[derive(Copy)] +// struct Foo; +// "#, +// expect![[r#" +// #[derive(Copy)] +// struct Foo; + +// impl <> $crate::marker::Copy for Foo< > where {}"#]], +// ); +// } + +// #[test] +// fn test_copy_expand_in_core() { +// check( +// r#" +// //- /lib.rs crate:core +// #[rustc_builtin_macro] +// macro derive {} +// #[rustc_builtin_macro] +// macro Copy {} +// #[derive(Copy)] +// struct Foo; +// "#, +// expect![[r#" +// #[rustc_builtin_macro] +// macro derive {} +// #[rustc_builtin_macro] +// macro Copy {} +// #[derive(Copy)] +// struct Foo; + +// impl <> $crate::marker::Copy for Foo< > where {}"#]], +// ); +// } + +// #[test] +// fn test_copy_expand_with_type_params() { +// check( +// r#" +// //- minicore: derive, copy +// #[derive(Copy)] +// struct Foo; +// "#, +// expect![[r#" +// #[derive(Copy)] +// struct Foo; + +// impl $crate::marker::Copy for Foo where {}"#]], +// ); +// } + +// #[test] +// fn test_copy_expand_with_lifetimes() { +// // We currently just ignore lifetimes +// check( +// r#" +// //- minicore: derive, copy +// #[derive(Copy)] +// struct Foo; +// "#, +// expect![[r#" +// #[derive(Copy)] +// struct Foo; + +// impl $crate::marker::Copy for Foo where {}"#]], +// ); +// } + +// #[test] +// fn test_clone_expand() { +// check( +// r#" +// //- minicore: derive, clone +// #[derive(Clone)] +// enum Command { +// Move { x: A, y: B }, +// Do(&'static str), +// Jump, +// } +// "#, +// expect![[r#" +// #[derive(Clone)] +// enum Command { +// Move { x: A, y: B }, +// Do(&'static str), +// Jump, +// } + +// impl $crate::clone::Clone for Command where { +// fn clone(&self ) -> Self { +// match self { +// Command::Move { +// x: x, y: y, +// } +// =>Command::Move { +// x: x.clone(), y: y.clone(), +// } +// , Command::Do(f0, )=>Command::Do(f0.clone(), ), Command::Jump=>Command::Jump, +// } +// } +// }"#]], +// ); +// } + +// #[test] +// fn test_clone_expand_with_associated_types() { +// check( +// r#" +// //- minicore: derive, clone +// trait Trait { +// type InWc; +// type InFieldQualified; +// type InFieldShorthand; +// type InGenericArg; +// } +// trait Marker {} +// struct Vec(T); + +// #[derive(Clone)] +// struct Foo +// where +// ::InWc: Marker, +// { +// qualified: ::InFieldQualified, +// shorthand: T::InFieldShorthand, +// generic: Vec, +// } +// "#, +// expect![[r#" +// trait Trait { +// type InWc; +// type InFieldQualified; +// type InFieldShorthand; +// type InGenericArg; +// } +// trait Marker {} +// struct Vec(T); + +// #[derive(Clone)] +// struct Foo +// where +// ::InWc: Marker, +// { +// qualified: ::InFieldQualified, +// shorthand: T::InFieldShorthand, +// generic: Vec, +// } + +// impl $crate::clone::Clone for Foo where ::InWc: Marker, T: Trait, T::InFieldShorthand: $crate::clone::Clone, T::InGenericArg: $crate::clone::Clone, { +// fn clone(&self ) -> Self { +// match self { +// Foo { +// qualified: qualified, shorthand: shorthand, generic: generic, +// } +// =>Foo { +// qualified: qualified.clone(), shorthand: shorthand.clone(), generic: generic.clone(), +// } +// , +// } +// } +// }"#]], +// ); +// } + +// #[test] +// fn test_clone_expand_with_const_generics() { +// check( +// r#" +// //- minicore: derive, clone +// #[derive(Clone)] +// struct Foo(u32); +// "#, +// expect![[r#" +// #[derive(Clone)] +// struct Foo(u32); + +// impl $crate::clone::Clone for Foo where { +// fn clone(&self ) -> Self { +// match self { +// Foo(f0, )=>Foo(f0.clone(), ), +// } +// } +// }"#]], +// ); +// } + +// #[test] +// fn test_default_expand() { +// check( +// r#" +// //- minicore: derive, default +// #[derive(Default)] +// struct Foo { +// field1: i32, +// field2: (), +// } +// #[derive(Default)] +// enum Bar { +// Foo(u8), +// #[default] +// Bar, +// } +// "#, +// expect![[r#" +// #[derive(Default)] +// struct Foo { +// field1: i32, +// field2: (), +// } +// #[derive(Default)] +// enum Bar { +// Foo(u8), +// #[default] +// Bar, +// } + +// impl <> $crate::default::Default for Foo< > where { +// fn default() -> Self { +// Foo { +// field1: $crate::default::Default::default(), field2: $crate::default::Default::default(), +// } +// } +// } +// impl <> $crate::default::Default for Bar< > where { +// fn default() -> Self { +// Bar::Bar +// } +// }"#]], +// ); +// } + +// #[test] +// fn test_partial_eq_expand() { +// check( +// r#" +// //- minicore: derive, eq +// #[derive(PartialEq, Eq)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } +// "#, +// expect![[r#" +// #[derive(PartialEq, Eq)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } + +// impl <> $crate::cmp::PartialEq for Command< > where { +// fn eq(&self , other: &Self ) -> bool { +// match (self , other) { +// (Command::Move { +// x: x_self, y: y_self, +// } +// , Command::Move { +// x: x_other, y: y_other, +// } +// )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false +// } +// } +// } +// impl <> $crate::cmp::Eq for Command< > where {}"#]], +// ); +// } + +// #[test] +// fn test_partial_eq_expand_with_derive_const() { +// // FIXME: actually expand with const +// check( +// r#" +// //- minicore: derive, eq +// #[derive_const(PartialEq, Eq)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } +// "#, +// expect![[r#" +// #[derive_const(PartialEq, Eq)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } + +// impl <> $crate::cmp::PartialEq for Command< > where { +// fn eq(&self , other: &Self ) -> bool { +// match (self , other) { +// (Command::Move { +// x: x_self, y: y_self, +// } +// , Command::Move { +// x: x_other, y: y_other, +// } +// )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false +// } +// } +// } +// impl <> $crate::cmp::Eq for Command< > where {}"#]], +// ); +// } + +// #[test] +// fn test_partial_ord_expand() { +// check( +// r#" +// //- minicore: derive, ord +// #[derive(PartialOrd, Ord)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } +// "#, +// expect![[r#" +// #[derive(PartialOrd, Ord)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } + +// impl <> $crate::cmp::PartialOrd for Command< > where { +// fn partial_cmp(&self , other: &Self ) -> $crate::option::Option<$crate::cmp::Ordering> { +// match $crate::intrinsics::discriminant_value(self ).partial_cmp(&$crate::intrinsics::discriminant_value(other)) { +// $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { +// match (self , other) { +// (Command::Move { +// x: x_self, y: y_self, +// } +// , Command::Move { +// x: x_other, y: y_other, +// } +// )=>match x_self.partial_cmp(&x_other) { +// $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { +// match y_self.partial_cmp(&y_other) { +// $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { +// $crate::option::Option::Some($crate::cmp::Ordering::Equal) +// } +// c=>return c, +// } +// } +// c=>return c, +// } +// , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.partial_cmp(&f0_other) { +// $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { +// $crate::option::Option::Some($crate::cmp::Ordering::Equal) +// } +// c=>return c, +// } +// , (Command::Jump, Command::Jump)=>$crate::option::Option::Some($crate::cmp::Ordering::Equal), _unused=>$crate::option::Option::Some($crate::cmp::Ordering::Equal) +// } +// } +// c=>return c, +// } +// } +// } +// impl <> $crate::cmp::Ord for Command< > where { +// fn cmp(&self , other: &Self ) -> $crate::cmp::Ordering { +// match $crate::intrinsics::discriminant_value(self ).cmp(&$crate::intrinsics::discriminant_value(other)) { +// $crate::cmp::Ordering::Equal=> { +// match (self , other) { +// (Command::Move { +// x: x_self, y: y_self, +// } +// , Command::Move { +// x: x_other, y: y_other, +// } +// )=>match x_self.cmp(&x_other) { +// $crate::cmp::Ordering::Equal=> { +// match y_self.cmp(&y_other) { +// $crate::cmp::Ordering::Equal=> { +// $crate::cmp::Ordering::Equal +// } +// c=>return c, +// } +// } +// c=>return c, +// } +// , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.cmp(&f0_other) { +// $crate::cmp::Ordering::Equal=> { +// $crate::cmp::Ordering::Equal +// } +// c=>return c, +// } +// , (Command::Jump, Command::Jump)=>$crate::cmp::Ordering::Equal, _unused=>$crate::cmp::Ordering::Equal +// } +// } +// c=>return c, +// } +// } +// }"#]], +// ); +// } + +// #[test] +// fn test_hash_expand() { +// check( +// r#" +// //- minicore: derive, hash +// use core::hash::Hash; + +// #[derive(Hash)] +// struct Foo { +// x: i32, +// y: u64, +// z: (i32, u64), +// } +// "#, +// expect![[r#" +// use core::hash::Hash; + +// #[derive(Hash)] +// struct Foo { +// x: i32, +// y: u64, +// z: (i32, u64), +// } + +// impl <> $crate::hash::Hash for Foo< > where { +// fn hash(&self , ra_expand_state: &mut H) { +// match self { +// Foo { +// x: x, y: y, z: z, +// } +// => { +// x.hash(ra_expand_state); +// y.hash(ra_expand_state); +// z.hash(ra_expand_state); +// } +// , +// } +// } +// }"#]], +// ); +// check( +// r#" +// //- minicore: derive, hash +// use core::hash::Hash; + +// #[derive(Hash)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } +// "#, +// expect![[r#" +// use core::hash::Hash; + +// #[derive(Hash)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } + +// impl <> $crate::hash::Hash for Command< > where { +// fn hash(&self , ra_expand_state: &mut H) { +// $crate::mem::discriminant(self ).hash(ra_expand_state); +// match self { +// Command::Move { +// x: x, y: y, +// } +// => { +// x.hash(ra_expand_state); +// y.hash(ra_expand_state); +// } +// , Command::Do(f0, )=> { +// f0.hash(ra_expand_state); +// } +// , Command::Jump=> {} +// , +// } +// } +// }"#]], +// ); +// } + +// #[test] +// fn test_debug_expand() { +// check( +// r#" +// //- minicore: derive, fmt +// use core::fmt::Debug; + +// #[derive(Debug)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } +// "#, +// expect![[r#" +// use core::fmt::Debug; + +// #[derive(Debug)] +// enum Command { +// Move { x: i32, y: i32 }, +// Do(&'static str), +// Jump, +// } + +// impl <> $crate::fmt::Debug for Command< > where { +// fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { +// match self { +// Command::Move { +// x: x, y: y, +// } +// =>f.debug_struct("Move").field("x", &x).field("y", &y).finish(), Command::Do(f0, )=>f.debug_tuple("Do").field(&f0).finish(), Command::Jump=>f.write_str("Jump"), +// } +// } +// }"#]], +// ); +// } +// #[test] +// fn test_debug_expand_with_cfg() { +// check( +// r#" +// //- minicore: derive, fmt +// use core::fmt::Debug; + +// #[derive(Debug)] +// struct HideAndShow { +// #[cfg(never)] +// always_hide: u32, +// #[cfg(not(never))] +// always_show: u32, +// } +// #[derive(Debug)] +// enum HideAndShowEnum { +// #[cfg(never)] +// AlwaysHide, +// #[cfg(not(never))] +// AlwaysShow{ +// #[cfg(never)] +// always_hide: u32, +// #[cfg(not(never))] +// always_show: u32, +// } +// } +// "#, +// expect![[r#" +// use core::fmt::Debug; + +// #[derive(Debug)] +// struct HideAndShow { +// #[cfg(never)] +// always_hide: u32, +// #[cfg(not(never))] +// always_show: u32, +// } +// #[derive(Debug)] +// enum HideAndShowEnum { +// #[cfg(never)] +// AlwaysHide, +// #[cfg(not(never))] +// AlwaysShow{ +// #[cfg(never)] +// always_hide: u32, +// #[cfg(not(never))] +// always_show: u32, +// } +// } + +// impl <> $crate::fmt::Debug for HideAndShow< > where { +// fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { +// match self { +// HideAndShow { +// always_show: always_show, +// } +// =>f.debug_struct("HideAndShow").field("always_show", &always_show).finish() +// } +// } +// } +// impl <> $crate::fmt::Debug for HideAndShowEnum< > where { +// fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { +// match self { +// HideAndShowEnum::AlwaysShow { +// always_show: always_show, +// } +// =>f.debug_struct("AlwaysShow").field("always_show", &always_show).finish(), +// } +// } +// }"#]], +// ); +// } +// #[test] +// fn test_default_expand_with_cfg() { +// check( +// r#" +// //- minicore: derive, default +// #[derive(Default)] +// struct Foo { +// field1: i32, +// #[cfg(never)] +// field2: (), +// #[cfg(feature = "never")] +// field3: (), +// #[cfg(not(feature = "never"))] +// field4: (), +// } +// #[derive(Default)] +// enum Bar { +// Foo, +// #[cfg_attr(not(never), default)] +// Bar, +// } +// "#, +// expect![[r##" +// #[derive(Default)] +// struct Foo { +// field1: i32, +// #[cfg(never)] +// field2: (), +// #[cfg(feature = "never")] +// field3: (), +// #[cfg(not(feature = "never"))] +// field4: (), +// } +// #[derive(Default)] +// enum Bar { +// Foo, +// #[cfg_attr(not(never), default)] +// Bar, +// } + +// impl <> $crate::default::Default for Foo< > where { +// fn default() -> Self { +// Foo { +// field1: $crate::default::Default::default(), field4: $crate::default::Default::default(), +// } +// } +// } +// impl <> $crate::default::Default for Bar< > where { +// fn default() -> Self { +// Bar::Bar +// } +// }"##]], +// ); +// } + +// #[test] +// fn coerce_pointee_expansion() { +// check( +// r#" +// //- minicore: coerce_pointee + +// use core::marker::CoercePointee; + +// pub trait Trait {} + +// #[derive(CoercePointee)] +// #[repr(transparent)] +// pub struct Foo<'a, T: ?Sized + Trait, #[pointee] U: ?Sized, const N: u32>(T) +// where +// U: Trait + ToString;"#, +// expect![[r#" + +// use core::marker::CoercePointee; + +// pub trait Trait {} + +// #[derive(CoercePointee)] +// #[repr(transparent)] +// pub struct Foo<'a, T: ?Sized + Trait, #[pointee] U: ?Sized, const N: u32>(T) +// where +// U: Trait + ToString; +// impl $crate::ops::DispatchFromDyn> for Foo where U: Trait +ToString, T: Trait<__S>, __S: ?Sized, __S: Trait<__S> +ToString, U: ::core::marker::Unsize<__S>, T:?Sized+Trait, U:?Sized, {} +// impl $crate::ops::CoerceUnsized> for Foo where U: Trait +ToString, T: Trait<__S>, __S: ?Sized, __S: Trait<__S> +ToString, U: ::core::marker::Unsize<__S>, T:?Sized+Trait, U:?Sized, {}"#]], +// ); +// } + +// #[test] +// fn coerce_pointee_errors() { +// check_errors( +// r#" +// //- minicore: coerce_pointee + +// use core::marker::CoercePointee; + +// #[derive(CoercePointee)] +// enum Enum {} + +// #[derive(CoercePointee)] +// struct Struct1; + +// #[derive(CoercePointee)] +// struct Struct2(); + +// #[derive(CoercePointee)] +// struct Struct3 {} + +// #[derive(CoercePointee)] +// struct Struct4(T); + +// #[derive(CoercePointee)] +// #[repr(transparent)] +// struct Struct5(i32); + +// #[derive(CoercePointee)] +// #[repr(transparent)] +// struct Struct6<#[pointee] T: ?Sized, #[pointee] U: ?Sized>(T, U); + +// #[derive(CoercePointee)] +// #[repr(transparent)] +// struct Struct7(T, U); + +// #[derive(CoercePointee)] +// #[repr(transparent)] +// struct Struct8<#[pointee] T, U: ?Sized>(T); + +// #[derive(CoercePointee)] +// #[repr(transparent)] +// struct Struct9(T); + +// #[derive(CoercePointee)] +// #[repr(transparent)] +// struct Struct9<#[pointee] T, U>(T) where T: ?Sized; +// "#, +// expect![[r#" +// 35..72: `CoercePointee` can only be derived on `struct`s +// 74..114: `CoercePointee` can only be derived on `struct`s with at least one field +// 116..158: `CoercePointee` can only be derived on `struct`s with at least one field +// 160..202: `CoercePointee` can only be derived on `struct`s with at least one field +// 204..258: `CoercePointee` can only be derived on `struct`s with `#[repr(transparent)]` +// 260..326: `CoercePointee` can only be derived on `struct`s that are generic over at least one type +// 328..439: only one type parameter can be marked as `#[pointee]` when deriving `CoercePointee` traits +// 441..530: exactly one generic type parameter must be marked as `#[pointee]` to derive `CoercePointee` traits +// 532..621: `derive(CoercePointee)` requires `T` to be marked `?Sized` +// 623..690: `derive(CoercePointee)` requires `T` to be marked `?Sized`"#]], +// ); +// } + +// #[test] +// fn union_derive() { +// check_errors( +// r#" +// //- minicore: clone, copy, default, fmt, hash, ord, eq, derive + +// #[derive(Copy)] +// union Foo1 { _v: () } +// #[derive(Clone)] +// union Foo2 { _v: () } +// #[derive(Default)] +// union Foo3 { _v: () } +// #[derive(Debug)] +// union Foo4 { _v: () } +// #[derive(Hash)] +// union Foo5 { _v: () } +// #[derive(Ord)] +// union Foo6 { _v: () } +// #[derive(PartialOrd)] +// union Foo7 { _v: () } +// #[derive(Eq)] +// union Foo8 { _v: () } +// #[derive(PartialEq)] +// union Foo9 { _v: () } +// "#, +// expect![[r#" +// 78..118: this trait cannot be derived for unions +// 119..157: this trait cannot be derived for unions +// 158..195: this trait cannot be derived for unions +// 196..232: this trait cannot be derived for unions +// 233..276: this trait cannot be derived for unions +// 313..355: this trait cannot be derived for unions"#]], +// ); +// } + +// #[test] +// fn default_enum_without_default_attr() { +// check_errors( +// r#" +// //- minicore: default, derive + +// #[derive(Default)] +// enum Foo { +// Bar, +// } +// "#, +// expect!["1..41: `#[derive(Default)]` on enum with no `#[default]`"], +// ); +// } + +// #[test] +// fn generic_enum_default() { +// check( +// r#" +// //- minicore: default, derive + +// #[derive(Default)] +// enum Foo { +// Bar(T), +// #[default] +// Baz, +// } +// "#, +// expect![[r#" + +// #[derive(Default)] +// enum Foo { +// Bar(T), +// #[default] +// Baz, +// } + +// impl $crate::default::Default for Foo where { +// fn default() -> Self { +// Foo::Baz +// } +// }"#]], +// ); +// } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs index 6f30ca04af8ca..bf04a500a57dc 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs @@ -122,16 +122,16 @@ struct Foo { v4: bool // No comma here } +#[attr1] +#[derive(Bar)] +#[attr2] struct S; #[attr1] #[my_cool_derive()] struct Foo { v1: i32, #[attr3]v2: fn(#[attr4]param2: u32), v3: Foo< { 456 } >, -} -#[attr1] -#[derive(Bar)] -#[attr2] struct S;"#]], +}"#]], ); } 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 2fac0837de22c..8694ebe4e40ac 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 @@ -224,6 +224,8 @@ struct DeferredBuiltinDerive { module_id: ModuleId, depth: usize, container: ItemContainerId, + derive_attr_id: AttrId, + derive_index: u32, } /// Walks the tree of module recursively @@ -1479,10 +1481,10 @@ impl<'db> DefCollector<'db> { let ast_id = ast_id.with_value(ast_adt_id); + let mut derive_call_ids = SmallVec::new(); match attr.parse_path_comma_token_tree(self.db) { Some(derive_macros) => { let call_id = call_id(); - let mut len = 0; for (idx, (path, call_site, _)) in derive_macros.enumerate() { let ast_id = AstIdWithPath::new( file_id, @@ -1505,14 +1507,7 @@ impl<'db> DefCollector<'db> { ); if let Ok((macro_id, def_id, call_id)) = id { - self.def_map.modules[directive.module_id] - .scope - .set_derive_macro_invoc( - ast_id.ast_id, - call_id, - *attr_id, - idx, - ); + derive_call_ids.push(Some(call_id)); // Record its helper attributes. if def_id.krate != self.def_map.krate { let def_map = crate_def_map(self.db, def_id.krate); @@ -1543,11 +1538,14 @@ impl<'db> DefCollector<'db> { module_id: directive.module_id, container: directive.container, depth: directive.depth, + derive_attr_id: *attr_id, + derive_index: idx as u32, }); } else { push_resolved(&mut resolved, directive, call_id); } } else { + derive_call_ids.push(None); self.unresolved_macros.push(MacroDirective { module_id: directive.module_id, depth: directive.depth + 1, @@ -1561,7 +1559,6 @@ impl<'db> DefCollector<'db> { container: directive.container, }); } - len = idx; } // We treat the #[derive] macro as an attribute call, but we do not resolve it for nameres collection. @@ -1570,7 +1567,12 @@ impl<'db> DefCollector<'db> { // Check the comment in [`builtin_attr_macro`]. self.def_map.modules[directive.module_id] .scope - .init_derive_attribute(ast_id, *attr_id, call_id, len + 1); + .init_derive_attribute( + ast_id, + *attr_id, + call_id, + derive_call_ids, + ); } None => { let diag = DefDiagnostic::malformed_derive( @@ -1863,8 +1865,15 @@ impl ModCollector<'_, '_> { let module = &mut def_map.modules[module_id]; for deferred_derive in deferred_derives { crate::builtin_derive::with_derive_traits(deferred_derive.derive, |trait_| { - let impl_id = - BuiltinDeriveImplId::new(db, BuiltinDeriveImplLoc { adt: id, trait_ }); + let impl_id = BuiltinDeriveImplId::new( + db, + BuiltinDeriveImplLoc { + adt: id, + trait_, + derive_attr_id: deferred_derive.derive_attr_id, + derive_index: deferred_derive.derive_index, + }, + ); module.scope.define_builtin_derive_impl(impl_id); }); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs index 15d9634cfab6c..f3e67d01e5662 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs @@ -20,16 +20,18 @@ use crate::{ GenericPredicates, db::HirDatabase, next_solver::{ - Clause, Clauses, DbInterner, EarlyBinder, GenericArgs, StoredEarlyBinder, StoredTy, - TraitRef, Ty, TyKind, fold::fold_tys, generics::Generics, + Clause, Clauses, DbInterner, EarlyBinder, GenericArgs, ParamEnv, StoredEarlyBinder, + StoredTy, TraitRef, Ty, TyKind, fold::fold_tys, generics::Generics, }, }; -fn fake_type_param(adt: AdtId) -> TypeParamId { +fn coerce_pointee_new_type_param(trait_id: TraitId) -> TypeParamId { // HACK: Fake the param. + // We cannot use a dummy param here, because it can leak into the IDE layer and that'll cause panics + // when e.g. trying to display it. So we use an existing param. TypeParamId::from_unchecked(TypeOrConstParamId { - parent: adt.into(), - local_id: la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(u32::MAX)), + parent: trait_id.into(), + local_id: la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(1)), }) } @@ -48,13 +50,35 @@ pub(crate) fn generics_of<'db>(interner: DbInterner<'db>, id: BuiltinDeriveImplI | BuiltinDeriveImplTrait::PartialEq => interner.generics_of(loc.adt.into()), BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { let mut generics = interner.generics_of(loc.adt.into()); - generics.push_param(fake_type_param(loc.adt).into()); + let trait_id = loc + .trait_ + .get_id(interner.lang_items()) + .expect("we don't pass the impl to the solver if we can't resolve the trait"); + generics.push_param(coerce_pointee_new_type_param(trait_id).into()); generics } } } -pub(crate) fn impl_trait<'db>( +pub fn generic_params_count(db: &dyn HirDatabase, id: BuiltinDeriveImplId) -> usize { + let loc = id.loc(db); + let adt_params = GenericParams::new(db, loc.adt.into()); + let extra_params_count = match loc.trait_ { + BuiltinDeriveImplTrait::Copy + | BuiltinDeriveImplTrait::Clone + | BuiltinDeriveImplTrait::Default + | BuiltinDeriveImplTrait::Debug + | BuiltinDeriveImplTrait::Hash + | BuiltinDeriveImplTrait::Ord + | BuiltinDeriveImplTrait::PartialOrd + | BuiltinDeriveImplTrait::Eq + | BuiltinDeriveImplTrait::PartialEq => 0, + BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => 1, + }; + adt_params.len() + extra_params_count +} + +pub fn impl_trait<'db>( interner: DbInterner<'db>, id: BuiltinDeriveImplId, ) -> EarlyBinder<'db, TraitRef<'db>> { @@ -93,7 +117,7 @@ pub(crate) fn impl_trait<'db>( let args = GenericArgs::identity_for_item(interner, loc.adt.into()); let self_ty = Ty::new_adt(interner, loc.adt, args); let Some((pointee_param_idx, _, new_param_ty)) = - coerce_pointee_params(interner, loc, &generic_params) + coerce_pointee_params(interner, loc, &generic_params, trait_id) else { // Malformed derive. return EarlyBinder::bind(TraitRef::new( @@ -110,14 +134,15 @@ pub(crate) fn impl_trait<'db>( } #[salsa::tracked(returns(ref), unsafe(non_update_types))] -pub(crate) fn builtin_derive_predicates<'db>( - db: &'db dyn HirDatabase, - impl_: BuiltinDeriveImplId, -) -> GenericPredicates { +pub fn predicates<'db>(db: &'db dyn HirDatabase, impl_: BuiltinDeriveImplId) -> GenericPredicates { let loc = impl_.loc(db); let generic_params = GenericParams::new(db, loc.adt.into()); let interner = DbInterner::new_with(db, loc.module(db).krate(db)); let adt_predicates = GenericPredicates::query(db, loc.adt.into()); + let trait_id = loc + .trait_ + .get_id(interner.lang_items()) + .expect("we don't pass the impl to the solver if we can't resolve the trait"); match loc.trait_ { BuiltinDeriveImplTrait::Copy | BuiltinDeriveImplTrait::Clone @@ -127,7 +152,7 @@ pub(crate) fn builtin_derive_predicates<'db>( | BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::Eq | BuiltinDeriveImplTrait::PartialEq => { - simple_trait_predicates(interner, loc, &generic_params, adt_predicates) + simple_trait_predicates(interner, loc, &generic_params, adt_predicates, trait_id) } BuiltinDeriveImplTrait::Default => { if matches!(loc.adt, AdtId::EnumId(_)) { @@ -137,12 +162,12 @@ pub(crate) fn builtin_derive_predicates<'db>( .store(), )) } else { - simple_trait_predicates(interner, loc, &generic_params, adt_predicates) + simple_trait_predicates(interner, loc, &generic_params, adt_predicates, trait_id) } } BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => { let Some((pointee_param_idx, pointee_param_id, new_param_ty)) = - coerce_pointee_params(interner, loc, &generic_params) + coerce_pointee_params(interner, loc, &generic_params, trait_id) else { // Malformed derive. return GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind( @@ -181,6 +206,12 @@ pub(crate) fn builtin_derive_predicates<'db>( } } +/// Not cached in a query, currently used in `hir` only. If you need this in `hir-ty` consider introducing a query. +pub fn param_env<'db>(interner: DbInterner<'db>, id: BuiltinDeriveImplId) -> ParamEnv<'db> { + let predicates = predicates(interner.db, id); + crate::lower::param_env_from_predicates(interner, predicates) +} + struct MentionsPointee { pointee_param_idx: u32, } @@ -216,11 +247,8 @@ fn simple_trait_predicates<'db>( loc: &BuiltinDeriveImplLoc, generic_params: &GenericParams, adt_predicates: &GenericPredicates, + trait_id: TraitId, ) -> GenericPredicates { - let trait_id = loc - .trait_ - .get_id(interner.lang_items()) - .expect("we don't pass the impl to the solver if we can't resolve the trait"); let extra_predicates = generic_params .iter_type_or_consts() .filter(|(_, data)| matches!(data, TypeOrConstParamData::TypeParamData(_))) @@ -309,6 +337,7 @@ fn coerce_pointee_params<'db>( interner: DbInterner<'db>, loc: &BuiltinDeriveImplLoc, generic_params: &GenericParams, + trait_id: TraitId, ) -> Option<(u32, TypeParamId, Ty<'db>)> { let pointee_param = { if let Ok((pointee_param, _)) = generic_params @@ -339,7 +368,7 @@ fn coerce_pointee_params<'db>( let pointee_param_idx = pointee_param.into_raw().into_u32() + (generic_params.len_lifetimes() as u32); let new_param_idx = generic_params.len() as u32; - let new_param_id = fake_type_param(loc.adt); + let new_param_id = coerce_pointee_new_type_param(trait_id); let new_param_ty = Ty::new_param(interner, new_param_id, new_param_idx); Some((pointee_param_idx, pointee_param_id, new_param_ty)) } @@ -352,11 +381,7 @@ mod tests { use stdx::format_to; use test_fixture::WithFixture; - use crate::{ - builtin_derive::{builtin_derive_predicates, impl_trait}, - next_solver::DbInterner, - test_db::TestDB, - }; + use crate::{builtin_derive::impl_trait, next_solver::DbInterner, test_db::TestDB}; fn check_trait_refs(#[rust_analyzer::rust_fixture] ra_fixture: &str, expectation: Expect) { let db = TestDB::with_files(ra_fixture); @@ -384,8 +409,7 @@ mod tests { let mut predicates = String::new(); for (_, module) in def_map.modules() { for derive in module.scope.builtin_derive_impls() { - let preds = - builtin_derive_predicates(&db, derive).all_predicates().skip_binder(); + let preds = super::predicates(&db, derive).all_predicates().skip_binder(); format_to!( predicates, "{}\n\n", diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs index 8816e13ba7b66..08f201fea90ba 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs @@ -1568,6 +1568,7 @@ const GOAL: u8 = { } #[test] +#[ignore = "builtin derive macros are currently not working with MIR eval"] fn builtin_derive_macro() { check_number( r#" diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs index f0f65eedbce9a..70474fc469195 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -2,10 +2,12 @@ //! type inference-related queries. use base_db::{Crate, target::TargetLoadError}; +use either::Either; use hir_def::{ - AdtId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumVariantId, FunctionId, - GenericDefId, ImplId, LifetimeParamId, LocalFieldId, StaticId, TraitId, TypeAliasId, VariantId, - db::DefDatabase, hir::ExprId, layout::TargetDataLayout, + AdtId, BuiltinDeriveImplId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumVariantId, + FunctionId, GenericDefId, ImplId, LifetimeParamId, LocalFieldId, StaticId, TraitId, + TypeAliasId, VariantId, builtin_derive::BuiltinDeriveImplMethod, db::DefDatabase, hir::ExprId, + layout::TargetDataLayout, }; use la_arena::ArenaMap; use salsa::plumbing::AsId; @@ -83,7 +85,7 @@ pub trait HirDatabase: DefDatabase + std::fmt::Debug { env: ParamEnvAndCrate<'db>, func: FunctionId, fn_subst: GenericArgs<'db>, - ) -> (FunctionId, GenericArgs<'db>); + ) -> (Either, GenericArgs<'db>); // endregion:mir diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index b9e23464e9803..efce9ab1f1262 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -7,7 +7,7 @@ use std::{ mem, }; -use base_db::Crate; +use base_db::{Crate, FxIndexMap}; use either::Either; use hir_def::{ FindPathConfig, GenericDefId, HasModule, LocalFieldId, Lookup, ModuleDefId, ModuleId, TraitId, @@ -143,11 +143,11 @@ impl<'db> BoundsFormattingCtx<'db> { } impl<'db> HirFormatter<'_, 'db> { - fn start_location_link(&mut self, location: ModuleDefId) { + pub fn start_location_link(&mut self, location: ModuleDefId) { self.fmt.start_location_link(location); } - fn end_location_link(&mut self) { + pub fn end_location_link(&mut self) { self.fmt.end_location_link(); } @@ -1978,6 +1978,49 @@ fn write_bounds_like_dyn_trait<'db>( Ok(()) } +pub fn write_params_bounds<'db>( + f: &mut HirFormatter<'_, 'db>, + predicates: &[Clause<'db>], +) -> Result { + // Use an FxIndexMap to keep user's order, as far as possible. + let mut per_type = FxIndexMap::<_, Vec<_>>::default(); + for &predicate in predicates { + let base_ty = match predicate.kind().skip_binder() { + ClauseKind::Trait(clause) => Either::Left(clause.self_ty()), + ClauseKind::RegionOutlives(clause) => Either::Right(clause.0), + ClauseKind::TypeOutlives(clause) => Either::Left(clause.0), + ClauseKind::Projection(clause) => Either::Left(clause.self_ty()), + ClauseKind::ConstArgHasType(..) + | ClauseKind::WellFormed(_) + | ClauseKind::ConstEvaluatable(_) + | ClauseKind::HostEffect(..) + | ClauseKind::UnstableFeature(_) => continue, + }; + per_type.entry(base_ty).or_default().push(predicate); + } + + for (base_ty, clauses) in per_type { + f.write_str(" ")?; + match base_ty { + Either::Left(it) => it.hir_fmt(f)?, + Either::Right(it) => it.hir_fmt(f)?, + } + f.write_str(": ")?; + // Rudimentary approximation: type params are `Sized` by default, everything else not. + // FIXME: This is not correct, really. But I'm not sure how we can from the ty representation + // to extract the default sizedness, and if it's possible at all. + let default_sized = match base_ty { + Either::Left(ty) if matches!(ty.kind(), TyKind::Param(_)) => { + SizedByDefault::Sized { anchor: f.krate() } + } + _ => SizedByDefault::NotSized, + }; + write_bounds_like_dyn_trait(f, base_ty, &clauses, default_sized)?; + f.write_str(",\n")?; + } + Ok(()) +} + impl<'db> HirDisplay<'db> for TraitRef<'db> { fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result { let trait_ = self.def_id.0; 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 7b414cd551749..373862229bd19 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -25,7 +25,7 @@ extern crate ra_ap_rustc_next_trait_solver as rustc_next_trait_solver; extern crate self as hir_ty; -mod builtin_derive; +pub mod builtin_derive; mod infer; mod inhabitedness; mod lower; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index ebbf29e6ff465..9307868f39826 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -1855,6 +1855,20 @@ pub(crate) fn trait_environment_for_body_query( db.trait_environment(def) } +pub(crate) fn param_env_from_predicates<'db>( + interner: DbInterner<'db>, + predicates: &'db GenericPredicates, +) -> ParamEnv<'db> { + let clauses = rustc_type_ir::elaborate::elaborate( + interner, + predicates.all_predicates().iter_identity_copied(), + ); + let clauses = Clauses::new_from_iter(interner, clauses); + + // FIXME: We should normalize projections here, like rustc does. + ParamEnv { clauses } +} + pub(crate) fn trait_environment<'db>(db: &'db dyn HirDatabase, def: GenericDefId) -> ParamEnv<'db> { return ParamEnv { clauses: trait_environment_query(db, def).as_ref() }; @@ -1865,13 +1879,8 @@ pub(crate) fn trait_environment<'db>(db: &'db dyn HirDatabase, def: GenericDefId ) -> StoredClauses { let module = def.module(db); let interner = DbInterner::new_with(db, module.krate(db)); - let predicates = GenericPredicates::query_all(db, def); - let clauses = - rustc_type_ir::elaborate::elaborate(interner, predicates.iter_identity_copied()); - let clauses = Clauses::new_from_iter(interner, clauses); - - // FIXME: We should normalize projections here, like rustc does. - clauses.store() + let predicates = GenericPredicates::query(db, def); + param_env_from_predicates(interner, predicates).clauses.store() } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs index 88f48fdbc67a7..50dbd87d693e6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs @@ -16,6 +16,7 @@ use hir_def::{ AssocItemId, BlockId, BuiltinDeriveImplId, ConstId, FunctionId, GenericParamId, HasModule, ImplId, ItemContainerId, ModuleId, TraitId, attrs::AttrFlags, + builtin_derive::BuiltinDeriveImplMethod, expr_store::path::GenericArgs as HirGenericArgs, hir::ExprId, lang_item::LangItems, @@ -372,9 +373,13 @@ pub fn lookup_impl_const<'db>( }; lookup_impl_assoc_item_for_trait_ref(infcx, trait_ref, env, name) - .and_then( - |assoc| if let (AssocItemId::ConstId(id), s) = assoc { Some((id, s)) } else { None }, - ) + .and_then(|assoc| { + if let (Either::Left(AssocItemId::ConstId(id)), s) = assoc { + Some((id, s)) + } else { + None + } + }) .unwrap_or((const_id, subs)) } @@ -420,12 +425,12 @@ pub(crate) fn lookup_impl_method_query<'db>( env: ParamEnvAndCrate<'db>, func: FunctionId, fn_subst: GenericArgs<'db>, -) -> (FunctionId, GenericArgs<'db>) { +) -> (Either, GenericArgs<'db>) { let interner = DbInterner::new_with(db, env.krate); let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis); let ItemContainerId::TraitId(trait_id) = func.loc(db).container else { - return (func, fn_subst); + return (Either::Left(func), fn_subst); }; let trait_params = db.generic_params(trait_id.into()).len(); let trait_ref = TraitRef::new_from_args( @@ -435,16 +440,19 @@ pub(crate) fn lookup_impl_method_query<'db>( ); let name = &db.function_signature(func).name; - let Some((impl_fn, impl_subst)) = lookup_impl_assoc_item_for_trait_ref( - &infcx, - trait_ref, - env.param_env, - name, - ) - .and_then(|assoc| { - if let (AssocItemId::FunctionId(id), subst) = assoc { Some((id, subst)) } else { None } - }) else { - return (func, fn_subst); + let Some((impl_fn, impl_subst)) = + lookup_impl_assoc_item_for_trait_ref(&infcx, trait_ref, env.param_env, name).and_then( + |(assoc, impl_args)| { + let assoc = match assoc { + Either::Left(AssocItemId::FunctionId(id)) => Either::Left(id), + Either::Right(it) => Either::Right(it), + _ => return None, + }; + Some((assoc, impl_args)) + }, + ) + else { + return (Either::Left(func), fn_subst); }; ( @@ -461,11 +469,18 @@ fn lookup_impl_assoc_item_for_trait_ref<'db>( trait_ref: TraitRef<'db>, env: ParamEnv<'db>, name: &Name, -) -> Option<(AssocItemId, GenericArgs<'db>)> { +) -> Option<(Either, GenericArgs<'db>)> +{ let (impl_id, impl_subst) = find_matching_impl(infcx, env, trait_ref)?; - let AnyImplId::ImplId(impl_id) = impl_id else { - // FIXME: Handle resolution to builtin derive. - return None; + let impl_id = match impl_id { + AnyImplId::ImplId(it) => it, + AnyImplId::BuiltinDeriveImplId(impl_) => { + return impl_ + .loc(infcx.interner.db) + .trait_ + .get_method(name.symbol()) + .map(|method| (Either::Right((impl_, method)), impl_subst)); + } }; let item = impl_id.impl_items(infcx.interner.db).items.iter().find_map(|(n, it)| match *it { @@ -473,7 +488,7 @@ fn lookup_impl_assoc_item_for_trait_ref<'db>( AssocItemId::ConstId(c) => (n == name).then_some(AssocItemId::ConstId(c)), AssocItemId::TypeAliasId(_) => None, })?; - Some((item, impl_subst)) + Some((Either::Left(item), impl_subst)) } pub(crate) fn find_matching_impl<'db>( @@ -793,19 +808,29 @@ impl TraitImpls { .unwrap_or_default() } - pub fn for_trait(&self, trait_: TraitId, mut callback: impl FnMut(&[ImplId])) { + pub fn for_trait( + &self, + trait_: TraitId, + mut callback: impl FnMut(Either<&[ImplId], &[BuiltinDeriveImplId]>), + ) { if let Some(impls) = self.map.get(&trait_) { - callback(&impls.blanket_impls); + callback(Either::Left(&impls.blanket_impls)); for impls in impls.non_blanket_impls.values() { - callback(&impls.0); + callback(Either::Left(&impls.0)); + callback(Either::Right(&impls.1)); } } } - pub fn for_self_ty(&self, self_ty: &SimplifiedType, mut callback: impl FnMut(&[ImplId])) { + pub fn for_self_ty( + &self, + self_ty: &SimplifiedType, + mut callback: impl FnMut(Either<&[ImplId], &[BuiltinDeriveImplId]>), + ) { for for_trait in self.map.values() { if let Some(for_ty) = for_trait.non_blanket_impls.get(self_ty) { - callback(&for_ty.0); + callback(Either::Left(&for_ty.0)); + callback(Either::Right(&for_ty.1)); } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index c7156bb11ed63..cd860700c12f7 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -77,12 +77,14 @@ macro_rules! from_bytes { }).into()) }; } +use from_bytes; macro_rules! not_supported { ($it: expr) => { - return Err(MirEvalError::NotSupported(format!($it))) + return Err($crate::mir::eval::MirEvalError::NotSupported(format!($it))) }; } +use not_supported; #[derive(Debug, Default, Clone, PartialEq, Eq, GenericTypeVisitable)] pub struct VTableMap<'db> { @@ -2622,6 +2624,9 @@ impl<'db> Evaluator<'db> { def, generic_args, ); + let Either::Left(imp) = imp else { + not_supported!("evaluating builtin derive impls is not supported") + }; let mir_body = self .db diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs index a47a8c4400071..76c8701ea2090 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs @@ -16,29 +16,14 @@ use crate::{ mir::eval::{ Address, AdtId, Arc, Evaluator, FunctionId, GenericArgs, HasModule, HirDisplay, InternedClosure, Interval, IntervalAndTy, IntervalOrOwned, ItemContainerId, Layout, Locals, - Lookup, MirEvalError, MirSpan, Mutability, Result, Ty, TyKind, pad16, + Lookup, MirEvalError, MirSpan, Mutability, Result, Ty, TyKind, from_bytes, not_supported, + pad16, }, next_solver::Region, }; mod simd; -macro_rules! from_bytes { - ($ty:tt, $value:expr) => { - ($ty::from_le_bytes(match ($value).try_into() { - Ok(it) => it, - #[allow(unreachable_patterns)] - Err(_) => return Err(MirEvalError::InternalError("mismatched size".into())), - })) - }; -} - -macro_rules! not_supported { - ($it: expr) => { - return Err(MirEvalError::NotSupported(format!($it))) - }; -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum EvalLangItem { BeginPanic, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs index 3896917cab1a1..e0b3e571b8567 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs @@ -6,21 +6,6 @@ use crate::consteval::try_const_usize; use super::*; -macro_rules! from_bytes { - ($ty:tt, $value:expr) => { - ($ty::from_le_bytes(match ($value).try_into() { - Ok(it) => it, - Err(_) => return Err(MirEvalError::InternalError("mismatched size".into())), - })) - }; -} - -macro_rules! not_supported { - ($it: expr) => { - return Err(MirEvalError::NotSupported(format!($it))) - }; -} - impl<'db> Evaluator<'db> { fn detect_simd_ty(&self, ty: Ty<'db>) -> Result<'db, (usize, Ty<'db>)> { match ty.kind() { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/format_proof_tree.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/format_proof_tree.rs index 59fb0d65c5e26..66da6d540082d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/format_proof_tree.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/format_proof_tree.rs @@ -1,8 +1,8 @@ use rustc_type_ir::{solve::GoalSource, solve::inspect::GoalEvaluation}; use serde_derive::{Deserialize, Serialize}; -use crate::next_solver::infer::InferCtxt; use crate::next_solver::inspect::{InspectCandidate, InspectGoal}; +use crate::next_solver::{AnyImplId, infer::InferCtxt}; use crate::next_solver::{DbInterner, Span}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -76,14 +76,31 @@ impl<'a, 'db> ProofTreeSerializer<'a, 'db> { use rustc_type_ir::solve::inspect::ProbeKind; match candidate.kind() { ProbeKind::TraitCandidate { source, .. } => { + use hir_def::{Lookup, src::HasSource}; use rustc_type_ir::solve::CandidateSource; + let db = self.infcx.interner.db; match source { - CandidateSource::Impl(impl_def_id) => { - use hir_def::{Lookup, src::HasSource}; - let db = self.infcx.interner.db; - let impl_src = impl_def_id.0.lookup(db).source(db); - Some(impl_src.value.to_string()) - } + CandidateSource::Impl(impl_def_id) => match impl_def_id { + AnyImplId::ImplId(impl_def_id) => { + let impl_src = impl_def_id.lookup(db).source(db); + Some(impl_src.value.to_string()) + } + AnyImplId::BuiltinDeriveImplId(impl_id) => { + let impl_loc = impl_id.loc(db); + let adt_src = match impl_loc.adt { + hir_def::AdtId::StructId(adt) => { + adt.loc(db).source(db).value.to_string() + } + hir_def::AdtId::UnionId(adt) => { + adt.loc(db).source(db).value.to_string() + } + hir_def::AdtId::EnumId(adt) => { + adt.loc(db).source(db).value.to_string() + } + }; + Some(format!("#[derive(${})]\n{}", impl_loc.trait_.name(), adt_src)) + } + }, _ => None, } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs index 269474e0153e3..2a3df1d32a30d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs @@ -2370,7 +2370,7 @@ impl<'db> DbInterner<'db> { fn predicates_of(db: &dyn HirDatabase, def_id: SolverDefId) -> &GenericPredicates { if let SolverDefId::BuiltinDeriveImplId(impl_) = def_id { - crate::builtin_derive::builtin_derive_predicates(db, impl_) + crate::builtin_derive::predicates(db, impl_) } else { GenericPredicates::query(db, def_id.try_into().unwrap()) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs index ea33c9bcd4f1c..1457bb2e10171 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs @@ -539,7 +539,7 @@ impl SomeStruct { "AttrFlags::query_", "AttrFlags::query_", "AttrFlags::query_", - "impl_trait_with_diagnostics_shim", + "impl_trait_with_diagnostics_query", "impl_signature_shim", "impl_signature_with_source_map_shim", "impl_self_ty_with_diagnostics_query", diff --git a/src/tools/rust-analyzer/crates/hir/src/attrs.rs b/src/tools/rust-analyzer/crates/hir/src/attrs.rs index d1056f31e19ef..cba1b39e5254b 100644 --- a/src/tools/rust-analyzer/crates/hir/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir/src/attrs.rs @@ -35,6 +35,8 @@ pub enum AttrsOwner { Field(FieldId), LifetimeParam(LifetimeParamId), TypeOrConstParam(TypeOrConstParamId), + /// Things that do not have attributes. Used for builtin derives. + Dummy, } impl AttrsOwner { @@ -123,7 +125,9 @@ impl AttrsWithOwner { let owner = match self.owner { AttrsOwner::AttrDef(it) => Either::Left(it), AttrsOwner::Field(it) => Either::Right(it), - AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => return &[], + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => { + return &[]; + } }; self.attrs.doc_aliases(db, owner) } @@ -133,7 +137,9 @@ impl AttrsWithOwner { let owner = match self.owner { AttrsOwner::AttrDef(it) => Either::Left(it), AttrsOwner::Field(it) => Either::Right(it), - AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => return None, + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => { + return None; + } }; self.attrs.cfgs(db, owner) } @@ -143,7 +149,9 @@ impl AttrsWithOwner { match self.owner { AttrsOwner::AttrDef(it) => AttrFlags::docs(db, it).as_deref(), AttrsOwner::Field(it) => AttrFlags::field_docs(db, it), - AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => None, + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => { + None + } } } } @@ -156,6 +164,9 @@ pub trait HasAttrs: Sized { AttrsOwner::Field(it) => AttrsWithOwner::new_field(db, it), AttrsOwner::LifetimeParam(it) => AttrsWithOwner::new_lifetime_param(db, it), AttrsOwner::TypeOrConstParam(it) => AttrsWithOwner::new_type_or_const_param(db, it), + AttrsOwner::Dummy => { + AttrsWithOwner { attrs: AttrFlags::empty(), owner: AttrsOwner::Dummy } + } } } @@ -167,7 +178,9 @@ pub trait HasAttrs: Sized { match self.attr_id(db) { AttrsOwner::AttrDef(it) => AttrFlags::docs(db, it).as_deref(), AttrsOwner::Field(it) => AttrFlags::field_docs(db, it), - AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => None, + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => { + None + } } } } @@ -190,12 +203,28 @@ impl_has_attrs![ (Trait, TraitId), (TypeAlias, TypeAliasId), (Macro, MacroId), - (Function, FunctionId), (Adt, AdtId), - (Impl, ImplId), (ExternCrateDecl, ExternCrateId), ]; +impl HasAttrs for Function { + fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner { + match self.id { + crate::AnyFunctionId::FunctionId(id) => AttrsOwner::AttrDef(id.into()), + crate::AnyFunctionId::BuiltinDeriveImplMethod { .. } => AttrsOwner::Dummy, + } + } +} + +impl HasAttrs for Impl { + fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner { + match self.id { + hir_ty::next_solver::AnyImplId::ImplId(id) => AttrsOwner::AttrDef(id.into()), + hir_ty::next_solver::AnyImplId::BuiltinDeriveImplId(..) => AttrsOwner::Dummy, + } + } +} + macro_rules! impl_has_attrs_enum { ($($variant:ident),* for $enum:ident) => {$( impl HasAttrs for $variant { @@ -294,7 +323,9 @@ fn resolve_doc_path_on_( AttrsOwner::AttrDef(AttrDefId::MacroId(it)) => it.resolver(db), AttrsOwner::AttrDef(AttrDefId::ExternCrateId(it)) => it.resolver(db), AttrsOwner::Field(it) => it.parent.resolver(db), - AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) => return None, + AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => { + return None; + } }; let mut modpath = doc_modpath_from_str(link)?; diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index d0d8c4877d21f..1f9af564c3599 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -2,19 +2,22 @@ use either::Either; use hir_def::{ - AdtId, GenericDefId, + AdtId, BuiltinDeriveImplId, FunctionId, GenericDefId, ImplId, ItemContainerId, + builtin_derive::BuiltinDeriveImplMethod, expr_store::ExpressionStore, hir::generics::{GenericParams, TypeOrConstParamData, TypeParamProvenance, WherePredicate}, item_tree::FieldsShape, signatures::{StaticFlags, TraitFlags}, type_ref::{TypeBound, TypeRef, TypeRefId}, }; +use hir_expand::name::Name; use hir_ty::{ GenericPredicates, db::HirDatabase, display::{ HirDisplay, HirDisplayWithExpressionStore, HirFormatter, Result, SizedByDefault, - hir_display_with_store, write_bounds_like_dyn_trait_with_prefix, write_visibility, + hir_display_with_store, write_bounds_like_dyn_trait_with_prefix, write_params_bounds, + write_visibility, }, next_solver::ClauseKind, }; @@ -22,25 +25,78 @@ use itertools::Itertools; use rustc_type_ir::inherent::IntoKind; use crate::{ - Adt, AsAssocItem, AssocItem, AssocItemContainer, Const, ConstParam, Crate, Enum, + Adt, AnyFunctionId, AsAssocItem, AssocItem, AssocItemContainer, Const, ConstParam, Crate, Enum, ExternCrateDecl, Field, Function, GenericParam, HasCrate, HasVisibility, Impl, LifetimeParam, Macro, Module, SelfParam, Static, Struct, StructKind, Trait, TraitRef, TupleField, Type, TypeAlias, TypeNs, TypeOrConstParam, TypeParam, Union, Variant, }; +fn write_builtin_derive_impl_method<'db>( + f: &mut HirFormatter<'_, 'db>, + impl_: BuiltinDeriveImplId, + method: BuiltinDeriveImplMethod, +) -> Result { + let db = f.db; + let loc = impl_.loc(db); + let (adt_params, _adt_params_store) = db.generic_params_and_store(loc.adt.into()); + + if f.show_container_bounds() && !adt_params.is_empty() { + f.write_str("impl")?; + write_generic_params(loc.adt.into(), f)?; + f.write_char(' ')?; + let trait_id = loc.trait_.get_id(f.lang_items()); + if let Some(trait_id) = trait_id { + f.start_location_link(trait_id.into()); + } + write!(f, "{}", Name::new_symbol_root(loc.trait_.name()).display(db, f.edition()))?; + if trait_id.is_some() { + f.end_location_link(); + } + f.write_str(" for ")?; + f.start_location_link(loc.adt.into()); + write!(f, "{}", Adt::from(loc.adt).name(db).display(db, f.edition()))?; + f.end_location_link(); + write_generic_args(loc.adt.into(), f)?; + f.write_char('\n')?; + } + + let Some(trait_method) = method.trait_method(db, impl_) else { + return write!(f, "fn {}(…)", method.name()); + }; + let has_written_where = write_function(f, trait_method)?; + + if f.show_container_bounds() && !adt_params.is_empty() { + if !has_written_where { + f.write_str("\nwhere")? + } + write!(f, "\n // Bounds from impl:")?; + + let predicates = + hir_ty::builtin_derive::predicates(db, impl_).explicit_predicates().skip_binder(); + write_params_bounds(f, predicates)?; + } + + Ok(()) +} + impl<'db> HirDisplay<'db> for Function { fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result { + let id = match self.id { + AnyFunctionId::FunctionId(id) => id, + AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } => { + return write_builtin_derive_impl_method(f, impl_, method); + } + }; + let db = f.db; - let data = db.function_signature(self.id); - let container = self.as_assoc_item(db).map(|it| it.container(db)); - let mut module = self.module(db); + let container = id.loc(db).container; // Write container (trait or impl) let container_params = match container { - Some(AssocItemContainer::Trait(trait_)) => { - let (params, params_store) = f.db.generic_params_and_store(trait_.id.into()); + ItemContainerId::TraitId(trait_) => { + let (params, params_store) = f.db.generic_params_and_store(trait_.into()); if f.show_container_bounds() && !params.is_empty() { - write_trait_header(&trait_, f)?; + write_trait_header(trait_.into(), f)?; f.write_char('\n')?; has_disaplayable_predicates(f.db, ¶ms, ¶ms_store) .then_some((params, params_store)) @@ -48,10 +104,10 @@ impl<'db> HirDisplay<'db> for Function { None } } - Some(AssocItemContainer::Impl(impl_)) => { - let (params, params_store) = f.db.generic_params_and_store(impl_.id.into()); + ItemContainerId::ImplId(impl_) => { + let (params, params_store) = f.db.generic_params_and_store(impl_.into()); if f.show_container_bounds() && !params.is_empty() { - write_impl_header(&impl_, f)?; + write_impl_header(impl_, f)?; f.write_char('\n')?; has_disaplayable_predicates(f.db, ¶ms, ¶ms_store) .then_some((params, params_store)) @@ -59,140 +115,151 @@ impl<'db> HirDisplay<'db> for Function { None } } - None => None, + _ => None, }; // Write signature of the function - // Block-local impls are "hoisted" to the nearest (non-block) module. - if let Some(AssocItemContainer::Impl(_)) = container { - module = module.nearest_non_block_module(db); + let has_written_where = write_function(f, id)?; + if let Some((container_params, container_params_store)) = container_params { + if !has_written_where { + f.write_str("\nwhere")?; + } + let container_name = match container { + ItemContainerId::TraitId(_) => "trait", + ItemContainerId::ImplId(_) => "impl", + _ => unreachable!(), + }; + write!(f, "\n // Bounds from {container_name}:",)?; + write_where_predicates(&container_params, &container_params_store, f)?; } - let module_id = module.id; - - write_visibility(module_id, self.visibility(db), f)?; + Ok(()) + } +} - if data.is_default() { - f.write_str("default ")?; - } - if data.is_const() { - f.write_str("const ")?; - } - if data.is_async() { - f.write_str("async ")?; - } - // FIXME: This will show `unsafe` for functions that are `#[target_feature]` but not unsafe - // (they are conditionally unsafe to call). We probably should show something else. - if self.is_unsafe_to_call(db, None, f.edition()) { - f.write_str("unsafe ")?; - } - if let Some(abi) = &data.abi { - write!(f, "extern \"{}\" ", abi.as_str())?; - } - write!(f, "fn {}", data.name.display(f.db, f.edition()))?; +fn write_function<'db>(f: &mut HirFormatter<'_, 'db>, func_id: FunctionId) -> Result { + let db = f.db; + let func = Function::from(func_id); + let data = db.function_signature(func_id); - write_generic_params(GenericDefId::FunctionId(self.id), f)?; + let mut module = func.module(db); + // Block-local impls are "hoisted" to the nearest (non-block) module. + if let ItemContainerId::ImplId(_) = func_id.loc(db).container { + module = module.nearest_non_block_module(db); + } + let module_id = module.id; - f.write_char('(')?; + write_visibility(module_id, func.visibility(db), f)?; - let mut first = true; - let mut skip_self = 0; - if let Some(self_param) = self.self_param(db) { - self_param.hir_fmt(f)?; - first = false; - skip_self = 1; - } + if data.is_default() { + f.write_str("default ")?; + } + if data.is_const() { + f.write_str("const ")?; + } + if data.is_async() { + f.write_str("async ")?; + } + // FIXME: This will show `unsafe` for functions that are `#[target_feature]` but not unsafe + // (they are conditionally unsafe to call). We probably should show something else. + if func.is_unsafe_to_call(db, None, f.edition()) { + f.write_str("unsafe ")?; + } + if let Some(abi) = &data.abi { + write!(f, "extern \"{}\" ", abi.as_str())?; + } + write!(f, "fn {}", data.name.display(f.db, f.edition()))?; - // FIXME: Use resolved `param.ty` once we no longer discard lifetimes - let body = db.body(self.id.into()); - for (type_ref, param) in data.params.iter().zip(self.assoc_fn_params(db)).skip(skip_self) { - if !first { - f.write_str(", ")?; - } else { - first = false; - } + write_generic_params(GenericDefId::FunctionId(func_id), f)?; - let pat_id = body.params[param.idx - body.self_param.is_some() as usize]; - let pat_str = body.pretty_print_pat(db, self.id.into(), pat_id, true, f.edition()); - f.write_str(&pat_str)?; + f.write_char('(')?; - f.write_str(": ")?; - type_ref.hir_fmt(f, &data.store)?; + let mut first = true; + let mut skip_self = 0; + if let Some(self_param) = func.self_param(db) { + self_param.hir_fmt(f)?; + first = false; + skip_self = 1; + } + + // FIXME: Use resolved `param.ty` once we no longer discard lifetimes + let body = db.body(func_id.into()); + for (type_ref, param) in data.params.iter().zip(func.assoc_fn_params(db)).skip(skip_self) { + if !first { + f.write_str(", ")?; + } else { + first = false; } - if data.is_varargs() { - if !first { - f.write_str(", ")?; - } - f.write_str("...")?; - } - - f.write_char(')')?; - - // `FunctionData::ret_type` will be `::core::future::Future` for async fns. - // Use ugly pattern match to strip the Future trait. - // Better way? - let ret_type = if !data.is_async() { - data.ret_type - } else if let Some(ret_type) = data.ret_type { - match &data.store[ret_type] { - TypeRef::ImplTrait(bounds) => match &bounds[0] { - &TypeBound::Path(path, _) => Some( - *data.store[path] - .segments() - .iter() - .last() - .unwrap() - .args_and_bindings - .unwrap() - .bindings[0] - .type_ref - .as_ref() - .unwrap(), - ), - _ => None, - }, + let pat_id = body.params[param.idx - body.self_param.is_some() as usize]; + let pat_str = body.pretty_print_pat(db, func_id.into(), pat_id, true, f.edition()); + f.write_str(&pat_str)?; + + f.write_str(": ")?; + type_ref.hir_fmt(f, &data.store)?; + } + + if data.is_varargs() { + if !first { + f.write_str(", ")?; + } + f.write_str("...")?; + } + + f.write_char(')')?; + + // `FunctionData::ret_type` will be `::core::future::Future` for async fns. + // Use ugly pattern match to strip the Future trait. + // Better way? + let ret_type = if !data.is_async() { + data.ret_type + } else if let Some(ret_type) = data.ret_type { + match &data.store[ret_type] { + TypeRef::ImplTrait(bounds) => match &bounds[0] { + &TypeBound::Path(path, _) => Some( + *data.store[path] + .segments() + .iter() + .last() + .unwrap() + .args_and_bindings + .unwrap() + .bindings[0] + .type_ref + .as_ref() + .unwrap(), + ), _ => None, - } - } else { - None - }; - - if let Some(ret_type) = ret_type { - match &data.store[ret_type] { - TypeRef::Tuple(tup) if tup.is_empty() => {} - _ => { - f.write_str(" -> ")?; - ret_type.hir_fmt(f, &data.store)?; - } - } + }, + _ => None, } + } else { + None + }; - // Write where clauses - let has_written_where = write_where_clause(GenericDefId::FunctionId(self.id), f)?; - if let Some((container_params, container_params_store)) = container_params { - if !has_written_where { - f.write_str("\nwhere")?; + if let Some(ret_type) = ret_type { + match &data.store[ret_type] { + TypeRef::Tuple(tup) if tup.is_empty() => {} + _ => { + f.write_str(" -> ")?; + ret_type.hir_fmt(f, &data.store)?; } - let container_name = match container.unwrap() { - AssocItemContainer::Trait(_) => "trait", - AssocItemContainer::Impl(_) => "impl", - }; - write!(f, "\n // Bounds from {container_name}:",)?; - write_where_predicates(&container_params, &container_params_store, f)?; } - Ok(()) } + + // Write where clauses + let has_written_where = write_where_clause(GenericDefId::FunctionId(func_id), f)?; + Ok(has_written_where) } -fn write_impl_header<'db>(impl_: &Impl, f: &mut HirFormatter<'_, 'db>) -> Result { +fn write_impl_header<'db>(impl_: ImplId, f: &mut HirFormatter<'_, 'db>) -> Result { let db = f.db; f.write_str("impl")?; - let def_id = GenericDefId::ImplId(impl_.id); + let def_id = GenericDefId::ImplId(impl_); write_generic_params(def_id, f)?; - let impl_data = db.impl_signature(impl_.id); + let impl_data = db.impl_signature(impl_); if let Some(target_trait) = &impl_data.target_trait { f.write_char(' ')?; hir_display_with_store(&impl_data.store[target_trait.path], &impl_data.store).hir_fmt(f)?; @@ -200,14 +267,28 @@ fn write_impl_header<'db>(impl_: &Impl, f: &mut HirFormatter<'_, 'db>) -> Result } f.write_char(' ')?; - impl_.self_ty(db).hir_fmt(f)?; + Impl::from(impl_).self_ty(db).hir_fmt(f)?; Ok(()) } impl<'db> HirDisplay<'db> for SelfParam { fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result { - let data = f.db.function_signature(self.func); + let func = match self.func.id { + AnyFunctionId::FunctionId(id) => id, + AnyFunctionId::BuiltinDeriveImplMethod { method, .. } => match method { + BuiltinDeriveImplMethod::clone + | BuiltinDeriveImplMethod::fmt + | BuiltinDeriveImplMethod::hash + | BuiltinDeriveImplMethod::cmp + | BuiltinDeriveImplMethod::partial_cmp + | BuiltinDeriveImplMethod::eq => return f.write_str("&self"), + BuiltinDeriveImplMethod::default => { + unreachable!("this trait method does not have a self param") + } + }, + }; + let data = f.db.function_signature(func); let param = *data.params.first().unwrap(); match &data.store[param] { TypeRef::Path(p) if p.is_self_type() => f.write_str("self"), @@ -553,6 +634,18 @@ impl<'db> HirDisplay<'db> for ConstParam { } fn write_generic_params<'db>(def: GenericDefId, f: &mut HirFormatter<'_, 'db>) -> Result { + write_generic_params_or_args(def, f, true) +} + +fn write_generic_args<'db>(def: GenericDefId, f: &mut HirFormatter<'_, 'db>) -> Result { + write_generic_params_or_args(def, f, false) +} + +fn write_generic_params_or_args<'db>( + def: GenericDefId, + f: &mut HirFormatter<'_, 'db>, + include_defaults: bool, +) -> Result { let (params, store) = f.db.generic_params_and_store(def); if params.iter_lt().next().is_none() && params.iter_type_or_consts().all(|it| it.1.const_param().is_none()) @@ -587,7 +680,7 @@ fn write_generic_params<'db>(def: GenericDefId, f: &mut HirFormatter<'_, 'db>) - } delim(f)?; write!(f, "{}", name.display(f.db, f.edition()))?; - if let Some(default) = &ty.default { + if include_defaults && let Some(default) = &ty.default { f.write_str(" = ")?; default.hir_fmt(f, &store)?; } @@ -597,7 +690,7 @@ fn write_generic_params<'db>(def: GenericDefId, f: &mut HirFormatter<'_, 'db>) - write!(f, "const {}: ", name.display(f.db, f.edition()))?; c.ty.hir_fmt(f, &store)?; - if let Some(default) = &c.default { + if include_defaults && let Some(default) = &c.default { f.write_str(" = ")?; default.hir_fmt(f, &store)?; } @@ -746,7 +839,7 @@ impl<'db> HirDisplay<'db> for TraitRef<'db> { impl<'db> HirDisplay<'db> for Trait { fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result { // FIXME(trait-alias) needs special handling to print the equal sign - write_trait_header(self, f)?; + write_trait_header(*self, f)?; let def_id = GenericDefId::TraitId(self.id); let has_where_clause = write_where_clause(def_id, f)?; @@ -783,7 +876,7 @@ impl<'db> HirDisplay<'db> for Trait { } } -fn write_trait_header<'db>(trait_: &Trait, f: &mut HirFormatter<'_, 'db>) -> Result { +fn write_trait_header<'db>(trait_: Trait, f: &mut HirFormatter<'_, 'db>) -> Result { write_visibility(trait_.module(f.db).id, trait_.visibility(f.db), f)?; let data = f.db.trait_signature(trait_.id); if data.flags.contains(TraitFlags::UNSAFE) { diff --git a/src/tools/rust-analyzer/crates/hir/src/from_id.rs b/src/tools/rust-analyzer/crates/hir/src/from_id.rs index bc025c5ef5cf1..fc20f4b46bb9d 100644 --- a/src/tools/rust-analyzer/crates/hir/src/from_id.rs +++ b/src/tools/rust-analyzer/crates/hir/src/from_id.rs @@ -4,14 +4,15 @@ //! are splitting the hir. use hir_def::{ - AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, GenericDefId, GenericParamId, - ModuleDefId, VariantId, + AdtId, AssocItemId, BuiltinDeriveImplId, DefWithBodyId, EnumVariantId, FieldId, GenericDefId, + GenericParamId, ModuleDefId, VariantId, hir::{BindingId, LabelId}, }; +use hir_ty::next_solver::AnyImplId; use crate::{ - Adt, AssocItem, BuiltinType, DefWithBody, Field, GenericDef, GenericParam, ItemInNs, Label, - Local, ModuleDef, Variant, VariantDef, + Adt, AnyFunctionId, AssocItem, BuiltinType, DefWithBody, Field, GenericDef, GenericParam, + ItemInNs, Label, Local, ModuleDef, Variant, VariantDef, }; macro_rules! from_id { @@ -39,8 +40,8 @@ from_id![ (hir_def::TraitId, crate::Trait), (hir_def::StaticId, crate::Static), (hir_def::ConstId, crate::Const), - (hir_def::FunctionId, crate::Function), - (hir_def::ImplId, crate::Impl), + (crate::AnyFunctionId, crate::Function), + (hir_ty::next_solver::AnyImplId, crate::Impl), (hir_def::TypeOrConstParamId, crate::TypeOrConstParam), (hir_def::TypeParamId, crate::TypeParam), (hir_def::ConstParamId, crate::ConstParam), @@ -119,11 +120,15 @@ impl From for ModuleDef { } } -impl From for ModuleDefId { - fn from(id: ModuleDef) -> Self { - match id { +impl TryFrom for ModuleDefId { + type Error = (); + fn try_from(id: ModuleDef) -> Result { + Ok(match id { ModuleDef::Module(it) => ModuleDefId::ModuleId(it.into()), - ModuleDef::Function(it) => ModuleDefId::FunctionId(it.into()), + ModuleDef::Function(it) => match it.id { + AnyFunctionId::FunctionId(it) => it.into(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => return Err(()), + }, ModuleDef::Adt(it) => ModuleDefId::AdtId(it.into()), ModuleDef::Variant(it) => ModuleDefId::EnumVariantId(it.into()), ModuleDef::Const(it) => ModuleDefId::ConstId(it.into()), @@ -132,18 +137,22 @@ impl From for ModuleDefId { ModuleDef::TypeAlias(it) => ModuleDefId::TypeAliasId(it.into()), ModuleDef::BuiltinType(it) => ModuleDefId::BuiltinType(it.into()), ModuleDef::Macro(it) => ModuleDefId::MacroId(it.into()), - } + }) } } -impl From for DefWithBodyId { - fn from(def: DefWithBody) -> Self { - match def { - DefWithBody::Function(it) => DefWithBodyId::FunctionId(it.id), +impl TryFrom for DefWithBodyId { + type Error = (); + fn try_from(def: DefWithBody) -> Result { + Ok(match def { + DefWithBody::Function(it) => match it.id { + AnyFunctionId::FunctionId(it) => it.into(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => return Err(()), + }, DefWithBody::Static(it) => DefWithBodyId::StaticId(it.id), DefWithBody::Const(it) => DefWithBodyId::ConstId(it.id), DefWithBody::Variant(it) => DefWithBodyId::VariantId(it.into()), - } + }) } } @@ -168,17 +177,11 @@ impl From for AssocItem { } } -impl From for GenericDefId { - fn from(def: GenericDef) -> Self { - match def { - GenericDef::Function(it) => GenericDefId::FunctionId(it.id), - GenericDef::Adt(it) => GenericDefId::AdtId(it.into()), - GenericDef::Trait(it) => GenericDefId::TraitId(it.id), - GenericDef::TypeAlias(it) => GenericDefId::TypeAliasId(it.id), - GenericDef::Impl(it) => GenericDefId::ImplId(it.id), - GenericDef::Const(it) => GenericDefId::ConstId(it.id), - GenericDef::Static(it) => GenericDefId::StaticId(it.id), - } +impl TryFrom for GenericDefId { + type Error = (); + + fn try_from(def: GenericDef) -> Result { + def.id().ok_or(()) } } @@ -238,13 +241,17 @@ impl From for Field { } } -impl From for GenericDefId { - fn from(item: AssocItem) -> Self { - match item { - AssocItem::Function(f) => f.id.into(), +impl TryFrom for GenericDefId { + type Error = (); + fn try_from(item: AssocItem) -> Result { + Ok(match item { + AssocItem::Function(f) => match f.id { + AnyFunctionId::FunctionId(it) => it.into(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => return Err(()), + }, AssocItem::Const(c) => c.id.into(), AssocItem::TypeAlias(t) => t.id.into(), - } + }) } } @@ -270,13 +277,14 @@ impl From for ItemInNs { } } -impl From for hir_def::item_scope::ItemInNs { - fn from(it: ItemInNs) -> Self { - match it { - ItemInNs::Types(it) => Self::Types(it.into()), - ItemInNs::Values(it) => Self::Values(it.into()), +impl TryFrom for hir_def::item_scope::ItemInNs { + type Error = (); + fn try_from(it: ItemInNs) -> Result { + Ok(match it { + ItemInNs::Types(it) => Self::Types(it.try_into()?), + ItemInNs::Values(it) => Self::Values(it.try_into()?), ItemInNs::Macros(it) => Self::Macros(it.into()), - } + }) } } @@ -291,3 +299,21 @@ impl From for hir_def::builtin_type::BuiltinType { it.inner } } + +impl From for crate::Impl { + fn from(value: hir_def::ImplId) -> Self { + crate::Impl { id: AnyImplId::ImplId(value) } + } +} + +impl From for crate::Impl { + fn from(value: BuiltinDeriveImplId) -> Self { + crate::Impl { id: AnyImplId::BuiltinDeriveImplId(value) } + } +} + +impl From for crate::Function { + fn from(value: hir_def::FunctionId) -> Self { + crate::Function { id: AnyFunctionId::FunctionId(value) } + } +} diff --git a/src/tools/rust-analyzer/crates/hir/src/has_source.rs b/src/tools/rust-analyzer/crates/hir/src/has_source.rs index 1aa799400106e..09c5b1cca7f3e 100644 --- a/src/tools/rust-analyzer/crates/hir/src/has_source.rs +++ b/src/tools/rust-analyzer/crates/hir/src/has_source.rs @@ -7,18 +7,18 @@ use hir_def::{ src::{HasChildSource, HasSource as _}, }; use hir_expand::{EditionedFileId, HirFileId, InFile}; -use hir_ty::db::InternedClosure; -use syntax::ast; +use hir_ty::{db::InternedClosure, next_solver::AnyImplId}; +use syntax::{AstNode, ast}; use tt::TextRange; use crate::{ - Adt, Callee, Const, Enum, ExternCrateDecl, Field, FieldSource, Function, Impl, + Adt, AnyFunctionId, Callee, Const, Enum, ExternCrateDecl, Field, FieldSource, Function, Impl, InlineAsmOperand, Label, LifetimeParam, LocalSource, Macro, Module, Param, SelfParam, Static, Struct, Trait, TypeAlias, TypeOrConstParam, Union, Variant, VariantDef, db::HirDatabase, }; -pub trait HasSource { - type Ast; +pub trait HasSource: Sized { + type Ast: AstNode; /// Fetches the definition's source node. /// Using [`crate::SemanticsImpl::source`] is preferred when working with [`crate::Semantics`], /// as that caches the parsed file in the semantics' cache. @@ -27,6 +27,20 @@ pub trait HasSource { /// But we made this method `Option` to support rlib in the future /// by fn source(self, db: &dyn HirDatabase) -> Option>; + + /// Fetches the source node, along with its full range. + /// + /// The reason for the separate existence of this method is that some things, notably builtin derive impls, + /// do not really have a source node, at least not of the correct type. But we still can trace them + /// to source code (the derive producing them). So this method will return the range if it is supported, + /// and if the node is supported too it will return it as well. + fn source_with_range( + self, + db: &dyn HirDatabase, + ) -> Option)>> { + let source = self.source(db)?; + Some(source.map(|node| (node.syntax().text_range(), Some(node)))) + } } /// NB: Module is !HasSource, because it has two source nodes at the same time: @@ -146,7 +160,30 @@ impl HasSource for Variant { impl HasSource for Function { type Ast = ast::Fn; fn source(self, db: &dyn HirDatabase) -> Option> { - Some(self.id.lookup(db).source(db)) + match self.id { + AnyFunctionId::FunctionId(id) => Some(id.loc(db).source(db)), + // When calling `source()`, we use the trait method source, but when calling `source_with_range()`, + // we return `None` as the syntax node source. This is relying on the assumption that if you are calling + // `source_with_range()` (e.g. in navigation) you're prepared to deal with no source node, while if + // you call `source()` maybe you don't - therefore we fall back to the trait method, to not lose features. + AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } => method + .trait_method(db, impl_) + .and_then(|trait_method| Function::from(trait_method).source(db)), + } + } + + fn source_with_range( + self, + db: &dyn HirDatabase, + ) -> Option)>> { + match self.id { + AnyFunctionId::FunctionId(id) => Some( + id.loc(db).source(db).map(|source| (source.syntax().text_range(), Some(source))), + ), + AnyFunctionId::BuiltinDeriveImplMethod { impl_, .. } => { + Some(impl_.loc(db).source(db).map(|range| (range, None))) + } + } } } impl HasSource for Const { @@ -190,7 +227,24 @@ impl HasSource for Macro { impl HasSource for Impl { type Ast = ast::Impl; fn source(self, db: &dyn HirDatabase) -> Option> { - Some(self.id.lookup(db).source(db)) + match self.id { + AnyImplId::ImplId(id) => Some(id.loc(db).source(db)), + AnyImplId::BuiltinDeriveImplId(_) => None, + } + } + + fn source_with_range( + self, + db: &dyn HirDatabase, + ) -> Option)>> { + match self.id { + AnyImplId::ImplId(id) => Some( + id.loc(db).source(db).map(|source| (source.syntax().text_range(), Some(source))), + ), + AnyImplId::BuiltinDeriveImplId(impl_) => { + Some(impl_.loc(db).source(db).map(|range| (range, None))) + } + } } } @@ -224,7 +278,7 @@ impl HasSource for Param<'_> { fn source(self, db: &dyn HirDatabase) -> Option> { match self.func { Callee::Def(CallableDefId::FunctionId(func)) => { - let InFile { file_id, value } = Function { id: func }.source(db)?; + let InFile { file_id, value } = Function::from(func).source(db)?; let params = value.param_list()?; if let Some(self_param) = params.self_param() { if let Some(idx) = self.idx.checked_sub(1) { @@ -261,7 +315,7 @@ impl HasSource for SelfParam { type Ast = ast::SelfParam; fn source(self, db: &dyn HirDatabase) -> Option> { - let InFile { file_id, value } = Function::from(self.func).source(db)?; + let InFile { file_id, value } = self.func.source(db)?; value .param_list() .and_then(|params| params.self_param()) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 9fc29de4a11c4..6a19603923c30 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -48,12 +48,13 @@ use arrayvec::ArrayVec; use base_db::{CrateDisplayName, CrateOrigin, LangCrateOrigin}; use either::Either; use hir_def::{ - AdtId, AssocItemId, AssocItemLoc, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, - EnumVariantId, ExternBlockId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, - HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, MacroExpander, - MacroId, StaticId, StructId, SyntheticSyntax, TupleId, TypeAliasId, TypeOrConstParamId, - TypeParamId, UnionId, + AdtId, AssocItemId, AssocItemLoc, BuiltinDeriveImplId, CallableDefId, ConstId, ConstParamId, + DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId, FunctionId, GenericDefId, + GenericParamId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, + MacroExpander, MacroId, StaticId, StructId, SyntheticSyntax, TupleId, TypeAliasId, + TypeOrConstParamId, TypeParamId, UnionId, attrs::AttrFlags, + builtin_derive::BuiltinDeriveImplMethod, expr_store::{ExpressionStoreDiagnostics, ExpressionStoreSourceMap}, hir::{ BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, LabelId, Pat, @@ -73,7 +74,8 @@ use hir_def::{ visibility::visibility_from_ast, }; use hir_expand::{ - AstId, MacroCallKind, RenderedExpandError, ValueResult, proc_macro::ProcMacroKind, + AstId, MacroCallKind, RenderedExpandError, ValueResult, builtin::BuiltinDeriveExpander, + proc_macro::ProcMacroKind, }; use hir_ty::{ GenericPredicates, InferenceResult, ParamEnvAndCrate, TyDefId, TyLoweringDiagnostic, @@ -88,8 +90,9 @@ use hir_ty::{ }, mir::{MutBorrowKind, interpret_mir}, next_solver::{ - AliasTy, ClauseKind, ConstKind, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, - ParamEnv, PolyFnSig, Region, SolverDefId, Ty, TyKind, TypingMode, + AliasTy, AnyImplId, ClauseKind, ConstKind, DbInterner, EarlyBinder, EarlyParamRegion, + ErrorGuaranteed, GenericArg, GenericArgs, ParamConst, ParamEnv, PolyFnSig, Region, + RegionKind, SolverDefId, Ty, TyKind, TypingMode, infer::{DbInternerInferExt, InferCtxt}, }, traits::{self, is_inherent_impl_coherent, structurally_normalize_ty}, @@ -97,7 +100,8 @@ use hir_ty::{ use itertools::Itertools; use rustc_hash::FxHashSet; use rustc_type_ir::{ - AliasTyKind, TypeSuperVisitable, TypeVisitable, TypeVisitor, fast_reject, + AliasTyKind, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, + TypeVisitor, fast_reject, inherent::{AdtDef, GenericArgs as _, IntoKind, SliceLike, Term as _, Ty as _}, }; use smallvec::SmallVec; @@ -105,7 +109,7 @@ use span::{AstIdNode, Edition, FileId}; use stdx::{format_to, impl_from, never, variance::PhantomCovariantLifetime}; use syntax::{ AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, ToSmolStr, - ast::{self, HasName, HasVisibility as _}, + ast::{self, HasName as _, HasVisibility as _}, format_smolstr, }; use triomphe::{Arc, ThinArc}; @@ -440,7 +444,10 @@ impl ModuleDef { Adt::Union(it) => it.id.into(), }, ModuleDef::Trait(it) => it.id.into(), - ModuleDef::Function(it) => it.id.into(), + ModuleDef::Function(it) => match it.id { + AnyFunctionId::FunctionId(it) => it.into(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => return Vec::new(), + }, ModuleDef::TypeAlias(it) => it.id.into(), ModuleDef::Module(it) => it.id.into(), ModuleDef::Const(it) => it.id.into(), @@ -504,7 +511,7 @@ impl ModuleDef { pub fn attrs(&self, db: &dyn HirDatabase) -> Option { Some(match self { ModuleDef::Module(it) => it.attrs(db), - ModuleDef::Function(it) => it.attrs(db), + ModuleDef::Function(it) => HasAttrs::attrs(*it, db), ModuleDef::Adt(it) => it.attrs(db), ModuleDef::Variant(it) => it.attrs(db), ModuleDef::Const(it) => it.attrs(db), @@ -772,8 +779,11 @@ impl Module { for impl_def in self.impl_defs(db) { GenericDef::Impl(impl_def).diagnostics(db, acc); - let loc = impl_def.id.lookup(db); - let (impl_signature, source_map) = db.impl_signature_with_source_map(impl_def.id); + let AnyImplId::ImplId(impl_id) = impl_def.id else { + continue; + }; + let loc = impl_id.lookup(db); + let (impl_signature, source_map) = db.impl_signature_with_source_map(impl_id); expr_store_diagnostics(db, acc, &source_map); let file_id = loc.id.file_id; @@ -789,12 +799,12 @@ impl Module { let ast_id_map = db.ast_id_map(file_id); - for diag in impl_def.id.impl_items_with_diagnostics(db).1.iter() { + for diag in impl_id.impl_items_with_diagnostics(db).1.iter() { emit_def_diagnostic(db, acc, diag, edition, loc.container.krate(db)); } if impl_signature.target_trait.is_none() - && !is_inherent_impl_coherent(db, def_map, impl_def.id) + && !is_inherent_impl_coherent(db, def_map, impl_id) { acc.push(IncoherentImpl { impl_: ast_id_map.get(loc.id.value), file_id }.into()) } @@ -822,7 +832,7 @@ impl Module { if drop_trait != trait_.into() { return None; } - let parent = impl_def.id.into(); + let parent = impl_id.into(); let (lifetimes_attrs, type_and_consts_attrs) = AttrFlags::query_generic_params(db, parent); let res = lifetimes_attrs.values().any(|it| it.contains(AttrFlags::MAY_DANGLE)) @@ -851,7 +861,7 @@ impl Module { AssocItemId::ConstId(id) => !db.const_signature(id).has_body(), AssocItemId::TypeAliasId(it) => db.type_alias_signature(it).ty.is_none(), }); - impl_assoc_items_scratch.extend(impl_def.id.impl_items(db).items.iter().cloned()); + impl_assoc_items_scratch.extend(impl_id.impl_items(db).items.iter().cloned()); let redundant = impl_assoc_items_scratch .iter() @@ -883,11 +893,11 @@ impl Module { .collect(); if !missing.is_empty() { - let self_ty = db.impl_self_ty(impl_def.id).instantiate_identity(); + let self_ty = db.impl_self_ty(impl_id).instantiate_identity(); let self_ty = structurally_normalize_ty( &infcx, self_ty, - db.trait_environment(impl_def.id.into()), + db.trait_environment(impl_id.into()), ); let self_ty_is_guaranteed_unsized = matches!( self_ty.kind(), @@ -896,7 +906,13 @@ impl Module { if self_ty_is_guaranteed_unsized { missing.retain(|(_, assoc_item)| { let assoc_item = match *assoc_item { - AssocItem::Function(it) => it.id.into(), + AssocItem::Function(it) => match it.id { + AnyFunctionId::FunctionId(id) => id.into(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => { + never!("should not have an `AnyFunctionId::BuiltinDeriveImplMethod` here"); + return false; + }, + }, AssocItem::Const(it) => it.id.into(), AssocItem::TypeAlias(it) => it.id.into(), }; @@ -918,20 +934,15 @@ impl Module { impl_assoc_items_scratch.clear(); } + push_ty_diagnostics(db, acc, db.impl_self_ty_with_diagnostics(impl_id).1, &source_map); push_ty_diagnostics( db, acc, - db.impl_self_ty_with_diagnostics(impl_def.id).1, - &source_map, - ); - push_ty_diagnostics( - db, - acc, - db.impl_trait_with_diagnostics(impl_def.id).and_then(|it| it.1), + db.impl_trait_with_diagnostics(impl_id).and_then(|it| it.1), &source_map, ); - for &(_, item) in impl_def.id.impl_items(db).items.iter() { + for &(_, item) in impl_id.impl_items(db).items.iter() { AssocItem::from(item).diagnostics(db, acc, style_lints); } } @@ -955,7 +966,8 @@ impl Module { pub fn impl_defs(self, db: &dyn HirDatabase) -> Vec { let def_map = self.id.def_map(db); - def_map[self.id].scope.impls().map(Impl::from).collect() + let scope = &def_map[self.id].scope; + scope.impls().map(Impl::from).chain(scope.builtin_derive_impls().map(Impl::from)).collect() } /// Finds a path that can be used to refer to the given item from within @@ -968,7 +980,7 @@ impl Module { ) -> Option { hir_def::find_path::find_path( db, - item.into().into(), + item.into().try_into().ok()?, self.into(), PrefixKind::Plain, false, @@ -985,7 +997,14 @@ impl Module { prefix_kind: PrefixKind, cfg: FindPathConfig, ) -> Option { - hir_def::find_path::find_path(db, item.into().into(), self.into(), prefix_kind, true, cfg) + hir_def::find_path::find_path( + db, + item.into().try_into().ok()?, + self.into(), + prefix_kind, + true, + cfg, + ) } #[inline] @@ -1863,9 +1882,9 @@ impl VariantDef { pub fn name(&self, db: &dyn HirDatabase) -> Name { match self { - VariantDef::Struct(s) => s.name(db), - VariantDef::Union(u) => u.name(db), - VariantDef::Variant(e) => e.name(db), + VariantDef::Struct(s) => (*s).name(db), + VariantDef::Union(u) => (*u).name(db), + VariantDef::Variant(e) => (*e).name(db), } } } @@ -1909,24 +1928,33 @@ impl DefWithBody { } } - fn id(&self) -> DefWithBodyId { - match self { - DefWithBody::Function(it) => it.id.into(), + fn id(&self) -> Option { + Some(match self { + DefWithBody::Function(it) => match it.id { + AnyFunctionId::FunctionId(id) => id.into(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => return None, + }, DefWithBody::Static(it) => it.id.into(), DefWithBody::Const(it) => it.id.into(), DefWithBody::Variant(it) => it.into(), - } + }) } /// A textual representation of the HIR of this def's body for debugging purposes. pub fn debug_hir(self, db: &dyn HirDatabase) -> String { - let body = db.body(self.id()); - body.pretty_print(db, self.id(), Edition::CURRENT) + let Some(id) = self.id() else { + return String::new(); + }; + let body = db.body(id); + body.pretty_print(db, id, Edition::CURRENT) } /// A textual representation of the MIR of this def's body for debugging purposes. pub fn debug_mir(self, db: &dyn HirDatabase) -> String { - let body = db.mir_body(self.id()); + let Some(id) = self.id() else { + return String::new(); + }; + let body = db.mir_body(id); match body { Ok(body) => body.pretty_print(db, self.module(db).krate(db).to_display_target(db)), Err(e) => format!("error:\n{e:?}"), @@ -1939,11 +1967,17 @@ impl DefWithBody { acc: &mut Vec>, style_lints: bool, ) { + let Ok(id) = self.try_into() else { + return; + }; let krate = self.module(db).id.krate(db); - let (body, source_map) = db.body_with_source_map(self.into()); + let (body, source_map) = db.body_with_source_map(id); let sig_source_map = match self { - DefWithBody::Function(id) => db.function_signature_with_source_map(id.into()).1, + DefWithBody::Function(id) => match id.id { + AnyFunctionId::FunctionId(id) => db.function_signature_with_source_map(id).1, + AnyFunctionId::BuiltinDeriveImplMethod { .. } => return, + }, DefWithBody::Static(id) => db.static_signature_with_source_map(id.into()).1, DefWithBody::Const(id) => db.const_signature_with_source_map(id.into()).1, DefWithBody::Variant(variant) => { @@ -1958,11 +1992,11 @@ impl DefWithBody { expr_store_diagnostics(db, acc, &source_map); - let infer = InferenceResult::for_body(db, self.into()); + let infer = InferenceResult::for_body(db, id); for d in infer.diagnostics() { acc.extend(AnyDiagnostic::inference_diagnostic( db, - self.into(), + id, d, &source_map, &sig_source_map, @@ -1989,14 +2023,14 @@ impl DefWithBody { acc.push( TypeMismatch { expr_or_pat, - expected: Type::new(db, DefWithBodyId::from(self), mismatch.expected.as_ref()), - actual: Type::new(db, DefWithBodyId::from(self), mismatch.actual.as_ref()), + expected: Type::new(db, id, mismatch.expected.as_ref()), + actual: Type::new(db, id, mismatch.actual.as_ref()), } .into(), ); } - let missing_unsafe = hir_ty::diagnostics::missing_unsafe(db, self.into()); + let missing_unsafe = hir_ty::diagnostics::missing_unsafe(db, id); for (node, reason) in missing_unsafe.unsafe_exprs { match source_map.expr_or_pat_syntax(node) { Ok(node) => acc.push( @@ -2031,7 +2065,7 @@ impl DefWithBody { } } - if let Ok(borrowck_results) = db.borrowck(self.into()) { + if let Ok(borrowck_results) = db.borrowck(id) { for borrowck_result in borrowck_results.iter() { let mir_body = &borrowck_result.mir_body; for moof in &borrowck_result.moved_out_of_ref { @@ -2088,7 +2122,7 @@ impl DefWithBody { { need_mut = &mir::MutabilityReason::Not; } - let local = Local { parent: self.into(), binding_id }; + let local = Local { parent: id, binding_id }; let is_mut = body[binding_id].mode == BindingAnnotation::Mutable; match (need_mut, is_mut) { @@ -2144,17 +2178,11 @@ impl DefWithBody { } } - for diagnostic in BodyValidationDiagnostic::collect(db, self.into(), style_lints) { + for diagnostic in BodyValidationDiagnostic::collect(db, id, style_lints) { acc.extend(AnyDiagnostic::body_validation_diagnostic(db, diagnostic, &source_map)); } - let def: ModuleDef = match self { - DefWithBody::Function(it) => it.into(), - DefWithBody::Static(it) => it.into(), - DefWithBody::Const(it) => it.into(), - DefWithBody::Variant(it) => it.into(), - }; - for diag in hir_ty::diagnostics::incorrect_case(db, def.into()) { + for diag in hir_ty::diagnostics::incorrect_case(db, id.into()) { acc.push(diag.into()) } } @@ -2192,45 +2220,181 @@ fn expr_store_diagnostics<'db>( .macro_calls() .for_each(|(_ast_id, call_id)| macro_call_diagnostics(db, call_id, acc)); } + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum AnyFunctionId { + FunctionId(FunctionId), + BuiltinDeriveImplMethod { method: BuiltinDeriveImplMethod, impl_: BuiltinDeriveImplId }, +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct Function { - pub(crate) id: FunctionId, + pub(crate) id: AnyFunctionId, +} + +impl fmt::Debug for Function { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.id, f) + } } impl Function { pub fn module(self, db: &dyn HirDatabase) -> Module { - self.id.module(db).into() + match self.id { + AnyFunctionId::FunctionId(id) => id.module(db).into(), + AnyFunctionId::BuiltinDeriveImplMethod { impl_, .. } => impl_.module(db).into(), + } } pub fn name(self, db: &dyn HirDatabase) -> Name { - db.function_signature(self.id).name.clone() + match self.id { + AnyFunctionId::FunctionId(id) => db.function_signature(id).name.clone(), + AnyFunctionId::BuiltinDeriveImplMethod { method, .. } => { + Name::new_symbol_root(method.name()) + } + } } pub fn ty(self, db: &dyn HirDatabase) -> Type<'_> { - Type::from_value_def(db, self.id) + match self.id { + AnyFunctionId::FunctionId(id) => Type::from_value_def(db, id), + AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } => { + // Get the type for the trait function, as we can't get the type for the impl function + // because it has not `CallableDefId`. + let krate = impl_.module(db).krate(db); + let interner = DbInterner::new_with(db, krate); + let param_env = hir_ty::builtin_derive::param_env(interner, impl_); + let env = ParamEnvAndCrate { param_env, krate }; + let Some(trait_method) = method.trait_method(db, impl_) else { + return Type { env, ty: Ty::new_error(interner, ErrorGuaranteed) }; + }; + Function::from(trait_method).ty(db) + } + } } pub fn fn_ptr_type(self, db: &dyn HirDatabase) -> Type<'_> { - let resolver = self.id.resolver(db); - let interner = DbInterner::new_no_crate(db); - // FIXME: This shouldn't be `instantiate_identity()`, we shouldn't leak `TyKind::Param`s. - let callable_sig = db.callable_item_signature(self.id.into()).instantiate_identity(); - let ty = Ty::new_fn_ptr(interner, callable_sig); - Type::new_with_resolver_inner(db, &resolver, ty) + match self.id { + AnyFunctionId::FunctionId(id) => { + let resolver = id.resolver(db); + let interner = DbInterner::new_no_crate(db); + // FIXME: This shouldn't be `instantiate_identity()`, we shouldn't leak `TyKind::Param`s. + let callable_sig = db.callable_item_signature(id.into()).instantiate_identity(); + let ty = Ty::new_fn_ptr(interner, callable_sig); + Type::new_with_resolver_inner(db, &resolver, ty) + } + AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } => { + struct ParamsShifter<'db> { + interner: DbInterner<'db>, + shift_by: i32, + } + + impl<'db> TypeFolder> for ParamsShifter<'db> { + fn cx(&self) -> DbInterner<'db> { + self.interner + } + + fn fold_ty(&mut self, ty: Ty<'db>) -> Ty<'db> { + if let TyKind::Param(param) = ty.kind() { + Ty::new_param( + self.interner, + param.id, + param.index.checked_add_signed(self.shift_by).unwrap(), + ) + } else { + ty.super_fold_with(self) + } + } + + fn fold_const( + &mut self, + ct: hir_ty::next_solver::Const<'db>, + ) -> hir_ty::next_solver::Const<'db> { + if let ConstKind::Param(param) = ct.kind() { + hir_ty::next_solver::Const::new_param( + self.interner, + ParamConst { + id: param.id, + index: param.index.checked_add_signed(self.shift_by).unwrap(), + }, + ) + } else { + ct.super_fold_with(self) + } + } + + fn fold_region(&mut self, r: Region<'db>) -> Region<'db> { + if let RegionKind::ReEarlyParam(param) = r.kind() { + Region::new_early_param( + self.interner, + EarlyParamRegion { + id: param.id, + index: param.index.checked_add_signed(self.shift_by).unwrap(), + }, + ) + } else { + r + } + } + } + + // Get the type for the trait function, as we can't get the type for the impl function + // because it has not `CallableDefId`. + let krate = impl_.module(db).krate(db); + let interner = DbInterner::new_with(db, krate); + let param_env = hir_ty::builtin_derive::param_env(interner, impl_); + let env = ParamEnvAndCrate { param_env, krate }; + let Some(trait_method) = method.trait_method(db, impl_) else { + return Type { env, ty: Ty::new_error(interner, ErrorGuaranteed) }; + }; + // The procedure works as follows: the method may have additional generic parameters (e.g. `Hash::hash()`), + // and we want them to be params of the impl method as well. So we start with the trait method identity + // args and extract from them the trait method own args. In parallel, we retrieve the impl trait ref. + // Now we can put our args as [...impl_trait_ref.args, ...trait_method_own_args], but we have one problem: + // the args in `trait_method_own_args` use indices appropriate for the trait method, which are not necessarily + // good for the impl method. So we shift them by `impl_generics_len - trait_generics_len`, which is essentially + // `impl_generics_len - impl_trait_ref.args.len()`. + let trait_method_fn_ptr = Ty::new_fn_ptr( + interner, + db.callable_item_signature(trait_method.into()).instantiate_identity(), + ); + let impl_trait_ref = + hir_ty::builtin_derive::impl_trait(interner, impl_).instantiate_identity(); + let trait_method_args = + GenericArgs::identity_for_item(interner, trait_method.into()); + let trait_method_own_args = GenericArgs::new_from_iter( + interner, + trait_method_args.iter().skip(impl_trait_ref.args.len()), + ); + let impl_params_count = hir_ty::builtin_derive::generic_params_count(db, impl_); + let shift_args_by = impl_params_count as i32 - impl_trait_ref.args.len() as i32; + let shifted_trait_method_own_args = trait_method_own_args + .fold_with(&mut ParamsShifter { interner, shift_by: shift_args_by }); + let impl_method_args = GenericArgs::new_from_iter( + interner, + impl_trait_ref.args.iter().chain(shifted_trait_method_own_args), + ); + let impl_method_fn_ptr = + EarlyBinder::bind(trait_method_fn_ptr).instantiate(interner, impl_method_args); + Type { env, ty: impl_method_fn_ptr } + } + } + } + + fn fn_sig<'db>(self, db: &'db dyn HirDatabase) -> (ParamEnvAndCrate<'db>, PolyFnSig<'db>) { + let fn_ptr = self.fn_ptr_type(db); + let TyKind::FnPtr(sig_tys, hdr) = fn_ptr.ty.kind() else { + unreachable!(); + }; + (fn_ptr.env, sig_tys.with(hdr)) } // FIXME: Find a better API to express all combinations here, perhaps we should have `PreInstantiationType`? /// Get this function's return type pub fn ret_type(self, db: &dyn HirDatabase) -> Type<'_> { - let resolver = self.id.resolver(db); - // FIXME: This shouldn't be `instantiate_identity()`, we shouldn't leak `TyKind::Param`s. - let ty = db - .callable_item_signature(self.id.into()) - .instantiate_identity() - .skip_binder() - .output(); - Type::new_with_resolver_inner(db, &resolver, ty) + let (env, sig) = self.fn_sig(db); + Type { env, ty: sig.skip_binder().output() } } // FIXME: Find better API to also handle const generics @@ -2239,30 +2403,41 @@ impl Function { db: &'db dyn HirDatabase, generics: impl Iterator>, ) -> Type<'db> { - let resolver = self.id.resolver(db); + let ret_type = self.ret_type(db); let interner = DbInterner::new_no_crate(db); - let args = generic_args_from_tys(interner, self.id.into(), generics.map(|ty| ty.ty)); + let args = self.adapt_generic_args(interner, generics); + ret_type.derived(EarlyBinder::bind(ret_type.ty).instantiate(interner, args)) + } - let interner = DbInterner::new_no_crate(db); - let ty = db - .callable_item_signature(self.id.into()) - .instantiate(interner, args) - .skip_binder() - .output(); - Type::new_with_resolver_inner(db, &resolver, ty) + fn adapt_generic_args<'db>( + self, + interner: DbInterner<'db>, + generics: impl Iterator>, + ) -> GenericArgs<'db> { + let generics = generics.map(|ty| ty.ty); + match self.id { + AnyFunctionId::FunctionId(id) => generic_args_from_tys(interner, id.into(), generics), + AnyFunctionId::BuiltinDeriveImplMethod { impl_, .. } => { + let impl_args = GenericArgs::identity_for_item(interner, impl_.into()); + GenericArgs::new_from_iter( + interner, + impl_args.iter().chain(generics.map(Into::into)), + ) + } + } } pub fn async_ret_type<'db>(self, db: &'db dyn HirDatabase) -> Option> { + let AnyFunctionId::FunctionId(id) = self.id else { + return None; + }; if !self.is_async(db) { return None; } - let resolver = self.id.resolver(db); + let resolver = id.resolver(db); // FIXME: This shouldn't be `instantiate_identity()`, we shouldn't leak `TyKind::Param`s. - let ret_ty = db - .callable_item_signature(self.id.into()) - .instantiate_identity() - .skip_binder() - .output(); + let ret_ty = + db.callable_item_signature(id.into()).instantiate_identity().skip_binder().output(); for pred in ret_ty.impl_trait_bounds(db).into_iter().flatten() { if let ClauseKind::Projection(projection) = pred.kind().skip_binder() && let Some(output_ty) = projection.term.as_type() @@ -2274,31 +2449,47 @@ impl Function { } pub fn has_self_param(self, db: &dyn HirDatabase) -> bool { - db.function_signature(self.id).has_self_param() + match self.id { + AnyFunctionId::FunctionId(id) => db.function_signature(id).has_self_param(), + AnyFunctionId::BuiltinDeriveImplMethod { method, .. } => match method { + BuiltinDeriveImplMethod::clone + | BuiltinDeriveImplMethod::fmt + | BuiltinDeriveImplMethod::hash + | BuiltinDeriveImplMethod::cmp + | BuiltinDeriveImplMethod::partial_cmp + | BuiltinDeriveImplMethod::eq => true, + BuiltinDeriveImplMethod::default => false, + }, + } } pub fn self_param(self, db: &dyn HirDatabase) -> Option { - self.has_self_param(db).then_some(SelfParam { func: self.id }) + self.has_self_param(db).then_some(SelfParam { func: self }) } pub fn assoc_fn_params(self, db: &dyn HirDatabase) -> Vec> { - let environment = param_env_from_has_crate(db, self.id); - // FIXME: This shouldn't be `instantiate_identity()`, we shouldn't leak `TyKind::Param`s. - let callable_sig = - db.callable_item_signature(self.id.into()).instantiate_identity().skip_binder(); - callable_sig + let (env, sig) = self.fn_sig(db); + let func = match self.id { + AnyFunctionId::FunctionId(id) => Callee::Def(CallableDefId::FunctionId(id)), + AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } => { + Callee::BuiltinDeriveImplMethod { method, impl_ } + } + }; + sig.skip_binder() .inputs() .iter() .enumerate() - .map(|(idx, &ty)| { - let ty = Type { env: environment, ty }; - Param { func: Callee::Def(CallableDefId::FunctionId(self.id)), ty, idx } - }) + .map(|(idx, &ty)| Param { func: func.clone(), ty: Type { env, ty }, idx }) .collect() } pub fn num_params(self, db: &dyn HirDatabase) -> usize { - db.function_signature(self.id).params.len() + match self.id { + AnyFunctionId::FunctionId(id) => db.function_signature(id).params.len(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => { + self.fn_sig(db).1.skip_binder().inputs().len() + } + } } pub fn method_params(self, db: &dyn HirDatabase) -> Option>> { @@ -2307,21 +2498,11 @@ impl Function { } pub fn params_without_self(self, db: &dyn HirDatabase) -> Vec> { - let environment = param_env_from_has_crate(db, self.id); - // FIXME: This shouldn't be `instantiate_identity()`, we shouldn't leak `TyKind::Param`s. - let callable_sig = - db.callable_item_signature(self.id.into()).instantiate_identity().skip_binder(); - let skip = if db.function_signature(self.id).has_self_param() { 1 } else { 0 }; - callable_sig - .inputs() - .iter() - .enumerate() - .skip(skip) - .map(|(idx, &ty)| { - let ty = Type { env: environment, ty }; - Param { func: Callee::Def(CallableDefId::FunctionId(self.id)), ty, idx } - }) - .collect() + let mut params = self.assoc_fn_params(db); + if self.has_self_param(db) { + params.remove(0); + } + params } // FIXME: Find better API to also handle const generics @@ -2330,40 +2511,50 @@ impl Function { db: &'db dyn HirDatabase, generics: impl Iterator>, ) -> Vec> { - let environment = param_env_from_has_crate(db, self.id); let interner = DbInterner::new_no_crate(db); - let args = generic_args_from_tys(interner, self.id.into(), generics.map(|ty| ty.ty)); - let callable_sig = - db.callable_item_signature(self.id.into()).instantiate(interner, args).skip_binder(); - let skip = if db.function_signature(self.id).has_self_param() { 1 } else { 0 }; - callable_sig - .inputs() - .iter() - .enumerate() - .skip(skip) - .map(|(idx, &ty)| { - let ty = Type { env: environment, ty }; - Param { func: Callee::Def(CallableDefId::FunctionId(self.id)), ty, idx } + let args = self.adapt_generic_args(interner, generics); + let params = self.params_without_self(db); + params + .into_iter() + .map(|param| Param { + func: param.func, + idx: param.idx, + ty: Type { + env: param.ty.env, + ty: EarlyBinder::bind(param.ty.ty).instantiate(interner, args), + }, }) .collect() } pub fn is_const(self, db: &dyn HirDatabase) -> bool { - db.function_signature(self.id).is_const() + match self.id { + AnyFunctionId::FunctionId(id) => db.function_signature(id).is_const(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => false, + } } pub fn is_async(self, db: &dyn HirDatabase) -> bool { - db.function_signature(self.id).is_async() + match self.id { + AnyFunctionId::FunctionId(id) => db.function_signature(id).is_async(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => false, + } } pub fn is_varargs(self, db: &dyn HirDatabase) -> bool { - db.function_signature(self.id).is_varargs() + match self.id { + AnyFunctionId::FunctionId(id) => db.function_signature(id).is_varargs(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => false, + } } pub fn extern_block(self, db: &dyn HirDatabase) -> Option { - match self.id.lookup(db).container { - ItemContainerId::ExternBlockId(id) => Some(ExternBlock { id }), - _ => None, + match self.id { + AnyFunctionId::FunctionId(id) => match id.lookup(db).container { + ItemContainerId::ExternBlockId(id) => Some(ExternBlock { id }), + _ => None, + }, + AnyFunctionId::BuiltinDeriveImplMethod { .. } => None, } } @@ -2396,33 +2587,46 @@ impl Function { /// Does this function have `#[test]` attribute? pub fn is_test(self, db: &dyn HirDatabase) -> bool { - self.attrs(db).is_test() + self.attrs(db).contains(AttrFlags::IS_TEST) } /// is this a `fn main` or a function with an `export_name` of `main`? pub fn is_main(self, db: &dyn HirDatabase) -> bool { - self.exported_main(db) - || self.module(db).is_crate_root(db) && db.function_signature(self.id).name == sym::main + match self.id { + AnyFunctionId::FunctionId(id) => { + self.exported_main(db) + || self.module(db).is_crate_root(db) + && db.function_signature(id).name == sym::main + } + AnyFunctionId::BuiltinDeriveImplMethod { .. } => false, + } + } + + fn attrs(self, db: &dyn HirDatabase) -> AttrFlags { + match self.id { + AnyFunctionId::FunctionId(id) => AttrFlags::query(db, id.into()), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => AttrFlags::empty(), + } } /// Is this a function with an `export_name` of `main`? pub fn exported_main(self, db: &dyn HirDatabase) -> bool { - AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_EXPORT_NAME_MAIN) + self.attrs(db).contains(AttrFlags::IS_EXPORT_NAME_MAIN) } /// Does this function have the ignore attribute? pub fn is_ignore(self, db: &dyn HirDatabase) -> bool { - AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_IGNORE) + self.attrs(db).contains(AttrFlags::IS_IGNORE) } /// Does this function have `#[bench]` attribute? pub fn is_bench(self, db: &dyn HirDatabase) -> bool { - AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_BENCH) + self.attrs(db).contains(AttrFlags::IS_BENCH) } /// Is this function marked as unstable with `#[feature]` attribute? pub fn is_unstable(self, db: &dyn HirDatabase) -> bool { - AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_UNSTABLE) + self.attrs(db).contains(AttrFlags::IS_UNSTABLE) } pub fn is_unsafe_to_call( @@ -2431,9 +2635,17 @@ impl Function { caller: Option, call_edition: Edition, ) -> bool { + let AnyFunctionId::FunctionId(id) = self.id else { + return false; + }; let (target_features, target_feature_is_safe_in_target) = caller .map(|caller| { - let target_features = hir_ty::TargetFeatures::from_fn(db, caller.id); + let target_features = match caller.id { + AnyFunctionId::FunctionId(id) => hir_ty::TargetFeatures::from_fn(db, id), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => { + hir_ty::TargetFeatures::default() + } + }; let target_feature_is_safe_in_target = match &caller.krate(db).id.workspace_data(db).target { Ok(target) => hir_ty::target_feature_is_safe_in_target(target), @@ -2447,7 +2659,7 @@ impl Function { matches!( hir_ty::is_fn_unsafe_to_call( db, - self.id, + id, &target_features, call_edition, target_feature_is_safe_in_target @@ -2460,12 +2672,18 @@ impl Function { /// /// This is false in the case of required (not provided) trait methods. pub fn has_body(self, db: &dyn HirDatabase) -> bool { - db.function_signature(self.id).has_body() + match self.id { + AnyFunctionId::FunctionId(id) => db.function_signature(id).has_body(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => true, + } } pub fn as_proc_macro(self, db: &dyn HirDatabase) -> Option { - let def_map = crate_def_map(db, HasModule::krate(&self.id, db)); - def_map.fn_as_proc_macro(self.id).map(|id| Macro { id: id.into() }) + let AnyFunctionId::FunctionId(id) = self.id else { + return None; + }; + let def_map = crate_def_map(db, HasModule::krate(&id, db)); + def_map.fn_as_proc_macro(id).map(|id| Macro { id: id.into() }) } pub fn eval( @@ -2473,13 +2691,18 @@ impl Function { db: &dyn HirDatabase, span_formatter: impl Fn(FileId, TextRange) -> String, ) -> Result { + let AnyFunctionId::FunctionId(id) = self.id else { + return Err(ConstEvalError::MirEvalError(MirEvalError::NotSupported( + "evaluation of builtin derive impl methods is not supported".to_owned(), + ))); + }; let interner = DbInterner::new_no_crate(db); let body = db.monomorphized_mir_body( - self.id.into(), + id.into(), GenericArgs::empty(interner).store(), ParamEnvAndCrate { - param_env: db.trait_environment(self.id.into()), - krate: self.id.module(db).krate(db), + param_env: db.trait_environment(id.into()), + krate: id.module(db).krate(db), } .store(), )?; @@ -2596,36 +2819,47 @@ impl<'db> Param<'db> { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct SelfParam { - func: FunctionId, + func: Function, } impl SelfParam { pub fn access(self, db: &dyn HirDatabase) -> Access { - let func_data = db.function_signature(self.func); - func_data - .params - .first() - .map(|¶m| match &func_data.store[param] { - TypeRef::Reference(ref_) => match ref_.mutability { - hir_def::type_ref::Mutability::Shared => Access::Shared, - hir_def::type_ref::Mutability::Mut => Access::Exclusive, - }, - _ => Access::Owned, - }) - .unwrap_or(Access::Owned) + match self.func.id { + AnyFunctionId::FunctionId(id) => { + let func_data = db.function_signature(id); + func_data + .params + .first() + .map(|¶m| match &func_data.store[param] { + TypeRef::Reference(ref_) => match ref_.mutability { + hir_def::type_ref::Mutability::Shared => Access::Shared, + hir_def::type_ref::Mutability::Mut => Access::Exclusive, + }, + _ => Access::Owned, + }) + .unwrap_or(Access::Owned) + } + AnyFunctionId::BuiltinDeriveImplMethod { method, .. } => match method { + BuiltinDeriveImplMethod::clone + | BuiltinDeriveImplMethod::fmt + | BuiltinDeriveImplMethod::hash + | BuiltinDeriveImplMethod::cmp + | BuiltinDeriveImplMethod::partial_cmp + | BuiltinDeriveImplMethod::eq => Access::Shared, + BuiltinDeriveImplMethod::default => { + unreachable!("this function does not have a self param") + } + }, + } } pub fn parent_fn(&self) -> Function { - Function::from(self.func) + self.func } pub fn ty<'db>(&self, db: &'db dyn HirDatabase) -> Type<'db> { - // FIXME: This shouldn't be `instantiate_identity()`, we shouldn't leak `TyKind::Param`s. - let callable_sig = - db.callable_item_signature(self.func.into()).instantiate_identity().skip_binder(); - let environment = param_env_from_has_crate(db, self.func); - let ty = rustc_type_ir::inherent::SliceLike::as_slice(&callable_sig.inputs())[0]; - Type { env: environment, ty } + let (env, sig) = self.func.fn_sig(db); + Type { env, ty: sig.skip_binder().inputs()[0] } } // FIXME: Find better API to also handle const generics @@ -2635,18 +2869,18 @@ impl SelfParam { generics: impl Iterator>, ) -> Type<'db> { let interner = DbInterner::new_no_crate(db); - let args = generic_args_from_tys(interner, self.func.into(), generics.map(|ty| ty.ty)); - let callable_sig = - db.callable_item_signature(self.func.into()).instantiate(interner, args).skip_binder(); - let environment = param_env_from_has_crate(db, self.func); - let ty = rustc_type_ir::inherent::SliceLike::as_slice(&callable_sig.inputs())[0]; - Type { env: environment, ty } + let args = self.func.adapt_generic_args(interner, generics); + let Type { env, ty } = self.ty(db); + Type { env, ty: EarlyBinder::bind(ty).instantiate(interner, args) } } } impl HasVisibility for Function { fn visibility(&self, db: &dyn HirDatabase) -> Visibility { - db.assoc_visibility(self.id.into()) + match self.id { + AnyFunctionId::FunctionId(id) => db.assoc_visibility(id.into()), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => Visibility::Public, + } } } @@ -2870,7 +3104,7 @@ impl Trait { pub fn function(self, db: &dyn HirDatabase, name: impl PartialEq) -> Option { self.id.trait_items(db).items.iter().find(|(n, _)| name == *n).and_then(|&(_, it)| match it { - AssocItemId::FunctionId(id) => Some(Function { id }), + AssocItemId::FunctionId(id) => Some(id.into()), _ => None, }) } @@ -3151,15 +3385,15 @@ impl Macro { ) } - pub fn is_builtin_derive(&self, db: &dyn HirDatabase) -> bool { - match self.id { - MacroId::Macro2Id(it) => { - matches!(it.lookup(db).expander, MacroExpander::BuiltInDerive(_)) - } - MacroId::MacroRulesId(it) => { - matches!(it.lookup(db).expander, MacroExpander::BuiltInDerive(_)) - } - MacroId::ProcMacroId(_) => false, + pub fn builtin_derive_kind(&self, db: &dyn HirDatabase) -> Option { + let expander = match self.id { + MacroId::Macro2Id(it) => it.lookup(db).expander, + MacroId::MacroRulesId(it) => it.lookup(db).expander, + MacroId::ProcMacroId(_) => return None, + }; + match expander { + MacroExpander::BuiltInDerive(kind) => Some(BuiltinDeriveMacroKind(kind)), + _ => None, } } @@ -3197,6 +3431,9 @@ impl Macro { } } +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct BuiltinDeriveMacroKind(BuiltinDeriveExpander); + impl HasVisibility for Macro { fn visibility(&self, db: &dyn HirDatabase) -> Visibility { match self.id { @@ -3275,7 +3512,10 @@ pub trait AsExternAssocItem { impl AsExternAssocItem for Function { fn as_extern_assoc_item(self, db: &dyn HirDatabase) -> Option { - as_extern_assoc_item(db, ExternAssocItem::Function, self.id) + let AnyFunctionId::FunctionId(id) = self.id else { + return None; + }; + as_extern_assoc_item(db, ExternAssocItem::Function, id) } } @@ -3303,7 +3543,7 @@ pub enum AssocItem { impl From for AssocItem { fn from(value: method_resolution::CandidateId) -> Self { match value { - method_resolution::CandidateId::FunctionId(id) => AssocItem::Function(Function { id }), + method_resolution::CandidateId::FunctionId(id) => AssocItem::Function(id.into()), method_resolution::CandidateId::ConstId(id) => AssocItem::Const(Const { id }), } } @@ -3321,7 +3561,10 @@ pub trait AsAssocItem { impl AsAssocItem for Function { fn as_assoc_item(self, db: &dyn HirDatabase) -> Option { - as_assoc_item(db, AssocItem::Function, self.id) + match self.id { + AnyFunctionId::FunctionId(id) => as_assoc_item(db, AssocItem::Function, id), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => Some(AssocItem::Function(self)), + } } } @@ -3450,7 +3693,14 @@ impl AssocItem { pub fn container(self, db: &dyn HirDatabase) -> AssocItemContainer { let container = match self { - AssocItem::Function(it) => it.id.lookup(db).container, + AssocItem::Function(it) => match it.id { + AnyFunctionId::FunctionId(id) => id.lookup(db).container, + AnyFunctionId::BuiltinDeriveImplMethod { impl_, .. } => { + return AssocItemContainer::Impl(Impl { + id: AnyImplId::BuiltinDeriveImplId(impl_), + }); + } + }, AssocItem::Const(it) => it.id.lookup(db).container, AssocItem::TypeAlias(it) => it.id.lookup(db).container, }; @@ -3587,9 +3837,13 @@ impl_from!( impl GenericDef { pub fn params(self, db: &dyn HirDatabase) -> Vec { - let generics = db.generic_params(self.into()); + let Ok(id) = self.try_into() else { + // Let's pretend builtin derive impls don't have generic parameters. + return Vec::new(); + }; + let generics = db.generic_params(id); let ty_params = generics.iter_type_or_consts().map(|(local_id, _)| { - let toc = TypeOrConstParam { id: TypeOrConstParamId { parent: self.into(), local_id } }; + let toc = TypeOrConstParam { id: TypeOrConstParamId { parent: id, local_id } }; match toc.split(db) { Either::Left(it) => GenericParam::ConstParam(it), Either::Right(it) => GenericParam::TypeParam(it), @@ -3603,39 +3857,51 @@ impl GenericDef { } pub fn lifetime_params(self, db: &dyn HirDatabase) -> Vec { - let generics = db.generic_params(self.into()); + let Ok(id) = self.try_into() else { + // Let's pretend builtin derive impls don't have generic parameters. + return Vec::new(); + }; + let generics = db.generic_params(id); generics .iter_lt() - .map(|(local_id, _)| LifetimeParam { - id: LifetimeParamId { parent: self.into(), local_id }, - }) + .map(|(local_id, _)| LifetimeParam { id: LifetimeParamId { parent: id, local_id } }) .collect() } pub fn type_or_const_params(self, db: &dyn HirDatabase) -> Vec { - let generics = db.generic_params(self.into()); + let Ok(id) = self.try_into() else { + // Let's pretend builtin derive impls don't have generic parameters. + return Vec::new(); + }; + let generics = db.generic_params(id); generics .iter_type_or_consts() .map(|(local_id, _)| TypeOrConstParam { - id: TypeOrConstParamId { parent: self.into(), local_id }, + id: TypeOrConstParamId { parent: id, local_id }, }) .collect() } - fn id(self) -> GenericDefId { - match self { - GenericDef::Function(it) => it.id.into(), + fn id(self) -> Option { + Some(match self { + GenericDef::Function(it) => match it.id { + AnyFunctionId::FunctionId(it) => it.into(), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => return None, + }, GenericDef::Adt(it) => it.into(), GenericDef::Trait(it) => it.id.into(), GenericDef::TypeAlias(it) => it.id.into(), - GenericDef::Impl(it) => it.id.into(), + GenericDef::Impl(it) => match it.id { + AnyImplId::ImplId(it) => it.into(), + AnyImplId::BuiltinDeriveImplId(_) => return None, + }, GenericDef::Const(it) => it.id.into(), GenericDef::Static(it) => it.id.into(), - } + }) } pub fn diagnostics<'db>(self, db: &'db dyn HirDatabase, acc: &mut Vec>) { - let def = self.id(); + let Some(def) = self.id() else { return }; let generics = db.generic_params(def); @@ -3708,6 +3974,17 @@ impl<'db> GenericSubstitution<'db> { Self { def, subst, env } } + fn new_from_fn( + def: Function, + subst: GenericArgs<'db>, + env: ParamEnvAndCrate<'db>, + ) -> Option { + match def.id { + AnyFunctionId::FunctionId(def) => Some(Self::new(def.into(), subst, env)), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => None, + } + } + pub fn types(&self, db: &'db dyn HirDatabase) -> Vec<(Symbol, Type<'db>)> { let container = match self.def { GenericDefId::ConstId(id) => Some(id.lookup(db).container), @@ -3820,7 +4097,9 @@ impl Local { pub fn as_self_param(self, db: &dyn HirDatabase) -> Option { match self.parent { - DefWithBodyId::FunctionId(func) if self.is_self(db) => Some(SelfParam { func }), + DefWithBodyId::FunctionId(func) if self.is_self(db) => { + Some(SelfParam { func: func.into() }) + } _ => None, } } @@ -4308,7 +4587,7 @@ impl TypeOrConstParam { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Impl { - pub(crate) id: ImplId, + pub(crate) id: AnyImplId, } impl Impl { @@ -4320,6 +4599,7 @@ impl Impl { fn extend_with_def_map(db: &dyn HirDatabase, def_map: &DefMap, result: &mut Vec) { for (_, module) in def_map.modules() { result.extend(module.scope.impls().map(Impl::from)); + result.extend(module.scope.builtin_derive_impls().map(Impl::from)); for unnamed_const in module.scope.unnamed_consts() { for (_, block_def_map) in db.body(unnamed_const.into()).blocks(db) { @@ -4331,7 +4611,7 @@ impl Impl { } pub fn all_in_module(db: &dyn HirDatabase, module: Module) -> Vec { - module.id.def_map(db)[module.id].scope.impls().map(Into::into).collect() + module.impl_defs(db) } /// **Note:** This is an **approximation** that strives to give the *human-perceived notion* of an "impl for type", @@ -4347,20 +4627,19 @@ impl Impl { else { return Vec::new(); }; - let mut extend_with_impls = - |impls: &[ImplId]| result.extend(impls.iter().copied().map(Impl::from)); - method_resolution::with_incoherent_inherent_impls( - db, - env.krate, - &simplified_ty, - &mut extend_with_impls, - ); + let mut extend_with_impls = |impls: Either<&[ImplId], &[BuiltinDeriveImplId]>| match impls { + Either::Left(impls) => result.extend(impls.iter().copied().map(Impl::from)), + Either::Right(impls) => result.extend(impls.iter().copied().map(Impl::from)), + }; + method_resolution::with_incoherent_inherent_impls(db, env.krate, &simplified_ty, |impls| { + extend_with_impls(Either::Left(impls)) + }); if let Some(module) = method_resolution::simplified_type_module(db, &simplified_ty) { InherentImpls::for_each_crate_and_block( db, module.krate(db), module.block(db), - &mut |impls| extend_with_impls(impls.for_self_ty(&simplified_ty)), + &mut |impls| extend_with_impls(Either::Left(impls.for_self_ty(&simplified_ty))), ); std::iter::successors(module.block(db), |block| block.loc(db).module.block(db)) .filter_map(|block| TraitImpls::for_block(db, block).as_deref()) @@ -4382,7 +4661,10 @@ impl Impl { let module = trait_.module(db).id; let mut all = Vec::new(); let mut handle_impls = |impls: &TraitImpls| { - impls.for_trait(trait_.id, |impls| all.extend(impls.iter().copied().map(Impl::from))); + impls.for_trait(trait_.id, |impls| match impls { + Either::Left(impls) => all.extend(impls.iter().copied().map(Impl::from)), + Either::Right(impls) => all.extend(impls.iter().copied().map(Impl::from)), + }); }; for krate in module.krate(db).transitive_rev_deps(db) { handle_impls(TraitImpls::for_crate(db, krate)); @@ -4396,75 +4678,118 @@ impl Impl { } pub fn trait_(self, db: &dyn HirDatabase) -> Option { - let trait_ref = db.impl_trait(self.id)?; - let id = trait_ref.skip_binder().def_id; - Some(Trait { id: id.0 }) + match self.id { + AnyImplId::ImplId(id) => { + let trait_ref = db.impl_trait(id)?; + let id = trait_ref.skip_binder().def_id; + Some(Trait { id: id.0 }) + } + AnyImplId::BuiltinDeriveImplId(id) => { + let loc = id.loc(db); + let lang_items = hir_def::lang_item::lang_items(db, loc.adt.module(db).krate(db)); + loc.trait_.get_id(lang_items).map(Trait::from) + } + } } pub fn trait_ref(self, db: &dyn HirDatabase) -> Option> { - let trait_ref = db.impl_trait(self.id)?.instantiate_identity(); - let resolver = self.id.resolver(db); - Some(TraitRef::new_with_resolver(db, &resolver, trait_ref)) + match self.id { + AnyImplId::ImplId(id) => { + let trait_ref = db.impl_trait(id)?.instantiate_identity(); + let resolver = id.resolver(db); + Some(TraitRef::new_with_resolver(db, &resolver, trait_ref)) + } + AnyImplId::BuiltinDeriveImplId(id) => { + let loc = id.loc(db); + let krate = loc.module(db).krate(db); + let interner = DbInterner::new_with(db, krate); + let env = ParamEnvAndCrate { + param_env: hir_ty::builtin_derive::param_env(interner, id), + krate, + }; + let trait_ref = + hir_ty::builtin_derive::impl_trait(interner, id).instantiate_identity(); + Some(TraitRef { env, trait_ref }) + } + } } pub fn self_ty(self, db: &dyn HirDatabase) -> Type<'_> { - let resolver = self.id.resolver(db); - // FIXME: This shouldn't be `instantiate_identity()`, we shouldn't leak `TyKind::Param`s. - let ty = db.impl_self_ty(self.id).instantiate_identity(); - Type::new_with_resolver_inner(db, &resolver, ty) + match self.id { + AnyImplId::ImplId(id) => { + let resolver = id.resolver(db); + // FIXME: This shouldn't be `instantiate_identity()`, we shouldn't leak `TyKind::Param`s. + let ty = db.impl_self_ty(id).instantiate_identity(); + Type::new_with_resolver_inner(db, &resolver, ty) + } + AnyImplId::BuiltinDeriveImplId(id) => { + let loc = id.loc(db); + let krate = loc.module(db).krate(db); + let interner = DbInterner::new_with(db, krate); + let env = ParamEnvAndCrate { + param_env: hir_ty::builtin_derive::param_env(interner, id), + krate, + }; + let ty = hir_ty::builtin_derive::impl_trait(interner, id) + .instantiate_identity() + .self_ty(); + Type { env, ty } + } + } } pub fn items(self, db: &dyn HirDatabase) -> Vec { - self.id.impl_items(db).items.iter().map(|&(_, it)| it.into()).collect() + match self.id { + AnyImplId::ImplId(id) => { + id.impl_items(db).items.iter().map(|&(_, it)| it.into()).collect() + } + AnyImplId::BuiltinDeriveImplId(impl_) => impl_ + .loc(db) + .trait_ + .all_methods() + .iter() + .map(|&method| { + AssocItem::Function(Function { + id: AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ }, + }) + }) + .collect(), + } } pub fn is_negative(self, db: &dyn HirDatabase) -> bool { - db.impl_signature(self.id).flags.contains(ImplFlags::NEGATIVE) + match self.id { + AnyImplId::ImplId(id) => db.impl_signature(id).flags.contains(ImplFlags::NEGATIVE), + AnyImplId::BuiltinDeriveImplId(_) => false, + } } pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool { - db.impl_signature(self.id).flags.contains(ImplFlags::UNSAFE) + match self.id { + AnyImplId::ImplId(id) => db.impl_signature(id).flags.contains(ImplFlags::UNSAFE), + AnyImplId::BuiltinDeriveImplId(_) => false, + } } pub fn module(self, db: &dyn HirDatabase) -> Module { - self.id.lookup(db).container.into() - } - - pub fn as_builtin_derive_path(self, db: &dyn HirDatabase) -> Option> { - let src = self.source(db)?; - - let macro_file = src.file_id.macro_file()?; - let loc = macro_file.lookup(db); - let (derive_attr, derive_index) = match loc.kind { - MacroCallKind::Derive { ast_id, derive_attr_index, derive_index, .. } => { - let module_id = self.id.lookup(db).container; - ( - module_id.def_map(db)[module_id] - .scope - .derive_macro_invoc(ast_id, derive_attr_index)?, - derive_index, - ) - } - _ => return None, - }; - let path = db - .parse_macro_expansion(derive_attr) - .value - .0 - .syntax_node() - .children() - .nth(derive_index as usize) - .and_then(::cast) - .and_then(|it| it.path())?; - Some(InMacroFile { file_id: derive_attr, value: path }) + match self.id { + AnyImplId::ImplId(id) => id.module(db).into(), + AnyImplId::BuiltinDeriveImplId(id) => id.module(db).into(), + } } pub fn check_orphan_rules(self, db: &dyn HirDatabase) -> bool { - check_orphan_rules(db, self.id) + match self.id { + AnyImplId::ImplId(id) => check_orphan_rules(db, id), + AnyImplId::BuiltinDeriveImplId(_) => true, + } } fn all_macro_calls(&self, db: &dyn HirDatabase) -> Box<[(AstId, MacroCallId)]> { - self.id.impl_items(db).macro_calls.to_vec().into_boxed_slice() + match self.id { + AnyImplId::ImplId(id) => id.impl_items(db).macro_calls.to_vec().into_boxed_slice(), + AnyImplId::BuiltinDeriveImplId(_) => Box::default(), + } } } @@ -5540,7 +5865,7 @@ impl<'db> Type<'db> { else { unreachable!("`Mode::MethodCall` can only return functions"); }; - let id = Function { id }; + let id = Function { id: AnyFunctionId::FunctionId(id) }; match candidate.kind { method_resolution::PickKind::InherentImplPick(_) | method_resolution::PickKind::ObjectPick(..) @@ -5564,7 +5889,7 @@ impl<'db> Type<'db> { else { unreachable!("`Mode::MethodCall` can only return functions"); }; - let id = Function { id }; + let id = Function { id: AnyFunctionId::FunctionId(id) }; match candidate.candidate.kind { method_resolution::CandidateKind::InherentImplCandidate { .. @@ -5919,6 +6244,7 @@ enum Callee<'db> { CoroutineClosure(InternedCoroutineId, GenericArgs<'db>), FnPtr, FnImpl(traits::FnTrait), + BuiltinDeriveImplMethod { method: BuiltinDeriveImplMethod, impl_: BuiltinDeriveImplId }, } pub enum CallableKind<'db> { @@ -5934,6 +6260,9 @@ impl<'db> Callable<'db> { pub fn kind(&self) -> CallableKind<'db> { match self.callee { Callee::Def(CallableDefId::FunctionId(it)) => CallableKind::Function(it.into()), + Callee::BuiltinDeriveImplMethod { method, impl_ } => CallableKind::Function(Function { + id: AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ }, + }), Callee::Def(CallableDefId::StructId(it)) => CallableKind::TupleStruct(it.into()), Callee::Def(CallableDefId::EnumVariantId(it)) => { CallableKind::TupleEnumVariant(it.into()) @@ -5948,12 +6277,22 @@ impl<'db> Callable<'db> { Callee::FnImpl(fn_) => CallableKind::FnImpl(fn_.into()), } } + + fn as_function(&self) -> Option { + match self.callee { + Callee::Def(CallableDefId::FunctionId(it)) => Some(it.into()), + Callee::BuiltinDeriveImplMethod { method, impl_ } => { + Some(Function { id: AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } }) + } + _ => None, + } + } + pub fn receiver_param(&self, db: &'db dyn HirDatabase) -> Option<(SelfParam, Type<'db>)> { - let func = match self.callee { - Callee::Def(CallableDefId::FunctionId(it)) if self.is_bound_method => it, - _ => return None, - }; - let func = Function { id: func }; + if !self.is_bound_method { + return None; + } + let func = self.as_function()?; Some(( func.self_param(db)?, self.ty.derived(self.sig.skip_binder().inputs_and_output.inputs()[0]), @@ -6350,7 +6689,12 @@ impl HasContainer for Module { impl HasContainer for Function { fn container(&self, db: &dyn HirDatabase) -> ItemContainer { - container_id_to_hir(self.id.lookup(db).container) + match self.id { + AnyFunctionId::FunctionId(id) => container_id_to_hir(id.lookup(db).container), + AnyFunctionId::BuiltinDeriveImplMethod { impl_, .. } => { + ItemContainer::Impl(Impl { id: AnyImplId::BuiltinDeriveImplId(impl_) }) + } + } } } @@ -6402,11 +6746,79 @@ impl HasContainer for ExternBlock { } } +pub trait HasName { + fn name(&self, db: &dyn HirDatabase) -> Option; +} + +macro_rules! impl_has_name { + ( $( $ty:ident ),* $(,)? ) => { + $( + impl HasName for $ty { + fn name(&self, db: &dyn HirDatabase) -> Option { + (*self).name(db).into() + } + } + )* + }; +} + +impl_has_name!( + ModuleDef, + Module, + Field, + Struct, + Union, + Enum, + Variant, + Adt, + VariantDef, + DefWithBody, + Function, + ExternCrateDecl, + Const, + Static, + Trait, + TypeAlias, + Macro, + ExternAssocItem, + AssocItem, + Local, + DeriveHelper, + ToolModule, + Label, + GenericParam, + TypeParam, + LifetimeParam, + ConstParam, + TypeOrConstParam, + InlineAsmOperand, +); + +macro_rules! impl_has_name_no_db { + ( $( $ty:ident ),* $(,)? ) => { + $( + impl HasName for $ty { + fn name(&self, _db: &dyn HirDatabase) -> Option { + (*self).name().into() + } + } + )* + }; +} + +impl_has_name_no_db!(TupleField, StaticLifetime, BuiltinType, BuiltinAttr); + +impl HasName for Param<'_> { + fn name(&self, db: &dyn HirDatabase) -> Option { + self.name(db) + } +} + fn container_id_to_hir(c: ItemContainerId) -> ItemContainer { match c { ItemContainerId::ExternBlockId(id) => ItemContainer::ExternBlock(ExternBlock { id }), ItemContainerId::ModuleId(id) => ItemContainer::Module(Module { id }), - ItemContainerId::ImplId(id) => ItemContainer::Impl(Impl { id }), + ItemContainerId::ImplId(id) => ItemContainer::Impl(id.into()), ItemContainerId::TraitId(id) => ItemContainer::Trait(Trait { id }), } } diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index fcb97ab34e93f..485011c38d7ec 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -13,7 +13,7 @@ use std::{ use base_db::FxIndexSet; use either::Either; use hir_def::{ - DefWithBodyId, FunctionId, MacroId, StructId, TraitId, VariantId, + DefWithBodyId, MacroId, StructId, TraitId, VariantId, attrs::parse_extra_crate_attrs, expr_store::{Body, ExprOrPatSource, HygieneId, path::Path}, hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat}, @@ -34,7 +34,7 @@ use hir_ty::{ diagnostics::{unsafe_operations, unsafe_operations_for_body}, infer_query_with_inspect, next_solver::{ - DbInterner, Span, + AnyImplId, DbInterner, Span, format_proof_tree::{ProofTreeData, dump_proof_tree_structured}, }, }; @@ -53,11 +53,11 @@ use syntax::{ }; use crate::{ - Adjust, Adjustment, Adt, AutoBorrow, BindingMode, BuiltinAttr, Callable, Const, ConstParam, - Crate, DefWithBody, DeriveHelper, Enum, Field, Function, GenericSubstitution, HasSource, Impl, - InFile, InlineAsmOperand, ItemInNs, Label, LifetimeParam, Local, Macro, Module, ModuleDef, - Name, OverloadedDeref, ScopeDef, Static, Struct, ToolModule, Trait, TupleField, Type, - TypeAlias, TypeParam, Union, Variant, VariantDef, + Adjust, Adjustment, Adt, AnyFunctionId, AutoBorrow, BindingMode, BuiltinAttr, Callable, Const, + ConstParam, Crate, DefWithBody, DeriveHelper, Enum, Field, Function, GenericSubstitution, + HasSource, Impl, InFile, InlineAsmOperand, ItemInNs, Label, LifetimeParam, Local, Macro, + Module, ModuleDef, Name, OverloadedDeref, ScopeDef, Static, Struct, ToolModule, Trait, + TupleField, Type, TypeAlias, TypeParam, Union, Variant, VariantDef, db::HirDatabase, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, source_analyzer::{SourceAnalyzer, resolve_hir_path}, @@ -106,7 +106,10 @@ impl PathResolution { | PathResolution::DeriveHelper(_) | PathResolution::ConstParam(_) => None, PathResolution::TypeParam(param) => Some(TypeNs::GenericParam((*param).into())), - PathResolution::SelfType(impl_def) => Some(TypeNs::SelfType((*impl_def).into())), + PathResolution::SelfType(impl_def) => match impl_def.id { + AnyImplId::ImplId(id) => Some(TypeNs::SelfType(id)), + AnyImplId::BuiltinDeriveImplId(_) => None, + }, } } } @@ -345,23 +348,23 @@ impl Semantics<'_, DB> { } pub fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option { - self.imp.resolve_await_to_poll(await_expr).map(Function::from) + self.imp.resolve_await_to_poll(await_expr) } pub fn resolve_prefix_expr(&self, prefix_expr: &ast::PrefixExpr) -> Option { - self.imp.resolve_prefix_expr(prefix_expr).map(Function::from) + self.imp.resolve_prefix_expr(prefix_expr) } pub fn resolve_index_expr(&self, index_expr: &ast::IndexExpr) -> Option { - self.imp.resolve_index_expr(index_expr).map(Function::from) + self.imp.resolve_index_expr(index_expr) } pub fn resolve_bin_expr(&self, bin_expr: &ast::BinExpr) -> Option { - self.imp.resolve_bin_expr(bin_expr).map(Function::from) + self.imp.resolve_bin_expr(bin_expr) } pub fn resolve_try_expr(&self, try_expr: &ast::TryExpr) -> Option { - self.imp.resolve_try_expr(try_expr).map(Function::from) + self.imp.resolve_try_expr(try_expr) } pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option { @@ -1749,6 +1752,7 @@ impl<'db> SemanticsImpl<'db> { func: Function, subst: impl IntoIterator>, ) -> Option { + let AnyFunctionId::FunctionId(func) = func.id else { return Some(func) }; let interner = DbInterner::new_no_crate(self.db); let mut subst = subst.into_iter(); let substs = @@ -1757,7 +1761,12 @@ impl<'db> SemanticsImpl<'db> { subst.next().expect("too few subst").ty.into() }); assert!(subst.next().is_none(), "too many subst"); - Some(self.db.lookup_impl_method(env.env, func.into(), substs).0.into()) + Some(match self.db.lookup_impl_method(env.env, func, substs).0 { + Either::Left(it) => it.into(), + Either::Right((impl_, method)) => { + Function { id: AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } } + } + }) } fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option { @@ -1768,23 +1777,23 @@ impl<'db> SemanticsImpl<'db> { self.analyze(range_expr.syntax())?.resolve_range_expr(self.db, range_expr) } - fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option { + fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option { self.analyze(await_expr.syntax())?.resolve_await_to_poll(self.db, await_expr) } - fn resolve_prefix_expr(&self, prefix_expr: &ast::PrefixExpr) -> Option { + fn resolve_prefix_expr(&self, prefix_expr: &ast::PrefixExpr) -> Option { self.analyze(prefix_expr.syntax())?.resolve_prefix_expr(self.db, prefix_expr) } - fn resolve_index_expr(&self, index_expr: &ast::IndexExpr) -> Option { + fn resolve_index_expr(&self, index_expr: &ast::IndexExpr) -> Option { self.analyze(index_expr.syntax())?.resolve_index_expr(self.db, index_expr) } - fn resolve_bin_expr(&self, bin_expr: &ast::BinExpr) -> Option { + fn resolve_bin_expr(&self, bin_expr: &ast::BinExpr) -> Option { self.analyze(bin_expr.syntax())?.resolve_bin_expr(self.db, bin_expr) } - fn resolve_try_expr(&self, try_expr: &ast::TryExpr) -> Option { + fn resolve_try_expr(&self, try_expr: &ast::TryExpr) -> Option { self.analyze(try_expr.syntax())?.resolve_try_expr(self.db, try_expr) } @@ -1861,7 +1870,9 @@ impl<'db> SemanticsImpl<'db> { } pub fn get_unsafe_ops(&self, def: DefWithBody) -> FxHashSet { - let def = DefWithBodyId::from(def); + let Ok(def) = DefWithBodyId::try_from(def) else { + return FxHashSet::default(); + }; let (body, source_map) = self.db.body_with_source_map(def); let infer = InferenceResult::for_body(self.db, def); let mut res = FxHashSet::default(); @@ -1877,7 +1888,9 @@ impl<'db> SemanticsImpl<'db> { always!(block.unsafe_token().is_some()); let block = self.wrap_node_infile(ast::Expr::from(block)); let Some(def) = self.body_for(block.syntax()) else { return Vec::new() }; - let def = def.into(); + let Ok(def) = def.try_into() else { + return Vec::new(); + }; let (body, source_map) = self.db.body_with_source_map(def); let infer = InferenceResult::for_body(self.db, def); let Some(ExprOrPatId::ExprId(block)) = source_map.node_expr(block.as_ref()) else { @@ -2023,16 +2036,22 @@ impl<'db> SemanticsImpl<'db> { } /// Search for a definition's source and cache its syntax tree - pub fn source(&self, def: Def) -> Option> - where - Def::Ast: AstNode, - { + pub fn source(&self, def: Def) -> Option> { // FIXME: source call should go through the parse cache let res = def.source(self.db)?; self.cache(find_root(res.value.syntax()), res.file_id); Some(res) } + pub fn source_with_range( + &self, + def: Def, + ) -> Option)>> { + let res = def.source_with_range(self.db)?; + self.parse_or_expand(res.file_id); + Some(res) + } + pub fn body_for(&self, node: InFile<&SyntaxNode>) -> Option { let container = self.with_ctx(|ctx| ctx.find_container(node))?; @@ -2162,9 +2181,10 @@ impl<'db> SemanticsImpl<'db> { let def = match &enclosing_item { Either::Left(ast::Item::Fn(it)) if it.unsafe_token().is_some() => return true, - Either::Left(ast::Item::Fn(it)) => { - self.to_def(it).map(<_>::into).map(DefWithBodyId::FunctionId) - } + Either::Left(ast::Item::Fn(it)) => (|| match self.to_def(it)?.id { + AnyFunctionId::FunctionId(id) => Some(DefWithBodyId::FunctionId(id)), + AnyFunctionId::BuiltinDeriveImplMethod { .. } => None, + })(), Either::Left(ast::Item::Const(it)) => { self.to_def(it).map(<_>::into).map(DefWithBodyId::ConstId) } @@ -2201,7 +2221,11 @@ impl<'db> SemanticsImpl<'db> { } pub fn impl_generated_from_derive(&self, impl_: Impl) -> Option { - let source = hir_def::src::HasSource::ast_ptr(&impl_.id.loc(self.db), self.db); + let id = match impl_.id { + AnyImplId::ImplId(id) => id, + AnyImplId::BuiltinDeriveImplId(id) => return Some(id.loc(self.db).adt.into()), + }; + let source = hir_def::src::HasSource::ast_ptr(&id.loc(self.db), self.db); let mut file_id = source.file_id; let adt_ast_id = loop { let macro_call = file_id.macro_file()?; 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 848ad33801330..bf123e13f94d4 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -57,9 +57,9 @@ use syntax::{ use triomphe::Arc; use crate::{ - Adt, AssocItem, BindingMode, BuiltinAttr, BuiltinType, Callable, Const, DeriveHelper, Field, - Function, GenericSubstitution, Local, Macro, ModuleDef, Static, Struct, ToolModule, Trait, - TupleField, Type, TypeAlias, Variant, + Adt, AnyFunctionId, AssocItem, BindingMode, BuiltinAttr, BuiltinType, Callable, Const, + DeriveHelper, Field, Function, GenericSubstitution, Local, Macro, ModuleDef, Static, Struct, + ToolModule, Trait, TupleField, Type, TypeAlias, Variant, db::HirDatabase, semantics::{PathResolution, PathResolutionPerNs}, }; @@ -431,7 +431,7 @@ impl<'db> SourceAnalyzer<'db> { let expr_id = self.expr_id(call.clone().into())?.as_expr()?; let (f_in_trait, substs) = self.infer()?.method_resolution(expr_id)?; - Some(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs).into()) + Some(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs)) } pub(crate) fn resolve_method_call_fallback( @@ -446,8 +446,8 @@ impl<'db> SourceAnalyzer<'db> { let (fn_, subst) = self.resolve_impl_method_or_trait_def_with_subst(db, f_in_trait, substs); Some(( - Either::Left(fn_.into()), - Some(GenericSubstitution::new(fn_.into(), subst, self.trait_environment(db))), + Either::Left(fn_), + GenericSubstitution::new_from_fn(fn_, subst, self.trait_environment(db)), )) } None => { @@ -519,8 +519,8 @@ impl<'db> SourceAnalyzer<'db> { None => inference_result.method_resolution(expr_id).map(|(f, substs)| { let (f, subst) = self.resolve_impl_method_or_trait_def_with_subst(db, f, substs); ( - Either::Right(f.into()), - Some(GenericSubstitution::new(f.into(), subst, self.trait_environment(db))), + Either::Right(f), + GenericSubstitution::new_from_fn(f, subst, self.trait_environment(db)), ) }), } @@ -569,7 +569,7 @@ impl<'db> SourceAnalyzer<'db> { &self, db: &'db dyn HirDatabase, await_expr: &ast::AwaitExpr, - ) -> Option { + ) -> Option { let mut ty = self.ty_of_expr(await_expr.expr()?)?; let into_future_trait = self @@ -605,7 +605,7 @@ impl<'db> SourceAnalyzer<'db> { &self, db: &'db dyn HirDatabase, prefix_expr: &ast::PrefixExpr, - ) -> Option { + ) -> Option { let (_op_trait, op_fn) = match prefix_expr.op_kind()? { ast::UnaryOp::Deref => { // This can be either `Deref::deref` or `DerefMut::deref_mut`. @@ -650,7 +650,7 @@ impl<'db> SourceAnalyzer<'db> { &self, db: &'db dyn HirDatabase, index_expr: &ast::IndexExpr, - ) -> Option { + ) -> Option { let base_ty = self.ty_of_expr(index_expr.base()?)?; let index_ty = self.ty_of_expr(index_expr.index()?)?; @@ -679,7 +679,7 @@ impl<'db> SourceAnalyzer<'db> { &self, db: &'db dyn HirDatabase, binop_expr: &ast::BinExpr, - ) -> Option { + ) -> Option { let op = binop_expr.op_kind()?; let lhs = self.ty_of_expr(binop_expr.lhs()?)?; let rhs = self.ty_of_expr(binop_expr.rhs()?)?; @@ -699,7 +699,7 @@ impl<'db> SourceAnalyzer<'db> { &self, db: &'db dyn HirDatabase, try_expr: &ast::TryExpr, - ) -> Option { + ) -> Option { let ty = self.ty_of_expr(try_expr.expr()?)?; let op_fn = self.lang_items(db).TryTraitBranch?; @@ -905,7 +905,7 @@ impl<'db> SourceAnalyzer<'db> { subs, self.trait_environment(db), ); - (AssocItemId::from(f_in_trait), subst) + (AssocItem::Function(f_in_trait.into()), Some(subst)) } Some(func_ty) => { if let TyKind::FnDef(_fn_def, subs) = func_ty.kind() { @@ -913,19 +913,19 @@ impl<'db> SourceAnalyzer<'db> { .resolve_impl_method_or_trait_def_with_subst( db, f_in_trait, subs, ); - let subst = GenericSubstitution::new( - fn_.into(), + let subst = GenericSubstitution::new_from_fn( + fn_, subst, self.trait_environment(db), ); - (fn_.into(), subst) + (AssocItem::Function(fn_), subst) } else { let subst = GenericSubstitution::new( f_in_trait.into(), subs, self.trait_environment(db), ); - (f_in_trait.into(), subst) + (AssocItem::Function(f_in_trait.into()), Some(subst)) } } } @@ -938,11 +938,11 @@ impl<'db> SourceAnalyzer<'db> { subst, self.trait_environment(db), ); - (konst.into(), subst) + (AssocItem::Const(konst.into()), Some(subst)) } }; - return Some((PathResolution::Def(AssocItem::from(assoc).into()), Some(subst))); + return Some((PathResolution::Def(assoc.into()), subst)); } if let Some(VariantId::EnumVariantId(variant)) = infer.variant_resolution_for_expr_or_pat(expr_id) @@ -1401,7 +1401,7 @@ impl<'db> SourceAnalyzer<'db> { db: &'db dyn HirDatabase, func: FunctionId, substs: GenericArgs<'db>, - ) -> FunctionId { + ) -> Function { self.resolve_impl_method_or_trait_def_with_subst(db, func, substs).0 } @@ -1410,13 +1410,19 @@ impl<'db> SourceAnalyzer<'db> { db: &'db dyn HirDatabase, func: FunctionId, substs: GenericArgs<'db>, - ) -> (FunctionId, GenericArgs<'db>) { + ) -> (Function, GenericArgs<'db>) { let owner = match self.resolver.body_owner() { Some(it) => it, - None => return (func, substs), + None => return (func.into(), substs), }; let env = self.param_and(db.trait_environment_for_body(owner)); - db.lookup_impl_method(env, func, substs) + let (func, args) = db.lookup_impl_method(env, func, substs); + match func { + Either::Left(func) => (func.into(), args), + Either::Right((impl_, method)) => { + (Function { id: AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } }, args) + } + } } fn resolve_impl_const_or_trait_def_with_subst( 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 953bc73da9d84..7692a7d61abf5 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 @@ -734,11 +734,11 @@ FileSymbol { name: "generic_impl_fn", def: Function( - Function { - id: FunctionId( + FunctionId( + FunctionId( 6402, ), - }, + ), ), loc: DeclarationLocation { hir_file_id: FileId( @@ -769,11 +769,11 @@ FileSymbol { name: "impl_fn", def: Function( - Function { - id: FunctionId( + FunctionId( + FunctionId( 6401, ), - }, + ), ), loc: DeclarationLocation { hir_file_id: FileId( @@ -839,11 +839,11 @@ FileSymbol { name: "main", def: Function( - Function { - id: FunctionId( + FunctionId( + FunctionId( 6400, ), - }, + ), ), loc: DeclarationLocation { hir_file_id: FileId( @@ -907,11 +907,11 @@ FileSymbol { name: "trait_fn", def: Function( - Function { - id: FunctionId( + FunctionId( + FunctionId( 6403, ), - }, + ), ), loc: DeclarationLocation { hir_file_id: FileId( 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 f57f2883b1c38..06ae0b1d73d1e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs @@ -767,14 +767,30 @@ fn label_of_ty( ) }); + let module_def_location = |label_builder: &mut InlayHintLabelBuilder<'_>, + def: ModuleDef, + name| { + let def = def.try_into(); + if let Ok(def) = def { + label_builder.start_location_link(def); + } + #[expect( + clippy::question_mark, + reason = "false positive; replacing with `?` leads to 'type annotations needed' error" + )] + if let Err(err) = label_builder.write_str(name) { + return Err(err); + } + if def.is_ok() { + label_builder.end_location_link(); + } + Ok(()) + }; + label_builder.write_str(LABEL_START)?; - label_builder.start_location_link(ModuleDef::from(iter_trait).into()); - label_builder.write_str(LABEL_ITERATOR)?; - label_builder.end_location_link(); + module_def_location(label_builder, ModuleDef::from(iter_trait), LABEL_ITERATOR)?; label_builder.write_str(LABEL_MIDDLE)?; - label_builder.start_location_link(ModuleDef::from(item).into()); - label_builder.write_str(LABEL_ITEM)?; - label_builder.end_location_link(); + module_def_location(label_builder, ModuleDef::from(item), LABEL_ITEM)?; label_builder.write_str(LABEL_MIDDLE2)?; rec(sema, famous_defs, max_length, &ty, label_builder, config, display_target)?; label_builder.write_str(LABEL_END)?; diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs index 1317684a08779..e5e4c899ec03c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs @@ -34,9 +34,10 @@ pub(super) fn hints( let def = sema.to_def(node)?; let def: DefWithBody = def.into(); - let (hir, source_map) = sema.db.body_with_source_map(def.into()); + let def = def.try_into().ok()?; + let (hir, source_map) = sema.db.body_with_source_map(def); - let mir = sema.db.mir_body(def.into()).ok()?; + let mir = sema.db.mir_body(def).ok()?; let local_to_binding = mir.local_to_binding_map(); diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs index 020f235d3a929..a271cac6fcd07 100644 --- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs +++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs @@ -6,7 +6,7 @@ use arrayvec::ArrayVec; use either::Either; use hir::{ AssocItem, Crate, FieldSource, HasContainer, HasCrate, HasSource, HirDisplay, HirFileId, - InFile, LocalSource, ModuleSource, Semantics, Symbol, db::ExpandDatabase, sym, + InFile, LocalSource, ModuleSource, Name, Semantics, Symbol, db::ExpandDatabase, sym, symbols::FileSymbol, }; use ide_db::{ @@ -204,6 +204,22 @@ impl NavigationTarget { ) } + pub(crate) fn from_named_with_range( + db: &RootDatabase, + ranges: InFile<(TextRange, Option)>, + name: Option, + kind: SymbolKind, + ) -> UpmappingResult { + let InFile { file_id, value: (full_range, focus_range) } = ranges; + let name = name.map(|name| name.symbol().clone()).unwrap_or_else(|| sym::underscore); + + orig_range_with_focus_r(db, file_id, full_range, focus_range).map( + |(FileRange { file_id, range: full_range }, focus_range)| { + NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind) + }, + ) + } + pub(crate) fn from_syntax( file_id: FileId, name: Symbol, @@ -414,7 +430,13 @@ impl ToNavFromAst for hir::Trait { impl TryToNav for D where - D: HasSource + ToNavFromAst + Copy + HasDocs + for<'db> HirDisplay<'db> + HasCrate, + D: HasSource + + ToNavFromAst + + Copy + + HasDocs + + for<'db> HirDisplay<'db> + + HasCrate + + hir::HasName, D::Ast: ast::HasName, { fn try_to_nav( @@ -422,11 +444,19 @@ where sema: &Semantics<'_, RootDatabase>, ) -> Option> { let db = sema.db; - let src = self.source(db)?; + let src = self.source_with_range(db)?; Some( - NavigationTarget::from_named( + NavigationTarget::from_named_with_range( db, - src.as_ref().map(|it| it as &dyn ast::HasName), + src.map(|(full_range, node)| { + ( + full_range, + node.and_then(|node| { + Some(ast::HasName::name(&node)?.syntax().text_range()) + }), + ) + }), + self.name(db), D::KIND, ) .map(|mut res| { @@ -477,16 +507,16 @@ impl TryToNav for hir::Impl { sema: &Semantics<'_, RootDatabase>, ) -> Option> { let db = sema.db; - let InFile { file_id, value } = self.source(db)?; - let derive_path = self.as_builtin_derive_path(db); - - let (file_id, focus, syntax) = match &derive_path { - Some(attr) => (attr.file_id.into(), None, attr.value.syntax()), - None => (file_id, value.self_ty(), value.syntax()), - }; + let InFile { file_id, value: (full_range, source) } = self.source_with_range(db)?; - Some(orig_range_with_focus(db, file_id, syntax, focus).map( - |(FileRange { file_id, range: full_range }, focus_range)| { + Some( + orig_range_with_focus_r( + db, + file_id, + full_range, + source.and_then(|source| Some(source.self_ty()?.syntax().text_range())), + ) + .map(|(FileRange { file_id, range: full_range }, focus_range)| { NavigationTarget::from_syntax( file_id, sym::kw_impl, @@ -494,8 +524,8 @@ impl TryToNav for hir::Impl { full_range, SymbolKind::Impl, ) - }, - )) + }), + ) } } diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 0738b7fadcc16..ffae7bf6c7c5a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -2503,7 +2503,7 @@ fn r#fn$0() {} fn main() { r#fn(); } "#, expect![[r#" - r#fn Function FileId(0) 0..12 3..7 + fn Function FileId(0) 0..12 3..7 FileId(0) 25..29 "#]], diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs index c562a9b30b040..6cec912503515 100644 --- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs +++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs @@ -1679,11 +1679,11 @@ mod r#mod { [ "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 1..461, focus_range: 5..10, name: \"mod\", kind: Module, description: \"mod r#mod\" })", "(Test, NavigationTarget { file_id: FileId(0), full_range: 17..41, focus_range: 32..36, name: \"r#fn\", kind: Function })", - "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 47..84, name: \"r#for\", container_name: \"mod\" })", - "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 90..146, name: \"r#struct\", container_name: \"mod\" })", + "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 47..84, name: \"for\", container_name: \"mod\" })", + "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 90..146, name: \"struct\", container_name: \"mod\" })", "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 152..266, focus_range: 189..205, name: \"impl\", kind: Impl })", - "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 216..260, name: \"r#fn\" })", - "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 323..367, name: \"r#fn\" })", + "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 216..260, name: \"fn\" })", + "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 323..367, name: \"fn\" })", "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 401..459, focus_range: 445..456, name: \"impl\", kind: Impl })", ] "#]], 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 aa7e40c65848e..3e325b2f990d4 100644 --- a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs +++ b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs @@ -526,6 +526,9 @@ define_symbols! { arbitrary_self_types_pointers, supertrait_item_shadowing, hash, + partial_cmp, cmp, + CoerceUnsized, + DispatchFromDyn, define_opaque, } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 76256b0a22530..9374d97d83fbf 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -693,21 +693,24 @@ impl flags::AnalysisStats { let mut sw = self.stop_watch(); let mut all = 0; let mut fail = 0; - for &body_id in bodies { + for &body in bodies { bar.set_message(move || { - format!("mir lowering: {}", full_name(db, body_id, body_id.module(db))) + format!("mir lowering: {}", full_name(db, body, body.module(db))) }); bar.inc(1); - if matches!(body_id, DefWithBody::Variant(_)) { + if matches!(body, DefWithBody::Variant(_)) { continue; } - let module = body_id.module(db); - if !self.should_process(db, body_id, module) { + let module = body.module(db); + if !self.should_process(db, body, module) { continue; } all += 1; - let Err(e) = db.mir_body(body_id.into()) else { + let Ok(body_id) = body.try_into() else { + continue; + }; + let Err(e) = db.mir_body(body_id) else { continue; }; if verbosity.is_spammy() { @@ -716,7 +719,7 @@ impl flags::AnalysisStats { .into_iter() .rev() .filter_map(|it| it.name(db)) - .chain(Some(body_id.name(db).unwrap_or_else(Name::missing))) + .chain(Some(body.name(db).unwrap_or_else(Name::missing))) .map(|it| it.display(db, Edition::LATEST).to_string()) .join("::"); bar.println(format!("Mir body for {full_name} failed due {e:?}")); @@ -747,11 +750,12 @@ impl flags::AnalysisStats { if self.parallel { let mut inference_sw = self.stop_watch(); + let bodies = bodies.iter().filter_map(|&body| body.try_into().ok()).collect::>(); bodies .par_iter() .map_with(db.clone(), |snap, &body| { - snap.body(body.into()); - InferenceResult::for_body(snap, body.into()); + snap.body(body); + InferenceResult::for_body(snap, body); }) .count(); eprintln!("{:<20} {}", "Parallel Inference:", inference_sw.elapsed()); @@ -769,6 +773,7 @@ impl flags::AnalysisStats { let mut num_pat_type_mismatches = 0; let mut panics = 0; for &body_id in bodies { + let Ok(body_def_id) = body_id.try_into() else { continue }; let name = body_id.name(db).unwrap_or_else(Name::missing); let module = body_id.module(db); let display_target = module.krate(db).to_display_target(db); @@ -807,9 +812,9 @@ impl flags::AnalysisStats { bar.println(msg()); } bar.set_message(msg); - let body = db.body(body_id.into()); + let body = db.body(body_def_id); let inference_result = - catch_unwind(AssertUnwindSafe(|| InferenceResult::for_body(db, body_id.into()))); + catch_unwind(AssertUnwindSafe(|| InferenceResult::for_body(db, body_def_id))); let inference_result = match inference_result { Ok(inference_result) => inference_result, Err(p) => { @@ -826,7 +831,7 @@ impl flags::AnalysisStats { } }; // This query is LRU'd, so actually calling it will skew the timing results. - let sm = || db.body_with_source_map(body_id.into()).1; + let sm = || db.body_with_source_map(body_def_id).1; // region:expressions let (previous_exprs, previous_unknown, previous_partially_unknown) = @@ -1081,6 +1086,7 @@ impl flags::AnalysisStats { let mut sw = self.stop_watch(); bar.tick(); for &body_id in bodies { + let Ok(body_def_id) = body_id.try_into() else { continue }; let module = body_id.module(db); if !self.should_process(db, body_id, module) { continue; @@ -1114,7 +1120,7 @@ impl flags::AnalysisStats { bar.println(msg()); } bar.set_message(msg); - db.body(body_id.into()); + db.body(body_def_id); bar.inc(1); } From 02763ff0ec8ac3e60a2c5508dbc7fda2b8900fee Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 26 Dec 2025 15:00:27 +0200 Subject: [PATCH 30/77] Test builtin derives expansions Via a hack to disable their fast path. --- .../builtin_derive_macro.rs | 1657 ++++++++--------- .../hir-def/src/macro_expansion_tests/mod.rs | 9 + .../crates/hir-def/src/nameres.rs | 19 + .../crates/hir-def/src/nameres/collector.rs | 5 +- 4 files changed, 857 insertions(+), 833 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs index 814afdd16cf93..0013c2a25679a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs @@ -1,833 +1,828 @@ //! Tests for `builtin_derive_macro.rs` from `hir_expand`. -// FIXME: This file is commented out because due to the fast path for builtin derives, -// the macros do not really get expanded, and we cannot check their expansion. -// It's not removed because we still need to find some way to do that nevertheless. -// Maybe going through the list of registered derive calls in the def map? - -// use expect_test::expect; - -// use crate::macro_expansion_tests::{check, check_errors}; - -// #[test] -// fn test_copy_expand_simple() { -// check( -// r#" -// //- minicore: derive, copy -// #[derive(Copy)] -// struct Foo; -// "#, -// expect![[r#" -// #[derive(Copy)] -// struct Foo; - -// impl <> $crate::marker::Copy for Foo< > where {}"#]], -// ); -// } - -// #[test] -// fn test_copy_expand_in_core() { -// check( -// r#" -// //- /lib.rs crate:core -// #[rustc_builtin_macro] -// macro derive {} -// #[rustc_builtin_macro] -// macro Copy {} -// #[derive(Copy)] -// struct Foo; -// "#, -// expect![[r#" -// #[rustc_builtin_macro] -// macro derive {} -// #[rustc_builtin_macro] -// macro Copy {} -// #[derive(Copy)] -// struct Foo; - -// impl <> $crate::marker::Copy for Foo< > where {}"#]], -// ); -// } - -// #[test] -// fn test_copy_expand_with_type_params() { -// check( -// r#" -// //- minicore: derive, copy -// #[derive(Copy)] -// struct Foo; -// "#, -// expect![[r#" -// #[derive(Copy)] -// struct Foo; - -// impl $crate::marker::Copy for Foo where {}"#]], -// ); -// } - -// #[test] -// fn test_copy_expand_with_lifetimes() { -// // We currently just ignore lifetimes -// check( -// r#" -// //- minicore: derive, copy -// #[derive(Copy)] -// struct Foo; -// "#, -// expect![[r#" -// #[derive(Copy)] -// struct Foo; - -// impl $crate::marker::Copy for Foo where {}"#]], -// ); -// } - -// #[test] -// fn test_clone_expand() { -// check( -// r#" -// //- minicore: derive, clone -// #[derive(Clone)] -// enum Command { -// Move { x: A, y: B }, -// Do(&'static str), -// Jump, -// } -// "#, -// expect![[r#" -// #[derive(Clone)] -// enum Command { -// Move { x: A, y: B }, -// Do(&'static str), -// Jump, -// } - -// impl $crate::clone::Clone for Command where { -// fn clone(&self ) -> Self { -// match self { -// Command::Move { -// x: x, y: y, -// } -// =>Command::Move { -// x: x.clone(), y: y.clone(), -// } -// , Command::Do(f0, )=>Command::Do(f0.clone(), ), Command::Jump=>Command::Jump, -// } -// } -// }"#]], -// ); -// } - -// #[test] -// fn test_clone_expand_with_associated_types() { -// check( -// r#" -// //- minicore: derive, clone -// trait Trait { -// type InWc; -// type InFieldQualified; -// type InFieldShorthand; -// type InGenericArg; -// } -// trait Marker {} -// struct Vec(T); - -// #[derive(Clone)] -// struct Foo -// where -// ::InWc: Marker, -// { -// qualified: ::InFieldQualified, -// shorthand: T::InFieldShorthand, -// generic: Vec, -// } -// "#, -// expect![[r#" -// trait Trait { -// type InWc; -// type InFieldQualified; -// type InFieldShorthand; -// type InGenericArg; -// } -// trait Marker {} -// struct Vec(T); - -// #[derive(Clone)] -// struct Foo -// where -// ::InWc: Marker, -// { -// qualified: ::InFieldQualified, -// shorthand: T::InFieldShorthand, -// generic: Vec, -// } - -// impl $crate::clone::Clone for Foo where ::InWc: Marker, T: Trait, T::InFieldShorthand: $crate::clone::Clone, T::InGenericArg: $crate::clone::Clone, { -// fn clone(&self ) -> Self { -// match self { -// Foo { -// qualified: qualified, shorthand: shorthand, generic: generic, -// } -// =>Foo { -// qualified: qualified.clone(), shorthand: shorthand.clone(), generic: generic.clone(), -// } -// , -// } -// } -// }"#]], -// ); -// } - -// #[test] -// fn test_clone_expand_with_const_generics() { -// check( -// r#" -// //- minicore: derive, clone -// #[derive(Clone)] -// struct Foo(u32); -// "#, -// expect![[r#" -// #[derive(Clone)] -// struct Foo(u32); - -// impl $crate::clone::Clone for Foo where { -// fn clone(&self ) -> Self { -// match self { -// Foo(f0, )=>Foo(f0.clone(), ), -// } -// } -// }"#]], -// ); -// } - -// #[test] -// fn test_default_expand() { -// check( -// r#" -// //- minicore: derive, default -// #[derive(Default)] -// struct Foo { -// field1: i32, -// field2: (), -// } -// #[derive(Default)] -// enum Bar { -// Foo(u8), -// #[default] -// Bar, -// } -// "#, -// expect![[r#" -// #[derive(Default)] -// struct Foo { -// field1: i32, -// field2: (), -// } -// #[derive(Default)] -// enum Bar { -// Foo(u8), -// #[default] -// Bar, -// } - -// impl <> $crate::default::Default for Foo< > where { -// fn default() -> Self { -// Foo { -// field1: $crate::default::Default::default(), field2: $crate::default::Default::default(), -// } -// } -// } -// impl <> $crate::default::Default for Bar< > where { -// fn default() -> Self { -// Bar::Bar -// } -// }"#]], -// ); -// } - -// #[test] -// fn test_partial_eq_expand() { -// check( -// r#" -// //- minicore: derive, eq -// #[derive(PartialEq, Eq)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } -// "#, -// expect![[r#" -// #[derive(PartialEq, Eq)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } - -// impl <> $crate::cmp::PartialEq for Command< > where { -// fn eq(&self , other: &Self ) -> bool { -// match (self , other) { -// (Command::Move { -// x: x_self, y: y_self, -// } -// , Command::Move { -// x: x_other, y: y_other, -// } -// )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false -// } -// } -// } -// impl <> $crate::cmp::Eq for Command< > where {}"#]], -// ); -// } - -// #[test] -// fn test_partial_eq_expand_with_derive_const() { -// // FIXME: actually expand with const -// check( -// r#" -// //- minicore: derive, eq -// #[derive_const(PartialEq, Eq)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } -// "#, -// expect![[r#" -// #[derive_const(PartialEq, Eq)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } - -// impl <> $crate::cmp::PartialEq for Command< > where { -// fn eq(&self , other: &Self ) -> bool { -// match (self , other) { -// (Command::Move { -// x: x_self, y: y_self, -// } -// , Command::Move { -// x: x_other, y: y_other, -// } -// )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false -// } -// } -// } -// impl <> $crate::cmp::Eq for Command< > where {}"#]], -// ); -// } - -// #[test] -// fn test_partial_ord_expand() { -// check( -// r#" -// //- minicore: derive, ord -// #[derive(PartialOrd, Ord)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } -// "#, -// expect![[r#" -// #[derive(PartialOrd, Ord)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } - -// impl <> $crate::cmp::PartialOrd for Command< > where { -// fn partial_cmp(&self , other: &Self ) -> $crate::option::Option<$crate::cmp::Ordering> { -// match $crate::intrinsics::discriminant_value(self ).partial_cmp(&$crate::intrinsics::discriminant_value(other)) { -// $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { -// match (self , other) { -// (Command::Move { -// x: x_self, y: y_self, -// } -// , Command::Move { -// x: x_other, y: y_other, -// } -// )=>match x_self.partial_cmp(&x_other) { -// $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { -// match y_self.partial_cmp(&y_other) { -// $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { -// $crate::option::Option::Some($crate::cmp::Ordering::Equal) -// } -// c=>return c, -// } -// } -// c=>return c, -// } -// , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.partial_cmp(&f0_other) { -// $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { -// $crate::option::Option::Some($crate::cmp::Ordering::Equal) -// } -// c=>return c, -// } -// , (Command::Jump, Command::Jump)=>$crate::option::Option::Some($crate::cmp::Ordering::Equal), _unused=>$crate::option::Option::Some($crate::cmp::Ordering::Equal) -// } -// } -// c=>return c, -// } -// } -// } -// impl <> $crate::cmp::Ord for Command< > where { -// fn cmp(&self , other: &Self ) -> $crate::cmp::Ordering { -// match $crate::intrinsics::discriminant_value(self ).cmp(&$crate::intrinsics::discriminant_value(other)) { -// $crate::cmp::Ordering::Equal=> { -// match (self , other) { -// (Command::Move { -// x: x_self, y: y_self, -// } -// , Command::Move { -// x: x_other, y: y_other, -// } -// )=>match x_self.cmp(&x_other) { -// $crate::cmp::Ordering::Equal=> { -// match y_self.cmp(&y_other) { -// $crate::cmp::Ordering::Equal=> { -// $crate::cmp::Ordering::Equal -// } -// c=>return c, -// } -// } -// c=>return c, -// } -// , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.cmp(&f0_other) { -// $crate::cmp::Ordering::Equal=> { -// $crate::cmp::Ordering::Equal -// } -// c=>return c, -// } -// , (Command::Jump, Command::Jump)=>$crate::cmp::Ordering::Equal, _unused=>$crate::cmp::Ordering::Equal -// } -// } -// c=>return c, -// } -// } -// }"#]], -// ); -// } - -// #[test] -// fn test_hash_expand() { -// check( -// r#" -// //- minicore: derive, hash -// use core::hash::Hash; - -// #[derive(Hash)] -// struct Foo { -// x: i32, -// y: u64, -// z: (i32, u64), -// } -// "#, -// expect![[r#" -// use core::hash::Hash; - -// #[derive(Hash)] -// struct Foo { -// x: i32, -// y: u64, -// z: (i32, u64), -// } - -// impl <> $crate::hash::Hash for Foo< > where { -// fn hash(&self , ra_expand_state: &mut H) { -// match self { -// Foo { -// x: x, y: y, z: z, -// } -// => { -// x.hash(ra_expand_state); -// y.hash(ra_expand_state); -// z.hash(ra_expand_state); -// } -// , -// } -// } -// }"#]], -// ); -// check( -// r#" -// //- minicore: derive, hash -// use core::hash::Hash; - -// #[derive(Hash)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } -// "#, -// expect![[r#" -// use core::hash::Hash; - -// #[derive(Hash)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } - -// impl <> $crate::hash::Hash for Command< > where { -// fn hash(&self , ra_expand_state: &mut H) { -// $crate::mem::discriminant(self ).hash(ra_expand_state); -// match self { -// Command::Move { -// x: x, y: y, -// } -// => { -// x.hash(ra_expand_state); -// y.hash(ra_expand_state); -// } -// , Command::Do(f0, )=> { -// f0.hash(ra_expand_state); -// } -// , Command::Jump=> {} -// , -// } -// } -// }"#]], -// ); -// } - -// #[test] -// fn test_debug_expand() { -// check( -// r#" -// //- minicore: derive, fmt -// use core::fmt::Debug; - -// #[derive(Debug)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } -// "#, -// expect![[r#" -// use core::fmt::Debug; - -// #[derive(Debug)] -// enum Command { -// Move { x: i32, y: i32 }, -// Do(&'static str), -// Jump, -// } - -// impl <> $crate::fmt::Debug for Command< > where { -// fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { -// match self { -// Command::Move { -// x: x, y: y, -// } -// =>f.debug_struct("Move").field("x", &x).field("y", &y).finish(), Command::Do(f0, )=>f.debug_tuple("Do").field(&f0).finish(), Command::Jump=>f.write_str("Jump"), -// } -// } -// }"#]], -// ); -// } -// #[test] -// fn test_debug_expand_with_cfg() { -// check( -// r#" -// //- minicore: derive, fmt -// use core::fmt::Debug; - -// #[derive(Debug)] -// struct HideAndShow { -// #[cfg(never)] -// always_hide: u32, -// #[cfg(not(never))] -// always_show: u32, -// } -// #[derive(Debug)] -// enum HideAndShowEnum { -// #[cfg(never)] -// AlwaysHide, -// #[cfg(not(never))] -// AlwaysShow{ -// #[cfg(never)] -// always_hide: u32, -// #[cfg(not(never))] -// always_show: u32, -// } -// } -// "#, -// expect![[r#" -// use core::fmt::Debug; - -// #[derive(Debug)] -// struct HideAndShow { -// #[cfg(never)] -// always_hide: u32, -// #[cfg(not(never))] -// always_show: u32, -// } -// #[derive(Debug)] -// enum HideAndShowEnum { -// #[cfg(never)] -// AlwaysHide, -// #[cfg(not(never))] -// AlwaysShow{ -// #[cfg(never)] -// always_hide: u32, -// #[cfg(not(never))] -// always_show: u32, -// } -// } - -// impl <> $crate::fmt::Debug for HideAndShow< > where { -// fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { -// match self { -// HideAndShow { -// always_show: always_show, -// } -// =>f.debug_struct("HideAndShow").field("always_show", &always_show).finish() -// } -// } -// } -// impl <> $crate::fmt::Debug for HideAndShowEnum< > where { -// fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { -// match self { -// HideAndShowEnum::AlwaysShow { -// always_show: always_show, -// } -// =>f.debug_struct("AlwaysShow").field("always_show", &always_show).finish(), -// } -// } -// }"#]], -// ); -// } -// #[test] -// fn test_default_expand_with_cfg() { -// check( -// r#" -// //- minicore: derive, default -// #[derive(Default)] -// struct Foo { -// field1: i32, -// #[cfg(never)] -// field2: (), -// #[cfg(feature = "never")] -// field3: (), -// #[cfg(not(feature = "never"))] -// field4: (), -// } -// #[derive(Default)] -// enum Bar { -// Foo, -// #[cfg_attr(not(never), default)] -// Bar, -// } -// "#, -// expect![[r##" -// #[derive(Default)] -// struct Foo { -// field1: i32, -// #[cfg(never)] -// field2: (), -// #[cfg(feature = "never")] -// field3: (), -// #[cfg(not(feature = "never"))] -// field4: (), -// } -// #[derive(Default)] -// enum Bar { -// Foo, -// #[cfg_attr(not(never), default)] -// Bar, -// } - -// impl <> $crate::default::Default for Foo< > where { -// fn default() -> Self { -// Foo { -// field1: $crate::default::Default::default(), field4: $crate::default::Default::default(), -// } -// } -// } -// impl <> $crate::default::Default for Bar< > where { -// fn default() -> Self { -// Bar::Bar -// } -// }"##]], -// ); -// } - -// #[test] -// fn coerce_pointee_expansion() { -// check( -// r#" -// //- minicore: coerce_pointee - -// use core::marker::CoercePointee; - -// pub trait Trait {} - -// #[derive(CoercePointee)] -// #[repr(transparent)] -// pub struct Foo<'a, T: ?Sized + Trait, #[pointee] U: ?Sized, const N: u32>(T) -// where -// U: Trait + ToString;"#, -// expect![[r#" - -// use core::marker::CoercePointee; - -// pub trait Trait {} - -// #[derive(CoercePointee)] -// #[repr(transparent)] -// pub struct Foo<'a, T: ?Sized + Trait, #[pointee] U: ?Sized, const N: u32>(T) -// where -// U: Trait + ToString; -// impl $crate::ops::DispatchFromDyn> for Foo where U: Trait +ToString, T: Trait<__S>, __S: ?Sized, __S: Trait<__S> +ToString, U: ::core::marker::Unsize<__S>, T:?Sized+Trait, U:?Sized, {} -// impl $crate::ops::CoerceUnsized> for Foo where U: Trait +ToString, T: Trait<__S>, __S: ?Sized, __S: Trait<__S> +ToString, U: ::core::marker::Unsize<__S>, T:?Sized+Trait, U:?Sized, {}"#]], -// ); -// } - -// #[test] -// fn coerce_pointee_errors() { -// check_errors( -// r#" -// //- minicore: coerce_pointee - -// use core::marker::CoercePointee; - -// #[derive(CoercePointee)] -// enum Enum {} - -// #[derive(CoercePointee)] -// struct Struct1; - -// #[derive(CoercePointee)] -// struct Struct2(); - -// #[derive(CoercePointee)] -// struct Struct3 {} - -// #[derive(CoercePointee)] -// struct Struct4(T); - -// #[derive(CoercePointee)] -// #[repr(transparent)] -// struct Struct5(i32); - -// #[derive(CoercePointee)] -// #[repr(transparent)] -// struct Struct6<#[pointee] T: ?Sized, #[pointee] U: ?Sized>(T, U); - -// #[derive(CoercePointee)] -// #[repr(transparent)] -// struct Struct7(T, U); - -// #[derive(CoercePointee)] -// #[repr(transparent)] -// struct Struct8<#[pointee] T, U: ?Sized>(T); - -// #[derive(CoercePointee)] -// #[repr(transparent)] -// struct Struct9(T); - -// #[derive(CoercePointee)] -// #[repr(transparent)] -// struct Struct9<#[pointee] T, U>(T) where T: ?Sized; -// "#, -// expect![[r#" -// 35..72: `CoercePointee` can only be derived on `struct`s -// 74..114: `CoercePointee` can only be derived on `struct`s with at least one field -// 116..158: `CoercePointee` can only be derived on `struct`s with at least one field -// 160..202: `CoercePointee` can only be derived on `struct`s with at least one field -// 204..258: `CoercePointee` can only be derived on `struct`s with `#[repr(transparent)]` -// 260..326: `CoercePointee` can only be derived on `struct`s that are generic over at least one type -// 328..439: only one type parameter can be marked as `#[pointee]` when deriving `CoercePointee` traits -// 441..530: exactly one generic type parameter must be marked as `#[pointee]` to derive `CoercePointee` traits -// 532..621: `derive(CoercePointee)` requires `T` to be marked `?Sized` -// 623..690: `derive(CoercePointee)` requires `T` to be marked `?Sized`"#]], -// ); -// } - -// #[test] -// fn union_derive() { -// check_errors( -// r#" -// //- minicore: clone, copy, default, fmt, hash, ord, eq, derive - -// #[derive(Copy)] -// union Foo1 { _v: () } -// #[derive(Clone)] -// union Foo2 { _v: () } -// #[derive(Default)] -// union Foo3 { _v: () } -// #[derive(Debug)] -// union Foo4 { _v: () } -// #[derive(Hash)] -// union Foo5 { _v: () } -// #[derive(Ord)] -// union Foo6 { _v: () } -// #[derive(PartialOrd)] -// union Foo7 { _v: () } -// #[derive(Eq)] -// union Foo8 { _v: () } -// #[derive(PartialEq)] -// union Foo9 { _v: () } -// "#, -// expect![[r#" -// 78..118: this trait cannot be derived for unions -// 119..157: this trait cannot be derived for unions -// 158..195: this trait cannot be derived for unions -// 196..232: this trait cannot be derived for unions -// 233..276: this trait cannot be derived for unions -// 313..355: this trait cannot be derived for unions"#]], -// ); -// } - -// #[test] -// fn default_enum_without_default_attr() { -// check_errors( -// r#" -// //- minicore: default, derive - -// #[derive(Default)] -// enum Foo { -// Bar, -// } -// "#, -// expect!["1..41: `#[derive(Default)]` on enum with no `#[default]`"], -// ); -// } - -// #[test] -// fn generic_enum_default() { -// check( -// r#" -// //- minicore: default, derive - -// #[derive(Default)] -// enum Foo { -// Bar(T), -// #[default] -// Baz, -// } -// "#, -// expect![[r#" - -// #[derive(Default)] -// enum Foo { -// Bar(T), -// #[default] -// Baz, -// } - -// impl $crate::default::Default for Foo where { -// fn default() -> Self { -// Foo::Baz -// } -// }"#]], -// ); -// } +use expect_test::expect; + +use crate::macro_expansion_tests::{check, check_errors}; + +#[test] +fn test_copy_expand_simple() { + check( + r#" +//- minicore: derive, copy +#[derive(Copy)] +struct Foo; +"#, + expect![[r#" +#[derive(Copy)] +struct Foo; + +impl <> $crate::marker::Copy for Foo< > where {}"#]], + ); +} + +#[test] +fn test_copy_expand_in_core() { + check( + r#" +//- /lib.rs crate:core +#[rustc_builtin_macro] +macro derive {} +#[rustc_builtin_macro] +macro Copy {} +#[derive(Copy)] +struct Foo; +"#, + expect![[r#" +#[rustc_builtin_macro] +macro derive {} +#[rustc_builtin_macro] +macro Copy {} +#[derive(Copy)] +struct Foo; + +impl <> $crate::marker::Copy for Foo< > where {}"#]], + ); +} + +#[test] +fn test_copy_expand_with_type_params() { + check( + r#" +//- minicore: derive, copy +#[derive(Copy)] +struct Foo; +"#, + expect![[r#" +#[derive(Copy)] +struct Foo; + +impl $crate::marker::Copy for Foo where {}"#]], + ); +} + +#[test] +fn test_copy_expand_with_lifetimes() { + // We currently just ignore lifetimes + check( + r#" +//- minicore: derive, copy +#[derive(Copy)] +struct Foo; +"#, + expect![[r#" +#[derive(Copy)] +struct Foo; + +impl $crate::marker::Copy for Foo where {}"#]], + ); +} + +#[test] +fn test_clone_expand() { + check( + r#" +//- minicore: derive, clone +#[derive(Clone)] +enum Command { + Move { x: A, y: B }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +#[derive(Clone)] +enum Command { + Move { x: A, y: B }, + Do(&'static str), + Jump, +} + +impl $crate::clone::Clone for Command where { + fn clone(&self ) -> Self { + match self { + Command::Move { + x: x, y: y, + } + =>Command::Move { + x: x.clone(), y: y.clone(), + } + , Command::Do(f0, )=>Command::Do(f0.clone(), ), Command::Jump=>Command::Jump, + } + } +}"#]], + ); +} + +#[test] +fn test_clone_expand_with_associated_types() { + check( + r#" +//- minicore: derive, clone +trait Trait { + type InWc; + type InFieldQualified; + type InFieldShorthand; + type InGenericArg; +} +trait Marker {} +struct Vec(T); + +#[derive(Clone)] +struct Foo +where + ::InWc: Marker, +{ + qualified: ::InFieldQualified, + shorthand: T::InFieldShorthand, + generic: Vec, +} +"#, + expect![[r#" +trait Trait { + type InWc; + type InFieldQualified; + type InFieldShorthand; + type InGenericArg; +} +trait Marker {} +struct Vec(T); + +#[derive(Clone)] +struct Foo +where + ::InWc: Marker, +{ + qualified: ::InFieldQualified, + shorthand: T::InFieldShorthand, + generic: Vec, +} + +impl $crate::clone::Clone for Foo where ::InWc: Marker, T: Trait, T::InFieldShorthand: $crate::clone::Clone, T::InGenericArg: $crate::clone::Clone, { + fn clone(&self ) -> Self { + match self { + Foo { + qualified: qualified, shorthand: shorthand, generic: generic, + } + =>Foo { + qualified: qualified.clone(), shorthand: shorthand.clone(), generic: generic.clone(), + } + , + } + } +}"#]], + ); +} + +#[test] +fn test_clone_expand_with_const_generics() { + check( + r#" +//- minicore: derive, clone +#[derive(Clone)] +struct Foo(u32); +"#, + expect![[r#" +#[derive(Clone)] +struct Foo(u32); + +impl $crate::clone::Clone for Foo where { + fn clone(&self ) -> Self { + match self { + Foo(f0, )=>Foo(f0.clone(), ), + } + } +}"#]], + ); +} + +#[test] +fn test_default_expand() { + check( + r#" +//- minicore: derive, default +#[derive(Default)] +struct Foo { + field1: i32, + field2: (), +} +#[derive(Default)] +enum Bar { + Foo(u8), + #[default] + Bar, +} +"#, + expect![[r#" +#[derive(Default)] +struct Foo { + field1: i32, + field2: (), +} +#[derive(Default)] +enum Bar { + Foo(u8), + #[default] + Bar, +} + +impl <> $crate::default::Default for Foo< > where { + fn default() -> Self { + Foo { + field1: $crate::default::Default::default(), field2: $crate::default::Default::default(), + } + } +} +impl <> $crate::default::Default for Bar< > where { + fn default() -> Self { + Bar::Bar + } +}"#]], + ); +} + +#[test] +fn test_partial_eq_expand() { + check( + r#" +//- minicore: derive, eq +#[derive(PartialEq, Eq)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +#[derive(PartialEq, Eq)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} + +impl <> $crate::cmp::PartialEq for Command< > where { + fn eq(&self , other: &Self ) -> bool { + match (self , other) { + (Command::Move { + x: x_self, y: y_self, + } + , Command::Move { + x: x_other, y: y_other, + } + )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false + } + } +} +impl <> $crate::cmp::Eq for Command< > where {}"#]], + ); +} + +#[test] +fn test_partial_eq_expand_with_derive_const() { + // FIXME: actually expand with const + check( + r#" +//- minicore: derive, eq +#[derive_const(PartialEq, Eq)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +#[derive_const(PartialEq, Eq)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} + +impl <> $crate::cmp::PartialEq for Command< > where { + fn eq(&self , other: &Self ) -> bool { + match (self , other) { + (Command::Move { + x: x_self, y: y_self, + } + , Command::Move { + x: x_other, y: y_other, + } + )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false + } + } +} +impl <> $crate::cmp::Eq for Command< > where {}"#]], + ); +} + +#[test] +fn test_partial_ord_expand() { + check( + r#" +//- minicore: derive, ord +#[derive(PartialOrd, Ord)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +#[derive(PartialOrd, Ord)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} + +impl <> $crate::cmp::PartialOrd for Command< > where { + fn partial_cmp(&self , other: &Self ) -> $crate::option::Option<$crate::cmp::Ordering> { + match $crate::intrinsics::discriminant_value(self ).partial_cmp(&$crate::intrinsics::discriminant_value(other)) { + $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { + match (self , other) { + (Command::Move { + x: x_self, y: y_self, + } + , Command::Move { + x: x_other, y: y_other, + } + )=>match x_self.partial_cmp(&x_other) { + $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { + match y_self.partial_cmp(&y_other) { + $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { + $crate::option::Option::Some($crate::cmp::Ordering::Equal) + } + c=>return c, + } + } + c=>return c, + } + , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.partial_cmp(&f0_other) { + $crate::option::Option::Some($crate::cmp::Ordering::Equal)=> { + $crate::option::Option::Some($crate::cmp::Ordering::Equal) + } + c=>return c, + } + , (Command::Jump, Command::Jump)=>$crate::option::Option::Some($crate::cmp::Ordering::Equal), _unused=>$crate::option::Option::Some($crate::cmp::Ordering::Equal) + } + } + c=>return c, + } + } +} +impl <> $crate::cmp::Ord for Command< > where { + fn cmp(&self , other: &Self ) -> $crate::cmp::Ordering { + match $crate::intrinsics::discriminant_value(self ).cmp(&$crate::intrinsics::discriminant_value(other)) { + $crate::cmp::Ordering::Equal=> { + match (self , other) { + (Command::Move { + x: x_self, y: y_self, + } + , Command::Move { + x: x_other, y: y_other, + } + )=>match x_self.cmp(&x_other) { + $crate::cmp::Ordering::Equal=> { + match y_self.cmp(&y_other) { + $crate::cmp::Ordering::Equal=> { + $crate::cmp::Ordering::Equal + } + c=>return c, + } + } + c=>return c, + } + , (Command::Do(f0_self, ), Command::Do(f0_other, ))=>match f0_self.cmp(&f0_other) { + $crate::cmp::Ordering::Equal=> { + $crate::cmp::Ordering::Equal + } + c=>return c, + } + , (Command::Jump, Command::Jump)=>$crate::cmp::Ordering::Equal, _unused=>$crate::cmp::Ordering::Equal + } + } + c=>return c, + } + } +}"#]], + ); +} + +#[test] +fn test_hash_expand() { + check( + r#" +//- minicore: derive, hash +use core::hash::Hash; + +#[derive(Hash)] +struct Foo { + x: i32, + y: u64, + z: (i32, u64), +} +"#, + expect![[r#" +use core::hash::Hash; + +#[derive(Hash)] +struct Foo { + x: i32, + y: u64, + z: (i32, u64), +} + +impl <> $crate::hash::Hash for Foo< > where { + fn hash(&self , ra_expand_state: &mut H) { + match self { + Foo { + x: x, y: y, z: z, + } + => { + x.hash(ra_expand_state); + y.hash(ra_expand_state); + z.hash(ra_expand_state); + } + , + } + } +}"#]], + ); + check( + r#" +//- minicore: derive, hash +use core::hash::Hash; + +#[derive(Hash)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +use core::hash::Hash; + +#[derive(Hash)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} + +impl <> $crate::hash::Hash for Command< > where { + fn hash(&self , ra_expand_state: &mut H) { + $crate::mem::discriminant(self ).hash(ra_expand_state); + match self { + Command::Move { + x: x, y: y, + } + => { + x.hash(ra_expand_state); + y.hash(ra_expand_state); + } + , Command::Do(f0, )=> { + f0.hash(ra_expand_state); + } + , Command::Jump=> {} + , + } + } +}"#]], + ); +} + +#[test] +fn test_debug_expand() { + check( + r#" +//- minicore: derive, fmt +use core::fmt::Debug; + +#[derive(Debug)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} +"#, + expect![[r#" +use core::fmt::Debug; + +#[derive(Debug)] +enum Command { + Move { x: i32, y: i32 }, + Do(&'static str), + Jump, +} + +impl <> $crate::fmt::Debug for Command< > where { + fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { + match self { + Command::Move { + x: x, y: y, + } + =>f.debug_struct("Move").field("x", &x).field("y", &y).finish(), Command::Do(f0, )=>f.debug_tuple("Do").field(&f0).finish(), Command::Jump=>f.write_str("Jump"), + } + } +}"#]], + ); +} +#[test] +fn test_debug_expand_with_cfg() { + check( + r#" + //- minicore: derive, fmt + use core::fmt::Debug; + + #[derive(Debug)] + struct HideAndShow { + #[cfg(never)] + always_hide: u32, + #[cfg(not(never))] + always_show: u32, + } + #[derive(Debug)] + enum HideAndShowEnum { + #[cfg(never)] + AlwaysHide, + #[cfg(not(never))] + AlwaysShow{ + #[cfg(never)] + always_hide: u32, + #[cfg(not(never))] + always_show: u32, + } + } + "#, + expect![[r#" +use core::fmt::Debug; + +#[derive(Debug)] +struct HideAndShow { + #[cfg(never)] + always_hide: u32, + #[cfg(not(never))] + always_show: u32, +} +#[derive(Debug)] +enum HideAndShowEnum { + #[cfg(never)] + AlwaysHide, + #[cfg(not(never))] + AlwaysShow{ + #[cfg(never)] + always_hide: u32, + #[cfg(not(never))] + always_show: u32, + } +} + +impl <> $crate::fmt::Debug for HideAndShow< > where { + fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { + match self { + HideAndShow { + always_show: always_show, + } + =>f.debug_struct("HideAndShow").field("always_show", &always_show).finish() + } + } +} +impl <> $crate::fmt::Debug for HideAndShowEnum< > where { + fn fmt(&self , f: &mut $crate::fmt::Formatter) -> $crate::fmt::Result { + match self { + HideAndShowEnum::AlwaysShow { + always_show: always_show, + } + =>f.debug_struct("AlwaysShow").field("always_show", &always_show).finish(), + } + } +}"#]], + ); +} +#[test] +fn test_default_expand_with_cfg() { + check( + r#" +//- minicore: derive, default +#[derive(Default)] +struct Foo { + field1: i32, + #[cfg(never)] + field2: (), + #[cfg(feature = "never")] + field3: (), + #[cfg(not(feature = "never"))] + field4: (), +} +#[derive(Default)] +enum Bar { + Foo, + #[cfg_attr(not(never), default)] + Bar, +} +"#, + expect![[r##" +#[derive(Default)] +struct Foo { + field1: i32, + #[cfg(never)] + field2: (), + #[cfg(feature = "never")] + field3: (), + #[cfg(not(feature = "never"))] + field4: (), +} +#[derive(Default)] +enum Bar { + Foo, + #[cfg_attr(not(never), default)] + Bar, +} + +impl <> $crate::default::Default for Foo< > where { + fn default() -> Self { + Foo { + field1: $crate::default::Default::default(), field4: $crate::default::Default::default(), + } + } +} +impl <> $crate::default::Default for Bar< > where { + fn default() -> Self { + Bar::Bar + } +}"##]], + ); +} + +#[test] +fn coerce_pointee_expansion() { + check( + r#" +//- minicore: coerce_pointee + +use core::marker::CoercePointee; + +pub trait Trait {} + +#[derive(CoercePointee)] +#[repr(transparent)] +pub struct Foo<'a, T: ?Sized + Trait, #[pointee] U: ?Sized, const N: u32>(T) +where + U: Trait + ToString;"#, + expect![[r#" + +use core::marker::CoercePointee; + +pub trait Trait {} + +#[derive(CoercePointee)] +#[repr(transparent)] +pub struct Foo<'a, T: ?Sized + Trait, #[pointee] U: ?Sized, const N: u32>(T) +where + U: Trait + ToString; +impl $crate::ops::DispatchFromDyn> for Foo where U: Trait +ToString, T: Trait<__S>, __S: ?Sized, __S: Trait<__S> +ToString, U: ::core::marker::Unsize<__S>, T:?Sized+Trait, U:?Sized, {} +impl $crate::ops::CoerceUnsized> for Foo where U: Trait +ToString, T: Trait<__S>, __S: ?Sized, __S: Trait<__S> +ToString, U: ::core::marker::Unsize<__S>, T:?Sized+Trait, U:?Sized, {}"#]], + ); +} + +#[test] +fn coerce_pointee_errors() { + check_errors( + r#" +//- minicore: coerce_pointee + +use core::marker::CoercePointee; + +#[derive(CoercePointee)] +enum Enum {} + +#[derive(CoercePointee)] +struct Struct1; + +#[derive(CoercePointee)] +struct Struct2(); + +#[derive(CoercePointee)] +struct Struct3 {} + +#[derive(CoercePointee)] +struct Struct4(T); + +#[derive(CoercePointee)] +#[repr(transparent)] +struct Struct5(i32); + +#[derive(CoercePointee)] +#[repr(transparent)] +struct Struct6<#[pointee] T: ?Sized, #[pointee] U: ?Sized>(T, U); + +#[derive(CoercePointee)] +#[repr(transparent)] +struct Struct7(T, U); + +#[derive(CoercePointee)] +#[repr(transparent)] +struct Struct8<#[pointee] T, U: ?Sized>(T); + +#[derive(CoercePointee)] +#[repr(transparent)] +struct Struct9(T); + +#[derive(CoercePointee)] +#[repr(transparent)] +struct Struct9<#[pointee] T, U>(T) where T: ?Sized; +"#, + expect![[r#" + 35..72: `CoercePointee` can only be derived on `struct`s + 74..114: `CoercePointee` can only be derived on `struct`s with at least one field + 116..158: `CoercePointee` can only be derived on `struct`s with at least one field + 160..202: `CoercePointee` can only be derived on `struct`s with at least one field + 204..258: `CoercePointee` can only be derived on `struct`s with `#[repr(transparent)]` + 260..326: `CoercePointee` can only be derived on `struct`s that are generic over at least one type + 328..439: only one type parameter can be marked as `#[pointee]` when deriving `CoercePointee` traits + 441..530: exactly one generic type parameter must be marked as `#[pointee]` to derive `CoercePointee` traits + 532..621: `derive(CoercePointee)` requires `T` to be marked `?Sized` + 623..690: `derive(CoercePointee)` requires `T` to be marked `?Sized`"#]], + ); +} + +#[test] +fn union_derive() { + check_errors( + r#" +//- minicore: clone, copy, default, fmt, hash, ord, eq, derive + +#[derive(Copy)] +union Foo1 { _v: () } +#[derive(Clone)] +union Foo2 { _v: () } +#[derive(Default)] +union Foo3 { _v: () } +#[derive(Debug)] +union Foo4 { _v: () } +#[derive(Hash)] +union Foo5 { _v: () } +#[derive(Ord)] +union Foo6 { _v: () } +#[derive(PartialOrd)] +union Foo7 { _v: () } +#[derive(Eq)] +union Foo8 { _v: () } +#[derive(PartialEq)] +union Foo9 { _v: () } + "#, + expect![[r#" + 78..118: this trait cannot be derived for unions + 119..157: this trait cannot be derived for unions + 158..195: this trait cannot be derived for unions + 196..232: this trait cannot be derived for unions + 233..276: this trait cannot be derived for unions + 313..355: this trait cannot be derived for unions"#]], + ); +} + +#[test] +fn default_enum_without_default_attr() { + check_errors( + r#" +//- minicore: default, derive + +#[derive(Default)] +enum Foo { + Bar, +} + "#, + expect!["1..41: `#[derive(Default)]` on enum with no `#[default]`"], + ); +} + +#[test] +fn generic_enum_default() { + check( + r#" +//- minicore: default, derive + +#[derive(Default)] +enum Foo { + Bar(T), + #[default] + Baz, +} +"#, + expect![[r#" + +#[derive(Default)] +enum Foo { + Bar(T), + #[default] + Baz, +} + +impl $crate::default::Default for Foo where { + fn default() -> Self { + Foo::Baz + } +}"#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs index 59bd9474a9595..fa4409e117607 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -53,6 +53,8 @@ use crate::{ #[track_caller] fn check_errors(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { + crate::nameres::ENABLE_BUILTIN_DERIVE_FAST_PATH.set(false); + let db = TestDB::with_files(ra_fixture); let krate = db.fetch_test_crate(); let def_map = crate_def_map(&db, krate); @@ -80,10 +82,15 @@ fn check_errors(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) .sorted_unstable_by_key(|(range, _)| range.start()) .format_with("\n", |(range, err), format| format(&format_args!("{range:?}: {err}"))) .to_string(); + + crate::nameres::ENABLE_BUILTIN_DERIVE_FAST_PATH.set(true); + expect.assert_eq(&errors); } fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, mut expect: Expect) { + crate::nameres::ENABLE_BUILTIN_DERIVE_FAST_PATH.set(false); + let extra_proc_macros = vec![( r#" #[proc_macro_attribute] @@ -246,6 +253,8 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream } } + crate::nameres::ENABLE_BUILTIN_DERIVE_FAST_PATH.set(true); + expect.indent(false); expect.assert_eq(&expanded_text); } 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 3f29619bcb629..59ca38c7c087b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -87,6 +87,25 @@ use crate::{ pub use self::path_resolution::ResolvePathResultPrefixInfo; +#[cfg(test)] +thread_local! { + /// HACK: In order to test builtin derive expansion, we gate their fast path with this atomic when cfg(test). + pub(crate) static ENABLE_BUILTIN_DERIVE_FAST_PATH: std::cell::Cell = + const { std::cell::Cell::new(true) }; +} + +#[inline] +#[cfg(test)] +fn enable_builtin_derive_fast_path() -> bool { + ENABLE_BUILTIN_DERIVE_FAST_PATH.get() +} + +#[inline(always)] +#[cfg(not(test))] +fn enable_builtin_derive_fast_path() -> bool { + true +} + const PREDEFINED_TOOLS: &[SmolStr] = &[ SmolStr::new_static("clippy"), SmolStr::new_static("rustfmt"), 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 8694ebe4e40ac..a7f687a3168bc 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 @@ -1526,8 +1526,9 @@ impl<'db> DefCollector<'db> { } } - if let MacroDefKind::BuiltInDerive(_, builtin_derive) = - def_id.kind + if super::enable_builtin_derive_fast_path() + && let MacroDefKind::BuiltInDerive(_, builtin_derive) = + def_id.kind { self.deferred_builtin_derives .entry(ast_id.ast_id.upcast()) From abf22599e3fbe5898ea646b94f64ef888937641d Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 26 Dec 2025 23:03:42 +0530 Subject: [PATCH 31/77] add bidirectionalHandler trait --- src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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 705ac930ed9c0..c35bb2ba8ddeb 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 @@ -93,12 +93,8 @@ impl<'env> ProcMacroSrv<'env> { pub type SubCallback = Box SubResponse + Send + Sync + 'static>; -pub enum SubRequest { - SourceText { file_id: EditionedFileId, start: u32, end: u32 }, -} - -pub enum SubResponse { - SourceTextResult { text: Option }, +pub trait BidirectionalHandler { + fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option; } const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; From 8f141eaab3a9f95988e9d90a6bb68ae30e7d1004 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 26 Dec 2025 23:04:04 +0530 Subject: [PATCH 32/77] remove old subreq/resp constructs --- src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c35bb2ba8ddeb..5f0273d7bc5bf 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 @@ -47,7 +47,7 @@ use std::{ }; use paths::{Utf8Path, Utf8PathBuf}; -use span::{EditionedFileId, Span}; +use span::Span; use temp_dir::TempDir; pub use crate::server_impl::token_id::SpanId; @@ -91,7 +91,7 @@ impl<'env> ProcMacroSrv<'env> { } } -pub type SubCallback = Box SubResponse + Send + Sync + 'static>; +pub type SubCallback = Box; pub trait BidirectionalHandler { fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option; From 8aa859476c5d2f878ac45049c864eee6d16ddbef Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 26 Dec 2025 23:04:19 +0530 Subject: [PATCH 33/77] adapt source_text to new handler --- .../src/server_impl/rust_analyzer_span.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index 0bce67fcd9fec..ee834e37c15cd 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -14,7 +14,7 @@ use proc_macro::bridge::server; use span::{FIXUP_ERASED_FILE_AST_ID_MARKER, Span, TextRange, TextSize}; use crate::{ - SubCallback, SubRequest, SubResponse, + SubCallback, bridge::{Diagnostic, ExpnGlobals, Literal, TokenTree}, server_impl::literal_from_str, }; @@ -156,14 +156,7 @@ impl server::Span for RaSpanServer { let start: u32 = span.range.start().into(); let end: u32 = span.range.end().into(); - let req = SubRequest::SourceText { file_id, start, end }; - - let cb = self.callback.as_mut()?; - let response = cb(req); - - match response { - SubResponse::SourceTextResult { text } => text, - } + self.callback.as_mut()?.source_text(file_id.file_id().index(), start, end) } fn parent(&mut self, _span: Self::Span) -> Option { From 22fbbd430681b7bc4e3098766f040b37080ffb47 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 25 Sep 2025 13:05:45 +0800 Subject: [PATCH 34/77] Add macro segment completion Example --- ```rust macro_rules! foo { ($($x:$0)*) => (); } ``` **Completion items**: ```text ba block ba expr ba expr_2021 ba ident ba item ba lifetime ba literal ba meta ba pat ba pat_param ba path ba stmt ba tt ba ty ba vis ``` --- .../crates/ide-completion/src/completions.rs | 1 + .../src/completions/macro_def.rs | 31 +++ .../crates/ide-completion/src/context.rs | 5 +- .../ide-completion/src/context/analysis.rs | 17 +- .../crates/ide-completion/src/lib.rs | 3 + .../ide-completion/src/tests/special.rs | 229 ++++++++++++++++++ .../ide-db/src/syntax_helpers/node_ext.rs | 34 ++- 7 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/macro_def.rs diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs index b28764f6fc5a7..355687b2032be 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs @@ -13,6 +13,7 @@ pub(crate) mod format_string; pub(crate) mod item_list; pub(crate) mod keyword; pub(crate) mod lifetime; +pub(crate) mod macro_def; pub(crate) mod mod_; pub(crate) mod pattern; pub(crate) mod postfix; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/macro_def.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/macro_def.rs new file mode 100644 index 0000000000000..2c8e7a2e62cc8 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/macro_def.rs @@ -0,0 +1,31 @@ +//! Completion for macro meta-variable segments + +use ide_db::SymbolKind; + +use crate::{CompletionItem, Completions, context::CompletionContext}; + +pub(crate) fn complete_macro_segment(acc: &mut Completions, ctx: &CompletionContext<'_>) { + for &label in MACRO_SEGMENTS { + let item = + CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), label, ctx.edition); + item.add_to(acc, ctx.db); + } +} + +const MACRO_SEGMENTS: &[&str] = &[ + "ident", + "block", + "stmt", + "expr", + "pat", + "ty", + "lifetime", + "literal", + "path", + "meta", + "tt", + "item", + "vis", + "expr_2021", + "pat_param", +]; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs index 963e39670473d..d116f665adbdd 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -13,7 +13,7 @@ use hir::{ }; use ide_db::{ FilePosition, FxHashMap, FxHashSet, RootDatabase, famous_defs::FamousDefs, - helpers::is_editable_crate, + helpers::is_editable_crate, syntax_helpers::node_ext::is_in_macro_matcher, }; use itertools::Either; use syntax::{ @@ -389,6 +389,7 @@ pub(crate) enum CompletionAnalysis<'db> { fake_attribute_under_caret: Option, extern_crate: Option, }, + MacroSegment, } /// Information about the field or method access we are completing. @@ -729,7 +730,7 @@ impl<'db> CompletionContext<'db> { let prev_token = original_token.prev_token()?; // only has a single colon - if prev_token.kind() != T![:] { + if prev_token.kind() != T![:] && !is_in_macro_matcher(&original_token) { return None; } 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 49fb36ad04f89..65bae5b66e179 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 @@ -5,7 +5,7 @@ use hir::{ExpandResult, InFile, Semantics, Type, TypeInfo, Variant}; use ide_db::{ RootDatabase, active_parameter::ActiveParameter, syntax_helpers::node_ext::find_loops, }; -use itertools::Either; +use itertools::{Either, Itertools}; use stdx::always; use syntax::{ AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, @@ -510,6 +510,21 @@ fn analyze<'db>( colon_prefix, extern_crate: p.ancestors().find_map(ast::ExternCrate::cast), } + } else if p.kind() == SyntaxKind::TOKEN_TREE + && p.ancestors().any(|it| ast::Macro::can_cast(it.kind())) + { + if let Some([_ident, colon, _name, dollar]) = fake_ident_token + .siblings_with_tokens(Direction::Prev) + .filter(|it| !it.kind().is_trivia()) + .take(4) + .collect_array() + && dollar.kind() == T![$] + && colon.kind() == T![:] + { + CompletionAnalysis::MacroSegment + } else { + return None; + } } else { return None; } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs index 33ab43fa614a4..69ca2af7721b8 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs @@ -263,6 +263,9 @@ pub fn completions( extern_crate.as_ref(), ); } + CompletionAnalysis::MacroSegment => { + completions::macro_def::complete_macro_segment(acc, ctx); + } CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (), } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs index 59a0c144c8937..b82b23541c5c8 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs @@ -481,6 +481,226 @@ fn foo() {} ); } +#[test] +fn completes_macro_segment() { + check( + r#" +macro_rules! foo { + ($x:e$0) => (); +} +"#, + expect![[r#" + ba block + ba expr + ba expr_2021 + ba ident + ba item + ba lifetime + ba literal + ba meta + ba pat + ba pat_param + ba path + ba stmt + ba tt + ba ty + ba vis + "#]], + ); + + check( + r#" +macro_rules! foo { + ($x:$0) => (); +} +"#, + expect![[r#" + ba block + ba expr + ba expr_2021 + ba ident + ba item + ba lifetime + ba literal + ba meta + ba pat + ba pat_param + ba path + ba stmt + ba tt + ba ty + ba vis + "#]], + ); + + check( + r#" +macro_rules! foo { + ($($x:$0)*) => (); +} +"#, + expect![[r#" + ba block + ba expr + ba expr_2021 + ba ident + ba item + ba lifetime + ba literal + ba meta + ba pat + ba pat_param + ba path + ba stmt + ba tt + ba ty + ba vis + "#]], + ); + + check( + r#" +macro foo { + ($($x:$0)*) => (); +} +"#, + expect![[r#" + ba block + ba expr + ba expr_2021 + ba ident + ba item + ba lifetime + ba literal + ba meta + ba pat + ba pat_param + ba path + ba stmt + ba tt + ba ty + ba vis + "#]], + ); + + check( + r#" +macro foo($($x:$0)*) { + xxx; +} +"#, + expect![[r#" + ba block + ba expr + ba expr_2021 + ba ident + ba item + ba lifetime + ba literal + ba meta + ba pat + ba pat_param + ba path + ba stmt + ba tt + ba ty + ba vis + "#]], + ); + + check_edit( + "expr", + r#" +macro foo($($x:$0)*) { + xxx; +} +"#, + r#" +macro foo($($x:expr)*) { + xxx; +} +"#, + ); + + check( + r#" +macro_rules! foo { + ($fn : e$0) => (); +} +"#, + expect![[r#" + ba block + ba expr + ba expr_2021 + ba ident + ba item + ba lifetime + ba literal + ba meta + ba pat + ba pat_param + ba path + ba stmt + ba tt + ba ty + ba vis + "#]], + ); + + check_edit( + "expr", + r#" +macro foo($($x:ex$0)*) { + xxx; +} +"#, + r#" +macro foo($($x:expr)*) { + xxx; +} +"#, + ); +} + +#[test] +fn completes_in_macro_body() { + check( + r#" +macro_rules! foo { + ($x:expr) => ($y:$0); +} +"#, + expect![[r#""#]], + ); + + check( + r#" +macro_rules! foo { + ($x:expr) => ({$y:$0}); +} +"#, + expect![[r#""#]], + ); + + check( + r#" +macro foo { + ($x:expr) => ($y:$0); +} +"#, + expect![[r#""#]], + ); + + check( + r#" +macro foo($x:expr) { + $y:$0 +} +"#, + expect![[r#""#]], + ); +} + #[test] fn function_mod_share_name() { check_no_kw( @@ -942,6 +1162,15 @@ fn foo { crate::$0 } check_with_trigger_character( r#" fn foo { crate:$0 } +"#, + Some(':'), + expect![""], + ); + + check_with_trigger_character( + r#" +macro_rules! bar { ($($x:tt)*) => ($($x)*); } +fn foo { bar!(crate:$0) } "#, Some(':'), expect![""], diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs index acce066b8323f..de8b4b367a4de 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs @@ -5,8 +5,10 @@ use itertools::Itertools; use parser::T; use span::Edition; use syntax::{ - AstNode, AstToken, Preorder, RustLanguage, WalkEvent, + AstNode, AstToken, Direction, Preorder, RustLanguage, SyntaxToken, WalkEvent, + algo::non_trivia_sibling, ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind}, + syntax_editor::Element, }; pub fn expr_as_name_ref(expr: &ast::Expr) -> Option { @@ -542,3 +544,33 @@ pub fn macro_call_for_string_token(string: &ast::String) -> Option { let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?; Some(macro_call) } + +pub fn is_in_macro_matcher(token: &SyntaxToken) -> bool { + let Some(macro_def) = token.parent_ancestors().find_map(ast::Macro::cast) else { + return false; + }; + let range = token.text_range(); + let Some(body) = (match macro_def { + ast::Macro::MacroDef(macro_def) => { + if let Some(args) = macro_def.args() { + return args.syntax().text_range().contains_range(range); + } + macro_def.body() + } + ast::Macro::MacroRules(macro_rules) => macro_rules.token_tree(), + }) else { + return false; + }; + if !body.syntax().text_range().contains_range(range) { + return false; + } + body.token_trees_and_tokens().filter_map(|tt| tt.into_node()).any(|tt| { + let Some(next) = non_trivia_sibling(tt.syntax().syntax_element(), Direction::Next) else { + return false; + }; + let Some(next_next) = next.next_sibling_or_token() else { return false }; + next.kind() == T![=] + && next_next.kind() == T![>] + && tt.syntax().text_range().contains_range(range) + }) +} From 231690657ef8e6b8b2a6ebb9feebf7e63fb39446 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 09:07:08 +0530 Subject: [PATCH 35/77] add bidirectional handle in proc-macro-srv-cli to interact with client and srv --- .../proc-macro-srv-cli/src/main_loop.rs | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) 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 99e3d79ef29a9..bd2fd2df79e67 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -1,6 +1,4 @@ //! The main loop of the proc-macro server. -use std::io; - use proc_macro_api::{ Codec, bidirectional_protocol::msg as bidirectional, @@ -8,6 +6,7 @@ use proc_macro_api::{ transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, version::CURRENT_API_VERSION, }; +use std::{io, sync::mpsc}; use legacy::Message; @@ -170,6 +169,21 @@ fn handle_expand_id( send_response::<_, C>(stdout, bidirectional::Response::ExpandMacro(res)) } +struct BidirectionalProxy { + subreq_tx: mpsc::Sender, + subresp_rx: mpsc::Receiver, +} + +impl proc_macro_srv::BidirectionalHandler for BidirectionalProxy { + fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option { + self.subreq_tx.send(bidirectional::SubRequest::SourceText { file_id, start, end }).ok()?; + + match self.subresp_rx.recv().ok()? { + bidirectional::SubResponse::SourceTextResult { text } => text, + } + } +} + fn handle_expand_ra( srv: &proc_macro_srv::ProcMacroSrv<'_>, stdin: &mut R, @@ -201,22 +215,20 @@ fn handle_expand_ra( 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, |a, b| { srv.join_spans(a, b).unwrap_or(b) }) }); - let (subreq_tx, subreq_rx) = crossbeam_channel::unbounded(); - let (subresp_tx, subresp_rx) = crossbeam_channel::unbounded(); - let (result_tx, result_rx) = crossbeam_channel::bounded(1); + let (subreq_tx, subreq_rx) = mpsc::channel(); + let (subresp_tx, subresp_rx) = mpsc::channel(); + let (result_tx, result_rx) = mpsc::channel(); - std::thread::scope(|scope| { - scope.spawn(|| { - let callback = Box::new(move |req: proc_macro_srv::SubRequest| { - subreq_tx.send(req).unwrap(); - subresp_rx.recv().unwrap() - }); + std::thread::scope(|s| { + s.spawn(|| { + let callback = BidirectionalProxy { subreq_tx, subresp_rx }; let res = srv .expand( @@ -229,7 +241,7 @@ fn handle_expand_ra( def_site, call_site, mixed_site, - Some(callback), + Some(Box::new(callback)), ) .map(|it| { ( @@ -253,27 +265,31 @@ fn handle_expand_ra( loop { if let Ok(res) = result_rx.try_recv() { - send_response::<_, C>(stdout, bidirectional::Response::ExpandMacroExtended(res)) - .unwrap(); + let _ = send_response::<_, C>( + stdout, + bidirectional::Response::ExpandMacroExtended(res), + ); break; } - let subreq = match subreq_rx.recv() { + let sub_req = match subreq_rx.recv() { Ok(r) => r, Err(_) => break, }; - let api_req = from_srv_req(subreq); - bidirectional::BidirectionalMessage::SubRequest(api_req).write::<_, C>(stdout).unwrap(); - - let resp = bidirectional::BidirectionalMessage::read::<_, C>(stdin, buf) - .unwrap() - .expect("client closed connection"); - + if bidirectional::BidirectionalMessage::SubRequest(sub_req) + .write::<_, C>(stdout) + .is_err() + { + break; + } + let resp = match bidirectional::BidirectionalMessage::read::<_, C>(stdin, buf) { + Ok(Some(r)) => r, + _ => break, + }; match resp { - bidirectional::BidirectionalMessage::SubResponse(api_resp) => { - let srv_resp = from_client_res(api_resp); - subresp_tx.send(srv_resp).unwrap(); + bidirectional::BidirectionalMessage::SubResponse(resp) => { + let _ = subresp_tx.send(resp); } other => panic!("expected SubResponse, got {other:?}"), } @@ -425,22 +441,6 @@ fn run_() -> io::Result<()> { Ok(()) } -fn from_srv_req(value: proc_macro_srv::SubRequest) -> bidirectional::SubRequest { - match value { - proc_macro_srv::SubRequest::SourceText { file_id, start, end } => { - bidirectional::SubRequest::SourceText { file_id: file_id.file_id().index(), start, end } - } - } -} - -fn from_client_res(value: bidirectional::SubResponse) -> proc_macro_srv::SubResponse { - match value { - bidirectional::SubResponse::SourceTextResult { text } => { - proc_macro_srv::SubResponse::SourceTextResult { text } - } - } -} - fn send_response( stdout: &mut W, resp: bidirectional::Response, From 8ae38dc33380dd0787b7d65bc6a3674039d27022 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 09:21:32 +0530 Subject: [PATCH 36/77] rename handler's to be context specific --- .../crates/proc-macro-srv-cli/src/main_loop.rs | 6 +++--- .../rust-analyzer/crates/proc-macro-srv/src/dylib.rs | 6 +++--- .../crates/proc-macro-srv/src/dylib/proc_macros.rs | 4 ++-- .../rust-analyzer/crates/proc-macro-srv/src/lib.rs | 12 ++++++------ .../src/server_impl/rust_analyzer_span.rs | 4 ++-- .../proc-macro-srv/src/server_impl/token_id.rs | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) 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 bd2fd2df79e67..d0f3a2b15d626 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 @@ -169,12 +169,12 @@ fn handle_expand_id( send_response::<_, C>(stdout, bidirectional::Response::ExpandMacro(res)) } -struct BidirectionalProxy { +struct ProcMacroClientHandle { subreq_tx: mpsc::Sender, subresp_rx: mpsc::Receiver, } -impl proc_macro_srv::BidirectionalHandler for BidirectionalProxy { +impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle { fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option { self.subreq_tx.send(bidirectional::SubRequest::SourceText { file_id, start, end }).ok()?; @@ -228,7 +228,7 @@ fn handle_expand_ra( std::thread::scope(|s| { s.spawn(|| { - let callback = BidirectionalProxy { subreq_tx, subresp_rx }; + let callback = ProcMacroClientHandle { subreq_tx, subresp_rx }; let res = srv .expand( diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs index 082a1d77b5aa2..d34f37b16a274 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs @@ -12,8 +12,8 @@ use object::Object; use paths::{Utf8Path, Utf8PathBuf}; use crate::{ - PanicMessage, ProcMacroKind, ProcMacroSrvSpan, SubCallback, dylib::proc_macros::ProcMacros, - token_stream::TokenStream, + PanicMessage, ProcMacroClientHandle, ProcMacroKind, ProcMacroSrvSpan, + dylib::proc_macros::ProcMacros, token_stream::TokenStream, }; pub(crate) struct Expander { @@ -45,7 +45,7 @@ impl Expander { def_site: S, call_site: S, mixed_site: S, - callback: Option, + callback: Option, ) -> Result, PanicMessage> where ::TokenStream: Default, diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs index 6f6bd086de64a..ddbb0128f5e50 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs @@ -1,5 +1,5 @@ //! Proc macro ABI -use crate::{ProcMacroKind, ProcMacroSrvSpan, SubCallback, token_stream::TokenStream}; +use crate::{ProcMacroClientHandle, ProcMacroKind, ProcMacroSrvSpan, token_stream::TokenStream}; use proc_macro::bridge; #[repr(transparent)] @@ -20,7 +20,7 @@ impl ProcMacros { def_site: S, call_site: S, mixed_site: S, - callback: Option, + callback: Option, ) -> Result, crate::PanicMessage> { let parsed_attributes = attribute.unwrap_or_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 5f0273d7bc5bf..9fb81afdeeb45 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 @@ -91,9 +91,9 @@ impl<'env> ProcMacroSrv<'env> { } } -pub type SubCallback = Box; +pub type ProcMacroClientHandle = Box; -pub trait BidirectionalHandler { +pub trait ProcMacroClientInterface { fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option; } @@ -111,7 +111,7 @@ impl ProcMacroSrv<'_> { def_site: S, call_site: S, mixed_site: S, - callback: Option, + callback: Option, ) -> Result, PanicMessage> { let snapped_env = self.env; let expander = self.expander(lib.as_ref()).map_err(|err| PanicMessage { @@ -183,7 +183,7 @@ pub trait ProcMacroSrvSpan: Copy + Send + Sync { call_site: Self, def_site: Self, mixed_site: Self, - callback: Option, + callback: Option, ) -> Self::Server; } @@ -194,7 +194,7 @@ impl ProcMacroSrvSpan for SpanId { call_site: Self, def_site: Self, mixed_site: Self, - callback: Option, + callback: Option, ) -> Self::Server { Self::Server { call_site, @@ -213,7 +213,7 @@ impl ProcMacroSrvSpan for Span { call_site: Self, def_site: Self, mixed_site: Self, - callback: Option, + callback: Option, ) -> Self::Server { Self::Server { call_site, diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index ee834e37c15cd..1b496950df1a7 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -14,7 +14,7 @@ use proc_macro::bridge::server; use span::{FIXUP_ERASED_FILE_AST_ID_MARKER, Span, TextRange, TextSize}; use crate::{ - SubCallback, + ProcMacroClientHandle, bridge::{Diagnostic, ExpnGlobals, Literal, TokenTree}, server_impl::literal_from_str, }; @@ -29,7 +29,7 @@ pub struct RaSpanServer { pub call_site: Span, pub def_site: Span, pub mixed_site: Span, - pub callback: Option, + pub callback: Option, } impl server::Types for RaSpanServer { diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs index 9db7597d849f4..646dde7952bf3 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs @@ -9,7 +9,7 @@ use intern::Symbol; use proc_macro::bridge::server; use crate::{ - SubCallback, + ProcMacroClientHandle, bridge::{Diagnostic, ExpnGlobals, Literal, TokenTree}, server_impl::literal_from_str, }; @@ -35,7 +35,7 @@ pub struct SpanIdServer { pub call_site: Span, pub def_site: Span, pub mixed_site: Span, - pub callback: Option, + pub callback: Option, } impl server::Types for SpanIdServer { From 38f1cccef21c1fbd2673b282cb977685d697bc4a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 09:43:01 +0530 Subject: [PATCH 37/77] make source_text take non mutable reference of self --- .../rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs | 2 +- src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs | 2 +- .../crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 d0f3a2b15d626..6ed42204df182 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 @@ -175,7 +175,7 @@ struct ProcMacroClientHandle { } impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle { - fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option { + fn source_text(&self, file_id: u32, start: u32, end: u32) -> Option { self.subreq_tx.send(bidirectional::SubRequest::SourceText { file_id, start, end }).ok()?; match self.subresp_rx.recv().ok()? { 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 9fb81afdeeb45..ff5623f39ee70 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 @@ -94,7 +94,7 @@ impl<'env> ProcMacroSrv<'env> { pub type ProcMacroClientHandle = Box; pub trait ProcMacroClientInterface { - fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option; + fn source_text(&self, file_id: u32, start: u32, end: u32) -> Option; } const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index 1b496950df1a7..5f7c0a52025ad 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -156,7 +156,7 @@ impl server::Span for RaSpanServer { let start: u32 = span.range.start().into(); let end: u32 = span.range.end().into(); - self.callback.as_mut()?.source_text(file_id.file_id().index(), start, end) + self.callback.as_ref()?.source_text(file_id.file_id().index(), start, end) } fn parent(&mut self, _span: Self::Span) -> Option { From 720d9eecc7f67dc0ab8ba10f90f7cbcc3864d8ac Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 10:40:42 +0530 Subject: [PATCH 38/77] remove crossbeam-channel from proc-macro-srv-cli --- src/tools/rust-analyzer/Cargo.lock | 1 - src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index fb76f51cd31e9..a2eb8b1397b85 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1877,7 +1877,6 @@ name = "proc-macro-srv-cli" version = "0.0.0" dependencies = [ "clap", - "crossbeam-channel", "postcard", "proc-macro-api", "proc-macro-srv", 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 df3d21aefcb0e..2c6e5a16ee06f 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,7 +14,6 @@ publish = false proc-macro-srv.workspace = true proc-macro-api.workspace = true postcard.workspace = true -crossbeam-channel.workspace = true clap = {version = "4.5.42", default-features = false, features = ["std"]} [features] From 06a6a008957afac4dcac9fd31ea523a5e3ea7fc1 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 27 Dec 2025 17:04:12 +0800 Subject: [PATCH 39/77] Minor fixup --- .../crates/ide-db/src/syntax_helpers/node_ext.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs index de8b4b367a4de..94ecf6a02ddc0 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs @@ -1,6 +1,7 @@ //! Various helper functions to work with SyntaxNodes. use std::ops::ControlFlow; +use either::Either; use itertools::Itertools; use parser::T; use span::Edition; @@ -546,7 +547,11 @@ pub fn macro_call_for_string_token(string: &ast::String) -> Option { } pub fn is_in_macro_matcher(token: &SyntaxToken) -> bool { - let Some(macro_def) = token.parent_ancestors().find_map(ast::Macro::cast) else { + let Some(macro_def) = token + .parent_ancestors() + .map_while(Either::::cast) + .find_map(Either::right) + else { return false; }; let range = token.text_range(); From e80997d02c072b698b6e90acce2bb75a133657da Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Sat, 27 Dec 2025 11:58:14 +0200 Subject: [PATCH 40/77] Fix grammar in "Use of AI tools" section --- src/tools/rust-analyzer/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/CONTRIBUTING.md b/src/tools/rust-analyzer/CONTRIBUTING.md index 35d03780c1d5c..e6ab3d75a0cbe 100644 --- a/src/tools/rust-analyzer/CONTRIBUTING.md +++ b/src/tools/rust-analyzer/CONTRIBUTING.md @@ -38,6 +38,6 @@ considered accepted feel free to just drop a comment and ask! AI tool use is not discouraged on the rust-analyzer codebase, as long as it meets our quality standards. We kindly ask you to disclose usage of AI tools in your contributions. -If you used them without disclosing it, we may reject your contribution on that basis alone due to the assumption that you likely not reviewed your own submission (so why should we?). +If you used them without disclosing it, we may reject your contribution on that basis alone due to the assumption that you have, most likely, not reviewed your own submission (so why should we?). We may still reject AI-assisted contributions if we deem the quality of the contribution to be unsatisfactory as to reduce impact on the team's review budget. From 61203a098e96f4afecf18c237f5f7b85c1f944b6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 27 Dec 2025 11:00:58 +0100 Subject: [PATCH 41/77] internal: Move library and local root inputs to base-db --- .../crates/base-db/src/change.rs | 16 +++++++++++-- .../rust-analyzer/crates/base-db/src/lib.rs | 18 ++++++++++++++- .../src/expr_store/tests/body/block.rs | 6 ++--- .../hir-def/src/macro_expansion_tests/mbe.rs | 16 ++++++------- .../crates/hir-def/src/nameres.rs | 22 ++++++++++++++++-- .../crates/hir-def/src/test_db.rs | 6 +++++ .../crates/hir-ty/src/test_db.rs | 6 +++++ .../rust-analyzer/crates/hir/src/semantics.rs | 2 +- .../crates/ide-db/src/apply_change.rs | 23 ++----------------- .../rust-analyzer/crates/ide-db/src/lib.rs | 6 ++--- .../crates/ide-db/src/symbol_index.rs | 20 ++-------------- .../rust-analyzer/crates/ide-ssr/src/lib.rs | 2 +- .../crates/ide-ssr/src/search.rs | 3 +-- .../rust-analyzer/crates/ide-ssr/src/tests.rs | 3 +-- src/tools/rust-analyzer/crates/ide/src/ssr.rs | 4 +--- .../crates/rust-analyzer/src/cli/ssr.rs | 2 +- .../rust-analyzer/crates/stdx/src/lib.rs | 14 +++++++++++ 17 files changed, 101 insertions(+), 68 deletions(-) diff --git a/src/tools/rust-analyzer/crates/base-db/src/change.rs b/src/tools/rust-analyzer/crates/base-db/src/change.rs index da2fb27571c2f..c728f3e5ca83c 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/change.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/change.rs @@ -3,11 +3,14 @@ use std::fmt; -use salsa::Durability; +use rustc_hash::FxHashSet; +use salsa::{Durability, Setter as _}; use triomphe::Arc; use vfs::FileId; -use crate::{CrateGraphBuilder, CratesIdMap, RootQueryDb, SourceRoot, SourceRootId}; +use crate::{ + CrateGraphBuilder, CratesIdMap, LibraryRoots, LocalRoots, RootQueryDb, SourceRoot, SourceRootId, +}; /// Encapsulate a bunch of raw `.set` calls on the database. #[derive(Default)] @@ -49,8 +52,15 @@ impl FileChange { pub fn apply(self, db: &mut dyn RootQueryDb) -> Option { let _p = tracing::info_span!("FileChange::apply").entered(); if let Some(roots) = self.roots { + let mut local_roots = FxHashSet::default(); + let mut library_roots = FxHashSet::default(); for (idx, root) in roots.into_iter().enumerate() { let root_id = SourceRootId(idx as u32); + if root.is_library { + library_roots.insert(root_id); + } else { + local_roots.insert(root_id); + } let durability = source_root_durability(&root); for file_id in root.iter() { db.set_file_source_root_with_durability(file_id, root_id, durability); @@ -58,6 +68,8 @@ impl FileChange { db.set_source_root_with_durability(root_id, Arc::new(root), durability); } + LocalRoots::get(db).set_roots(db).to(local_roots); + LibraryRoots::get(db).set_roots(db).to(library_roots); } for (file_id, text) in self.files_changed { diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index aa06cdefe6385..24f6dd59a9c50 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -33,7 +33,7 @@ pub use crate::{ }; use dashmap::{DashMap, mapref::entry::Entry}; pub use query_group::{self}; -use rustc_hash::FxHasher; +use rustc_hash::{FxHashSet, FxHasher}; use salsa::{Durability, Setter}; pub use semver::{BuildMetadata, Prerelease, Version, VersionReq}; use syntax::{Parse, SyntaxError, ast}; @@ -203,6 +203,22 @@ impl Files { } } +/// The set of roots for crates.io libraries. +/// Files in libraries are assumed to never change. +#[salsa::input(singleton, debug)] +pub struct LibraryRoots { + #[returns(ref)] + pub roots: FxHashSet, +} + +/// The set of "local" (that is, from the current workspace) roots. +/// Files in local roots are assumed to change frequently. +#[salsa::input(singleton, debug)] +pub struct LocalRoots { + #[returns(ref)] + pub roots: FxHashSet, +} + #[salsa_macros::input(debug)] pub struct FileText { #[returns(ref)] diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs index 836a079e777f2..d457a4ca7a3b7 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs @@ -190,13 +190,13 @@ fn f() { "#, expect![[r#" ModuleIdLt { - [salsa id]: Id(3003), + [salsa id]: Id(3803), krate: Crate( - Id(1c00), + Id(2400), ), block: Some( BlockId( - 3c01, + 4401, ), ), }"#]], diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs index a12674f353c8d..7b5d0103e66ee 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -35,9 +35,9 @@ macro_rules! f { }; } -struct#0:MacroRules[BE8F, 0]@58..64#15360# MyTraitMap2#0:MacroCall[BE8F, 0]@31..42#ROOT2024# {#0:MacroRules[BE8F, 0]@72..73#15360# - map#0:MacroRules[BE8F, 0]@86..89#15360#:#0:MacroRules[BE8F, 0]@89..90#15360# #0:MacroRules[BE8F, 0]@89..90#15360#::#0:MacroRules[BE8F, 0]@91..93#15360#std#0:MacroRules[BE8F, 0]@93..96#15360#::#0:MacroRules[BE8F, 0]@96..98#15360#collections#0:MacroRules[BE8F, 0]@98..109#15360#::#0:MacroRules[BE8F, 0]@109..111#15360#HashSet#0:MacroRules[BE8F, 0]@111..118#15360#<#0:MacroRules[BE8F, 0]@118..119#15360#(#0:MacroRules[BE8F, 0]@119..120#15360#)#0:MacroRules[BE8F, 0]@120..121#15360#>#0:MacroRules[BE8F, 0]@121..122#15360#,#0:MacroRules[BE8F, 0]@122..123#15360# -}#0:MacroRules[BE8F, 0]@132..133#15360# +struct#0:MacroRules[BE8F, 0]@58..64#17408# MyTraitMap2#0:MacroCall[BE8F, 0]@31..42#ROOT2024# {#0:MacroRules[BE8F, 0]@72..73#17408# + map#0:MacroRules[BE8F, 0]@86..89#17408#:#0:MacroRules[BE8F, 0]@89..90#17408# #0:MacroRules[BE8F, 0]@89..90#17408#::#0:MacroRules[BE8F, 0]@91..93#17408#std#0:MacroRules[BE8F, 0]@93..96#17408#::#0:MacroRules[BE8F, 0]@96..98#17408#collections#0:MacroRules[BE8F, 0]@98..109#17408#::#0:MacroRules[BE8F, 0]@109..111#17408#HashSet#0:MacroRules[BE8F, 0]@111..118#17408#<#0:MacroRules[BE8F, 0]@118..119#17408#(#0:MacroRules[BE8F, 0]@119..120#17408#)#0:MacroRules[BE8F, 0]@120..121#17408#>#0:MacroRules[BE8F, 0]@121..122#17408#,#0:MacroRules[BE8F, 0]@122..123#17408# +}#0:MacroRules[BE8F, 0]@132..133#17408# "#]], ); } @@ -197,7 +197,7 @@ macro_rules! mk_struct { #[macro_use] mod foo; -struct#1:MacroRules[DB0C, 0]@59..65#15360# Foo#0:MacroCall[DB0C, 0]@32..35#ROOT2024#(#1:MacroRules[DB0C, 0]@70..71#15360#u32#0:MacroCall[DB0C, 0]@41..44#ROOT2024#)#1:MacroRules[DB0C, 0]@74..75#15360#;#1:MacroRules[DB0C, 0]@75..76#15360# +struct#1:MacroRules[DB0C, 0]@59..65#17408# Foo#0:MacroCall[DB0C, 0]@32..35#ROOT2024#(#1:MacroRules[DB0C, 0]@70..71#17408#u32#0:MacroCall[DB0C, 0]@41..44#ROOT2024#)#1:MacroRules[DB0C, 0]@74..75#17408#;#1:MacroRules[DB0C, 0]@75..76#17408# "#]], ); } @@ -423,10 +423,10 @@ m! { foo, bar } macro_rules! m { ($($i:ident),*) => ( impl Bar { $(fn $i() {})* } ); } -impl#\15360# Bar#\15360# {#\15360# - fn#\15360# foo#\ROOT2024#(#\15360#)#\15360# {#\15360#}#\15360# - fn#\15360# bar#\ROOT2024#(#\15360#)#\15360# {#\15360#}#\15360# -}#\15360# +impl#\17408# Bar#\17408# {#\17408# + fn#\17408# foo#\ROOT2024#(#\17408#)#\17408# {#\17408#}#\17408# + fn#\17408# bar#\ROOT2024#(#\17408#)#\17408# {#\17408#}#\17408# +}#\17408# "#]], ); } 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 59ca38c7c087b..5f05cdb1e2ba2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -502,6 +502,7 @@ impl DefMap { } impl DefMap { + /// Returns all modules in the crate that are associated with the given file. pub fn modules_for_file<'a>( &'a self, db: &'a dyn DefDatabase, @@ -509,16 +510,33 @@ impl DefMap { ) -> impl Iterator + 'a { self.modules .iter() - .filter(move |(_id, data)| { + .filter(move |(_, data)| { data.origin.file_id().map(|file_id| file_id.file_id(db)) == Some(file_id) }) - .map(|(id, _data)| id) + .map(|(id, _)| id) } pub fn modules(&self) -> impl Iterator + '_ { self.modules.iter() } + /// Returns all inline modules (mod name { ... }) in the crate that are associated with the given macro expansion. + pub fn inline_modules_for_macro_file( + &self, + file_id: MacroCallId, + ) -> impl Iterator + '_ { + self.modules + .iter() + .filter(move |(_, data)| { + matches!( + data.origin, + ModuleOrigin::Inline { definition_tree_id, .. } + if definition_tree_id.file_id().macro_file() == Some(file_id) + ) + }) + .map(|(id, _)| id) + } + pub fn derive_helpers_in_scope( &self, id: AstId, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs index cdb49b297042d..e8377fde49870 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs @@ -49,6 +49,12 @@ impl Default for TestDB { this.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH); // This needs to be here otherwise `CrateGraphBuilder` panics. this.set_all_crates(Arc::new(Box::new([]))); + _ = base_db::LibraryRoots::builder(Default::default()) + .durability(Durability::MEDIUM) + .new(&this); + _ = base_db::LocalRoots::builder(Default::default()) + .durability(Durability::MEDIUM) + .new(&this); CrateGraphBuilder::default().set_in_db(&mut this); this } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs index 7bd314cb8e397..243456c85fc48 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs @@ -46,6 +46,12 @@ impl Default for TestDB { this.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH); // This needs to be here otherwise `CrateGraphBuilder` panics. this.set_all_crates(Arc::new(Box::new([]))); + _ = base_db::LibraryRoots::builder(Default::default()) + .durability(Durability::MEDIUM) + .new(&this); + _ = base_db::LocalRoots::builder(Default::default()) + .durability(Durability::MEDIUM) + .new(&this); CrateGraphBuilder::default().set_in_db(&mut this); this } diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 485011c38d7ec..f4c42537de939 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -836,7 +836,7 @@ impl<'db> SemanticsImpl<'db> { // FIXME: Type the return type /// Returns the range (pre-expansion) in the string literal corresponding to the resolution, /// absolute file range (post-expansion) - /// of the part in the format string, the corresponding string token and the resolution if it + /// of the part in the format string (post-expansion), the corresponding string token and the resolution if it /// exists. // FIXME: Remove this in favor of `check_for_format_args_template_with_file` pub fn check_for_format_args_template( diff --git a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs index 6a85c6e54838c..9bbd12aaa9f89 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs @@ -1,14 +1,9 @@ //! Applies changes to the IDE state transactionally. -use base_db::SourceRootId; use profile::Bytes; -use rustc_hash::FxHashSet; -use salsa::{Database as _, Durability, Setter as _}; +use salsa::{Database as _, Durability}; -use crate::{ - ChangeWithProcMacros, RootDatabase, - symbol_index::{LibraryRoots, LocalRoots}, -}; +use crate::{ChangeWithProcMacros, RootDatabase}; impl RootDatabase { pub fn request_cancellation(&mut self) { @@ -20,20 +15,6 @@ impl RootDatabase { let _p = tracing::info_span!("RootDatabase::apply_change").entered(); self.request_cancellation(); tracing::trace!("apply_change {:?}", change); - if let Some(roots) = &change.source_change.roots { - let mut local_roots = FxHashSet::default(); - let mut library_roots = FxHashSet::default(); - for (idx, root) in roots.iter().enumerate() { - let root_id = SourceRootId(idx as u32); - if root.is_library { - library_roots.insert(root_id); - } else { - local_roots.insert(root_id); - } - } - LocalRoots::get(self).set_roots(self).to(local_roots); - LibraryRoots::get(self).set_roots(self).to(library_roots); - } change.apply(self); } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs index 338c423254575..413b58bf7980a 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs @@ -75,7 +75,7 @@ pub use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; pub use ::line_index; /// `base_db` is normally also needed in places where `ide_db` is used, so this re-export is for convenience. -pub use base_db::{self, FxIndexMap, FxIndexSet}; +pub use base_db::{self, FxIndexMap, FxIndexSet, LibraryRoots, LocalRoots}; pub use span::{self, FileId}; pub type FilePosition = FilePositionWrapper; @@ -200,10 +200,10 @@ impl RootDatabase { db.set_all_crates(Arc::new(Box::new([]))); CrateGraphBuilder::default().set_in_db(&mut db); db.set_proc_macros_with_durability(Default::default(), Durability::MEDIUM); - _ = crate::symbol_index::LibraryRoots::builder(Default::default()) + _ = base_db::LibraryRoots::builder(Default::default()) .durability(Durability::MEDIUM) .new(&db); - _ = crate::symbol_index::LocalRoots::builder(Default::default()) + _ = base_db::LocalRoots::builder(Default::default()) .durability(Durability::MEDIUM) .new(&db); db.set_expand_proc_attr_macros_with_durability(false, Durability::HIGH); diff --git a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs index e15d0b33bbaa5..eb0529d6b5e75 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs @@ -27,7 +27,7 @@ use std::{ ops::ControlFlow, }; -use base_db::{RootQueryDb, SourceRootId}; +use base_db::{LibraryRoots, LocalRoots, RootQueryDb, SourceRootId}; use fst::{Automaton, Streamer, raw::IndexedValue}; use hir::{ Crate, Module, @@ -36,7 +36,6 @@ use hir::{ symbols::{FileSymbol, SymbolCollector}, }; use rayon::prelude::*; -use rustc_hash::FxHashSet; use salsa::Update; use crate::RootDatabase; @@ -102,22 +101,6 @@ impl Query { } } -/// The set of roots for crates.io libraries. -/// Files in libraries are assumed to never change. -#[salsa::input(singleton, debug)] -pub struct LibraryRoots { - #[returns(ref)] - pub roots: FxHashSet, -} - -/// The set of "local" (that is, from the current workspace) roots. -/// Files in local roots are assumed to change frequently. -#[salsa::input(singleton, debug)] -pub struct LocalRoots { - #[returns(ref)] - pub roots: FxHashSet, -} - /// The symbol indices of modules that make up a given crate. pub fn crate_symbols(db: &dyn HirDatabase, krate: Crate) -> Box<[&SymbolIndex<'_>]> { let _p = tracing::info_span!("crate_symbols").entered(); @@ -443,6 +426,7 @@ impl Query { mod tests { use expect_test::expect_file; + use rustc_hash::FxHashSet; use salsa::Setter; use test_fixture::{WORKSPACE, WithFixture}; diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/lib.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/lib.rs index 958a26324fff9..a614d71c1f298 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/lib.rs @@ -85,7 +85,7 @@ pub use crate::{errors::SsrError, from_comment::ssr_from_comment, matching::Matc use crate::{errors::bail, matching::MatchFailureReason}; use hir::{FileRange, Semantics}; -use ide_db::symbol_index::LocalRoots; +use ide_db::LocalRoots; use ide_db::text_edit::TextEdit; use ide_db::{EditionedFileId, FileId, FxHashMap, RootDatabase, base_db::SourceDatabase}; use resolving::ResolvedRule; diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs index 5f54c66d3c2a4..51e4951cf662b 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs @@ -6,10 +6,9 @@ use crate::{ }; use hir::FileRange; use ide_db::{ - FileId, FxHashSet, + FileId, FxHashSet, LocalRoots, defs::Definition, search::{SearchScope, UsageSearchResult}, - symbol_index::LocalRoots, }; use syntax::{AstNode, SyntaxKind, SyntaxNode, ast}; diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs index 852033599a501..b3d09cac42cf5 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs @@ -1,9 +1,8 @@ use expect_test::{Expect, expect}; use hir::{FilePosition, FileRange}; use ide_db::{ - EditionedFileId, FxHashSet, + EditionedFileId, FxHashSet, LocalRoots, base_db::{SourceDatabase, salsa::Setter}, - symbol_index::LocalRoots, }; use test_utils::RangeOrOffset; diff --git a/src/tools/rust-analyzer/crates/ide/src/ssr.rs b/src/tools/rust-analyzer/crates/ide/src/ssr.rs index dc8f343207763..4dfb5fe8168fb 100644 --- a/src/tools/rust-analyzer/crates/ide/src/ssr.rs +++ b/src/tools/rust-analyzer/crates/ide/src/ssr.rs @@ -58,9 +58,7 @@ pub(crate) fn ssr_assists( mod tests { use expect_test::expect; use ide_assists::{Assist, AssistResolveStrategy}; - use ide_db::{ - FileRange, FxHashSet, RootDatabase, base_db::salsa::Setter as _, symbol_index::LocalRoots, - }; + use ide_db::{FileRange, FxHashSet, LocalRoots, RootDatabase, base_db::salsa::Setter as _}; use test_fixture::WithFixture; use super::ssr_assists; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs index 529cf120827d0..39186831459cc 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs @@ -68,7 +68,7 @@ impl flags::Search { match_finder.add_search_pattern(pattern)?; } if let Some(debug_snippet) = &self.debug { - for &root in ide_db::symbol_index::LocalRoots::get(db).roots(db).iter() { + for &root in ide_db::LocalRoots::get(db).roots(db).iter() { let sr = db.source_root(root).source_root(db); for file_id in sr.iter() { for debug_info in match_finder.debug_where_text_equal( diff --git a/src/tools/rust-analyzer/crates/stdx/src/lib.rs b/src/tools/rust-analyzer/crates/stdx/src/lib.rs index 5fa0074163713..7ab26b189065f 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/lib.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/lib.rs @@ -76,6 +76,20 @@ impl TupleExt for (T, U, V) { } } +impl TupleExt for &T +where + T: TupleExt + Copy, +{ + type Head = T::Head; + type Tail = T::Tail; + fn head(self) -> Self::Head { + (*self).head() + } + fn tail(self) -> Self::Tail { + (*self).tail() + } +} + pub fn to_lower_snake_case(s: &str) -> String { to_snake_case(s, char::to_lowercase) } From d494dbcf97e746f2c9f0169137545338a00b327a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 17:41:16 +0530 Subject: [PATCH 42/77] make framing buffer send --- .../crates/proc-macro-api/src/transport/framing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs index 2a11eb19c3654..1e29c1982acb9 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs @@ -3,7 +3,7 @@ use std::io::{self, BufRead, Write}; pub trait Framing { - type Buf: Default; + type Buf: Default + Send; fn read<'a, R: BufRead + ?Sized>( inp: &mut R, From 462b4a02cfffd2283cc58169e82f96804e90c848 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 17:43:14 +0530 Subject: [PATCH 43/77] attach lifetime to ProcMacroClientHandle and make necessary changes to proc-macro-srv --- .../crates/proc-macro-srv/src/dylib.rs | 6 ++-- .../proc-macro-srv/src/dylib/proc_macros.rs | 2 +- .../crates/proc-macro-srv/src/lib.rs | 32 ++++++++++--------- .../src/server_impl/rust_analyzer_span.rs | 18 +++++------ .../src/server_impl/token_id.rs | 16 +++++----- 5 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs index d34f37b16a274..1a2dbbd2b81b0 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs @@ -37,7 +37,7 @@ impl Expander { Ok(Expander { inner: library, modified_time }) } - pub(crate) fn expand( + pub(crate) fn expand<'a, S: ProcMacroSrvSpan>( &self, macro_name: &str, macro_body: TokenStream, @@ -45,10 +45,10 @@ impl Expander { def_site: S, call_site: S, mixed_site: S, - callback: Option, + callback: Option>, ) -> Result, PanicMessage> where - ::TokenStream: Default, + as bridge::server::Types>::TokenStream: Default, { self.inner .proc_macros diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs index ddbb0128f5e50..c763301135ee8 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib/proc_macros.rs @@ -20,7 +20,7 @@ impl ProcMacros { def_site: S, call_site: S, mixed_site: S, - callback: Option, + callback: Option>, ) -> Result, crate::PanicMessage> { let parsed_attributes = attribute.unwrap_or_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 ff5623f39ee70..687a4218b4bbe 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 @@ -91,10 +91,10 @@ impl<'env> ProcMacroSrv<'env> { } } -pub type ProcMacroClientHandle = Box; +pub type ProcMacroClientHandle<'a> = Box; pub trait ProcMacroClientInterface { - fn source_text(&self, file_id: u32, start: u32, end: u32) -> Option; + fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option; } const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; @@ -111,7 +111,7 @@ impl ProcMacroSrv<'_> { def_site: S, call_site: S, mixed_site: S, - callback: Option, + callback: Option>, ) -> Result, PanicMessage> { let snapped_env = self.env; let expander = self.expander(lib.as_ref()).map_err(|err| PanicMessage { @@ -178,24 +178,26 @@ impl ProcMacroSrv<'_> { } pub trait ProcMacroSrvSpan: Copy + Send + Sync { - type Server: proc_macro::bridge::server::Server>; - fn make_server( + type Server<'a>: proc_macro::bridge::server::Server> + where + Self: 'a; + fn make_server<'a>( call_site: Self, def_site: Self, mixed_site: Self, - callback: Option, - ) -> Self::Server; + callback: Option>, + ) -> Self::Server<'a>; } impl ProcMacroSrvSpan for SpanId { - type Server = server_impl::token_id::SpanIdServer; + type Server<'a> = server_impl::token_id::SpanIdServer<'a>; - fn make_server( + fn make_server<'a>( call_site: Self, def_site: Self, mixed_site: Self, - callback: Option, - ) -> Self::Server { + callback: Option>, + ) -> Self::Server<'a> { Self::Server { call_site, def_site, @@ -208,13 +210,13 @@ impl ProcMacroSrvSpan for SpanId { } impl ProcMacroSrvSpan for Span { - type Server = server_impl::rust_analyzer_span::RaSpanServer; - fn make_server( + type Server<'a> = server_impl::rust_analyzer_span::RaSpanServer<'a>; + fn make_server<'a>( call_site: Self, def_site: Self, mixed_site: Self, - callback: Option, - ) -> Self::Server { + callback: Option>, + ) -> Self::Server<'a> { Self::Server { call_site, def_site, diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index 5f7c0a52025ad..5d9090c060e04 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -21,7 +21,7 @@ use crate::{ pub struct FreeFunctions; -pub struct RaSpanServer { +pub struct RaSpanServer<'a> { // FIXME: Report this back to the caller to track as dependencies pub tracked_env_vars: HashMap, Option>>, // FIXME: Report this back to the caller to track as dependencies @@ -29,17 +29,17 @@ pub struct RaSpanServer { pub call_site: Span, pub def_site: Span, pub mixed_site: Span, - pub callback: Option, + pub callback: Option>, } -impl server::Types for RaSpanServer { +impl server::Types for RaSpanServer<'_> { type FreeFunctions = FreeFunctions; type TokenStream = crate::token_stream::TokenStream; type Span = Span; type Symbol = Symbol; } -impl server::FreeFunctions for RaSpanServer { +impl server::FreeFunctions for RaSpanServer<'_> { fn injected_env_var(&mut self, _: &str) -> Option { None } @@ -60,7 +60,7 @@ impl server::FreeFunctions for RaSpanServer { } } -impl server::TokenStream for RaSpanServer { +impl server::TokenStream for RaSpanServer<'_> { fn is_empty(&mut self, stream: &Self::TokenStream) -> bool { stream.is_empty() } @@ -123,7 +123,7 @@ impl server::TokenStream for RaSpanServer { } } -impl server::Span for RaSpanServer { +impl server::Span for RaSpanServer<'_> { fn debug(&mut self, span: Self::Span) -> String { format!("{:?}", span) } @@ -156,7 +156,7 @@ impl server::Span for RaSpanServer { let start: u32 = span.range.start().into(); let end: u32 = span.range.end().into(); - self.callback.as_ref()?.source_text(file_id.file_id().index(), start, end) + self.callback.as_mut()?.source_text(file_id.file_id().index(), start, end) } fn parent(&mut self, _span: Self::Span) -> Option { @@ -274,14 +274,14 @@ impl server::Span for RaSpanServer { } } -impl server::Symbol for RaSpanServer { +impl server::Symbol for RaSpanServer<'_> { fn normalize_and_validate_ident(&mut self, string: &str) -> Result { // FIXME: nfc-normalize and validate idents Ok(::intern_symbol(string)) } } -impl server::Server for RaSpanServer { +impl server::Server for RaSpanServer<'_> { fn globals(&mut self) -> ExpnGlobals { ExpnGlobals { def_site: self.def_site, diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs index 646dde7952bf3..a968ea4cd225e 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs @@ -27,7 +27,7 @@ type Span = SpanId; pub struct FreeFunctions; -pub struct SpanIdServer { +pub struct SpanIdServer<'a> { // FIXME: Report this back to the caller to track as dependencies pub tracked_env_vars: HashMap, Option>>, // FIXME: Report this back to the caller to track as dependencies @@ -35,17 +35,17 @@ pub struct SpanIdServer { pub call_site: Span, pub def_site: Span, pub mixed_site: Span, - pub callback: Option, + pub callback: Option>, } -impl server::Types for SpanIdServer { +impl server::Types for SpanIdServer<'_> { type FreeFunctions = FreeFunctions; type TokenStream = crate::token_stream::TokenStream; type Span = Span; type Symbol = Symbol; } -impl server::FreeFunctions for SpanIdServer { +impl server::FreeFunctions for SpanIdServer<'_> { fn injected_env_var(&mut self, _: &str) -> Option { None } @@ -63,7 +63,7 @@ impl server::FreeFunctions for SpanIdServer { fn emit_diagnostic(&mut self, _: Diagnostic) {} } -impl server::TokenStream for SpanIdServer { +impl server::TokenStream for SpanIdServer<'_> { fn is_empty(&mut self, stream: &Self::TokenStream) -> bool { stream.is_empty() } @@ -120,7 +120,7 @@ impl server::TokenStream for SpanIdServer { } } -impl server::Span for SpanIdServer { +impl server::Span for SpanIdServer<'_> { fn debug(&mut self, span: Self::Span) -> String { format!("{:?}", span.0) } @@ -187,14 +187,14 @@ impl server::Span for SpanIdServer { } } -impl server::Symbol for SpanIdServer { +impl server::Symbol for SpanIdServer<'_> { fn normalize_and_validate_ident(&mut self, string: &str) -> Result { // FIXME: nfc-normalize and validate idents Ok(::intern_symbol(string)) } } -impl server::Server for SpanIdServer { +impl server::Server for SpanIdServer<'_> { fn globals(&mut self) -> ExpnGlobals { ExpnGlobals { def_site: self.def_site, From 359fe9a231a9126bcd5d02fb859b69393a7d2fbe Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 17:44:31 +0530 Subject: [PATCH 44/77] remove read and write generics, also remove lock from the beginning and follow lock when required pattern --- .../proc-macro-srv-cli/src/main_loop.rs | 186 +++++++----------- 1 file changed, 70 insertions(+), 116 deletions(-) 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 6ed42204df182..305727b3721e6 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 @@ -6,7 +6,7 @@ use proc_macro_api::{ transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, version::CURRENT_API_VERSION, }; -use std::{io, sync::mpsc}; +use std::io; use legacy::Message; @@ -52,8 +52,8 @@ fn run_new() -> io::Result<()> { } let mut buf = C::Buf::default(); - let mut stdin = io::stdin().lock(); - let mut stdout = io::stdout().lock(); + let mut stdin = io::stdin(); + let mut stdout = io::stdout(); let env_snapshot = EnvSnapshot::default(); let srv = proc_macro_srv::ProcMacroSrv::new(&env_snapshot); @@ -61,7 +61,8 @@ fn run_new() -> io::Result<()> { let mut span_mode = legacy::SpanMode::Id; 'outer: loop { - let req_opt = bidirectional::BidirectionalMessage::read::<_, C>(&mut stdin, &mut buf)?; + let req_opt = + bidirectional::BidirectionalMessage::read::<_, C>(&mut stdin.lock(), &mut buf)?; let Some(req) = req_opt else { break 'outer; }; @@ -76,30 +77,23 @@ fn run_new() -> io::Result<()> { .collect() }); - send_response::<_, C>(&mut stdout, bidirectional::Response::ListMacros(res))?; + send_response::(&stdout, bidirectional::Response::ListMacros(res))?; } bidirectional::Request::ApiVersionCheck {} => { // bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION).write::<_, C>(stdout) - send_response::<_, C>( - &mut stdout, + send_response::( + &stdout, bidirectional::Response::ApiVersionCheck(CURRENT_API_VERSION), )?; } bidirectional::Request::SetConfig(config) => { span_mode = config.span_mode; - send_response::<_, C>(&mut stdout, bidirectional::Response::SetConfig(config))?; + send_response::(&stdout, bidirectional::Response::SetConfig(config))?; } bidirectional::Request::ExpandMacro(task) => { - handle_expand::<_, _, C>( - &srv, - &mut stdin, - &mut stdout, - &mut buf, - span_mode, - *task, - )?; + handle_expand::(&srv, &mut stdin, &mut stdout, &mut buf, span_mode, *task)?; } }, _ => continue, @@ -109,25 +103,23 @@ fn run_new() -> io::Result<()> { Ok(()) } -fn handle_expand( +fn handle_expand( srv: &proc_macro_srv::ProcMacroSrv<'_>, - stdin: &mut R, - stdout: &mut W, + stdin: &io::Stdin, + stdout: &io::Stdout, buf: &mut C::Buf, span_mode: legacy::SpanMode, task: bidirectional::ExpandMacro, ) -> io::Result<()> { match span_mode { - legacy::SpanMode::Id => handle_expand_id::<_, C>(srv, stdout, task), - legacy::SpanMode::RustAnalyzer => { - handle_expand_ra::<_, _, C>(srv, stdin, stdout, buf, task) - } + legacy::SpanMode::Id => handle_expand_id::(srv, stdout, task), + legacy::SpanMode::RustAnalyzer => handle_expand_ra::(srv, stdin, stdout, buf, task), } } -fn handle_expand_id( +fn handle_expand_id( srv: &proc_macro_srv::ProcMacroSrv<'_>, - stdout: &mut W, + stdout: &io::Stdout, task: bidirectional::ExpandMacro, ) -> io::Result<()> { let bidirectional::ExpandMacro { lib, env, current_dir, data } = task; @@ -166,28 +158,40 @@ fn handle_expand_id( }) .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); - send_response::<_, C>(stdout, bidirectional::Response::ExpandMacro(res)) + send_response::(&stdout, bidirectional::Response::ExpandMacro(res)) } -struct ProcMacroClientHandle { - subreq_tx: mpsc::Sender, - subresp_rx: mpsc::Receiver, +struct BidirectionalStruct<'a, C: Codec> { + stdin: &'a io::Stdin, + stdout: &'a io::Stdout, + buf: &'a mut C::Buf, } -impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle { - fn source_text(&self, file_id: u32, start: u32, end: u32) -> Option { - self.subreq_tx.send(bidirectional::SubRequest::SourceText { file_id, start, end }).ok()?; - - match self.subresp_rx.recv().ok()? { - bidirectional::SubResponse::SourceTextResult { text } => text, +impl proc_macro_srv::ProcMacroClientInterface for BidirectionalStruct<'_, C> { + fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option { + let subrequest = bidirectional::SubRequest::SourceText { file_id, start, end }; + bidirectional::BidirectionalMessage::SubRequest(subrequest) + .write::<_, C>(&mut self.stdout.lock()) + .unwrap(); + let resp = + bidirectional::BidirectionalMessage::read::<_, C>(&mut self.stdin.lock(), self.buf) + .unwrap() + .expect("client closed connection"); + match resp { + bidirectional::BidirectionalMessage::SubResponse(api_resp) => match api_resp { + bidirectional::SubResponse::SourceTextResult { text } => { + return text; + } + }, + other => panic!("expected SubResponse, got {other:?}"), } } } -fn handle_expand_ra( +fn handle_expand_ra( srv: &proc_macro_srv::ProcMacroSrv<'_>, - stdin: &mut R, - stdout: &mut W, + stdin: &io::Stdin, + stdout: &io::Stdout, buf: &mut C::Buf, task: bidirectional::ExpandMacro, ) -> io::Result<()> { @@ -222,81 +226,34 @@ fn handle_expand_ra( }) }); - let (subreq_tx, subreq_rx) = mpsc::channel(); - let (subresp_tx, subresp_rx) = mpsc::channel(); - let (result_tx, result_rx) = mpsc::channel(); - - std::thread::scope(|s| { - s.spawn(|| { - let callback = ProcMacroClientHandle { subreq_tx, subresp_rx }; - - let res = srv - .expand( - lib, - &env, - current_dir, - ¯o_name, - macro_body, - attributes, - def_site, + let res = srv + .expand( + lib, + &env, + current_dir, + ¯o_name, + macro_body, + attributes, + def_site, + call_site, + mixed_site, + Some(Box::new(BidirectionalStruct:: { stdin, stdout, buf })), + ) + .map(|it| { + ( + legacy::FlatTree::from_tokenstream( + it, + CURRENT_API_VERSION, call_site, - mixed_site, - Some(Box::new(callback)), - ) - .map(|it| { - ( - legacy::FlatTree::from_tokenstream( - it, - CURRENT_API_VERSION, - call_site, - &mut span_data_table, - ), - legacy::serialize_span_data_index_map(&span_data_table), - ) - }) - .map(|(tree, span_data_table)| bidirectional::ExpandMacroExtended { - tree, - span_data_table, - }) - .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); - - let _ = result_tx.send(res); - }); - - loop { - if let Ok(res) = result_rx.try_recv() { - let _ = send_response::<_, C>( - stdout, - bidirectional::Response::ExpandMacroExtended(res), - ); - break; - } - - let sub_req = match subreq_rx.recv() { - Ok(r) => r, - Err(_) => break, - }; - - if bidirectional::BidirectionalMessage::SubRequest(sub_req) - .write::<_, C>(stdout) - .is_err() - { - break; - } - let resp = match bidirectional::BidirectionalMessage::read::<_, C>(stdin, buf) { - Ok(Some(r)) => r, - _ => break, - }; - match resp { - bidirectional::BidirectionalMessage::SubResponse(resp) => { - let _ = subresp_tx.send(resp); - } - other => panic!("expected SubResponse, got {other:?}"), - } - } - }); + &mut span_data_table, + ), + legacy::serialize_span_data_index_map(&span_data_table), + ) + }) + .map(|(tree, span_data_table)| bidirectional::ExpandMacroExtended { tree, span_data_table }) + .map_err(|e| legacy::PanicMessage(e.into_string().unwrap_or_default())); - Ok(()) + send_response::(&stdout, bidirectional::Response::ExpandMacroExtended(res)) } fn run_() -> io::Result<()> { @@ -441,10 +398,7 @@ fn run_() -> io::Result<()> { Ok(()) } -fn send_response( - stdout: &mut W, - resp: bidirectional::Response, -) -> io::Result<()> { +fn send_response(stdout: &io::Stdout, resp: bidirectional::Response) -> io::Result<()> { let resp = bidirectional::BidirectionalMessage::Response(resp); - resp.write::(stdout) + resp.write::<_, C>(&mut stdout.lock()) } From 7e873dbeb4b95d163d5c19b60f1f8222a65b3a0b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 17:49:32 +0530 Subject: [PATCH 45/77] rename BidirectionalStruct to ProcMacroClientHandle --- .../crates/proc-macro-srv-cli/src/main_loop.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 305727b3721e6..feae84534dc78 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 @@ -161,13 +161,13 @@ fn handle_expand_id( send_response::(&stdout, bidirectional::Response::ExpandMacro(res)) } -struct BidirectionalStruct<'a, C: Codec> { +struct ProcMacroClientHandle<'a, C: Codec> { stdin: &'a io::Stdin, stdout: &'a io::Stdout, buf: &'a mut C::Buf, } -impl proc_macro_srv::ProcMacroClientInterface for BidirectionalStruct<'_, C> { +impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle<'_, C> { fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option { let subrequest = bidirectional::SubRequest::SourceText { file_id, start, end }; bidirectional::BidirectionalMessage::SubRequest(subrequest) @@ -237,7 +237,7 @@ fn handle_expand_ra( def_site, call_site, mixed_site, - Some(Box::new(BidirectionalStruct:: { stdin, stdout, buf })), + Some(Box::new(ProcMacroClientHandle:: { stdin, stdout, buf })), ) .map(|it| { ( From 43cda6d5eceb976201a563edd6c98eadbbe0fd7c Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 17:52:16 +0530 Subject: [PATCH 46/77] remove unwraps from source_text impl --- .../proc-macro-srv-cli/src/main_loop.rs | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) 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 feae84534dc78..4d98a8e29d125 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 @@ -169,21 +169,27 @@ struct ProcMacroClientHandle<'a, C: Codec> { impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle<'_, C> { fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option { - let subrequest = bidirectional::SubRequest::SourceText { file_id, start, end }; - bidirectional::BidirectionalMessage::SubRequest(subrequest) - .write::<_, C>(&mut self.stdout.lock()) - .unwrap(); - let resp = - bidirectional::BidirectionalMessage::read::<_, C>(&mut self.stdin.lock(), self.buf) - .unwrap() - .expect("client closed connection"); - match resp { - bidirectional::BidirectionalMessage::SubResponse(api_resp) => match api_resp { - bidirectional::SubResponse::SourceTextResult { text } => { - return text; - } - }, - other => panic!("expected SubResponse, got {other:?}"), + let req = bidirectional::BidirectionalMessage::SubRequest( + bidirectional::SubRequest::SourceText { file_id, start, end }, + ); + + if req.write::<_, C>(&mut self.stdout.lock()).is_err() { + return None; + } + + let msg = match bidirectional::BidirectionalMessage::read::<_, C>( + &mut self.stdin.lock(), + self.buf, + ) { + Ok(Some(msg)) => msg, + _ => return None, + }; + + match msg { + bidirectional::BidirectionalMessage::SubResponse( + bidirectional::SubResponse::SourceTextResult { text }, + ) => text, + _ => None, } } } From 671aab6dbde5e6302265728d926966cb14fca18a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Dec 2025 18:08:42 +0530 Subject: [PATCH 47/77] make expand ProcMacroServerSpan type live longer --- src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs index 1a2dbbd2b81b0..02bdcc50d3871 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs @@ -37,7 +37,7 @@ impl Expander { Ok(Expander { inner: library, modified_time }) } - pub(crate) fn expand<'a, S: ProcMacroSrvSpan>( + pub(crate) fn expand<'a, S: ProcMacroSrvSpan + 'a>( &self, macro_name: &str, macro_body: TokenStream, From a9da655c9b8c28105a5c8e61c35b114fb3c666ed Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 27 Dec 2025 19:57:42 +0200 Subject: [PATCH 48/77] Bring back fixpoint iteration for variance --- .../crates/hir-ty/src/variance.rs | 79 ++++++++----------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs b/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs index 5b8122a0a5dfc..e5cfe85573b8c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs @@ -36,9 +36,8 @@ pub(crate) fn variances_of(db: &dyn HirDatabase, def: GenericDefId) -> Variances #[salsa::tracked( returns(ref), - // cycle_fn = crate::variance::variances_of_cycle_fn, - // cycle_initial = crate::variance::variances_of_cycle_initial, - cycle_result = crate::variance::variances_of_cycle_initial, + cycle_fn = crate::variance::variances_of_cycle_fn, + cycle_initial = crate::variance::variances_of_cycle_initial, )] fn variances_of_query(db: &dyn HirDatabase, def: GenericDefId) -> StoredVariancesOf { tracing::debug!("variances_of(def={:?})", def); @@ -64,35 +63,20 @@ fn variances_of_query(db: &dyn HirDatabase, def: GenericDefId) -> StoredVariance if count == 0 { return VariancesOf::empty(interner).store(); } - let mut variances = - Context { generics, variances: vec![Variance::Bivariant; count], db }.solve(); - - // FIXME(next-solver): This is *not* the correct behavior. I don't know if it has an actual effect, - // since bivariance is prohibited in Rust, but rustc definitely does not fallback bivariance. - // So why do we do this? Because, with the new solver, the effects of bivariance are catastrophic: - // it leads to not relating types properly, and to very, very hard to debug bugs (speaking from experience). - // Furthermore, our variance infra is known to not handle cycles properly. Therefore, at least until we fix - // cycles, and perhaps forever at least for out tests, not allowing bivariance makes sense. - // Why specifically invariance? I don't have a strong reason, mainly that invariance is a stronger relationship - // (therefore, less room for mistakes) and that IMO incorrect covariance can be more problematic that incorrect - // bivariance, at least while we don't handle lifetimes anyway. - for variance in &mut variances { - if *variance == Variance::Bivariant { - *variance = Variance::Invariant; - } - } + let variances = Context { generics, variances: vec![Variance::Bivariant; count], db }.solve(); VariancesOf::new_from_slice(&variances).store() } -// pub(crate) fn variances_of_cycle_fn( -// _db: &dyn HirDatabase, -// _result: &Option>, -// _count: u32, -// _def: GenericDefId, -// ) -> salsa::CycleRecoveryAction>> { -// salsa::CycleRecoveryAction::Iterate -// } +pub(crate) fn variances_of_cycle_fn( + _db: &dyn HirDatabase, + _: &salsa::Cycle<'_>, + _last_provisional_value: &StoredVariancesOf, + value: StoredVariancesOf, + _def: GenericDefId, +) -> StoredVariancesOf { + value +} fn glb(v1: Variance, v2: Variance) -> Variance { // Greatest lower bound of the variance lattice as defined in The Paper: @@ -123,8 +107,7 @@ pub(crate) fn variances_of_cycle_initial( let generics = generics(db, def); let count = generics.len(); - // FIXME(next-solver): Returns `Invariance` and not `Bivariance` here, see the comment in the main query. - VariancesOf::new_from_iter(interner, std::iter::repeat_n(Variance::Invariant, count)).store() + VariancesOf::new_from_iter(interner, std::iter::repeat_n(Variance::Bivariant, count)).store() } struct Context<'db> { @@ -484,8 +467,8 @@ struct Other<'a> { } "#, expect![[r#" - Hello['a: invariant] - Other['a: invariant] + Hello['a: bivariant] + Other['a: bivariant] "#]], ); } @@ -504,7 +487,7 @@ struct Foo { //~ ERROR [T: o] } "#, expect![[r#" - Foo[T: invariant] + Foo[T: bivariant] "#]], ); } @@ -586,9 +569,9 @@ struct TestBox+Setter> { //~ ERROR [U: *, T: +] get[Self: contravariant, T: covariant] get[Self: contravariant, T: contravariant] TestStruct[U: covariant, T: covariant] - TestEnum[U: invariant, T: covariant] - TestContraStruct[U: invariant, T: covariant] - TestBox[U: invariant, T: covariant] + TestEnum[U: bivariant, T: covariant] + TestContraStruct[U: bivariant, T: covariant] + TestBox[U: bivariant, T: covariant] "#]], ); } @@ -708,8 +691,8 @@ enum SomeEnum<'a> { Nothing } //~ ERROR parameter `'a` is never used trait SomeTrait<'a> { fn foo(&self); } // OK on traits. "#, expect![[r#" - SomeStruct['a: invariant] - SomeEnum['a: invariant] + SomeStruct['a: bivariant] + SomeEnum['a: bivariant] foo[Self: contravariant, 'a: invariant] "#]], ); @@ -737,14 +720,14 @@ struct DoubleNothing { "#, expect![[r#" - SomeStruct[A: invariant] - SomeEnum[A: invariant] - ListCell[T: invariant] - SelfTyAlias[T: invariant] - WithBounds[T: invariant] - WithWhereBounds[T: invariant] - WithOutlivesBounds[T: invariant] - DoubleNothing[T: invariant] + SomeStruct[A: bivariant] + SomeEnum[A: bivariant] + ListCell[T: bivariant] + SelfTyAlias[T: bivariant] + WithBounds[T: bivariant] + WithWhereBounds[T: bivariant] + WithOutlivesBounds[T: bivariant] + DoubleNothing[T: bivariant] "#]], ); } @@ -855,7 +838,7 @@ struct S3(S); "#, expect![[r#" S[T: covariant] - S2[T: invariant] + S2[T: bivariant] S3[T: covariant] "#]], ); @@ -868,7 +851,7 @@ struct S3(S); struct FixedPoint(&'static FixedPoint<(), T, U>, V); "#, expect![[r#" - FixedPoint[T: invariant, U: invariant, V: invariant] + FixedPoint[T: covariant, U: covariant, V: covariant] "#]], ); } From fc1cb48d3b7e2fbf02bf9f98040b2af908cf30dc Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 27 Dec 2025 20:06:24 +0200 Subject: [PATCH 49/77] Fix tests that relied on the default variance to be invariant And now it changed to bivariant. --- .../crates/hir-ty/src/tests/traits.rs | 24 +++++++++---------- .../ide-completion/src/completions/dot.rs | 3 ++- .../crates/ide/src/inlay_hints/bind_pat.rs | 12 +++++----- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index a54c0a799dfc9..38591f486e971 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -851,7 +851,7 @@ struct S; trait Trait {} impl Trait<&str> for S {} -struct O; +struct O(T); impl> O { fn foo(&self) -> U { loop {} } } @@ -1492,7 +1492,7 @@ fn dyn_trait_in_impl() { trait Trait { fn foo(&self) -> (T, U); } -struct S {} +struct S(T, U); impl S { fn bar(&self) -> &dyn Trait { loop {} } } @@ -1506,16 +1506,16 @@ fn test(s: S) { }"#, expect![[r#" 32..36 'self': &'? Self - 102..106 'self': &'? S - 128..139 '{ loop {} }': &'? (dyn Trait + 'static) - 130..137 'loop {}': ! - 135..137 '{}': () - 175..179 'self': &'? Self - 251..252 's': S - 267..289 '{ ...z(); }': () - 273..274 's': S - 273..280 's.bar()': &'? (dyn Trait + 'static) - 273..286 's.bar().baz()': (u32, i32) + 106..110 'self': &'? S + 132..143 '{ loop {} }': &'? (dyn Trait + 'static) + 134..141 'loop {}': ! + 139..141 '{}': () + 179..183 'self': &'? Self + 255..256 's': S + 271..293 '{ ...z(); }': () + 277..278 's': S + 277..284 's.bar()': &'? (dyn Trait + 'static) + 277..290 's.bar().baz()': (u32, i32) "#]], ); } 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 18cfa53f8e960..18c1992afa246 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 @@ -652,7 +652,7 @@ fn foo(u: U) { u.$0 } fn test_method_completion_only_fitting_impls() { check_no_kw( r#" -struct A {} +struct A(T); impl A { fn the_method(&self) {} } @@ -662,6 +662,7 @@ impl A { fn foo(a: A) { a.$0 } "#, expect![[r#" + fd 0 u32 me the_method() fn(&self) "#]], ) diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs index de207c7821da0..547004687c73a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs @@ -339,14 +339,14 @@ fn main(a: SliceIter<'_, Container>) { fn lt_hints() { check_types( r#" -struct S<'lt>; +struct S<'lt>(*mut &'lt ()); fn f<'a>() { - let x = S::<'static>; + let x = S::<'static>(loop {}); //^ S<'static> - let y = S::<'_>; + let y = S::<'_>(loop {}); //^ S<'_> - let z = S::<'a>; + let z = S::<'a>(loop {}); //^ S<'a> } @@ -632,10 +632,10 @@ fn main() { fn multi_dyn_trait_bounds() { check_types( r#" -pub struct Vec {} +pub struct Vec(*mut T); impl Vec { - pub fn new() -> Self { Vec {} } + pub fn new() -> Self { Vec(0 as *mut T) } } pub struct Box {} From bb5c82c40627621a863f594b8ee0f0f4f2d94026 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Thu, 4 Dec 2025 13:22:23 +0100 Subject: [PATCH 50/77] Don't use MIR ProjectionElem in closure analysis Almost all of the enum variants were unused, it's not very useful. --- .../hir-ty/src/infer/closure/analysis.rs | 190 ++++++++++-------- .../crates/hir-ty/src/mir/lower.rs | 36 ++-- .../hir-ty/src/tests/closure_captures.rs | 25 +++ 3 files changed, 152 insertions(+), 99 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs index 6ae6f75525028..b25901cc3b995 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs @@ -1,10 +1,10 @@ //! Post-inference closure analysis: captures and closure kind. -use std::{cmp, convert::Infallible, mem}; +use std::{cmp, mem}; -use either::Either; +use base_db::Crate; use hir_def::{ - DefWithBodyId, FieldId, HasModule, TupleFieldId, TupleId, VariantId, + DefWithBodyId, FieldId, HasModule, VariantId, expr_store::path::Path, hir::{ Array, AsmOperand, BinaryOp, BindingId, CaptureBy, Expr, ExprId, ExprOrPatId, Pat, PatId, @@ -23,33 +23,97 @@ use syntax::utils::is_raw_identifier; use crate::{ Adjust, Adjustment, BindingMode, db::{HirDatabase, InternedClosure, InternedClosureId}, + display::{DisplayTarget, HirDisplay as _}, infer::InferenceContext, - mir::{BorrowKind, MirSpan, MutBorrowKind, ProjectionElem}, - next_solver::{DbInterner, GenericArgs, StoredEarlyBinder, StoredTy, Ty, TyKind}, + mir::{BorrowKind, MirSpan, MutBorrowKind}, + next_solver::{ + DbInterner, ErrorGuaranteed, GenericArgs, ParamEnv, StoredEarlyBinder, StoredTy, Ty, + TyKind, + infer::{InferCtxt, traits::ObligationCause}, + obligation_ctxt::ObligationCtxt, + }, traits::FnTrait, }; // The below functions handle capture and closure kind (Fn, FnMut, ..) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) enum HirPlaceProjection { + Deref, + Field(FieldId), + TupleField(u32), +} + +impl HirPlaceProjection { + fn projected_ty<'db>( + self, + infcx: &InferCtxt<'db>, + env: ParamEnv<'db>, + mut base: Ty<'db>, + krate: Crate, + ) -> Ty<'db> { + let interner = infcx.interner; + let db = interner.db; + if base.is_ty_error() { + return Ty::new_error(interner, ErrorGuaranteed); + } + + if matches!(base.kind(), TyKind::Alias(..)) { + let mut ocx = ObligationCtxt::new(infcx); + match ocx.structurally_normalize_ty(&ObligationCause::dummy(), env, base) { + Ok(it) => base = it, + Err(_) => return Ty::new_error(interner, ErrorGuaranteed), + } + } + match self { + HirPlaceProjection::Deref => match base.kind() { + TyKind::RawPtr(inner, _) | TyKind::Ref(_, inner, _) => inner, + TyKind::Adt(adt_def, subst) if adt_def.is_box() => subst.type_at(0), + _ => { + never!( + "Overloaded deref on type {} is not a projection", + base.display(db, DisplayTarget::from_crate(db, krate)) + ); + Ty::new_error(interner, ErrorGuaranteed) + } + }, + HirPlaceProjection::Field(f) => match base.kind() { + TyKind::Adt(_, subst) => { + db.field_types(f.parent)[f.local_id].get().instantiate(interner, subst) + } + ty => { + never!("Only adt has field, found {:?}", ty); + Ty::new_error(interner, ErrorGuaranteed) + } + }, + HirPlaceProjection::TupleField(idx) => match base.kind() { + TyKind::Tuple(subst) => { + subst.as_slice().get(idx as usize).copied().unwrap_or_else(|| { + never!("Out of bound tuple field"); + Ty::new_error(interner, ErrorGuaranteed) + }) + } + ty => { + never!("Only tuple has tuple field: {:?}", ty); + Ty::new_error(interner, ErrorGuaranteed) + } + }, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, salsa::Update)] pub(crate) struct HirPlace { pub(crate) local: BindingId, - pub(crate) projections: Vec>, + pub(crate) projections: Vec, } impl HirPlace { fn ty<'db>(&self, ctx: &mut InferenceContext<'_, 'db>) -> Ty<'db> { + let krate = ctx.krate(); let mut ty = ctx.table.resolve_completely(ctx.result.binding_ty(self.local)); for p in &self.projections { - ty = p.projected_ty( - &ctx.table.infer_ctxt, - ctx.table.param_env, - ty, - |_, _, _| { - unreachable!("Closure field only happens in MIR"); - }, - ctx.owner.module(ctx.db).krate(ctx.db), - ); + ty = p.projected_ty(ctx.infcx(), ctx.table.param_env, ty, krate); } ty } @@ -62,7 +126,7 @@ impl HirPlace { if let CaptureKind::ByRef(BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhasedBorrow, }) = current_capture - && self.projections[len..].contains(&ProjectionElem::Deref) + && self.projections[len..].contains(&HirPlaceProjection::Deref) { current_capture = CaptureKind::ByRef(BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }); @@ -98,7 +162,7 @@ impl CapturedItem { /// Returns whether this place has any field (aka. non-deref) projections. pub fn has_field_projections(&self) -> bool { - self.place.projections.iter().any(|it| !matches!(it, ProjectionElem::Deref)) + self.place.projections.iter().any(|it| !matches!(it, HirPlaceProjection::Deref)) } pub fn ty<'db>(&self, db: &'db dyn HirDatabase, subst: GenericArgs<'db>) -> Ty<'db> { @@ -120,8 +184,8 @@ impl CapturedItem { let mut result = body[self.place.local].name.as_str().to_owned(); for proj in &self.place.projections { match proj { - ProjectionElem::Deref => {} - ProjectionElem::Field(Either::Left(f)) => { + HirPlaceProjection::Deref => {} + HirPlaceProjection::Field(f) => { let variant_data = f.parent.fields(db); match variant_data.shape { FieldsShape::Record => { @@ -138,14 +202,8 @@ impl CapturedItem { FieldsShape::Unit => {} } } - ProjectionElem::Field(Either::Right(f)) => format_to!(result, "_{}", f.index), - &ProjectionElem::ClosureField(field) => format_to!(result, "_{field}"), - ProjectionElem::Index(_) - | ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Subslice { .. } - | ProjectionElem::OpaqueCast(_) => { - never!("Not happen in closure capture"); - continue; + HirPlaceProjection::TupleField(idx) => { + format_to!(result, "_{idx}") } } } @@ -163,8 +221,8 @@ impl CapturedItem { for proj in &self.place.projections { match proj { // In source code autoderef kicks in. - ProjectionElem::Deref => {} - ProjectionElem::Field(Either::Left(f)) => { + HirPlaceProjection::Deref => {} + HirPlaceProjection::Field(f) => { let variant_data = f.parent.fields(db); match variant_data.shape { FieldsShape::Record => format_to!( @@ -184,19 +242,8 @@ impl CapturedItem { FieldsShape::Unit => {} } } - ProjectionElem::Field(Either::Right(f)) => { - let field = f.index; - format_to!(result, ".{field}"); - } - &ProjectionElem::ClosureField(field) => { - format_to!(result, ".{field}"); - } - ProjectionElem::Index(_) - | ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Subslice { .. } - | ProjectionElem::OpaqueCast(_) => { - never!("Not happen in closure capture"); - continue; + HirPlaceProjection::TupleField(idx) => { + format_to!(result, ".{idx}") } } } @@ -205,7 +252,7 @@ impl CapturedItem { .projections .iter() .rev() - .take_while(|proj| matches!(proj, ProjectionElem::Deref)) + .take_while(|proj| matches!(proj, HirPlaceProjection::Deref)) .count(); result.insert_str(0, &"*".repeat(final_derefs_count)); result @@ -219,11 +266,11 @@ impl CapturedItem { let mut field_need_paren = false; for proj in &self.place.projections { match proj { - ProjectionElem::Deref => { + HirPlaceProjection::Deref => { result = format!("*{result}"); field_need_paren = true; } - ProjectionElem::Field(Either::Left(f)) => { + HirPlaceProjection::Field(f) => { if field_need_paren { result = format!("({result})"); } @@ -243,28 +290,13 @@ impl CapturedItem { result = format!("{result}.{field}"); field_need_paren = false; } - ProjectionElem::Field(Either::Right(f)) => { - let field = f.index; - if field_need_paren { - result = format!("({result})"); - } - result = format!("{result}.{field}"); - field_need_paren = false; - } - &ProjectionElem::ClosureField(field) => { + HirPlaceProjection::TupleField(idx) => { if field_need_paren { result = format!("({result})"); } - result = format!("{result}.{field}"); + result = format!("{result}.{idx}"); field_need_paren = false; } - ProjectionElem::Index(_) - | ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Subslice { .. } - | ProjectionElem::OpaqueCast(_) => { - never!("Not happen in closure capture"); - continue; - } } } result @@ -345,7 +377,9 @@ impl<'db> InferenceContext<'_, 'db> { let mut place = self.place_of_expr(*expr)?; let field = self.result.field_resolution(tgt_expr)?; self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); - place.projections.push(ProjectionElem::Field(field)); + place.projections.push(field.either(HirPlaceProjection::Field, |f| { + HirPlaceProjection::TupleField(f.index) + })); return Some(place); } Expr::UnaryOp { expr, op: UnaryOp::Deref } => { @@ -357,7 +391,7 @@ impl<'db> InferenceContext<'_, 'db> { if is_builtin_deref { let mut place = self.place_of_expr(*expr)?; self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); - place.projections.push(ProjectionElem::Deref); + place.projections.push(HirPlaceProjection::Deref); return Some(place); } } @@ -832,9 +866,6 @@ impl<'db> InferenceContext<'_, 'db> { &self.table.infer_ctxt, self.table.param_env, ty, - |_, _, _| { - unreachable!("Closure field only happens in MIR"); - }, self.owner.module(self.db).krate(self.db), ); if ty.is_raw_ptr() || ty.is_union() { @@ -853,7 +884,7 @@ impl<'db> InferenceContext<'_, 'db> { let mut current_captures = std::mem::take(&mut self.current_captures); for capture in &mut current_captures { if let Some(first_deref) = - capture.place.projections.iter().position(|proj| *proj == ProjectionElem::Deref) + capture.place.projections.iter().position(|proj| *proj == HirPlaceProjection::Deref) { self.truncate_capture_spans(capture, first_deref); capture.place.projections.truncate(first_deref); @@ -876,7 +907,7 @@ impl<'db> InferenceContext<'_, 'db> { } match it.next() { Some(it) => { - lookup_place.projections.push(it.clone()); + lookup_place.projections.push(*it); } None => break None, } @@ -903,7 +934,7 @@ impl<'db> InferenceContext<'_, 'db> { fn consume_with_pat(&mut self, mut place: HirPlace, tgt_pat: PatId) { let adjustments_count = self.result.pat_adjustments.get(&tgt_pat).map(|it| it.len()).unwrap_or_default(); - place.projections.extend((0..adjustments_count).map(|_| ProjectionElem::Deref)); + place.projections.extend((0..adjustments_count).map(|_| HirPlaceProjection::Deref)); self.current_capture_span_stack .extend((0..adjustments_count).map(|_| MirSpan::PatId(tgt_pat))); 'reset_span_stack: { @@ -920,10 +951,7 @@ impl<'db> InferenceContext<'_, 'db> { for (&arg, i) in it { let mut p = place.clone(); self.current_capture_span_stack.push(MirSpan::PatId(arg)); - p.projections.push(ProjectionElem::Field(Either::Right(TupleFieldId { - tuple: TupleId(!0), // dummy this, as its unused anyways - index: i as u32, - }))); + p.projections.push(HirPlaceProjection::TupleField(i as u32)); self.consume_with_pat(p, arg); self.current_capture_span_stack.pop(); } @@ -950,10 +978,10 @@ impl<'db> InferenceContext<'_, 'db> { }; let mut p = place.clone(); self.current_capture_span_stack.push(MirSpan::PatId(arg)); - p.projections.push(ProjectionElem::Field(Either::Left(FieldId { + p.projections.push(HirPlaceProjection::Field(FieldId { parent: variant, local_id, - }))); + })); self.consume_with_pat(p, arg); self.current_capture_span_stack.pop(); } @@ -1005,10 +1033,10 @@ impl<'db> InferenceContext<'_, 'db> { for (&arg, (i, _)) in it { let mut p = place.clone(); self.current_capture_span_stack.push(MirSpan::PatId(arg)); - p.projections.push(ProjectionElem::Field(Either::Left(FieldId { + p.projections.push(HirPlaceProjection::Field(FieldId { parent: variant, local_id: i, - }))); + })); self.consume_with_pat(p, arg); self.current_capture_span_stack.pop(); } @@ -1017,7 +1045,7 @@ impl<'db> InferenceContext<'_, 'db> { } Pat::Ref { pat, mutability: _ } => { self.current_capture_span_stack.push(MirSpan::PatId(tgt_pat)); - place.projections.push(ProjectionElem::Deref); + place.projections.push(HirPlaceProjection::Deref); self.consume_with_pat(place, *pat); self.current_capture_span_stack.pop(); } @@ -1071,7 +1099,7 @@ impl<'db> InferenceContext<'_, 'db> { CaptureKind::ByRef(BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhasedBorrow }) - ) && !item.place.projections.contains(&ProjectionElem::Deref) + ) && !item.place.projections.contains(&HirPlaceProjection::Deref) { // FIXME: remove the `mutated_bindings_in_closure` completely and add proper fake reads in // MIR. I didn't do that due duplicate diagnostics. @@ -1221,7 +1249,7 @@ fn apply_adjusts_to_place( match &adj.kind { Adjust::Deref(None) => { current_capture_span_stack.push(span); - r.projections.push(ProjectionElem::Deref); + r.projections.push(HirPlaceProjection::Deref); } _ => return None, } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index 762f91fb0d997..1579f00e92666 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -30,7 +30,10 @@ use crate::{ db::{HirDatabase, InternedClosure, InternedClosureId}, display::{DisplayTarget, HirDisplay, hir_display_with_store}, generics::generics, - infer::{CaptureKind, CapturedItem, TypeMismatch, cast::CastTy}, + infer::{ + CaptureKind, CapturedItem, TypeMismatch, cast::CastTy, + closure::analysis::HirPlaceProjection, + }, inhabitedness::is_ty_uninhabited_from, layout::LayoutError, method_resolution::CandidateId, @@ -1258,22 +1261,16 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { .clone() .into_iter() .map(|it| match it { - ProjectionElem::Deref => ProjectionElem::Deref, - ProjectionElem::Field(it) => ProjectionElem::Field(it), - ProjectionElem::ClosureField(it) => { - ProjectionElem::ClosureField(it) - } - ProjectionElem::ConstantIndex { offset, from_end } => { - ProjectionElem::ConstantIndex { offset, from_end } + HirPlaceProjection::Deref => ProjectionElem::Deref, + HirPlaceProjection::Field(field_id) => { + ProjectionElem::Field(Either::Left(field_id)) } - ProjectionElem::Subslice { from, to } => { - ProjectionElem::Subslice { from, to } + HirPlaceProjection::TupleField(idx) => { + ProjectionElem::Field(Either::Right(TupleFieldId { + tuple: TupleId(!0), // Dummy as it's unused + index: idx, + })) } - ProjectionElem::OpaqueCast(it) => { - ProjectionElem::OpaqueCast(it) - } - #[allow(unreachable_patterns)] - ProjectionElem::Index(it) => match it {}, }) .collect(), ), @@ -2173,10 +2170,13 @@ pub fn mir_body_for_closure_query<'db>( for (it, y) in p.projection.lookup(store).iter().zip(it.0.place.projections.iter()) { match (it, y) { - (ProjectionElem::Deref, ProjectionElem::Deref) => (), - (ProjectionElem::Field(it), ProjectionElem::Field(y)) if it == y => (), - (ProjectionElem::ClosureField(it), ProjectionElem::ClosureField(y)) + (ProjectionElem::Deref, HirPlaceProjection::Deref) => (), + (ProjectionElem::Field(Either::Left(it)), HirPlaceProjection::Field(y)) if it == y => {} + ( + ProjectionElem::Field(Either::Right(it)), + HirPlaceProjection::TupleField(y), + ) if it.index == *y => (), _ => return false, } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs index 3bdc72d015006..8408c0a7bfcd3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/closure_captures.rs @@ -503,3 +503,28 @@ fn main() { expect!["73..149;37..38;103..104 ByValue b Option"], ); } + +#[test] +fn alias_needs_to_be_normalized() { + check_closure_captures( + r#" +//- minicore:copy +trait Trait { + type Associated; +} +struct A; +struct B { x: i32 } +impl Trait for A { + type Associated = B; +} +struct C { b: ::Associated } +fn main() { + let c: C = C { b: B { x: 1 } }; + let closure = || { + let _move = c.b.x; + }; +} +"#, + expect!["220..257;174..175;245..250 ByRef(Shared) c.b.x &'? i32"], + ); +} From c27f6d548166cef8e537c749a52c6646911bd411 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 28 Dec 2025 06:35:10 +0200 Subject: [PATCH 51/77] Fix parsing of `format_args!("...", keyword=...)` --- .../src/expr_store/lower/format_args.rs | 11 ++-- .../test_data/highlight_strings.html | 22 ++++---- .../parser/src/grammar/expressions/atom.rs | 10 +++- .../parser/src/syntax_kind/generated.rs | 2 + .../parser/test_data/generated/runner.rs | 4 ++ .../parser/inline/ok/builtin_expr.rast | 6 +-- .../ok/format_args_named_arg_keyword.rast | 35 +++++++++++++ .../ok/format_args_named_arg_keyword.rs | 3 ++ .../rust-analyzer/crates/syntax/rust.ungram | 5 +- .../crates/syntax/src/ast/generated/nodes.rs | 50 ++++++++++++++++--- .../crates/syntax/src/ast/node_ext.rs | 9 ++++ 11 files changed, 127 insertions(+), 30 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/format_args_named_arg_keyword.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/format_args_named_arg_keyword.rs diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs index 4bbfc5b144f16..32c9df3dfefb2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs @@ -1,12 +1,9 @@ //! Lowering of `format_args!()`. use base_db::FxIndexSet; -use hir_expand::name::{AsName, Name}; +use hir_expand::name::Name; use intern::{Symbol, sym}; -use syntax::{ - AstPtr, AstToken as _, - ast::{self, HasName}, -}; +use syntax::{AstPtr, AstToken as _, ast}; use crate::{ builtin_type::BuiltinUint, @@ -32,8 +29,8 @@ impl<'db> ExprCollector<'db> { let mut args = FormatArgumentsCollector::default(); f.args().for_each(|arg| { args.add(FormatArgument { - kind: match arg.name() { - Some(name) => FormatArgumentKind::Named(name.as_name()), + kind: match arg.arg_name() { + Some(name) => FormatArgumentKind::Named(Name::new_root(name.name().text())), None => FormatArgumentKind::Normal, }, expr: self.collect_expr_opt(arg.expr()), diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html index 1b0512977a384..e178782c79c4e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html @@ -101,18 +101,18 @@ println!("Hello, {}!", "world"); // => "Hello, world!" println!("The number is {}", 1); // => "The number is 1" println!("{:?}", (3, 4)); // => "(3, 4)" - println!("{value}", value=4); // => "4" + println!("{value}", value=4); // => "4" println!("{} {}", 1, 2); // => "1 2" println!("{:04}", 42); // => "0042" with leading zerosV println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2" - println!("{argument}", argument = "test"); // => "test" - println!("{name} {}", 1, name = 2); // => "2 1" - println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" + println!("{argument}", argument = "test"); // => "test" + println!("{name} {}", 1, name = 2); // => "2 1" + println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" println!("{{{}}}", 2); // => "{2}" println!("Hello {:5}!", "x"); println!("Hello {:1$}!", "x", 5); println!("Hello {1:0$}!", 5, "x"); - println!("Hello {:width$}!", "x", width = 5); + println!("Hello {:width$}!", "x", width = 5); println!("Hello {:<5}!", "x"); println!("Hello {:-<5}!", "x"); println!("Hello {:^5}!", "x"); @@ -127,10 +127,10 @@ println!("Hello {0} is {2:.1$}", "x", 5, 0.01); println!("Hello {} is {:.*}", "x", 5, 0.01); println!("Hello {} is {2:.*}", "x", 5, 0.01); - println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01); - println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56); - println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56"); - println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56"); + println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01); + println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56); + println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56"); + println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56"); let _ = "{}" let _ = "{{}}"; @@ -154,8 +154,8 @@ let _ = c"\u{FF}\xFF"; // valid bytes, valid unicodes let backslash = r"\\"; - println!("{\x41}", A = 92); - println!("{ничоси}", ничоси = 92); + println!("{\x41}", A = 92); + println!("{ничоси}", ничоси = 92); println!("{:x?} {} ", thingy, n2); panic!("{}", 0); diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs index ab18309308544..d83e2eb2b4ae4 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs @@ -278,14 +278,20 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option { } Some(m.complete(p, OFFSET_OF_EXPR)) } else if p.eat_contextual_kw(T![format_args]) { + // test format_args_named_arg_keyword + // fn main() { + // builtin#format_args("{type}", type=1); + // } p.expect(T!['(']); expr(p); if p.eat(T![,]) { while !p.at(EOF) && !p.at(T![')']) { let m = p.start(); - if p.at(IDENT) && p.nth_at(1, T![=]) && !p.nth_at(2, T![=]) { - name(p); + if p.current().is_any_identifier() && p.nth_at(1, T![=]) && !p.nth_at(2, T![=]) { + let m = p.start(); + p.bump_any(); p.bump(T![=]); + m.complete(p, FORMAT_ARGS_ARG_NAME); } if expr(p).is_none() { m.abandon(p); diff --git a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs index 6cf2524c16c34..5d22d966b2b79 100644 --- a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs +++ b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs @@ -201,6 +201,7 @@ pub enum SyntaxKind { FN, FN_PTR_TYPE, FORMAT_ARGS_ARG, + FORMAT_ARGS_ARG_NAME, FORMAT_ARGS_EXPR, FOR_BINDER, FOR_EXPR, @@ -373,6 +374,7 @@ impl SyntaxKind { | FN | FN_PTR_TYPE | FORMAT_ARGS_ARG + | FORMAT_ARGS_ARG_NAME | FORMAT_ARGS_EXPR | FOR_BINDER | FOR_EXPR diff --git a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs index 7b0d32d9d1e45..9f919f6cea424 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs +++ b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs @@ -265,6 +265,10 @@ mod ok { #[test] fn for_type() { run_and_expect_no_errors("test_data/parser/inline/ok/for_type.rs"); } #[test] + fn format_args_named_arg_keyword() { + run_and_expect_no_errors("test_data/parser/inline/ok/format_args_named_arg_keyword.rs"); + } + #[test] fn frontmatter() { run_and_expect_no_errors("test_data/parser/inline/ok/frontmatter.rs"); } #[test] fn full_range_expr() { diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rast index 19a84ac540961..78c3bee4c88ae 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rast @@ -44,10 +44,10 @@ SOURCE_FILE COMMA "," WHITESPACE " " FORMAT_ARGS_ARG - NAME + FORMAT_ARGS_ARG_NAME IDENT "a" - WHITESPACE " " - EQ "=" + WHITESPACE " " + EQ "=" WHITESPACE " " BIN_EXPR LITERAL diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/format_args_named_arg_keyword.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/format_args_named_arg_keyword.rast new file mode 100644 index 0000000000000..03bc2ecf7c211 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/format_args_named_arg_keyword.rast @@ -0,0 +1,35 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "main" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + FORMAT_ARGS_EXPR + BUILTIN_KW "builtin" + POUND "#" + FORMAT_ARGS_KW "format_args" + L_PAREN "(" + LITERAL + STRING "\"{type}\"" + COMMA "," + WHITESPACE " " + FORMAT_ARGS_ARG + FORMAT_ARGS_ARG_NAME + TYPE_KW "type" + EQ "=" + LITERAL + INT_NUMBER "1" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/format_args_named_arg_keyword.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/format_args_named_arg_keyword.rs new file mode 100644 index 0000000000000..7af45894ed673 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/format_args_named_arg_keyword.rs @@ -0,0 +1,3 @@ +fn main() { + builtin#format_args("{type}", type=1); +} diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram index d73d60c51f0c8..991fe7d83a0e2 100644 --- a/src/tools/rust-analyzer/crates/syntax/rust.ungram +++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram @@ -438,7 +438,10 @@ FormatArgsExpr = ')' FormatArgsArg = - (Name '=')? Expr + arg_name:FormatArgsArgName? Expr + +FormatArgsArgName = + '=' // This also has a name, but it's any token and we can't put it here MacroExpr = MacroCall diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs index 6c1dcf336ac50..7b9f5b9166bbb 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs @@ -639,10 +639,16 @@ impl ForType { pub struct FormatArgsArg { pub(crate) syntax: SyntaxNode, } -impl ast::HasName for FormatArgsArg {} impl FormatArgsArg { + #[inline] + pub fn arg_name(&self) -> Option { support::child(&self.syntax) } #[inline] pub fn expr(&self) -> Option { support::child(&self.syntax) } +} +pub struct FormatArgsArgName { + pub(crate) syntax: SyntaxNode, +} +impl FormatArgsArgName { #[inline] pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) } } @@ -3722,6 +3728,38 @@ impl fmt::Debug for FormatArgsArg { f.debug_struct("FormatArgsArg").field("syntax", &self.syntax).finish() } } +impl AstNode for FormatArgsArgName { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + FORMAT_ARGS_ARG_NAME + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == FORMAT_ARGS_ARG_NAME } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for FormatArgsArgName { + fn hash(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for FormatArgsArgName {} +impl PartialEq for FormatArgsArgName { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for FormatArgsArgName { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for FormatArgsArgName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FormatArgsArgName").field("syntax", &self.syntax).finish() + } +} impl AstNode for FormatArgsExpr { #[inline] fn kind() -> SyntaxKind @@ -8947,7 +8985,6 @@ impl AstNode for AnyHasName { | CONST_PARAM | ENUM | FN - | FORMAT_ARGS_ARG | IDENT_PAT | MACRO_DEF | MACRO_RULES @@ -9006,10 +9043,6 @@ impl From for AnyHasName { #[inline] fn from(node: Fn) -> AnyHasName { AnyHasName { syntax: node.syntax } } } -impl From for AnyHasName { - #[inline] - fn from(node: FormatArgsArg) -> AnyHasName { AnyHasName { syntax: node.syntax } } -} impl From for AnyHasName { #[inline] fn from(node: IdentPat) -> AnyHasName { AnyHasName { syntax: node.syntax } } @@ -9541,6 +9574,11 @@ impl std::fmt::Display for FormatArgsArg { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for FormatArgsArgName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for FormatArgsExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs index 800dd5f4ac329..3357b250115c3 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs @@ -1118,6 +1118,15 @@ impl From for ast::AnyHasAttrs { } } +impl ast::FormatArgsArgName { + /// This is not a [`ast::Name`], because the name may be a keyword. + pub fn name(&self) -> SyntaxToken { + let name = self.syntax.first_token().unwrap(); + assert!(name.kind().is_any_identifier()); + name + } +} + impl ast::OrPat { pub fn leading_pipe(&self) -> Option { self.syntax From 6851e398ae023c49bfb9bda3aba9ebaf0efaca32 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 28 Dec 2025 04:13:49 +0200 Subject: [PATCH 52/77] Make token trees no longer generic over the span Since the proc macro server no longer uses them (with a different span type). --- src/tools/rust-analyzer/Cargo.lock | 2 + src/tools/rust-analyzer/crates/cfg/Cargo.toml | 3 + .../rust-analyzer/crates/cfg/src/cfg_expr.rs | 16 +- .../crates/hir-def/src/item_tree.rs | 2 +- .../crates/hir-def/src/item_tree/attrs.rs | 3 +- .../hir-expand/src/builtin/derive_macro.rs | 6 +- .../crates/hir-expand/src/builtin/quote.rs | 2 +- .../crates/hir-expand/src/lib.rs | 20 +- .../crates/hir-expand/src/name.rs | 2 +- .../crates/hir-expand/src/span_map.rs | 8 +- .../crates/ide/src/expand_macro.rs | 8 +- .../crates/load-cargo/src/lib.rs | 6 +- .../rust-analyzer/crates/mbe/src/benchmark.rs | 15 +- .../rust-analyzer/crates/mbe/src/expander.rs | 12 +- .../crates/mbe/src/expander/matcher.rs | 33 ++- .../crates/mbe/src/expander/transcriber.rs | 16 +- src/tools/rust-analyzer/crates/mbe/src/lib.rs | 18 +- .../rust-analyzer/crates/mbe/src/parser.rs | 48 ++-- .../src/bidirectional_protocol.rs | 7 +- .../proc-macro-api/src/legacy_protocol.rs | 7 +- .../proc-macro-api/src/legacy_protocol/msg.rs | 6 +- .../src/legacy_protocol/msg/flat.rs | 22 +- .../crates/proc-macro-api/src/lib.rs | 8 +- .../crates/proc-macro-api/src/process.rs | 6 +- .../rust-analyzer/crates/span/src/lib.rs | 18 +- .../rust-analyzer/crates/span/src/map.rs | 57 ++--- .../crates/syntax-bridge/src/lib.rs | 234 ++++++++---------- .../syntax-bridge/src/to_parser_input.rs | 11 +- src/tools/rust-analyzer/crates/tt/Cargo.toml | 1 + .../rust-analyzer/crates/tt/src/buffer.rs | 18 +- src/tools/rust-analyzer/crates/tt/src/iter.rs | 63 ++--- src/tools/rust-analyzer/crates/tt/src/lib.rs | 226 +++++++++-------- 32 files changed, 419 insertions(+), 485 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index a2eb8b1397b85..10927728a105f 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -234,6 +234,7 @@ dependencies = [ "intern", "oorandom", "rustc-hash 2.1.1", + "span", "syntax", "syntax-bridge", "tracing", @@ -3086,6 +3087,7 @@ dependencies = [ "arrayvec", "intern", "ra-ap-rustc_lexer", + "span", "stdx", "text-size 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/src/tools/rust-analyzer/crates/cfg/Cargo.toml b/src/tools/rust-analyzer/crates/cfg/Cargo.toml index 7207cfcf7dba1..cf2a7607b0192 100644 --- a/src/tools/rust-analyzer/crates/cfg/Cargo.toml +++ b/src/tools/rust-analyzer/crates/cfg/Cargo.toml @@ -19,6 +19,7 @@ tracing.workspace = true # locals deps tt = { workspace = true, optional = true } syntax = { workspace = true, optional = true } +span = { path = "../span", version = "0.0", optional = true } intern.workspace = true [dev-dependencies] @@ -35,6 +36,8 @@ cfg = { path = ".", default-features = false, features = ["tt"] } [features] default = [] +syntax = ["dep:syntax", "dep:span"] +tt = ["dep:tt"] in-rust-tree = [] [lints] diff --git a/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs b/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs index a0e0dc5ff090e..2f1fc8ae49e9d 100644 --- a/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs +++ b/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs @@ -96,12 +96,12 @@ impl CfgExpr { // FIXME: Parsing from `tt` is only used in a handful of places, reconsider // if we should switch them to AST. #[cfg(feature = "tt")] - pub fn parse(tt: &tt::TopSubtree) -> CfgExpr { + pub fn parse(tt: &tt::TopSubtree) -> CfgExpr { next_cfg_expr(&mut tt.iter()).unwrap_or(CfgExpr::Invalid) } #[cfg(feature = "tt")] - pub fn parse_from_iter(tt: &mut tt::iter::TtIter<'_, S>) -> CfgExpr { + pub fn parse_from_iter(tt: &mut tt::iter::TtIter<'_>) -> CfgExpr { next_cfg_expr(tt).unwrap_or(CfgExpr::Invalid) } @@ -149,7 +149,15 @@ fn next_cfg_expr_from_ast( if let Some(NodeOrToken::Token(literal)) = it.peek() && matches!(literal.kind(), SyntaxKind::STRING) { - let literal = tt::token_to_literal(literal.text(), ()).symbol; + let dummy_span = span::Span { + range: span::TextRange::empty(span::TextSize::new(0)), + anchor: span::SpanAnchor { + file_id: span::EditionedFileId::from_raw(0), + ast_id: span::FIXUP_ERASED_FILE_AST_ID_MARKER, + }, + ctx: span::SyntaxContext::root(span::Edition::Edition2015), + }; + let literal = tt::token_to_literal(literal.text(), dummy_span).symbol; it.next(); CfgAtom::KeyValue { key: name, value: literal.clone() }.into() } else { @@ -179,7 +187,7 @@ fn next_cfg_expr_from_ast( } #[cfg(feature = "tt")] -fn next_cfg_expr(it: &mut tt::iter::TtIter<'_, S>) -> Option { +fn next_cfg_expr(it: &mut tt::iter::TtIter<'_>) -> Option { use intern::sym; use tt::iter::TtElement; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs index 6eab8888d92d9..2e838fad2e9f8 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs @@ -103,7 +103,7 @@ fn lower_extra_crate_attrs<'a>( struct FakeSpanMap { file_id: span::EditionedFileId, } - impl syntax_bridge::SpanMapper for FakeSpanMap { + impl syntax_bridge::SpanMapper for FakeSpanMap { fn span_for(&self, range: TextRange) -> Span { Span { range, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/attrs.rs index 81a9b28b628eb..bfebce0013759 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/attrs.rs @@ -18,7 +18,6 @@ use hir_expand::{ name::Name, }; use intern::{Interned, Symbol, sym}; -use span::Span; use syntax::{AstNode, T, ast}; use syntax_bridge::DocCommentDesugarMode; use tt::token_to_literal; @@ -49,7 +48,7 @@ impl AttrsOrCfg { span_map: S, ) -> AttrsOrCfg where - S: syntax_bridge::SpanMapper + Copy, + S: syntax_bridge::SpanMapper + Copy, { let mut attrs = Vec::new(); let result = diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs index c805197425856..f208203c931b7 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs @@ -5,7 +5,7 @@ use intern::sym; use itertools::{Itertools, izip}; use parser::SyntaxKind; use rustc_hash::FxHashSet; -use span::{Edition, Span, SyntaxContext}; +use span::{Edition, Span}; use stdx::never; use syntax_bridge::DocCommentDesugarMode; use tracing::debug; @@ -238,7 +238,7 @@ fn parse_adt( fn parse_adt_from_syntax( adt: &ast::Adt, - tm: &span::SpanMap, + tm: &span::SpanMap, call_site: Span, ) -> Result { let (name, generic_param_list, where_clause, shape) = match &adt { @@ -390,7 +390,7 @@ fn to_adt_syntax( db: &dyn ExpandDatabase, tt: &tt::TopSubtree, call_site: Span, -) -> Result<(ast::Adt, span::SpanMap), ExpandError> { +) -> Result<(ast::Adt, span::SpanMap), ExpandError> { let (parsed, tm) = crate::db::token_tree_to_syntax_node(db, tt, crate::ExpandTo::Items); let macro_items = ast::MacroItems::cast(parsed.syntax_node()) .ok_or_else(|| ExpandError::other(call_site, "invalid item definition"))?; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs index 84dd4a24d901f..b1d46f5c4e76f 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs @@ -8,7 +8,7 @@ use tt::IdentIsRaw; use crate::{name::Name, tt::TopSubtreeBuilder}; -pub(crate) fn dollar_crate(span: Span) -> tt::Ident { +pub(crate) fn dollar_crate(span: Span) -> tt::Ident { tt::Ident { sym: sym::dollar_crate, span, is_raw: tt::IdentIsRaw::No } } 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 047996c978539..7b6a6135b3509 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -66,25 +66,7 @@ pub use crate::{ pub use base_db::EditionedFileId; pub use mbe::{DeclarativeMacro, MacroCallStyle, MacroCallStyles, ValueResult}; -pub mod tt { - pub use span::Span; - pub use tt::{DelimiterKind, IdentIsRaw, LitKind, Spacing, token_to_literal}; - - pub type Delimiter = ::tt::Delimiter; - pub type DelimSpan = ::tt::DelimSpan; - pub type Subtree = ::tt::Subtree; - pub type Leaf = ::tt::Leaf; - pub type Literal = ::tt::Literal; - pub type Punct = ::tt::Punct; - pub type Ident = ::tt::Ident; - pub type TokenTree = ::tt::TokenTree; - pub type TopSubtree = ::tt::TopSubtree; - pub type TopSubtreeBuilder = ::tt::TopSubtreeBuilder; - pub type TokenTreesView<'a> = ::tt::TokenTreesView<'a, Span>; - pub type SubtreeView<'a> = ::tt::SubtreeView<'a, Span>; - pub type TtElement<'a> = ::tt::iter::TtElement<'a, Span>; - pub type TtIter<'a> = ::tt::iter::TtIter<'a, Span>; -} +pub use tt; #[macro_export] macro_rules! impl_intern_lookup { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs index 1e5efb6e146ff..0408a6943d59f 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs @@ -258,7 +258,7 @@ impl AsName for ast::NameOrNameRef { } } -impl AsName for tt::Ident { +impl AsName for tt::Ident { fn as_name(&self) -> Name { Name::new_root(self.sym.as_str()) } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs b/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs index 8b0c0d72cd49d..586b8152947b6 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs @@ -1,6 +1,6 @@ //! Span maps for real files and macro expansions. -use span::{Span, SyntaxContext}; +use span::Span; use syntax::{AstNode, TextRange, ast}; use triomphe::Arc; @@ -8,7 +8,7 @@ pub use span::RealSpanMap; use crate::{HirFileId, MacroCallId, db::ExpandDatabase}; -pub type ExpansionSpanMap = span::SpanMap; +pub type ExpansionSpanMap = span::SpanMap; /// Spanmap for a macro file or a real file #[derive(Clone, Debug, PartialEq, Eq)] @@ -27,13 +27,13 @@ pub enum SpanMapRef<'a> { RealSpanMap(&'a RealSpanMap), } -impl syntax_bridge::SpanMapper for SpanMap { +impl syntax_bridge::SpanMapper for SpanMap { fn span_for(&self, range: TextRange) -> Span { self.span_for_range(range) } } -impl syntax_bridge::SpanMapper for SpanMapRef<'_> { +impl syntax_bridge::SpanMapper for SpanMapRef<'_> { fn span_for(&self, range: TextRange) -> Span { self.span_for_range(range) } diff --git a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs index d43f13b79dfc1..7d02b8091890a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs +++ b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs @@ -4,7 +4,7 @@ use ide_db::{ FileId, RootDatabase, base_db::Crate, helpers::pick_best_token, syntax_helpers::prettify_macro_expansion, }; -use span::{SpanMap, SyntaxContext, TextRange, TextSize}; +use span::{SpanMap, TextRange, TextSize}; use stdx::format_to; use syntax::{AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T, ast, ted}; @@ -142,7 +142,7 @@ fn expand_macro_recur( sema: &Semantics<'_, RootDatabase>, macro_call: &ast::Item, error: &mut String, - result_span_map: &mut SpanMap, + result_span_map: &mut SpanMap, offset_in_original_node: TextSize, ) -> Option { let ExpandResult { value: expanded, err } = match macro_call { @@ -171,7 +171,7 @@ fn expand( sema: &Semantics<'_, RootDatabase>, expanded: SyntaxNode, error: &mut String, - result_span_map: &mut SpanMap, + result_span_map: &mut SpanMap, mut offset_in_original_node: i32, ) -> SyntaxNode { let children = expanded.descendants().filter_map(ast::Item::cast); @@ -208,7 +208,7 @@ fn format( kind: SyntaxKind, file_id: FileId, expanded: SyntaxNode, - span_map: &SpanMap, + span_map: &SpanMap, krate: Crate, ) -> String { let expansion = prettify_macro_expansion(db, expanded, span_map, krate).to_string(); 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 a7b22b0d6a042..5122051fb3c2f 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -531,14 +531,14 @@ impl ProcMacroExpander for Expander { fn expand( &self, db: &dyn SourceDatabase, - subtree: &tt::TopSubtree, - attrs: Option<&tt::TopSubtree>, + subtree: &tt::TopSubtree, + attrs: Option<&tt::TopSubtree>, env: &Env, def_site: Span, call_site: Span, mixed_site: Span, current_dir: String, - ) -> Result, ProcMacroExpansionError> { + ) -> Result { let mut cb = |req| match req { SubRequest::SourceText { file_id, start, end } => { let file = FileId::from_raw(file_id); diff --git a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs index ffe3bdd06cfb2..e0f1e616d851e 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs @@ -2,7 +2,6 @@ use intern::Symbol; use rustc_hash::FxHashMap; -use span::Span; use stdx::itertools::Itertools; use syntax::{ AstNode, @@ -70,7 +69,7 @@ fn macro_rules_fixtures() -> FxHashMap { .collect() } -fn macro_rules_fixtures_tt() -> FxHashMap> { +fn macro_rules_fixtures_tt() -> FxHashMap { let fixture = bench_fixture::numerous_macro_rules(); let source_file = ast::SourceFile::parse(&fixture, span::Edition::CURRENT).ok().unwrap(); @@ -95,7 +94,7 @@ fn macro_rules_fixtures_tt() -> FxHashMap> { fn invocation_fixtures( db: &dyn salsa::Database, rules: &FxHashMap, -) -> Vec<(String, tt::TopSubtree)> { +) -> Vec<(String, tt::TopSubtree)> { let mut seed = 123456789; let mut res = Vec::new(); @@ -140,7 +139,7 @@ fn invocation_fixtures( } return res; - fn collect_from_op(op: &Op, builder: &mut tt::TopSubtreeBuilder, seed: &mut usize) { + fn collect_from_op(op: &Op, builder: &mut tt::TopSubtreeBuilder, seed: &mut usize) { return match op { Op::Var { kind, .. } => match kind.as_ref() { Some(MetaVarKind::Ident) => builder.push(make_ident("foo")), @@ -226,17 +225,17 @@ fn invocation_fixtures( *seed = usize::wrapping_add(usize::wrapping_mul(*seed, a), c); *seed } - fn make_ident(ident: &str) -> tt::Leaf { + fn make_ident(ident: &str) -> tt::Leaf { tt::Leaf::Ident(tt::Ident { span: DUMMY, sym: Symbol::intern(ident), is_raw: tt::IdentIsRaw::No, }) } - fn make_punct(char: char) -> tt::Leaf { + fn make_punct(char: char) -> tt::Leaf { tt::Leaf::Punct(tt::Punct { span: DUMMY, char, spacing: tt::Spacing::Alone }) } - fn make_literal(lit: &str) -> tt::Leaf { + fn make_literal(lit: &str) -> tt::Leaf { tt::Leaf::Literal(tt::Literal { span: DUMMY, symbol: Symbol::intern(lit), @@ -244,7 +243,7 @@ fn invocation_fixtures( suffix: None, }) } - fn make_subtree(kind: tt::DelimiterKind, builder: &mut tt::TopSubtreeBuilder) { + fn make_subtree(kind: tt::DelimiterKind, builder: &mut tt::TopSubtreeBuilder) { builder.open(kind, DUMMY); builder.close(DUMMY); } diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander.rs b/src/tools/rust-analyzer/crates/mbe/src/expander.rs index 274b779c1b2e3..36c7871519aa0 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander.rs @@ -17,11 +17,11 @@ use crate::{ pub(crate) fn expand_rules( db: &dyn salsa::Database, rules: &[crate::Rule], - input: &tt::TopSubtree, + input: &tt::TopSubtree, marker: impl Fn(&mut Span) + Copy, call_style: MacroCallStyle, call_site: Span, -) -> ExpandResult<(tt::TopSubtree, MatchedArmIndex)> { +) -> 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). @@ -129,7 +129,7 @@ enum Fragment<'a> { Empty, /// token fragments are just copy-pasted into the output Tokens { - tree: tt::TokenTreesView<'a, Span>, + tree: tt::TokenTreesView<'a>, origin: TokensOrigin, }, /// Expr ast fragments are surrounded with `()` on transcription to preserve precedence. @@ -141,7 +141,7 @@ enum Fragment<'a> { /// tricky to handle in the parser, and rustc doesn't handle those either. /// /// The span of the outer delimiters is marked on transcription. - Expr(tt::TokenTreesView<'a, Span>), + Expr(tt::TokenTreesView<'a>), /// There are roughly two types of paths: paths in expression context, where a /// separator `::` between an identifier and its following generic argument list /// is mandatory, and paths in type context, where `::` can be omitted. @@ -151,8 +151,8 @@ enum Fragment<'a> { /// and is trasncribed as an expression-context path, verbatim transcription /// would cause a syntax error. We need to fix it up just before transcribing; /// see `transcriber::fix_up_and_push_path_tt()`. - Path(tt::TokenTreesView<'a, Span>), - TokensOwned(tt::TopSubtree), + Path(tt::TokenTreesView<'a>), + TokensOwned(tt::TopSubtree), } impl Fragment<'_> { diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs index a21468fbb0d59..c4c1e1f5654f5 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs @@ -63,7 +63,6 @@ use std::{rc::Rc, sync::Arc}; use intern::{Symbol, sym}; use smallvec::{SmallVec, smallvec}; -use span::Span; use tt::{ DelimSpan, iter::{TtElement, TtIter}, @@ -114,7 +113,7 @@ impl Match<'_> { pub(super) fn match_<'t>( db: &dyn salsa::Database, pattern: &'t MetaTemplate, - input: &'t tt::TopSubtree, + input: &'t tt::TopSubtree, ) -> Match<'t> { let mut res = match_loop(db, pattern, input); res.bound_count = count(res.bindings.bindings()); @@ -339,7 +338,7 @@ struct MatchState<'t> { bindings: BindingsIdx, /// Cached result of meta variable parsing - meta_result: Option<(TtIter<'t, Span>, ExpandResult>>)>, + meta_result: Option<(TtIter<'t>, ExpandResult>>)>, /// Is error occurred in this state, will `poised` to "parent" is_error: bool, @@ -366,8 +365,8 @@ struct MatchState<'t> { #[inline] fn match_loop_inner<'t>( db: &dyn salsa::Database, - src: TtIter<'t, Span>, - stack: &[TtIter<'t, Span>], + src: TtIter<'t>, + stack: &[TtIter<'t>], res: &mut Match<'t>, bindings_builder: &mut BindingsBuilder<'t>, cur_items: &mut SmallVec<[MatchState<'t>; 1]>, @@ -375,7 +374,7 @@ fn match_loop_inner<'t>( next_items: &mut Vec>, eof_items: &mut SmallVec<[MatchState<'t>; 1]>, error_items: &mut SmallVec<[MatchState<'t>; 1]>, - delim_span: tt::DelimSpan, + delim_span: tt::DelimSpan, ) { macro_rules! try_push { ($items: expr, $it:expr) => { @@ -623,11 +622,11 @@ fn match_loop_inner<'t>( fn match_loop<'t>( db: &dyn salsa::Database, pattern: &'t MetaTemplate, - src: &'t tt::TopSubtree, + src: &'t tt::TopSubtree, ) -> Match<'t> { let span = src.top_subtree().delimiter.delim_span(); let mut src = src.iter(); - let mut stack: SmallVec<[TtIter<'_, Span>; 1]> = SmallVec::new(); + let mut stack: SmallVec<[TtIter<'_>; 1]> = SmallVec::new(); let mut res = Match::default(); let mut error_recover_item = None; @@ -774,8 +773,8 @@ fn match_loop<'t>( fn match_meta_var<'t>( db: &dyn salsa::Database, kind: MetaVarKind, - input: &mut TtIter<'t, Span>, - delim_span: DelimSpan, + input: &mut TtIter<'t>, + delim_span: DelimSpan, ) -> ExpandResult> { let fragment = match kind { MetaVarKind::Path => { @@ -879,10 +878,10 @@ fn collect_vars(collector_fun: &mut impl FnMut(Symbol), pattern: &MetaTemplate) } } impl MetaTemplate { - fn iter_delimited_with(&self, delimiter: tt::Delimiter) -> OpDelimitedIter<'_> { + fn iter_delimited_with(&self, delimiter: tt::Delimiter) -> OpDelimitedIter<'_> { OpDelimitedIter { inner: &self.0, idx: 0, delimited: delimiter } } - fn iter_delimited(&self, span: tt::DelimSpan) -> OpDelimitedIter<'_> { + fn iter_delimited(&self, span: tt::DelimSpan) -> OpDelimitedIter<'_> { OpDelimitedIter { inner: &self.0, idx: 0, @@ -901,7 +900,7 @@ enum OpDelimited<'a> { #[derive(Debug, Clone, Copy)] struct OpDelimitedIter<'a> { inner: &'a [Op], - delimited: tt::Delimiter, + delimited: tt::Delimiter, idx: usize, } @@ -945,7 +944,7 @@ impl<'a> Iterator for OpDelimitedIter<'a> { } } -fn expect_separator(iter: &mut TtIter<'_, S>, separator: &Separator) -> bool { +fn expect_separator(iter: &mut TtIter<'_>, separator: &Separator) -> bool { let mut fork = iter.clone(); let ok = match separator { Separator::Ident(lhs) => match fork.expect_ident_or_underscore() { @@ -979,7 +978,7 @@ fn expect_separator(iter: &mut TtIter<'_, S>, separator: &Separator) -> ok } -fn expect_tt(iter: &mut TtIter<'_, S>) -> Result<(), ()> { +fn expect_tt(iter: &mut TtIter<'_>) -> Result<(), ()> { if let Some(TtElement::Leaf(tt::Leaf::Punct(punct))) = iter.peek() { if punct.char == '\'' { expect_lifetime(iter)?; @@ -992,7 +991,7 @@ fn expect_tt(iter: &mut TtIter<'_, S>) -> Result<(), ()> { Ok(()) } -fn expect_lifetime<'a, S: Copy>(iter: &mut TtIter<'a, S>) -> Result<&'a tt::Ident, ()> { +fn expect_lifetime<'a>(iter: &mut TtIter<'a>) -> Result<&'a tt::Ident, ()> { let punct = iter.expect_single_punct()?; if punct.char != '\'' { return Err(()); @@ -1000,7 +999,7 @@ fn expect_lifetime<'a, S: Copy>(iter: &mut TtIter<'a, S>) -> Result<&'a tt::Iden iter.expect_ident_or_underscore() } -fn eat_char(iter: &mut TtIter<'_, S>, c: char) { +fn eat_char(iter: &mut TtIter<'_>, c: char) { if matches!(iter.peek(), Some(TtElement::Leaf(tt::Leaf::Punct(tt::Punct { char, .. }))) if *char == c) { iter.next().expect("already peeked"); diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs index 006ef1af806ef..a0fd4550ba8c1 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs @@ -125,7 +125,7 @@ pub(super) fn transcribe( bindings: &Bindings<'_>, marker: impl Fn(&mut Span) + Copy, call_site: Span, -) -> ExpandResult> { +) -> ExpandResult { let mut ctx = ExpandCtx { bindings, nesting: Vec::new(), call_site }; let mut builder = tt::TopSubtreeBuilder::new(tt::Delimiter::invisible_spanned(ctx.call_site)); expand_subtree(&mut ctx, template, &mut builder, marker).map(|()| builder.build()) @@ -152,8 +152,8 @@ struct ExpandCtx<'a> { fn expand_subtree_with_delimiter( ctx: &mut ExpandCtx<'_>, template: &MetaTemplate, - builder: &mut tt::TopSubtreeBuilder, - delimiter: Option>, + builder: &mut tt::TopSubtreeBuilder, + delimiter: Option, marker: impl Fn(&mut Span) + Copy, ) -> ExpandResult<()> { let delimiter = delimiter.unwrap_or_else(|| tt::Delimiter::invisible_spanned(ctx.call_site)); @@ -166,7 +166,7 @@ fn expand_subtree_with_delimiter( fn expand_subtree( ctx: &mut ExpandCtx<'_>, template: &MetaTemplate, - builder: &mut tt::TopSubtreeBuilder, + builder: &mut tt::TopSubtreeBuilder, marker: impl Fn(&mut Span) + Copy, ) -> ExpandResult<()> { let mut err = None; @@ -382,7 +382,7 @@ fn expand_var( ctx: &mut ExpandCtx<'_>, v: &Symbol, id: Span, - builder: &mut tt::TopSubtreeBuilder, + builder: &mut tt::TopSubtreeBuilder, marker: impl Fn(&mut Span) + Copy, ) -> ExpandResult<()> { // We already handle $crate case in mbe parser @@ -466,7 +466,7 @@ fn expand_repeat( template: &MetaTemplate, kind: RepeatKind, separator: Option<&Separator>, - builder: &mut tt::TopSubtreeBuilder, + builder: &mut tt::TopSubtreeBuilder, marker: impl Fn(&mut Span) + Copy, ) -> ExpandResult<()> { ctx.nesting.push(NestingState { idx: 0, at_end: false, hit: false }); @@ -546,8 +546,8 @@ fn expand_repeat( /// we need this fixup. fn fix_up_and_push_path_tt( ctx: &ExpandCtx<'_>, - builder: &mut tt::TopSubtreeBuilder, - subtree: tt::TokenTreesView<'_, Span>, + builder: &mut tt::TopSubtreeBuilder, + subtree: tt::TokenTreesView<'_>, ) { let mut prev_was_ident = false; // Note that we only need to fix up the top-level `TokenTree`s because the diff --git a/src/tools/rust-analyzer/crates/mbe/src/lib.rs b/src/tools/rust-analyzer/crates/mbe/src/lib.rs index 1193c4290c4cd..936cf178f015a 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/lib.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/lib.rs @@ -152,7 +152,7 @@ impl DeclarativeMacro { /// The old, `macro_rules! m {}` flavor. pub fn parse_macro_rules( - tt: &tt::TopSubtree, + tt: &tt::TopSubtree, ctx_edition: impl Copy + Fn(SyntaxContext) -> Edition, ) -> DeclarativeMacro { // Note: this parsing can be implemented using mbe machinery itself, by @@ -191,8 +191,8 @@ impl DeclarativeMacro { /// The new, unstable `macro m {}` flavor. pub fn parse_macro2( - args: Option<&tt::TopSubtree>, - body: &tt::TopSubtree, + args: Option<&tt::TopSubtree>, + body: &tt::TopSubtree, ctx_edition: impl Copy + Fn(SyntaxContext) -> Edition, ) -> DeclarativeMacro { let mut rules = Vec::new(); @@ -276,11 +276,11 @@ impl DeclarativeMacro { pub fn expand( &self, db: &dyn salsa::Database, - tt: &tt::TopSubtree, + tt: &tt::TopSubtree, marker: impl Fn(&mut Span) + Copy, call_style: MacroCallStyle, call_site: Span, - ) -> ExpandResult<(tt::TopSubtree, MatchedArmIndex)> { + ) -> ExpandResult<(tt::TopSubtree, MatchedArmIndex)> { expander::expand_rules(db, &self.rules, tt, marker, call_style, call_site) } } @@ -288,7 +288,7 @@ impl DeclarativeMacro { impl Rule { fn parse( edition: impl Copy + Fn(SyntaxContext) -> Edition, - src: &mut TtIter<'_, Span>, + src: &mut TtIter<'_>, ) -> Result { // Parse an optional `attr()` or `derive()` prefix before the LHS pattern. let style = parser::parse_rule_style(src)?; @@ -391,10 +391,10 @@ impl From> for ValueResult { pub fn expect_fragment<'t>( db: &dyn salsa::Database, - tt_iter: &mut TtIter<'t, Span>, + tt_iter: &mut TtIter<'t>, entry_point: ::parser::PrefixEntryPoint, - delim_span: DelimSpan, -) -> ExpandResult> { + delim_span: DelimSpan, +) -> ExpandResult> { use ::parser; let buffer = tt_iter.remaining(); let parser_input = to_parser_input(buffer, &mut |ctx| ctx.edition(db)); diff --git a/src/tools/rust-analyzer/crates/mbe/src/parser.rs b/src/tools/rust-analyzer/crates/mbe/src/parser.rs index e1cb98abae778..ddf9afbf98ab7 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/parser.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/parser.rs @@ -13,7 +13,7 @@ use tt::{ use crate::{MacroCallStyle, ParseError}; -pub(crate) fn parse_rule_style(src: &mut TtIter<'_, Span>) -> Result { +pub(crate) fn parse_rule_style(src: &mut TtIter<'_>) -> 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() @@ -59,14 +59,14 @@ pub(crate) struct MetaTemplate(pub(crate) Box<[Op]>); impl MetaTemplate { pub(crate) fn parse_pattern( edition: impl Copy + Fn(SyntaxContext) -> Edition, - pattern: TtIter<'_, Span>, + pattern: TtIter<'_>, ) -> Result { MetaTemplate::parse(edition, pattern, Mode::Pattern) } pub(crate) fn parse_template( edition: impl Copy + Fn(SyntaxContext) -> Edition, - template: TtIter<'_, Span>, + template: TtIter<'_>, ) -> Result { MetaTemplate::parse(edition, template, Mode::Template) } @@ -77,7 +77,7 @@ impl MetaTemplate { fn parse( edition: impl Copy + Fn(SyntaxContext) -> Edition, - mut src: TtIter<'_, Span>, + mut src: TtIter<'_>, mode: Mode, ) -> Result { let mut res = Vec::new(); @@ -123,23 +123,23 @@ pub(crate) enum Op { }, Subtree { tokens: MetaTemplate, - delimiter: tt::Delimiter, + delimiter: tt::Delimiter, }, - Literal(tt::Literal), - Punct(Box, MAX_GLUED_PUNCT_LEN>>), - Ident(tt::Ident), + Literal(tt::Literal), + Punct(Box>), + Ident(tt::Ident), } #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ConcatMetaVarExprElem { /// There is NO preceding dollar sign, which means that this identifier should be interpreted /// as a literal. - Ident(tt::Ident), + Ident(tt::Ident), /// There is a preceding dollar sign, which means that this identifier should be expanded /// and interpreted as a variable. - Var(tt::Ident), + Var(tt::Ident), /// For example, a number or a string. - Literal(tt::Literal), + Literal(tt::Literal), } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -179,10 +179,10 @@ pub(crate) enum MetaVarKind { #[derive(Clone, Debug, Eq)] pub(crate) enum Separator { - Literal(tt::Literal), - Ident(tt::Ident), - Puncts(ArrayVec, MAX_GLUED_PUNCT_LEN>), - Lifetime(tt::Punct, tt::Ident), + Literal(tt::Literal), + Ident(tt::Ident), + Puncts(ArrayVec), + Lifetime(tt::Punct, tt::Ident), } // Note that when we compare a Separator, we just care about its textual value. @@ -212,8 +212,8 @@ enum Mode { fn next_op( edition: impl Copy + Fn(SyntaxContext) -> Edition, - first_peeked: TtElement<'_, Span>, - src: &mut TtIter<'_, Span>, + first_peeked: TtElement<'_>, + src: &mut TtIter<'_>, mode: Mode, ) -> Result { let res = match first_peeked { @@ -320,7 +320,7 @@ fn next_op( fn eat_fragment_kind( edition: impl Copy + Fn(SyntaxContext) -> Edition, - src: &mut TtIter<'_, Span>, + src: &mut TtIter<'_>, mode: Mode, ) -> Result, ParseError> { if let Mode::Pattern = mode { @@ -363,11 +363,11 @@ fn eat_fragment_kind( Ok(None) } -fn is_boolean_literal(lit: &tt::Literal) -> bool { +fn is_boolean_literal(lit: &tt::Literal) -> bool { matches!(lit.symbol.as_str(), "true" | "false") } -fn parse_repeat(src: &mut TtIter<'_, Span>) -> Result<(Option, RepeatKind), ParseError> { +fn parse_repeat(src: &mut TtIter<'_>) -> Result<(Option, RepeatKind), ParseError> { let mut separator = Separator::Puncts(ArrayVec::new()); for tt in src { let tt = match tt { @@ -413,7 +413,7 @@ fn parse_repeat(src: &mut TtIter<'_, Span>) -> Result<(Option, Repeat Err(ParseError::InvalidRepeat) } -fn parse_metavar_expr(src: &mut TtIter<'_, Span>) -> Result { +fn parse_metavar_expr(src: &mut TtIter<'_>) -> Result { let func = src.expect_ident()?; let (args, mut args_iter) = src.expect_subtree()?; @@ -475,7 +475,7 @@ fn parse_metavar_expr(src: &mut TtIter<'_, Span>) -> Result { Ok(op) } -fn parse_depth(src: &mut TtIter<'_, Span>) -> Result { +fn parse_depth(src: &mut TtIter<'_>) -> Result { if src.is_empty() { Ok(0) } else if let tt::Leaf::Literal(tt::Literal { symbol: text, suffix: None, .. }) = @@ -488,7 +488,7 @@ fn parse_depth(src: &mut TtIter<'_, Span>) -> Result { } } -fn try_eat_comma(src: &mut TtIter<'_, Span>) -> bool { +fn try_eat_comma(src: &mut TtIter<'_>) -> bool { if let Some(TtElement::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', .. }))) = src.peek() { let _ = src.next(); return true; @@ -496,7 +496,7 @@ fn try_eat_comma(src: &mut TtIter<'_, Span>) -> bool { false } -fn try_eat_dollar(src: &mut TtIter<'_, Span>) -> bool { +fn try_eat_dollar(src: &mut TtIter<'_>) -> bool { if let Some(TtElement::Leaf(tt::Leaf::Punct(tt::Punct { char: '$', .. }))) = src.peek() { let _ = src.next(); return true; diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs index e919ff48fe4cb..e44723a6a3898 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol.rs @@ -138,16 +138,15 @@ pub(crate) fn find_proc_macros( pub(crate) fn expand( proc_macro: &ProcMacro, - subtree: tt::SubtreeView<'_, Span>, - attr: Option>, + subtree: tt::SubtreeView<'_>, + attr: Option>, env: Vec<(String, String)>, def_site: Span, call_site: Span, mixed_site: Span, current_dir: String, callback: SubCallback<'_>, -) -> Result>, String>, crate::ServerError> -{ +) -> Result, crate::ServerError> { let version = proc_macro.process.version(); let mut span_data_table = SpanDataIndexMap::default(); let def_site = span_data_table.insert_full(def_site).0; 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 0d16b60025d2f..22a7d9868e215 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 @@ -77,15 +77,14 @@ pub(crate) fn find_proc_macros( pub(crate) fn expand( proc_macro: &ProcMacro, - subtree: tt::SubtreeView<'_, Span>, - attr: Option>, + subtree: tt::SubtreeView<'_>, + attr: Option>, env: Vec<(String, String)>, def_site: Span, call_site: Span, mixed_site: Span, current_dir: String, -) -> Result>, String>, crate::ServerError> -{ +) -> Result, crate::ServerError> { let version = proc_macro.process.version(); let mut span_data_table = SpanDataIndexMap::default(); let def_site = span_data_table.insert_full(def_site).0; 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 0ebb0e9f93d59..ac627b9232a05 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 @@ -185,7 +185,7 @@ mod tests { use super::*; - fn fixture_token_tree_top_many_none() -> TopSubtree { + fn fixture_token_tree_top_many_none() -> TopSubtree { let anchor = SpanAnchor { file_id: span::EditionedFileId::new( span::FileId::from_raw(0xe4e4e), @@ -292,7 +292,7 @@ mod tests { builder.build() } - fn fixture_token_tree_top_empty_none() -> TopSubtree { + fn fixture_token_tree_top_empty_none() -> TopSubtree { let anchor = SpanAnchor { file_id: span::EditionedFileId::new( span::FileId::from_raw(0xe4e4e), @@ -318,7 +318,7 @@ mod tests { builder.build() } - fn fixture_token_tree_top_empty_brace() -> TopSubtree { + fn fixture_token_tree_top_empty_brace() -> TopSubtree { let anchor = SpanAnchor { file_id: span::EditionedFileId::new( span::FileId::from_raw(0xe4e4e), 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 1ac8cd4006a03..8c96b06700a97 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 @@ -123,7 +123,7 @@ struct IdentRepr { impl FlatTree { pub fn from_subtree( - subtree: tt::SubtreeView<'_, Span>, + subtree: tt::SubtreeView<'_>, version: u32, span_data_table: &mut SpanDataIndexMap, ) -> FlatTree { @@ -168,7 +168,7 @@ impl FlatTree { self, version: u32, span_data_table: &SpanDataIndexMap, - ) -> tt::TopSubtree { + ) -> tt::TopSubtree { Reader:: { subtree: if version >= ENCODE_CLOSE_SPAN_VERSION { read_vec(self.subtree, SubtreeRepr::read_with_close_span) @@ -486,8 +486,8 @@ struct Writer<'a, 'span, S: SpanTransformer, W> { text: Vec, } -impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIter<'a, T::Span>> { - fn write_subtree(&mut self, root: tt::SubtreeView<'a, T::Span>) { +impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIter<'a>> { + fn write_subtree(&mut self, root: tt::SubtreeView<'a>) { let subtree = root.top_subtree(); self.enqueue(subtree, root.iter()); while let Some((idx, len, subtree)) = self.work.pop_front() { @@ -495,7 +495,7 @@ impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIter<'a, T::Span>> { } } - fn subtree(&mut self, idx: usize, n_tt: usize, subtree: tt::iter::TtIter<'a, T::Span>) { + fn subtree(&mut self, idx: usize, n_tt: usize, subtree: tt::iter::TtIter<'a>) { let mut first_tt = self.token_tree.len(); self.token_tree.resize(first_tt + n_tt, !0); @@ -565,11 +565,7 @@ impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIter<'a, T::Span>> { } } - fn enqueue( - &mut self, - subtree: &'a tt::Subtree, - contents: tt::iter::TtIter<'a, T::Span>, - ) -> u32 { + fn enqueue(&mut self, subtree: &'a tt::Subtree, contents: tt::iter::TtIter<'a>) -> u32 { let idx = self.subtree.len(); let open = self.token_id_of(subtree.delimiter.open); let close = self.token_id_of(subtree.delimiter.close); @@ -739,9 +735,9 @@ struct Reader<'span, S: SpanTransformer> { span_data_table: &'span S::Table, } -impl Reader<'_, T> { - pub(crate) fn read_subtree(self) -> tt::TopSubtree { - let mut res: Vec, Vec>)>> = +impl> Reader<'_, T> { + pub(crate) fn read_subtree(self) -> tt::TopSubtree { + 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() { 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 0ee0c3afb584a..e0d20c82b9e27 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 @@ -192,7 +192,7 @@ impl ProcMacro { } /// On some server versions, the fixup ast id is different than ours. So change it to match. - fn change_fixup_to_match_old_server(&self, tt: &mut tt::TopSubtree) { + fn change_fixup_to_match_old_server(&self, tt: &mut tt::TopSubtree) { const OLD_FIXUP_AST_ID: ErasedFileAstId = ErasedFileAstId::from_raw(!0 - 1); let change_ast_id = |ast_id: &mut ErasedFileAstId| { if *ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { @@ -222,15 +222,15 @@ impl ProcMacro { /// This includes span information and environmental context. pub fn expand( &self, - subtree: tt::SubtreeView<'_, Span>, - attr: Option>, + subtree: tt::SubtreeView<'_>, + attr: Option>, env: Vec<(String, String)>, def_site: Span, call_site: Span, mixed_site: Span, current_dir: String, callback: Option>, - ) -> Result, String>, ServerError> { + ) -> Result, ServerError> { let (mut subtree, mut attr) = (subtree, attr); let (mut subtree_changed, mut attr_changed); if self.needs_fixup_change() { 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 01de8e98ff56b..f6a656e3ce3a4 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 @@ -204,15 +204,15 @@ impl ProcMacroServerProcess { pub(crate) fn expand( &self, proc_macro: &ProcMacro, - subtree: tt::SubtreeView<'_, Span>, - attr: Option>, + subtree: tt::SubtreeView<'_>, + attr: Option>, env: Vec<(String, String)>, def_site: Span, call_site: Span, mixed_site: Span, current_dir: String, callback: Option>, - ) -> Result, String>, ServerError> { + ) -> Result, ServerError> { match self.protocol { Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { legacy_protocol::expand( diff --git a/src/tools/rust-analyzer/crates/span/src/lib.rs b/src/tools/rust-analyzer/crates/span/src/lib.rs index 1a8aaeb71517c..bfe7b2620d56c 100644 --- a/src/tools/rust-analyzer/crates/span/src/lib.rs +++ b/src/tools/rust-analyzer/crates/span/src/lib.rs @@ -24,8 +24,6 @@ pub use syntax::Edition; pub use text_size::{TextRange, TextSize}; pub use vfs::FileId; -pub type Span = SpanData; - impl Span { pub fn cover(self, other: Span) -> Span { if self.anchor != other.anchor { @@ -61,13 +59,17 @@ impl Span { } Some(Span { range: self.range.cover(other.range), anchor: other.anchor, ctx: other.ctx }) } + + pub fn eq_ignoring_ctx(self, other: Self) -> bool { + self.anchor == other.anchor && self.range == other.range + } } /// Spans represent a region of code, used by the IDE to be able link macro inputs and outputs /// together. Positions in spans are relative to some [`SpanAnchor`] to make them more incremental /// friendly. #[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct SpanData { +pub struct Span { /// The text range of this span, relative to the anchor. /// We need the anchor for incrementality, as storing absolute ranges will require /// recomputation on every change in a file at all times. @@ -75,10 +77,10 @@ pub struct SpanData { /// The anchor this span is relative to. pub anchor: SpanAnchor, /// The syntax context of the span. - pub ctx: Ctx, + pub ctx: SyntaxContext, } -impl fmt::Debug for SpanData { +impl fmt::Debug for Span { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if f.alternate() { fmt::Debug::fmt(&self.anchor.file_id.file_id().index(), f)?; @@ -98,12 +100,6 @@ impl fmt::Debug for SpanData { } } -impl SpanData { - pub fn eq_ignoring_ctx(self, other: Self) -> bool { - self.anchor == other.anchor && self.range == other.range - } -} - impl fmt::Display for Span { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.anchor.file_id.file_id().index(), f)?; diff --git a/src/tools/rust-analyzer/crates/span/src/map.rs b/src/tools/rust-analyzer/crates/span/src/map.rs index d14c497474bd4..7faf6f1b1a689 100644 --- a/src/tools/rust-analyzer/crates/span/src/map.rs +++ b/src/tools/rust-analyzer/crates/span/src/map.rs @@ -6,24 +6,21 @@ use std::{fmt, hash::Hash}; use stdx::{always, itertools::Itertools}; use crate::{ - EditionedFileId, ErasedFileAstId, ROOT_ERASED_FILE_AST_ID, Span, SpanAnchor, SpanData, - SyntaxContext, TextRange, TextSize, + EditionedFileId, ErasedFileAstId, ROOT_ERASED_FILE_AST_ID, Span, SpanAnchor, SyntaxContext, + TextRange, TextSize, }; /// Maps absolute text ranges for the corresponding file to the relevant span data. #[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub struct SpanMap { +pub struct SpanMap { /// The offset stored here is the *end* of the node. - spans: Vec<(TextSize, SpanData)>, + spans: Vec<(TextSize, Span)>, /// Index of the matched macro arm on successful expansion for declarative macros. // FIXME: Does it make sense to have this here? pub matched_arm: Option, } -impl SpanMap -where - SpanData: Copy, -{ +impl SpanMap { /// Creates a new empty [`SpanMap`]. pub fn empty() -> Self { Self { spans: Vec::new(), matched_arm: None } @@ -40,7 +37,7 @@ where } /// Pushes a new span onto the [`SpanMap`]. - pub fn push(&mut self, offset: TextSize, span: SpanData) { + pub fn push(&mut self, offset: TextSize, span: Span) { if cfg!(debug_assertions) && let Some(&(last_offset, _)) = self.spans.last() { @@ -57,11 +54,8 @@ where /// Note this does a linear search through the entire backing vector. pub fn ranges_with_span_exact( &self, - span: SpanData, - ) -> impl Iterator + '_ - where - S: Copy, - { + span: Span, + ) -> impl Iterator + '_ { self.spans.iter().enumerate().filter_map(move |(idx, &(end, s))| { if !s.eq_ignoring_ctx(span) { return None; @@ -74,10 +68,10 @@ where /// Returns all [`TextRange`]s whose spans contain the given span. /// /// Note this does a linear search through the entire backing vector. - pub fn ranges_with_span(&self, span: SpanData) -> impl Iterator + '_ - where - S: Copy, - { + pub fn ranges_with_span( + &self, + span: Span, + ) -> impl Iterator + '_ { self.spans.iter().enumerate().filter_map(move |(idx, &(end, s))| { if s.anchor != span.anchor { return None; @@ -91,28 +85,28 @@ where } /// Returns the span at the given position. - pub fn span_at(&self, offset: TextSize) -> SpanData { + pub fn span_at(&self, offset: TextSize) -> Span { let entry = self.spans.partition_point(|&(it, _)| it <= offset); self.spans[entry].1 } /// Returns the spans associated with the given range. /// In other words, this will return all spans that correspond to all offsets within the given range. - pub fn spans_for_range(&self, range: TextRange) -> impl Iterator> + '_ { + pub fn spans_for_range(&self, range: TextRange) -> impl Iterator + '_ { let (start, end) = (range.start(), range.end()); let start_entry = self.spans.partition_point(|&(it, _)| it <= start); let end_entry = self.spans[start_entry..].partition_point(|&(it, _)| it <= end); // FIXME: this might be wrong? self.spans[start_entry..][..end_entry].iter().map(|&(_, s)| s) } - pub fn iter(&self) -> impl Iterator)> + '_ { + pub fn iter(&self) -> impl Iterator + '_ { self.spans.iter().copied() } /// Merges this span map with another span map, where `other` is inserted at (and replaces) `other_range`. /// /// The length of the replacement node needs to be `other_size`. - pub fn merge(&mut self, other_range: TextRange, other_size: TextSize, other: &SpanMap) { + pub fn merge(&mut self, other_range: TextRange, other_size: TextSize, other: &SpanMap) { // I find the following diagram helpful to illustrate the bounds and why we use `<` or `<=`: // -------------------------------------------------------------------- // 1 3 5 6 7 10 11 <-- offsets we store @@ -157,7 +151,7 @@ where } #[cfg(not(no_salsa_async_drops))] -impl Drop for SpanMap { +impl Drop for SpanMap { fn drop(&mut self) { struct SendPtr(*mut [()]); unsafe impl Send for SendPtr {} @@ -175,18 +169,17 @@ impl Drop for SpanMap { }) .send(( unsafe { - SendPtr(std::mem::transmute::<*mut [(TextSize, SpanData)], *mut [()]>( - Box::<[(TextSize, SpanData)]>::into_raw( - std::mem::take(&mut self.spans).into_boxed_slice(), - ), - )) + SendPtr(std::mem::transmute::<*mut [(TextSize, Span)], *mut [()]>(Box::< + [(TextSize, Span)], + >::into_raw( + std::mem::take(&mut self.spans).into_boxed_slice(), + ))) }, |b: SendPtr| { _ = unsafe { - Box::from_raw(std::mem::transmute::< - *mut [()], - *mut [(TextSize, SpanData)], - >(b.0)) + Box::from_raw(std::mem::transmute::<*mut [()], *mut [(TextSize, Span)]>( + b.0, + )) } }, )) diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs index 815b4f2799005..82cf51f1627a0 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs @@ -9,7 +9,7 @@ use std::{collections::VecDeque, fmt, hash::Hash}; use intern::Symbol; use rustc_hash::{FxHashMap, FxHashSet}; -use span::{Edition, SpanAnchor, SpanData, SpanMap}; +use span::{Edition, Span, SpanAnchor, SpanMap, SyntaxContext}; use stdx::{format_to, never}; use syntax::{ AstToken, Parse, PreorderWithTokens, SmolStr, SyntaxElement, @@ -29,21 +29,18 @@ pub use ::parser::TopEntryPoint; #[cfg(test)] mod tests; -pub trait SpanMapper { - fn span_for(&self, range: TextRange) -> S; +pub trait SpanMapper { + fn span_for(&self, range: TextRange) -> Span; } -impl SpanMapper> for SpanMap -where - SpanData: Copy, -{ - fn span_for(&self, range: TextRange) -> SpanData { +impl SpanMapper for SpanMap { + fn span_for(&self, range: TextRange) -> Span { self.span_at(range.start()) } } -impl> SpanMapper for &SM { - fn span_for(&self, range: TextRange) -> S { +impl SpanMapper for &SM { + fn span_for(&self, range: TextRange) -> Span { SM::span_for(self, range) } } @@ -69,7 +66,7 @@ pub mod dummy_test_span_utils { pub struct DummyTestSpanMap; - impl SpanMapper for DummyTestSpanMap { + impl SpanMapper for DummyTestSpanMap { fn span_for(&self, range: syntax::TextRange) -> Span { Span { range, @@ -97,15 +94,14 @@ pub enum DocCommentDesugarMode { /// Converts a syntax tree to a [`tt::Subtree`] using the provided span map to populate the /// subtree's spans. -pub fn syntax_node_to_token_tree( +pub fn syntax_node_to_token_tree( node: &SyntaxNode, map: SpanMap, - span: SpanData, + span: Span, mode: DocCommentDesugarMode, -) -> tt::TopSubtree> +) -> tt::TopSubtree where - SpanData: Copy + fmt::Debug, - SpanMap: SpanMapper>, + SpanMap: SpanMapper, { let mut c = Converter::new(node, map, Default::default(), Default::default(), span, mode, |_, _| { @@ -117,22 +113,18 @@ where /// Converts a syntax tree to a [`tt::Subtree`] using the provided span map to populate the /// subtree's spans. Additionally using the append and remove parameters, the additional tokens can /// be injected or hidden from the output. -pub fn syntax_node_to_token_tree_modified( +pub fn syntax_node_to_token_tree_modified( node: &SyntaxNode, map: SpanMap, - append: FxHashMap>>>, + append: FxHashMap>, remove: FxHashSet, - call_site: SpanData, + call_site: Span, mode: DocCommentDesugarMode, on_enter: OnEvent, -) -> tt::TopSubtree> +) -> tt::TopSubtree where - SpanMap: SpanMapper>, - SpanData: Copy + fmt::Debug, - OnEvent: FnMut( - &mut PreorderWithTokens, - &WalkEvent, - ) -> (bool, Vec>>), + SpanMap: SpanMapper, + OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent) -> (bool, Vec), { let mut c = Converter::new(node, map, append, remove, call_site, mode, on_enter); convert_tokens(&mut c) @@ -152,13 +144,13 @@ where /// Converts a [`tt::Subtree`] back to a [`SyntaxNode`]. /// The produced `SpanMap` contains a mapping from the syntax nodes offsets to the subtree's spans. -pub fn token_tree_to_syntax_node( - tt: &tt::TopSubtree>, +pub fn token_tree_to_syntax_node( + tt: &tt::TopSubtree, entry_point: parser::TopEntryPoint, - span_to_edition: &mut dyn FnMut(Ctx) -> Edition, -) -> (Parse, SpanMap) + span_to_edition: &mut dyn FnMut(SyntaxContext) -> Edition, +) -> (Parse, SpanMap) where - Ctx: Copy + fmt::Debug + PartialEq + PartialEq + Eq + Hash, + SyntaxContext: Copy + fmt::Debug + PartialEq + PartialEq + Eq + Hash, { let buffer = tt.view().strip_invisible(); let parser_input = to_parser_input(buffer, span_to_edition); @@ -183,16 +175,12 @@ where /// Convert a string to a `TokenTree`. The spans of the subtree will be anchored to the provided /// anchor with the given context. -pub fn parse_to_token_tree( +pub fn parse_to_token_tree( edition: Edition, anchor: SpanAnchor, - ctx: Ctx, + ctx: SyntaxContext, text: &str, -) -> Option>> -where - SpanData: Copy + fmt::Debug, - Ctx: Copy, -{ +) -> Option { let lexed = parser::LexedStr::new(edition, text); if lexed.errors().next().is_some() { return None; @@ -203,14 +191,11 @@ where } /// Convert a string to a `TokenTree`. The passed span will be used for all spans of the produced subtree. -pub fn parse_to_token_tree_static_span( +pub fn parse_to_token_tree_static_span( edition: Edition, - span: S, + span: Span, text: &str, -) -> Option> -where - S: Copy + fmt::Debug, -{ +) -> Option { let lexed = parser::LexedStr::new(edition, text); if lexed.errors().next().is_some() { return None; @@ -220,10 +205,9 @@ where Some(convert_tokens(&mut conv)) } -fn convert_tokens(conv: &mut C) -> tt::TopSubtree +fn convert_tokens(conv: &mut C) -> tt::TopSubtree where - C: TokenConverter, - S: Copy + fmt::Debug, + C: TokenConverter, C::Token: fmt::Debug, { let mut builder = @@ -327,7 +311,7 @@ where .into() }; } - let leaf: tt::Leaf<_> = match kind { + let leaf: tt::Leaf = match kind { k if k.is_any_identifier() => { let text = token.to_text(conv); tt::Ident::new(&text, conv.span_for(abs_range)).into() @@ -435,11 +419,11 @@ pub fn desugar_doc_comment_text(text: &str, mode: DocCommentDesugarMode) -> (Sym } } -fn convert_doc_comment( +fn convert_doc_comment( token: &syntax::SyntaxToken, - span: S, + span: Span, mode: DocCommentDesugarMode, - builder: &mut tt::TopSubtreeBuilder, + builder: &mut tt::TopSubtreeBuilder, ) { let Some(comment) = ast::Comment::cast(token.clone()) else { return }; let Some(doc) = comment.kind().doc else { return }; @@ -479,92 +463,84 @@ fn convert_doc_comment( } /// A raw token (straight from lexer) converter -struct RawConverter<'a, Ctx> { +struct RawConverter<'a> { lexed: parser::LexedStr<'a>, pos: usize, anchor: SpanAnchor, - ctx: Ctx, + ctx: SyntaxContext, mode: DocCommentDesugarMode, } /// A raw token (straight from lexer) converter that gives every token the same span. -struct StaticRawConverter<'a, S> { +struct StaticRawConverter<'a> { lexed: parser::LexedStr<'a>, pos: usize, - span: S, + span: Span, mode: DocCommentDesugarMode, } -trait SrcToken { +trait SrcToken { fn kind(&self, ctx: &Ctx) -> SyntaxKind; fn to_char(&self, ctx: &Ctx) -> Option; fn to_text(&self, ctx: &Ctx) -> SmolStr; - fn as_leaf(&self) -> Option<&tt::Leaf> { + fn as_leaf(&self) -> Option<&tt::Leaf> { None } } -trait TokenConverter: Sized { - type Token: SrcToken; +trait TokenConverter: Sized { + type Token: SrcToken; fn convert_doc_comment( &self, token: &Self::Token, - span: S, - builder: &mut tt::TopSubtreeBuilder, + span: Span, + builder: &mut tt::TopSubtreeBuilder, ); fn bump(&mut self) -> Option<(Self::Token, TextRange)>; fn peek(&self) -> Option; - fn span_for(&self, range: TextRange) -> S; + fn span_for(&self, range: TextRange) -> Span; - fn call_site(&self) -> S; + fn call_site(&self) -> Span; } -impl SrcToken, S> for usize { - fn kind(&self, ctx: &RawConverter<'_, Ctx>) -> SyntaxKind { +impl SrcToken> for usize { + fn kind(&self, ctx: &RawConverter<'_>) -> SyntaxKind { ctx.lexed.kind(*self) } - fn to_char(&self, ctx: &RawConverter<'_, Ctx>) -> Option { + fn to_char(&self, ctx: &RawConverter<'_>) -> Option { ctx.lexed.text(*self).chars().next() } - fn to_text(&self, ctx: &RawConverter<'_, Ctx>) -> SmolStr { + fn to_text(&self, ctx: &RawConverter<'_>) -> SmolStr { ctx.lexed.text(*self).into() } } -impl SrcToken, S> for usize { - fn kind(&self, ctx: &StaticRawConverter<'_, S>) -> SyntaxKind { +impl SrcToken> for usize { + fn kind(&self, ctx: &StaticRawConverter<'_>) -> SyntaxKind { ctx.lexed.kind(*self) } - fn to_char(&self, ctx: &StaticRawConverter<'_, S>) -> Option { + fn to_char(&self, ctx: &StaticRawConverter<'_>) -> Option { ctx.lexed.text(*self).chars().next() } - fn to_text(&self, ctx: &StaticRawConverter<'_, S>) -> SmolStr { + fn to_text(&self, ctx: &StaticRawConverter<'_>) -> SmolStr { ctx.lexed.text(*self).into() } } -impl TokenConverter> for RawConverter<'_, Ctx> -where - SpanData: Copy, -{ +impl TokenConverter for RawConverter<'_> { type Token = usize; - fn convert_doc_comment( - &self, - &token: &usize, - span: SpanData, - builder: &mut tt::TopSubtreeBuilder>, - ) { + fn convert_doc_comment(&self, &token: &usize, span: Span, builder: &mut tt::TopSubtreeBuilder) { let text = self.lexed.text(token); convert_doc_comment(&doc_comment(text), span, self.mode, builder); } @@ -588,22 +564,19 @@ where Some(self.pos) } - fn span_for(&self, range: TextRange) -> SpanData { - SpanData { range, anchor: self.anchor, ctx: self.ctx } + fn span_for(&self, range: TextRange) -> Span { + Span { range, anchor: self.anchor, ctx: self.ctx } } - fn call_site(&self) -> SpanData { - SpanData { range: TextRange::empty(0.into()), anchor: self.anchor, ctx: self.ctx } + fn call_site(&self) -> Span { + Span { range: TextRange::empty(0.into()), anchor: self.anchor, ctx: self.ctx } } } -impl TokenConverter for StaticRawConverter<'_, S> -where - S: Copy, -{ +impl TokenConverter for StaticRawConverter<'_> { type Token = usize; - fn convert_doc_comment(&self, &token: &usize, span: S, builder: &mut tt::TopSubtreeBuilder) { + fn convert_doc_comment(&self, &token: &usize, span: Span, builder: &mut tt::TopSubtreeBuilder) { let text = self.lexed.text(token); convert_doc_comment(&doc_comment(text), span, self.mode, builder); } @@ -627,40 +600,40 @@ where Some(self.pos) } - fn span_for(&self, _: TextRange) -> S { + fn span_for(&self, _: TextRange) -> Span { self.span } - fn call_site(&self) -> S { + fn call_site(&self) -> Span { self.span } } -struct Converter { +struct Converter { current: Option, - current_leaves: VecDeque>, + current_leaves: VecDeque, preorder: PreorderWithTokens, range: TextRange, punct_offset: Option<(SyntaxToken, TextSize)>, /// Used to make the emitted text ranges in the spans relative to the span anchor. map: SpanMap, - append: FxHashMap>>, + append: FxHashMap>, remove: FxHashSet, - call_site: S, + call_site: Span, mode: DocCommentDesugarMode, on_event: OnEvent, } -impl Converter +impl Converter where - OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent) -> (bool, Vec>), + OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent) -> (bool, Vec), { fn new( node: &SyntaxNode, map: SpanMap, - append: FxHashMap>>, + append: FxHashMap>, remove: FxHashSet, - call_site: S, + call_site: Span, mode: DocCommentDesugarMode, on_enter: OnEvent, ) -> Self { @@ -720,13 +693,13 @@ where } #[derive(Debug)] -enum SynToken { +enum SynToken { Ordinary(SyntaxToken), Punct { token: SyntaxToken, offset: usize }, - Leaf(tt::Leaf), + Leaf(tt::Leaf), } -impl SynToken { +impl SynToken { fn token(&self) -> &SyntaxToken { match self { SynToken::Ordinary(it) | SynToken::Punct { token: it, offset: _ } => it, @@ -735,8 +708,8 @@ impl SynToken { } } -impl SrcToken, S> for SynToken { - fn kind(&self, _ctx: &Converter) -> SyntaxKind { +impl SrcToken> for SynToken { + fn kind(&self, _ctx: &Converter) -> SyntaxKind { match self { SynToken::Ordinary(token) => token.kind(), SynToken::Punct { token, offset: i } => { @@ -748,14 +721,14 @@ impl SrcToken, S> for SynTok } } } - fn to_char(&self, _ctx: &Converter) -> Option { + fn to_char(&self, _ctx: &Converter) -> Option { match self { SynToken::Ordinary(_) => None, SynToken::Punct { token: it, offset: i } => it.text().chars().nth(*i), SynToken::Leaf(_) => None, } } - fn to_text(&self, _ctx: &Converter) -> SmolStr { + fn to_text(&self, _ctx: &Converter) -> SmolStr { match self { SynToken::Ordinary(token) | SynToken::Punct { token, offset: _ } => token.text().into(), SynToken::Leaf(_) => { @@ -764,7 +737,7 @@ impl SrcToken, S> for SynTok } } } - fn as_leaf(&self) -> Option<&tt::Leaf> { + fn as_leaf(&self) -> Option<&tt::Leaf> { match self { SynToken::Ordinary(_) | SynToken::Punct { .. } => None, SynToken::Leaf(it) => Some(it), @@ -772,18 +745,17 @@ impl SrcToken, S> for SynTok } } -impl TokenConverter for Converter +impl TokenConverter for Converter where - S: Copy, - SpanMap: SpanMapper, - OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent) -> (bool, Vec>), + SpanMap: SpanMapper, + OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent) -> (bool, Vec), { - type Token = SynToken; + type Token = SynToken; fn convert_doc_comment( &self, token: &Self::Token, - span: S, - builder: &mut tt::TopSubtreeBuilder, + span: Span, + builder: &mut tt::TopSubtreeBuilder, ) { convert_doc_comment(token.token(), span, self.mode, builder); } @@ -847,30 +819,24 @@ where Some(token) } - fn span_for(&self, range: TextRange) -> S { + fn span_for(&self, range: TextRange) -> Span { self.map.span_for(range) } - fn call_site(&self) -> S { + fn call_site(&self) -> Span { self.call_site } } -struct TtTreeSink<'a, Ctx> -where - SpanData: Copy, -{ +struct TtTreeSink<'a> { buf: String, - cursor: Cursor<'a, SpanData>, + cursor: Cursor<'a>, text_pos: TextSize, inner: SyntaxTreeBuilder, - token_map: SpanMap, + token_map: SpanMap, } -impl<'a, Ctx> TtTreeSink<'a, Ctx> -where - SpanData: Copy, -{ - fn new(cursor: Cursor<'a, SpanData>) -> Self { +impl<'a> TtTreeSink<'a> { + fn new(cursor: Cursor<'a>) -> Self { TtTreeSink { buf: String::new(), cursor, @@ -880,7 +846,7 @@ where } } - fn finish(mut self) -> (Parse, SpanMap) { + fn finish(mut self) -> (Parse, SpanMap) { self.token_map.finish(); (self.inner.finish(), self.token_map) } @@ -898,11 +864,7 @@ fn delim_to_str(d: tt::DelimiterKind, closing: bool) -> Option<&'static str> { Some(&texts[idx..texts.len() - (1 - idx)]) } -impl TtTreeSink<'_, Ctx> -where - SpanData: Copy + fmt::Debug, - Ctx: PartialEq, -{ +impl TtTreeSink<'_> { /// Parses a float literal as if it was a one to two name ref nodes with a dot inbetween. /// This occurs when a float literal is used as a field access. fn float_split(&mut self, has_pseudo_dot: bool) { @@ -1053,10 +1015,10 @@ where self.inner.error(error, self.text_pos) } - fn merge_spans(a: SpanData, b: SpanData) -> SpanData { + fn merge_spans(a: Span, b: Span) -> Span { // We don't do what rustc does exactly, rustc does something clever when the spans have different syntax contexts // but this runs afoul of our separation between `span` and `hir-expand`. - SpanData { + Span { range: if a.ctx == b.ctx && a.anchor == b.anchor { TextRange::new( std::cmp::min(a.range.start(), b.range.start()), diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/to_parser_input.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/to_parser_input.rs index 5922994c084c9..6883f71307c81 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/to_parser_input.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/to_parser_input.rs @@ -1,16 +1,13 @@ //! Convert macro-by-example tokens which are specific to macro expansion into a //! format that works for our parser. -use std::fmt; -use std::hash::Hash; - use rustc_hash::FxHashMap; -use span::{Edition, SpanData}; +use span::{Edition, SyntaxContext}; use syntax::{SyntaxKind, SyntaxKind::*, T}; -pub fn to_parser_input( - buffer: tt::TokenTreesView<'_, SpanData>, - span_to_edition: &mut dyn FnMut(Ctx) -> Edition, +pub fn to_parser_input( + buffer: tt::TokenTreesView<'_>, + span_to_edition: &mut dyn FnMut(SyntaxContext) -> Edition, ) -> parser::Input { let mut res = parser::Input::with_capacity(buffer.len()); diff --git a/src/tools/rust-analyzer/crates/tt/Cargo.toml b/src/tools/rust-analyzer/crates/tt/Cargo.toml index 3183b72a6629a..4b38bfb1e5360 100644 --- a/src/tools/rust-analyzer/crates/tt/Cargo.toml +++ b/src/tools/rust-analyzer/crates/tt/Cargo.toml @@ -16,6 +16,7 @@ doctest = false arrayvec.workspace = true text-size.workspace = true +span = { path = "../span", version = "0.0", default-features = false } stdx.workspace = true intern.workspace = true ra-ap-rustc_lexer.workspace = true diff --git a/src/tools/rust-analyzer/crates/tt/src/buffer.rs b/src/tools/rust-analyzer/crates/tt/src/buffer.rs index 02a722895a4b8..c464e5ece1949 100644 --- a/src/tools/rust-analyzer/crates/tt/src/buffer.rs +++ b/src/tools/rust-analyzer/crates/tt/src/buffer.rs @@ -3,14 +3,14 @@ //! We use this as the source of tokens for parser. use crate::{Leaf, Subtree, TokenTree, TokenTreesView}; -pub struct Cursor<'a, Span> { - buffer: &'a [TokenTree], +pub struct Cursor<'a> { + buffer: &'a [TokenTree], index: usize, subtrees_stack: Vec, } -impl<'a, Span: Copy> Cursor<'a, Span> { - pub fn new(buffer: &'a [TokenTree]) -> Self { +impl<'a> Cursor<'a> { + pub fn new(buffer: &'a [TokenTree]) -> Self { Self { buffer, index: 0, subtrees_stack: Vec::new() } } @@ -23,7 +23,7 @@ impl<'a, Span: Copy> Cursor<'a, Span> { self.subtrees_stack.is_empty() } - fn last_subtree(&self) -> Option<(usize, &'a Subtree)> { + fn last_subtree(&self) -> Option<(usize, &'a Subtree)> { self.subtrees_stack.last().map(|&subtree_idx| { let TokenTree::Subtree(subtree) = &self.buffer[subtree_idx] else { panic!("subtree pointing to non-subtree"); @@ -32,7 +32,7 @@ impl<'a, Span: Copy> Cursor<'a, Span> { }) } - pub fn end(&mut self) -> &'a Subtree { + pub fn end(&mut self) -> &'a Subtree { let (last_subtree_idx, last_subtree) = self.last_subtree().expect("called `Cursor::end()` without an open subtree"); // +1 because `Subtree.len` excludes the subtree itself. @@ -46,7 +46,7 @@ impl<'a, Span: Copy> Cursor<'a, Span> { } /// Returns the `TokenTree` at the cursor if it is not at the end of a subtree. - pub fn token_tree(&self) -> Option<&'a TokenTree> { + pub fn token_tree(&self) -> Option<&'a TokenTree> { if let Some((last_subtree_idx, last_subtree)) = self.last_subtree() { // +1 because `Subtree.len` excludes the subtree itself. if last_subtree_idx + last_subtree.usize_len() + 1 == self.index { @@ -87,7 +87,7 @@ impl<'a, Span: Copy> Cursor<'a, Span> { self.index += 1; } - pub fn peek_two_leaves(&self) -> Option<[&'a Leaf; 2]> { + pub fn peek_two_leaves(&self) -> Option<[&'a Leaf; 2]> { if let Some((last_subtree_idx, last_subtree)) = self.last_subtree() { // +1 because `Subtree.len` excludes the subtree itself. let last_end = last_subtree_idx + last_subtree.usize_len() + 1; @@ -101,7 +101,7 @@ impl<'a, Span: Copy> Cursor<'a, Span> { }) } - pub fn crossed(&self) -> TokenTreesView<'a, Span> { + pub fn crossed(&self) -> TokenTreesView<'a> { assert!(self.is_root()); TokenTreesView::new(&self.buffer[..self.index]) } diff --git a/src/tools/rust-analyzer/crates/tt/src/iter.rs b/src/tools/rust-analyzer/crates/tt/src/iter.rs index 2e89d762a0e40..88c3c7f52e93d 100644 --- a/src/tools/rust-analyzer/crates/tt/src/iter.rs +++ b/src/tools/rust-analyzer/crates/tt/src/iter.rs @@ -5,31 +5,32 @@ use std::fmt; use arrayvec::ArrayVec; use intern::sym; +use span::Span; use crate::{Ident, Leaf, MAX_GLUED_PUNCT_LEN, Punct, Spacing, Subtree, TokenTree, TokenTreesView}; #[derive(Clone)] -pub struct TtIter<'a, S> { - inner: std::slice::Iter<'a, TokenTree>, +pub struct TtIter<'a> { + inner: std::slice::Iter<'a, TokenTree>, } -impl fmt::Debug for TtIter<'_, S> { +impl fmt::Debug for TtIter<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TtIter").field("remaining", &self.remaining()).finish() } } #[derive(Clone, Copy)] -pub struct TtIterSavepoint<'a, S>(&'a [TokenTree]); +pub struct TtIterSavepoint<'a>(&'a [TokenTree]); -impl<'a, S: Copy> TtIterSavepoint<'a, S> { - pub fn remaining(self) -> TokenTreesView<'a, S> { +impl<'a> TtIterSavepoint<'a> { + pub fn remaining(self) -> TokenTreesView<'a> { TokenTreesView::new(self.0) } } -impl<'a, S: Copy> TtIter<'a, S> { - pub(crate) fn new(tt: &'a [TokenTree]) -> TtIter<'a, S> { +impl<'a> TtIter<'a> { + pub(crate) fn new(tt: &'a [TokenTree]) -> TtIter<'a> { TtIter { inner: tt.iter() } } @@ -49,14 +50,14 @@ impl<'a, S: Copy> TtIter<'a, S> { } } - pub fn expect_subtree(&mut self) -> Result<(&'a Subtree, TtIter<'a, S>), ()> { + pub fn expect_subtree(&mut self) -> Result<(&'a Subtree, TtIter<'a>), ()> { match self.next() { Some(TtElement::Subtree(subtree, iter)) => Ok((subtree, iter)), _ => Err(()), } } - pub fn expect_leaf(&mut self) -> Result<&'a Leaf, ()> { + pub fn expect_leaf(&mut self) -> Result<&'a Leaf, ()> { match self.next() { Some(TtElement::Leaf(it)) => Ok(it), _ => Err(()), @@ -77,21 +78,21 @@ impl<'a, S: Copy> TtIter<'a, S> { } } - pub fn expect_ident(&mut self) -> Result<&'a Ident, ()> { + pub fn expect_ident(&mut self) -> Result<&'a Ident, ()> { match self.expect_leaf()? { Leaf::Ident(it) if it.sym != sym::underscore => Ok(it), _ => Err(()), } } - pub fn expect_ident_or_underscore(&mut self) -> Result<&'a Ident, ()> { + pub fn expect_ident_or_underscore(&mut self) -> Result<&'a Ident, ()> { match self.expect_leaf()? { Leaf::Ident(it) => Ok(it), _ => Err(()), } } - pub fn expect_literal(&mut self) -> Result<&'a Leaf, ()> { + pub fn expect_literal(&mut self) -> Result<&'a Leaf, ()> { let it = self.expect_leaf()?; match it { Leaf::Literal(_) => Ok(it), @@ -100,7 +101,7 @@ impl<'a, S: Copy> TtIter<'a, S> { } } - pub fn expect_single_punct(&mut self) -> Result<&'a Punct, ()> { + pub fn expect_single_punct(&mut self) -> Result<&'a Punct, ()> { match self.expect_leaf()? { Leaf::Punct(it) => Ok(it), _ => Err(()), @@ -111,7 +112,7 @@ impl<'a, S: Copy> TtIter<'a, S> { /// /// This method currently may return a single quotation, which is part of lifetime ident and /// conceptually not a punct in the context of mbe. Callers should handle this. - pub fn expect_glued_punct(&mut self) -> Result, MAX_GLUED_PUNCT_LEN>, ()> { + pub fn expect_glued_punct(&mut self) -> Result, ()> { let TtElement::Leaf(&Leaf::Punct(first)) = self.next().ok_or(())? else { return Err(()); }; @@ -161,11 +162,11 @@ impl<'a, S: Copy> TtIter<'a, S> { } /// This method won't check for subtrees, so the nth token tree may not be the nth sibling of the current tree. - fn peek_n(&self, n: usize) -> Option<&'a TokenTree> { + fn peek_n(&self, n: usize) -> Option<&'a TokenTree> { self.inner.as_slice().get(n) } - pub fn peek(&self) -> Option> { + pub fn peek(&self) -> Option> { match self.inner.as_slice().first()? { TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf)), TokenTree::Subtree(subtree) => { @@ -181,11 +182,11 @@ impl<'a, S: Copy> TtIter<'a, S> { self.inner.len() == 0 } - pub fn next_span(&self) -> Option { + pub fn next_span(&self) -> Option { Some(self.inner.as_slice().first()?.first_span()) } - pub fn remaining(&self) -> TokenTreesView<'a, S> { + pub fn remaining(&self) -> TokenTreesView<'a> { TokenTreesView::new(self.inner.as_slice()) } @@ -194,17 +195,17 @@ impl<'a, S: Copy> TtIter<'a, S> { self.inner = self.inner.as_slice()[skip..].iter(); } - pub fn savepoint(&self) -> TtIterSavepoint<'a, S> { + pub fn savepoint(&self) -> TtIterSavepoint<'a> { TtIterSavepoint(self.inner.as_slice()) } - pub fn from_savepoint(&self, savepoint: TtIterSavepoint<'a, S>) -> TokenTreesView<'a, S> { + pub fn from_savepoint(&self, savepoint: TtIterSavepoint<'a>) -> TokenTreesView<'a> { let len = (self.inner.as_slice().as_ptr() as usize - savepoint.0.as_ptr() as usize) - / size_of::>(); + / size_of::(); TokenTreesView::new(&savepoint.0[..len]) } - pub fn next_as_view(&mut self) -> Option> { + pub fn next_as_view(&mut self) -> Option> { let savepoint = self.savepoint(); self.next()?; Some(self.from_savepoint(savepoint)) @@ -212,12 +213,12 @@ impl<'a, S: Copy> TtIter<'a, S> { } #[derive(Clone)] -pub enum TtElement<'a, S> { - Leaf(&'a Leaf), - Subtree(&'a Subtree, TtIter<'a, S>), +pub enum TtElement<'a> { + Leaf(&'a Leaf), + Subtree(&'a Subtree, TtIter<'a>), } -impl fmt::Debug for TtElement<'_, S> { +impl fmt::Debug for TtElement<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Leaf(leaf) => f.debug_tuple("Leaf").field(leaf).finish(), @@ -228,9 +229,9 @@ impl fmt::Debug for TtElement<'_, S> { } } -impl TtElement<'_, S> { +impl TtElement<'_> { #[inline] - pub fn first_span(&self) -> S { + pub fn first_span(&self) -> Span { match self { TtElement::Leaf(it) => *it.span(), TtElement::Subtree(it, _) => it.delimiter.open, @@ -238,8 +239,8 @@ impl TtElement<'_, S> { } } -impl<'a, S> Iterator for TtIter<'a, S> { - type Item = TtElement<'a, S>; +impl<'a> Iterator for TtIter<'a> { + type Item = TtElement<'a>; fn next(&mut self) -> Option { match self.inner.next()? { TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf)), diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs index d6a743c695e8c..636f567f1aa7c 100644 --- a/src/tools/rust-analyzer/crates/tt/src/lib.rs +++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs @@ -20,11 +20,13 @@ use std::fmt; use buffer::Cursor; use intern::Symbol; -use iter::{TtElement, TtIter}; use stdx::{impl_from, itertools::Itertools as _}; +pub use span::Span; pub use text_size::{TextRange, TextSize}; +pub use self::iter::{TtElement, TtIter}; + pub const MAX_GLUED_PUNCT_LEN: usize = 3; #[derive(Clone, PartialEq, Debug)] @@ -77,13 +79,13 @@ pub enum LitKind { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum TokenTree { - Leaf(Leaf), - Subtree(Subtree), +pub enum TokenTree { + Leaf(Leaf), + Subtree(Subtree), } -impl_from!(Leaf, Subtree for TokenTree); -impl TokenTree { - pub fn first_span(&self) -> S { +impl_from!(Leaf, Subtree for TokenTree); +impl TokenTree { + pub fn first_span(&self) -> Span { match self { TokenTree::Leaf(l) => *l.span(), TokenTree::Subtree(s) => s.delimiter.open, @@ -92,14 +94,14 @@ impl TokenTree { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Leaf { - Literal(Literal), - Punct(Punct), - Ident(Ident), +pub enum Leaf { + Literal(Literal), + Punct(Punct), + Ident(Ident), } -impl Leaf { - pub fn span(&self) -> &S { +impl Leaf { + pub fn span(&self) -> &Span { match self { Leaf::Literal(it) => &it.span, Leaf::Punct(it) => &it.span, @@ -107,81 +109,81 @@ impl Leaf { } } } -impl_from!(Literal, Punct, Ident for Leaf); +impl_from!(Literal, Punct, Ident for Leaf); #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Subtree { - pub delimiter: Delimiter, +pub struct Subtree { + pub delimiter: Delimiter, /// Number of following token trees that belong to this subtree, excluding this subtree. pub len: u32, } -impl Subtree { +impl Subtree { pub fn usize_len(&self) -> usize { self.len as usize } } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct TopSubtree(pub Box<[TokenTree]>); +pub struct TopSubtree(pub Box<[TokenTree]>); -impl TopSubtree { - pub fn empty(span: DelimSpan) -> Self { +impl TopSubtree { + pub fn empty(span: DelimSpan) -> Self { Self(Box::new([TokenTree::Subtree(Subtree { delimiter: Delimiter::invisible_delim_spanned(span), len: 0, })])) } - pub fn invisible_from_leaves(delim_span: S, leaves: [Leaf; N]) -> Self { + pub fn invisible_from_leaves(delim_span: Span, leaves: [Leaf; N]) -> Self { let mut builder = TopSubtreeBuilder::new(Delimiter::invisible_spanned(delim_span)); builder.extend(leaves); builder.build() } - pub fn from_token_trees(delimiter: Delimiter, token_trees: TokenTreesView<'_, S>) -> Self { + pub fn from_token_trees(delimiter: Delimiter, token_trees: TokenTreesView<'_>) -> Self { let mut builder = TopSubtreeBuilder::new(delimiter); builder.extend_with_tt(token_trees); builder.build() } - pub fn from_subtree(subtree: SubtreeView<'_, S>) -> Self { + pub fn from_subtree(subtree: SubtreeView<'_>) -> Self { Self(subtree.0.into()) } - pub fn view(&self) -> SubtreeView<'_, S> { + pub fn view(&self) -> SubtreeView<'_> { SubtreeView::new(&self.0) } - pub fn iter(&self) -> TtIter<'_, S> { + pub fn iter(&self) -> TtIter<'_> { self.view().iter() } - pub fn top_subtree(&self) -> &Subtree { + pub fn top_subtree(&self) -> &Subtree { self.view().top_subtree() } - pub fn top_subtree_delimiter_mut(&mut self) -> &mut Delimiter { + pub fn top_subtree_delimiter_mut(&mut self) -> &mut Delimiter { let TokenTree::Subtree(subtree) = &mut self.0[0] else { unreachable!("the first token tree is always the top subtree"); }; &mut subtree.delimiter } - pub fn token_trees(&self) -> TokenTreesView<'_, S> { + pub fn token_trees(&self) -> TokenTreesView<'_> { self.view().token_trees() } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TopSubtreeBuilder { +pub struct TopSubtreeBuilder { unclosed_subtree_indices: Vec, - token_trees: Vec>, + token_trees: Vec, last_closed_subtree: Option, } -impl TopSubtreeBuilder { - pub fn new(top_delimiter: Delimiter) -> Self { +impl TopSubtreeBuilder { + pub fn new(top_delimiter: Delimiter) -> Self { let mut result = Self { unclosed_subtree_indices: Vec::new(), token_trees: Vec::new(), @@ -192,7 +194,7 @@ impl TopSubtreeBuilder { result } - pub fn open(&mut self, delimiter_kind: DelimiterKind, open_span: S) { + pub fn open(&mut self, delimiter_kind: DelimiterKind, open_span: Span) { self.unclosed_subtree_indices.push(self.token_trees.len()); self.token_trees.push(TokenTree::Subtree(Subtree { delimiter: Delimiter { @@ -204,7 +206,7 @@ impl TopSubtreeBuilder { })); } - pub fn close(&mut self, close_span: S) { + pub fn close(&mut self, close_span: Span) { let last_unclosed_index = self .unclosed_subtree_indices .pop() @@ -231,26 +233,26 @@ impl TopSubtreeBuilder { } } - pub fn push(&mut self, leaf: Leaf) { + pub fn push(&mut self, leaf: Leaf) { self.token_trees.push(TokenTree::Leaf(leaf)); } - pub fn extend(&mut self, leaves: impl IntoIterator>) { + pub fn extend(&mut self, leaves: impl IntoIterator) { self.token_trees.extend(leaves.into_iter().map(TokenTree::Leaf)); } /// This does not check the token trees are valid, beware! - pub fn extend_tt_dangerous(&mut self, tt: impl IntoIterator>) { + pub fn extend_tt_dangerous(&mut self, tt: impl IntoIterator) { self.token_trees.extend(tt); } - pub fn extend_with_tt(&mut self, tt: TokenTreesView<'_, S>) { + pub fn extend_with_tt(&mut self, tt: TokenTreesView<'_>) { self.token_trees.extend(tt.0.iter().cloned()); } /// Like [`Self::extend_with_tt()`], but makes sure the new tokens will never be /// joint with whatever comes after them. - pub fn extend_with_tt_alone(&mut self, tt: TokenTreesView<'_, S>) { + pub fn extend_with_tt_alone(&mut self, tt: TokenTreesView<'_>) { if let Some((last, before_last)) = tt.0.split_last() { self.token_trees.reserve(tt.0.len()); self.token_trees.extend(before_last.iter().cloned()); @@ -265,7 +267,7 @@ impl TopSubtreeBuilder { } } - pub fn expected_delimiters(&self) -> impl Iterator> { + pub fn expected_delimiters(&self) -> impl Iterator { self.unclosed_subtree_indices.iter().rev().map(|&subtree_idx| { let TokenTree::Subtree(subtree) = &self.token_trees[subtree_idx] else { unreachable!("unclosed token tree is always a subtree") @@ -275,7 +277,7 @@ impl TopSubtreeBuilder { } /// Builds, and remove the top subtree if it has only one subtree child. - pub fn build_skip_top_subtree(mut self) -> TopSubtree { + pub fn build_skip_top_subtree(mut self) -> TopSubtree { let top_tts = TokenTreesView::new(&self.token_trees[1..]); match top_tts.try_into_subtree() { Some(_) => { @@ -289,7 +291,7 @@ impl TopSubtreeBuilder { } } - pub fn build(mut self) -> TopSubtree { + pub fn build(mut self) -> TopSubtree { assert!( self.unclosed_subtree_indices.is_empty(), "attempt to build an unbalanced `TopSubtreeBuilder`" @@ -325,10 +327,10 @@ pub struct SubtreeBuilderRestorePoint { } #[derive(Clone, Copy)] -pub struct TokenTreesView<'a, S>(&'a [TokenTree]); +pub struct TokenTreesView<'a>(&'a [TokenTree]); -impl<'a, S: Copy> TokenTreesView<'a, S> { - pub fn new(tts: &'a [TokenTree]) -> Self { +impl<'a> TokenTreesView<'a> { + pub fn new(tts: &'a [TokenTree]) -> Self { if cfg!(debug_assertions) { tts.iter().enumerate().for_each(|(idx, tt)| { if let TokenTree::Subtree(tt) = &tt { @@ -343,11 +345,11 @@ impl<'a, S: Copy> TokenTreesView<'a, S> { Self(tts) } - pub fn iter(&self) -> TtIter<'a, S> { + pub fn iter(&self) -> TtIter<'a> { TtIter::new(self.0) } - pub fn cursor(&self) -> Cursor<'a, S> { + pub fn cursor(&self) -> Cursor<'a> { Cursor::new(self.0) } @@ -359,7 +361,7 @@ impl<'a, S: Copy> TokenTreesView<'a, S> { self.0.is_empty() } - pub fn try_into_subtree(self) -> Option> { + pub fn try_into_subtree(self) -> Option> { if let Some(TokenTree::Subtree(subtree)) = self.0.first() && subtree.usize_len() == (self.0.len() - 1) { @@ -368,21 +370,21 @@ impl<'a, S: Copy> TokenTreesView<'a, S> { None } - pub fn strip_invisible(self) -> TokenTreesView<'a, S> { + pub fn strip_invisible(self) -> TokenTreesView<'a> { self.try_into_subtree().map(|subtree| subtree.strip_invisible()).unwrap_or(self) } /// This returns a **flat** structure of tokens (subtrees will be represented by a single node /// preceding their children), so it isn't suited for most use cases, only for matching leaves /// at the beginning/end with no subtrees before them. If you need a structured pass, use [`TtIter`]. - pub fn flat_tokens(&self) -> &'a [TokenTree] { + pub fn flat_tokens(&self) -> &'a [TokenTree] { self.0 } pub fn split( self, - mut split_fn: impl FnMut(TtElement<'a, S>) -> bool, - ) -> impl Iterator> { + mut split_fn: impl FnMut(TtElement<'a>) -> bool, + ) -> impl Iterator> { let mut subtree_iter = self.iter(); let mut need_to_yield_even_if_empty = true; @@ -406,7 +408,7 @@ impl<'a, S: Copy> TokenTreesView<'a, S> { } } -impl fmt::Debug for TokenTreesView<'_, S> { +impl fmt::Debug for TokenTreesView<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut iter = self.iter(); while let Some(tt) = iter.next() { @@ -419,14 +421,14 @@ impl fmt::Debug for TokenTreesView<'_, S> { } } -impl fmt::Display for TokenTreesView<'_, S> { +impl fmt::Display for TokenTreesView<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { return token_trees_display(f, self.iter()); - fn subtree_display( - subtree: &Subtree, + fn subtree_display( + subtree: &Subtree, f: &mut fmt::Formatter<'_>, - iter: TtIter<'_, S>, + iter: TtIter<'_>, ) -> fmt::Result { let (l, r) = match subtree.delimiter.kind { DelimiterKind::Parenthesis => ("(", ")"), @@ -440,7 +442,7 @@ impl fmt::Display for TokenTreesView<'_, S> { Ok(()) } - fn token_trees_display(f: &mut fmt::Formatter<'_>, iter: TtIter<'_, S>) -> fmt::Result { + fn token_trees_display(f: &mut fmt::Formatter<'_>, iter: TtIter<'_>) -> fmt::Result { let mut needs_space = false; for child in iter { if needs_space { @@ -466,10 +468,10 @@ impl fmt::Display for TokenTreesView<'_, S> { #[derive(Clone, Copy)] // Invariant: always starts with `Subtree` that covers the entire thing. -pub struct SubtreeView<'a, S>(&'a [TokenTree]); +pub struct SubtreeView<'a>(&'a [TokenTree]); -impl<'a, S: Copy> SubtreeView<'a, S> { - pub fn new(tts: &'a [TokenTree]) -> Self { +impl<'a> SubtreeView<'a> { + pub fn new(tts: &'a [TokenTree]) -> Self { if cfg!(debug_assertions) { let TokenTree::Subtree(subtree) = &tts[0] else { panic!("first token tree must be a subtree in `SubtreeView`"); @@ -483,22 +485,22 @@ impl<'a, S: Copy> SubtreeView<'a, S> { Self(tts) } - pub fn as_token_trees(self) -> TokenTreesView<'a, S> { + pub fn as_token_trees(self) -> TokenTreesView<'a> { TokenTreesView::new(self.0) } - pub fn iter(&self) -> TtIter<'a, S> { + pub fn iter(&self) -> TtIter<'a> { TtIter::new(&self.0[1..]) } - pub fn top_subtree(&self) -> &'a Subtree { + pub fn top_subtree(&self) -> &'a Subtree { let TokenTree::Subtree(subtree) = &self.0[0] else { unreachable!("the first token tree is always the top subtree"); }; subtree } - pub fn strip_invisible(&self) -> TokenTreesView<'a, S> { + pub fn strip_invisible(&self) -> TokenTreesView<'a> { if self.top_subtree().delimiter.kind == DelimiterKind::Invisible { TokenTreesView::new(&self.0[1..]) } else { @@ -506,30 +508,30 @@ impl<'a, S: Copy> SubtreeView<'a, S> { } } - pub fn token_trees(&self) -> TokenTreesView<'a, S> { + pub fn token_trees(&self) -> TokenTreesView<'a> { TokenTreesView::new(&self.0[1..]) } } -impl fmt::Debug for SubtreeView<'_, S> { +impl fmt::Debug for SubtreeView<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&TokenTreesView(self.0), f) } } -impl fmt::Display for SubtreeView<'_, S> { +impl fmt::Display for SubtreeView<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&TokenTreesView(self.0), f) } } #[derive(Debug, Copy, Clone, PartialEq)] -pub struct DelimSpan { - pub open: S, - pub close: S, +pub struct DelimSpan { + pub open: Span, + pub close: Span, } -impl DelimSpan { +impl DelimSpan { pub fn from_single(sp: Span) -> Self { DelimSpan { open: sp, close: sp } } @@ -539,22 +541,22 @@ impl DelimSpan { } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Delimiter { - pub open: S, - pub close: S, +pub struct Delimiter { + pub open: Span, + pub close: Span, pub kind: DelimiterKind, } -impl Delimiter { - pub const fn invisible_spanned(span: S) -> Self { +impl Delimiter { + pub const fn invisible_spanned(span: Span) -> Self { Delimiter { open: span, close: span, kind: DelimiterKind::Invisible } } - pub const fn invisible_delim_spanned(span: DelimSpan) -> Self { + pub const fn invisible_delim_spanned(span: DelimSpan) -> Self { Delimiter { open: span.open, close: span.close, kind: DelimiterKind::Invisible } } - pub fn delim_span(&self) -> DelimSpan { + pub fn delim_span(&self) -> DelimSpan { DelimSpan { open: self.open, close: self.close } } } @@ -568,17 +570,17 @@ pub enum DelimiterKind { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Literal { +pub struct Literal { // escaped pub symbol: Symbol, - pub span: S, + pub span: Span, pub kind: LitKind, pub suffix: Option, } -pub fn token_to_literal(text: &str, span: S) -> Literal +pub fn token_to_literal(text: &str, span: Span) -> Literal where - S: Copy, + Span: Copy, { use rustc_lexer::LiteralKind; @@ -641,10 +643,10 @@ where } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Punct { +pub struct Punct { pub char: char, pub spacing: Spacing, - pub span: S, + pub span: Span, } /// Indicates whether a token can join with the following token to form a @@ -709,25 +711,25 @@ pub enum Spacing { /// Identifier or keyword. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Ident { +pub struct Ident { pub sym: Symbol, - pub span: S, + pub span: Span, pub is_raw: IdentIsRaw, } -impl Ident { - pub fn new(text: &str, span: S) -> Self { +impl Ident { + pub fn new(text: &str, span: Span) -> Self { // let raw_stripped = IdentIsRaw::split_from_symbol(text.as_ref()); let (is_raw, text) = IdentIsRaw::split_from_symbol(text); Ident { sym: Symbol::intern(text), span, is_raw } } } -fn print_debug_subtree( +fn print_debug_subtree( f: &mut fmt::Formatter<'_>, - subtree: &Subtree, + subtree: &Subtree, level: usize, - iter: TtIter<'_, S>, + iter: TtIter<'_>, ) -> fmt::Result { let align = " ".repeat(level); @@ -751,11 +753,7 @@ fn print_debug_subtree( Ok(()) } -fn print_debug_token( - f: &mut fmt::Formatter<'_>, - level: usize, - tt: TtElement<'_, S>, -) -> fmt::Result { +fn print_debug_token(f: &mut fmt::Formatter<'_>, level: usize, tt: TtElement<'_>) -> fmt::Result { let align = " ".repeat(level); match tt { @@ -800,19 +798,19 @@ fn print_debug_token( Ok(()) } -impl fmt::Debug for TopSubtree { +impl fmt::Debug for TopSubtree { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.view(), f) } } -impl fmt::Display for TopSubtree { +impl fmt::Display for TopSubtree { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.view(), f) } } -impl fmt::Display for Leaf { +impl fmt::Display for Leaf { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Leaf::Ident(it) => fmt::Display::fmt(it, f), @@ -822,14 +820,14 @@ impl fmt::Display for Leaf { } } -impl fmt::Display for Ident { +impl fmt::Display for Ident { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.is_raw.as_str(), f)?; fmt::Display::fmt(&self.sym, f) } } -impl fmt::Display for Literal { +impl fmt::Display for Literal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.kind { LitKind::Byte => write!(f, "b'{}'", self.symbol), @@ -873,26 +871,26 @@ impl fmt::Display for Literal { } } -impl fmt::Display for Punct { +impl fmt::Display for Punct { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.char, f) } } -impl Subtree { +impl Subtree { /// Count the number of tokens recursively pub fn count(&self) -> usize { self.usize_len() } } -impl TopSubtree { +impl TopSubtree { /// A simple line string used for debugging pub fn subtree_as_debug_string(&self, subtree_idx: usize) -> String { - fn debug_subtree( + fn debug_subtree( output: &mut String, - subtree: &Subtree, - iter: &mut std::slice::Iter<'_, TokenTree>, + subtree: &Subtree, + iter: &mut std::slice::Iter<'_, TokenTree>, ) { let delim = match subtree.delimiter.kind { DelimiterKind::Brace => ("{", "}"), @@ -914,11 +912,11 @@ impl TopSubtree { output.push_str(delim.1); } - fn debug_token_tree( + fn debug_token_tree( output: &mut String, - tt: &TokenTree, - last: Option<&TokenTree>, - iter: &mut std::slice::Iter<'_, TokenTree>, + tt: &TokenTree, + last: Option<&TokenTree>, + iter: &mut std::slice::Iter<'_, TokenTree>, ) { match tt { TokenTree::Leaf(it) => { @@ -958,8 +956,8 @@ impl TopSubtree { } } -pub fn pretty(mut tkns: &[TokenTree]) -> String { - fn tokentree_to_text(tkn: &TokenTree, tkns: &mut &[TokenTree]) -> String { +pub fn pretty(mut tkns: &[TokenTree]) -> String { + fn tokentree_to_text(tkn: &TokenTree, tkns: &mut &[TokenTree]) -> String { match tkn { TokenTree::Leaf(Leaf::Ident(ident)) => { format!("{}{}", ident.is_raw.as_str(), ident.sym) From fa8649fccdbe1bd6ccc696d1b3c7205966e6598a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 10:00:50 +0100 Subject: [PATCH 53/77] minor: Remove unneded allocation in proc-macro-srv-cli --- .../crates/proc-macro-api/src/transport/framing.rs | 2 +- .../crates/proc-macro-srv-cli/src/main_loop.rs | 2 +- src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs index 1e29c1982acb9..56c3b68e8cd2f 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/transport/framing.rs @@ -3,7 +3,7 @@ use std::io::{self, BufRead, Write}; pub trait Framing { - type Buf: Default + Send; + type Buf: Default + Send + Sync; fn read<'a, R: BufRead + ?Sized>( inp: &mut R, 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 4d98a8e29d125..25a5104c5df34 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 @@ -243,7 +243,7 @@ fn handle_expand_ra( def_site, call_site, mixed_site, - Some(Box::new(ProcMacroClientHandle:: { stdin, stdout, buf })), + Some(&mut ProcMacroClientHandle:: { stdin, stdout, buf }), ) .map(|it| { ( 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 687a4218b4bbe..17ffa29ce172d 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 @@ -91,7 +91,7 @@ impl<'env> ProcMacroSrv<'env> { } } -pub type ProcMacroClientHandle<'a> = Box; +pub type ProcMacroClientHandle<'a> = &'a mut (dyn ProcMacroClientInterface + Sync + Send); pub trait ProcMacroClientInterface { fn source_text(&mut self, file_id: u32, start: u32, end: u32) -> Option; @@ -178,9 +178,7 @@ impl ProcMacroSrv<'_> { } pub trait ProcMacroSrvSpan: Copy + Send + Sync { - type Server<'a>: proc_macro::bridge::server::Server> - where - Self: 'a; + type Server<'a>: proc_macro::bridge::server::Server>; fn make_server<'a>( call_site: Self, def_site: Self, From 1ce1faec2464f4e581a922540f39a9130a48fe12 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 28 Dec 2025 17:30:38 +0800 Subject: [PATCH 54/77] Fix duplicate record functional update Example --- ```rust fn main() { let thing = 1; let foo = Foo { foo1: 0, foo2: 0 }; let foo2 = Foo { thing, $0 ..Default::default() } } ``` **Before this PR** ```text fd ..Default::default() fd foo1 u32 fd foo2 u32 ``` **After this PR** ```text fd foo1 u32 fd foo2 u32 ``` --- .../ide-completion/src/completions/record.rs | 5 +++- .../crates/ide-completion/src/tests/record.rs | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs index bfa567009c015..c5bfdcb8b7347 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs @@ -70,8 +70,11 @@ pub(crate) fn complete_record_expr_fields( } _ => { let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); + let update_exists = record_expr + .record_expr_field_list() + .is_some_and(|list| list.dotdot_token().is_some()); - if !missing_fields.is_empty() { + if !missing_fields.is_empty() && !update_exists { cov_mark::hit!(functional_update_field); add_default_update(acc, ctx, ty); } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs index a1013b86548be..d9be6556fa5ba 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs @@ -263,6 +263,29 @@ fn main() { ); } +#[test] +fn functional_update_exist_update() { + check( + r#" +//- minicore:default +struct Foo { foo1: u32, foo2: u32 } +impl Default for Foo { + fn default() -> Self { loop {} } +} + +fn main() { + let thing = 1; + let foo = Foo { foo1: 0, foo2: 0 }; + let foo2 = Foo { thing, $0 ..Default::default() } +} +"#, + expect![[r#" + fd foo1 u32 + fd foo2 u32 + "#]], + ); +} + #[test] fn empty_union_literal() { check( From b717c74823256d439855bad453a08710e5731a31 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 11:18:43 +0100 Subject: [PATCH 55/77] perf: Pre-allocate some buffers in parsing --- src/tools/rust-analyzer/crates/parser/src/input.rs | 2 +- src/tools/rust-analyzer/crates/parser/src/lexed_str.rs | 7 ++++++- src/tools/rust-analyzer/crates/parser/src/parser.rs | 2 +- src/tools/rust-analyzer/crates/span/src/ast_id.rs | 5 +++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/parser/src/input.rs b/src/tools/rust-analyzer/crates/parser/src/input.rs index 57eeb431cdc26..42e8a400edd18 100644 --- a/src/tools/rust-analyzer/crates/parser/src/input.rs +++ b/src/tools/rust-analyzer/crates/parser/src/input.rs @@ -97,7 +97,7 @@ impl Input { let b_idx = n % (bits::BITS as usize); (idx, b_idx) } - fn len(&self) -> usize { + pub fn len(&self) -> usize { self.kind.len() } } diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs index 7c78ba8faf5f4..d7eec6cde8c01 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -150,7 +150,12 @@ struct Converter<'a> { impl<'a> Converter<'a> { fn new(edition: Edition, text: &'a str) -> Self { Self { - res: LexedStr { text, kind: Vec::new(), start: Vec::new(), error: Vec::new() }, + res: LexedStr { + text, + kind: Vec::with_capacity(text.len() / 3), + start: Vec::with_capacity(text.len() / 3), + error: Vec::new(), + }, offset: 0, edition, } diff --git a/src/tools/rust-analyzer/crates/parser/src/parser.rs b/src/tools/rust-analyzer/crates/parser/src/parser.rs index c41bd593c6a25..4557078de9916 100644 --- a/src/tools/rust-analyzer/crates/parser/src/parser.rs +++ b/src/tools/rust-analyzer/crates/parser/src/parser.rs @@ -32,7 +32,7 @@ const PARSER_STEP_LIMIT: usize = if cfg!(debug_assertions) { 150_000 } else { 15 impl<'t> Parser<'t> { pub(super) fn new(inp: &'t Input) -> Parser<'t> { - Parser { inp, pos: 0, events: Vec::new(), steps: Cell::new(0) } + Parser { inp, pos: 0, events: Vec::with_capacity(2 * inp.len()), steps: Cell::new(0) } } pub(crate) fn finish(self) -> Vec { diff --git a/src/tools/rust-analyzer/crates/span/src/ast_id.rs b/src/tools/rust-analyzer/crates/span/src/ast_id.rs index e54e0bd2fcc6d..3bf14dea75b3e 100644 --- a/src/tools/rust-analyzer/crates/span/src/ast_id.rs +++ b/src/tools/rust-analyzer/crates/span/src/ast_id.rs @@ -603,8 +603,9 @@ impl AstIdMap { // After all, the block will then contain the *outer* item, so we allocate // an ID for it anyway. let mut blocks = Vec::new(); - let mut curr_layer = vec![(node.clone(), None)]; - let mut next_layer = vec![]; + let mut curr_layer = Vec::with_capacity(32); + curr_layer.push((node.clone(), None)); + let mut next_layer = Vec::with_capacity(32); while !curr_layer.is_empty() { curr_layer.drain(..).for_each(|(node, parent_idx)| { let mut preorder = node.preorder(); From 5adad1a8adc6bcd46fffa2adb2411782a32b7623 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 11:43:08 +0100 Subject: [PATCH 56/77] perf: Reduce channel lock contention for drop-threads --- src/tools/rust-analyzer/crates/span/src/map.rs | 17 ++++++++++++++++- .../rust-analyzer/crates/syntax/src/lib.rs | 13 ++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/span/src/map.rs b/src/tools/rust-analyzer/crates/span/src/map.rs index 7faf6f1b1a689..f5c083a9f6501 100644 --- a/src/tools/rust-analyzer/crates/span/src/map.rs +++ b/src/tools/rust-analyzer/crates/span/src/map.rs @@ -163,7 +163,22 @@ impl Drop for SpanMap { let (sender, receiver) = std::sync::mpsc::channel::<(SendPtr, fn(SendPtr))>(); std::thread::Builder::new() .name("SpanMapDropper".to_owned()) - .spawn(move || receiver.iter().for_each(|(b, drop)| drop(b))) + .spawn(move || { + loop { + // block on a receive + if let Ok((b, drop)) = receiver.recv() { + drop(b); + } + // then drain the entire channel + while let Ok((b, drop)) = receiver.try_recv() { + drop(b); + } + // and sleep for a bit + std::thread::sleep(std::time::Duration::from_millis(100)); + } + // why do this over just a `receiver.iter().for_each(drop)`? To reduce contention on the channel lock. + // otherwise this thread will constantly wake up and sleep again. + }) .unwrap(); sender }) diff --git a/src/tools/rust-analyzer/crates/syntax/src/lib.rs b/src/tools/rust-analyzer/crates/syntax/src/lib.rs index 9e3083066c94a..d36019514016f 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/lib.rs @@ -218,7 +218,18 @@ impl Drop for Parse { let (sender, receiver) = std::sync::mpsc::channel::(); std::thread::Builder::new() .name("ParseNodeDropper".to_owned()) - .spawn(move || receiver.iter().for_each(drop)) + .spawn(move || { + loop { + // block on a receive + _ = receiver.recv(); + // then drain the entire channel + while let Ok(_) = receiver.try_recv() {} + // and sleep for a bit + std::thread::sleep(std::time::Duration::from_millis(100)); + } + // why do this over just a `receiver.iter().for_each(drop)`? To reduce contention on the channel lock. + // otherwise this thread will constantly wake up and sleep again. + }) .unwrap(); sender }) From 11df3a0dec58b3210bf858c200ef4ba60a7157fd Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 12:41:09 +0100 Subject: [PATCH 57/77] Drop AstIdMap asynchronously --- .../rust-analyzer/crates/span/src/ast_id.rs | 42 +++++++++++++++++++ .../rust-analyzer/crates/span/src/map.rs | 33 ++++----------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/tools/rust-analyzer/crates/span/src/ast_id.rs b/src/tools/rust-analyzer/crates/span/src/ast_id.rs index 3bf14dea75b3e..da537ba9900ca 100644 --- a/src/tools/rust-analyzer/crates/span/src/ast_id.rs +++ b/src/tools/rust-analyzer/crates/span/src/ast_id.rs @@ -777,6 +777,48 @@ impl AstIdMap { } } +#[cfg(not(no_salsa_async_drops))] +impl Drop for AstIdMap { + fn drop(&mut self) { + let arena = std::mem::take(&mut self.arena); + let ptr_map = std::mem::take(&mut self.ptr_map); + let id_map = std::mem::take(&mut self.id_map); + static AST_ID_MAP_DROP_THREAD: std::sync::OnceLock< + std::sync::mpsc::Sender<( + Arena<(SyntaxNodePtr, ErasedFileAstId)>, + hashbrown::HashTable, + hashbrown::HashTable, + )>, + > = std::sync::OnceLock::new(); + AST_ID_MAP_DROP_THREAD + .get_or_init(|| { + let (sender, receiver) = std::sync::mpsc::channel::<( + Arena<(SyntaxNodePtr, ErasedFileAstId)>, + hashbrown::HashTable, + hashbrown::HashTable, + )>(); + std::thread::Builder::new() + .name("AstIdMapDropper".to_owned()) + .spawn(move || { + loop { + // block on a receive + _ = receiver.recv(); + // then drain the entire channel + while let Ok(_) = receiver.try_recv() {} + // and sleep for a bit + std::thread::sleep(std::time::Duration::from_millis(100)); + } + // why do this over just a `receiver.iter().for_each(drop)`? To reduce contention on the channel lock. + // otherwise this thread will constantly wake up and sleep again. + }) + .unwrap(); + sender + }) + .send((arena, ptr_map, id_map)) + .unwrap(); + } +} + #[inline] fn hash_ptr(ptr: &SyntaxNodePtr) -> u64 { FxBuildHasher.hash_one(ptr) diff --git a/src/tools/rust-analyzer/crates/span/src/map.rs b/src/tools/rust-analyzer/crates/span/src/map.rs index f5c083a9f6501..75a1b0029cbc0 100644 --- a/src/tools/rust-analyzer/crates/span/src/map.rs +++ b/src/tools/rust-analyzer/crates/span/src/map.rs @@ -153,26 +153,22 @@ impl SpanMap { #[cfg(not(no_salsa_async_drops))] impl Drop for SpanMap { fn drop(&mut self) { - struct SendPtr(*mut [()]); - unsafe impl Send for SendPtr {} + let spans = std::mem::take(&mut self.spans); static SPAN_MAP_DROP_THREAD: std::sync::OnceLock< - std::sync::mpsc::Sender<(SendPtr, fn(SendPtr))>, + std::sync::mpsc::Sender>, > = std::sync::OnceLock::new(); + SPAN_MAP_DROP_THREAD .get_or_init(|| { - let (sender, receiver) = std::sync::mpsc::channel::<(SendPtr, fn(SendPtr))>(); + let (sender, receiver) = std::sync::mpsc::channel::>(); std::thread::Builder::new() .name("SpanMapDropper".to_owned()) .spawn(move || { loop { // block on a receive - if let Ok((b, drop)) = receiver.recv() { - drop(b); - } + _ = receiver.recv(); // then drain the entire channel - while let Ok((b, drop)) = receiver.try_recv() { - drop(b); - } + while let Ok(_) = receiver.try_recv() {} // and sleep for a bit std::thread::sleep(std::time::Duration::from_millis(100)); } @@ -182,22 +178,7 @@ impl Drop for SpanMap { .unwrap(); sender }) - .send(( - unsafe { - SendPtr(std::mem::transmute::<*mut [(TextSize, Span)], *mut [()]>(Box::< - [(TextSize, Span)], - >::into_raw( - std::mem::take(&mut self.spans).into_boxed_slice(), - ))) - }, - |b: SendPtr| { - _ = unsafe { - Box::from_raw(std::mem::transmute::<*mut [()], *mut [(TextSize, Span)]>( - b.0, - )) - } - }, - )) + .send(spans) .unwrap(); } } From ecb0c4cbfcd555e2bcf88ee07f89864019852085 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 12:30:57 +0100 Subject: [PATCH 58/77] internal: Collect garbage after events when quiescient --- .../crates/ide-db/src/apply_change.rs | 9 ++------- .../crates/ide-db/src/prime_caches.rs | 6 ++---- src/tools/rust-analyzer/crates/ide/src/lib.rs | 11 ++++++++--- .../rust-analyzer/src/cli/analysis_stats.rs | 5 ++--- .../rust-analyzer/src/cli/rustc_tests.rs | 2 +- .../crates/rust-analyzer/src/config.rs | 11 ----------- .../crates/rust-analyzer/src/global_state.rs | 18 +++--------------- .../crates/rust-analyzer/src/main_loop.rs | 9 +++++++-- .../crates/rust-analyzer/src/task_pool.rs | 4 ++++ .../crates/stdx/src/thread/pool.rs | 3 ++- .../docs/book/src/configuration_generated.md | 11 ----------- .../rust-analyzer/editors/code/package.json | 11 ----------- 12 files changed, 31 insertions(+), 69 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs index 9bbd12aaa9f89..b77a18f56ea21 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs @@ -1,19 +1,14 @@ //! Applies changes to the IDE state transactionally. use profile::Bytes; -use salsa::{Database as _, Durability}; +use salsa::Database as _; use crate::{ChangeWithProcMacros, RootDatabase}; impl RootDatabase { - pub fn request_cancellation(&mut self) { - let _p = tracing::info_span!("RootDatabase::request_cancellation").entered(); - self.synthetic_write(Durability::LOW); - } - pub fn apply_change(&mut self, change: ChangeWithProcMacros) { let _p = tracing::info_span!("RootDatabase::apply_change").entered(); - self.request_cancellation(); + self.trigger_cancellation(); tracing::trace!("apply_change {:?}", change); change.apply(self); } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs b/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs index e8f06a36be874..015b06e8e0b20 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs @@ -108,10 +108,9 @@ pub fn parallel_prime_caches( hir::attach_db(&db, || { // method resolution is likely to hit all trait impls at some point // we pre-populate it here as this will hit a lot of parses ... - _ = hir::TraitImpls::for_crate(&db, crate_id); - // we compute the lang items here as the work for them is also highly recursive and will be trigger by the module symbols query + // This also computes the lang items, which is what we want as the work for them is also highly recursive and will be trigger by the module symbols query // slowing down leaf crate analysis tremendously as we go back to being blocked on a single thread - _ = hir::crate_lang_items(&db, crate_id); + _ = hir::TraitImpls::for_crate(&db, crate_id); }) }); @@ -271,7 +270,6 @@ pub fn parallel_prime_caches( } if crate_def_maps_done == crate_def_maps_total { - // Can we trigger lru-eviction once at this point to reduce peak memory usage? cb(ParallelPrimeCachesProgress { crates_currently_indexing: vec![], crates_done: crate_def_maps_done, diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 192524192406c..930eaf2262d93 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -67,7 +67,7 @@ use ide_db::{ FxHashMap, FxIndexSet, LineIndexDatabase, base_db::{ CrateOrigin, CrateWorkspaceData, Env, FileSet, RootQueryDb, SourceDatabase, VfsPath, - salsa::Cancelled, + salsa::{Cancelled, Database}, }, prime_caches, symbol_index, }; @@ -199,8 +199,13 @@ impl AnalysisHost { pub fn per_query_memory_usage(&mut self) -> Vec<(String, profile::Bytes, usize)> { self.db.per_query_memory_usage() } - pub fn request_cancellation(&mut self) { - self.db.request_cancellation(); + pub fn trigger_cancellation(&mut self) { + self.db.trigger_cancellation(); + } + pub fn trigger_garbage_collection(&mut self) { + self.db.trigger_lru_eviction(); + // SAFETY: `trigger_lru_eviction` triggers cancellation, so all running queries were canceled. + unsafe { hir::collect_ty_garbage() }; } pub fn raw_database(&self) -> &RootDatabase { &self.db diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 503d5967bb102..a02d1a78564f6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -354,11 +354,10 @@ impl flags::AnalysisStats { self.run_term_search(&workspace, db, &vfs, &file_ids, verbosity); } - hir::clear_tls_solver_cache(); - unsafe { hir::collect_ty_garbage() }; - let db = host.raw_database_mut(); db.trigger_lru_eviction(); + hir::clear_tls_solver_cache(); + unsafe { hir::collect_ty_garbage() }; let total_span = analysis_sw.elapsed(); eprintln!("{:<20} {total_span}", "Total:"); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs index eb28a47ec0ad7..249566d2ac161 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -185,7 +185,7 @@ impl Tester { if !worker.is_finished() { // attempt to cancel the worker, won't work for chalk hangs unfortunately - self.host.request_cancellation(); + self.host.trigger_garbage_collection(); } worker.join().and_then(identity) }); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 4c998e58539dc..1b2d8c8d14fa3 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -98,13 +98,6 @@ config_data! { /// Code's `files.watcherExclude`. files_exclude | files_excludeDirs: Vec = vec![], - /// This config controls the frequency in which rust-analyzer will perform its internal Garbage - /// Collection. It is specified in revisions, roughly equivalent to number of changes. The default - /// is 1000. - /// - /// Setting a smaller value may help limit peak memory usage at the expense of speed. - gc_frequency: usize = 1000, - /// If this is `true`, when "Goto Implementations" and in "Implementations" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type. gotoImplementations_filterAdjacentDerives: bool = false, @@ -1712,10 +1705,6 @@ impl Config { &self.caps } - pub fn gc_freq(&self) -> usize { - *self.gc_frequency() - } - pub fn assist(&self, source_root: Option) -> AssistConfig { AssistConfig { snippet_cap: self.snippet_cap(), diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs index 41783584a9ba4..91ea2a343e85f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs @@ -193,8 +193,6 @@ pub(crate) struct GlobalState { /// which will usually end up causing a bunch of incorrect diagnostics on startup. pub(crate) incomplete_crate_graph: bool, - pub(crate) revisions_until_next_gc: usize, - pub(crate) minicore: MiniCoreRustAnalyzerInternalOnly, } @@ -321,8 +319,6 @@ impl GlobalState { incomplete_crate_graph: false, minicore: MiniCoreRustAnalyzerInternalOnly::default(), - - revisions_until_next_gc: config.gc_freq(), }; // Apply any required database inputs from the config. this.update_configuration(config); @@ -347,11 +343,11 @@ impl GlobalState { let (change, modified_rust_files, workspace_structure_change) = self.cancellation_pool.scoped(|s| { - // start cancellation in parallel, this will kick off lru eviction + // start cancellation in parallel, // allowing us to do meaningful work while waiting let analysis_host = AssertUnwindSafe(&mut self.analysis_host); s.spawn(thread::ThreadIntent::LatencySensitive, || { - { analysis_host }.0.request_cancellation() + { analysis_host }.0.trigger_cancellation() }); // downgrade to read lock to allow more readers while we are normalizing text @@ -440,14 +436,6 @@ impl GlobalState { self.analysis_host.apply_change(change); - if self.revisions_until_next_gc == 0 { - // SAFETY: Just changed some database inputs, all queries were canceled. - unsafe { hir::collect_ty_garbage() }; - self.revisions_until_next_gc = self.config.gc_freq(); - } else { - self.revisions_until_next_gc -= 1; - } - if !modified_ratoml_files.is_empty() || !self.config.same_source_root_parent_map(&self.local_roots_parent_map) { @@ -741,7 +729,7 @@ impl GlobalState { impl Drop for GlobalState { fn drop(&mut self) { - self.analysis_host.request_cancellation(); + self.analysis_host.trigger_cancellation(); } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 6e08b7bb88d4c..54cd824a037c7 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -9,7 +9,7 @@ use std::{ }; use crossbeam_channel::{Receiver, never, select}; -use ide_db::base_db::{SourceDatabase, VfsPath, salsa::Database as _}; +use ide_db::base_db::{SourceDatabase, VfsPath}; use lsp_server::{Connection, Notification, Request}; use lsp_types::{TextDocumentIdentifier, notification::Notification as _}; use stdx::thread::ThreadIntent; @@ -383,7 +383,7 @@ impl GlobalState { )); } PrimeCachesProgress::End { cancelled } => { - self.analysis_host.raw_database_mut().trigger_lru_eviction(); + self.analysis_host.trigger_garbage_collection(); self.prime_caches_queue.op_completed(()); if cancelled { self.prime_caches_queue @@ -535,6 +535,11 @@ impl GlobalState { if project_or_mem_docs_changed && self.config.test_explorer() { self.update_tests(); } + + // no work is currently being done, now we can block a bit and clean up our garbage + if self.task_pool.handle.is_empty() && self.fmt_pool.handle.is_empty() { + self.analysis_host.trigger_garbage_collection(); + } } self.cleanup_discover_handles(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/task_pool.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/task_pool.rs index ef0feb179698f..8b8876b801cf8 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/task_pool.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/task_pool.rs @@ -43,6 +43,10 @@ impl TaskPool { pub(crate) fn len(&self) -> usize { self.pool.len() } + + pub(crate) fn is_empty(&self) -> bool { + self.pool.is_empty() + } } /// `DeferredTaskQueue` holds deferred tasks. diff --git a/src/tools/rust-analyzer/crates/stdx/src/thread/pool.rs b/src/tools/rust-analyzer/crates/stdx/src/thread/pool.rs index 8d76c5fd1fb36..918b88d960f1a 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/thread/pool.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/thread/pool.rs @@ -66,7 +66,6 @@ impl Pool { job.requested_intent.apply_to_current_thread(); current_intent = job.requested_intent; } - extant_tasks.fetch_add(1, Ordering::SeqCst); // discard the panic, we should've logged the backtrace already drop(panic::catch_unwind(job.f)); extant_tasks.fetch_sub(1, Ordering::SeqCst); @@ -93,6 +92,7 @@ impl Pool { }); let job = Job { requested_intent: intent, f }; + self.extant_tasks.fetch_add(1, Ordering::SeqCst); self.job_sender.send(job).unwrap(); } @@ -147,6 +147,7 @@ impl<'scope> Scope<'_, 'scope> { >(f) }, }; + self.pool.extant_tasks.fetch_add(1, Ordering::SeqCst); self.pool.job_sender.send(job).unwrap(); } } diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index 5d64e6e14d65e..e208dbadea2bc 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -635,17 +635,6 @@ Default: `"client"` Controls file watching implementation. -## rust-analyzer.gc.frequency {#gc.frequency} - -Default: `1000` - -This config controls the frequency in which rust-analyzer will perform its internal Garbage -Collection. It is specified in revisions, roughly equivalent to number of changes. The default -is 1000. - -Setting a smaller value may help limit peak memory usage at the expense of speed. - - ## rust-analyzer.gotoImplementations.filterAdjacentDerives {#gotoImplementations.filterAdjacentDerives} Default: `false` diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 16f47ceb64a37..481721896227d 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -1632,17 +1632,6 @@ } } }, - { - "title": "Gc", - "properties": { - "rust-analyzer.gc.frequency": { - "markdownDescription": "This config controls the frequency in which rust-analyzer will perform its internal Garbage\nCollection. It is specified in revisions, roughly equivalent to number of changes. The default\nis 1000.\n\nSetting a smaller value may help limit peak memory usage at the expense of speed.", - "default": 1000, - "type": "integer", - "minimum": 0 - } - } - }, { "title": "Goto Implementations", "properties": { From 6f551be44b68b26882c68dac1db3eb6f1f507948 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 13:14:06 +0100 Subject: [PATCH 59/77] Reduce allocations in `GarbageCollector` --- src/tools/rust-analyzer/crates/intern/src/gc.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/intern/src/gc.rs b/src/tools/rust-analyzer/crates/intern/src/gc.rs index 0d500a9714e49..937de26831e28 100644 --- a/src/tools/rust-analyzer/crates/intern/src/gc.rs +++ b/src/tools/rust-analyzer/crates/intern/src/gc.rs @@ -87,20 +87,20 @@ pub trait GcInternedSliceVisit: SliceInternable { #[derive(Default)] pub struct GarbageCollector { alive: FxHashSet, - storages: Vec>, + storages: Vec<&'static (dyn Storage + Send + Sync)>, } impl GarbageCollector { pub fn add_storage(&mut self) { const { assert!(T::USE_GC) }; - self.storages.push(Box::new(InternedStorage::(PhantomData))); + self.storages.push(&InternedStorage::(PhantomData)); } pub fn add_slice_storage(&mut self) { const { assert!(T::USE_GC) }; - self.storages.push(Box::new(InternedSliceStorage::(PhantomData))); + self.storages.push(&InternedSliceStorage::(PhantomData)); } /// # Safety @@ -111,11 +111,12 @@ impl GarbageCollector { /// - [`GcInternedVisit`] and [`GcInternedSliceVisit`] must mark all values reachable from the node. pub unsafe fn collect(mut self) { let total_nodes = self.storages.iter().map(|storage| storage.len()).sum(); - self.alive = FxHashSet::with_capacity_and_hasher(total_nodes, FxBuildHasher); + self.alive.clear(); + self.alive.reserve(total_nodes); let storages = std::mem::take(&mut self.storages); - for storage in &storages { + for &storage in &storages { storage.mark(&mut self); } From 8235c351a31407fd2c26431d81d84c1bbf113e65 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 13:17:24 +0100 Subject: [PATCH 60/77] Do not unnecessarily re-trigger garbage collection if no inputs have changed --- .../crates/rust-analyzer/src/global_state.rs | 8 ++++++-- .../crates/rust-analyzer/src/main_loop.rs | 10 ++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs index 91ea2a343e85f..9beab3c0e45c1 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs @@ -15,7 +15,7 @@ use hir::ChangeWithProcMacros; use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId}; use ide_db::{ MiniCore, - base_db::{Crate, ProcMacroPaths, SourceDatabase}, + base_db::{Crate, ProcMacroPaths, SourceDatabase, salsa::Revision}, }; use itertools::Itertools; use load_cargo::SourceRootConfig; @@ -194,12 +194,13 @@ pub(crate) struct GlobalState { pub(crate) incomplete_crate_graph: bool, pub(crate) minicore: MiniCoreRustAnalyzerInternalOnly, + pub(crate) last_gc_revision: Revision, } // FIXME: This should move to the VFS once the rewrite is done. #[derive(Debug, Clone, Default)] pub(crate) struct MiniCoreRustAnalyzerInternalOnly { - pub(crate) minicore_text: Option, + pub(crate) minicore_text: Option>, } /// An immutable snapshot of the world's state at a point in time. @@ -256,6 +257,8 @@ impl GlobalState { let (discover_sender, discover_receiver) = unbounded(); + let last_gc_revision = analysis_host.raw_database().nonce_and_revision().1; + let mut this = GlobalState { sender, req_queue: ReqQueue::default(), @@ -319,6 +322,7 @@ impl GlobalState { incomplete_crate_graph: false, minicore: MiniCoreRustAnalyzerInternalOnly::default(), + last_gc_revision, }; // Apply any required database inputs from the config. this.update_configuration(config); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 54cd824a037c7..dd0813c14454c 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -536,9 +536,14 @@ impl GlobalState { self.update_tests(); } + let current_revision = self.analysis_host.raw_database().nonce_and_revision().1; // no work is currently being done, now we can block a bit and clean up our garbage - if self.task_pool.handle.is_empty() && self.fmt_pool.handle.is_empty() { + if self.task_pool.handle.is_empty() + && self.fmt_pool.handle.is_empty() + && current_revision != self.last_gc_revision + { self.analysis_host.trigger_garbage_collection(); + self.last_gc_revision = current_revision; } } @@ -912,7 +917,8 @@ impl GlobalState { // Not a lot of bad can happen from mistakenly identifying `minicore`, so proceed with that. self.minicore.minicore_text = contents .as_ref() - .and_then(|contents| String::from_utf8(contents.clone()).ok()); + .and_then(|contents| str::from_utf8(contents).ok()) + .map(triomphe::Arc::from); } let path = VfsPath::from(path); From 31fb8331674a6c0b85857250e83c332678257ef8 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 13:55:52 +0100 Subject: [PATCH 61/77] fix: Fix hover for infer type not working --- .../crates/ide/src/hover/render.rs | 35 ++++--------------- .../crates/ide/src/hover/tests.rs | 16 +++++++-- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index 9dc72a87af16f..feac5fff84a7a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -228,37 +228,14 @@ pub(super) fn underscore( return None; } let parent = token.parent()?; - let _it = match_ast! { + match_ast! { match parent { - ast::InferType(it) => it, - ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it)),edition, display_target), - ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it)),edition, display_target), - _ => return None, + ast::InferType(it) => type_info(sema, config, TypeInfo { original: sema.resolve_type(&ast::Type::InferType(it))?, adjusted: None}, edition, display_target), + ast::UnderscoreExpr(it) => type_info(sema, config, sema.type_of_expr(&ast::Expr::UnderscoreExpr(it))?, edition, display_target), + ast::WildcardPat(it) => type_info(sema, config, sema.type_of_pat(&ast::Pat::WildcardPat(it))?, edition, display_target), + _ => None, } - }; - // let it = infer_type.syntax().parent()?; - // match_ast! { - // match it { - // ast::LetStmt(_it) => (), - // ast::Param(_it) => (), - // ast::RetType(_it) => (), - // ast::TypeArg(_it) => (), - - // ast::CastExpr(_it) => (), - // ast::ParenType(_it) => (), - // ast::TupleType(_it) => (), - // ast::PtrType(_it) => (), - // ast::RefType(_it) => (), - // ast::ArrayType(_it) => (), - // ast::SliceType(_it) => (), - // ast::ForType(_it) => (), - // _ => return None, - // } - // } - - // FIXME: https://github.com/rust-lang/rust-analyzer/issues/11762, this currently always returns Unknown - // type_info(sema, config, sema.resolve_type(&ast::Type::InferType(it))?, None) - None + } } pub(super) fn keyword( diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index 5330b7eb99418..f42d3cf0dc413 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -8199,19 +8199,31 @@ fn main() { #[test] fn hover_underscore_type() { - check_hover_no_result( + check( r#" fn main() { let x: _$0 = 0; } "#, + expect![[r#" + *_* + ```rust + i32 + ``` + "#]], ); - check_hover_no_result( + check( r#" fn main() { let x: (_$0,) = (0,); } "#, + expect![[r#" + *_* + ```rust + i32 + ``` + "#]], ); } From 4ae28483e9bc408dc71753fe09f4a53f540d1537 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 14:19:31 +0100 Subject: [PATCH 62/77] Prompt the user in VSCode to add the rust-anaylzer componenet to the toolchain file --- .../editors/code/src/bootstrap.ts | 11 ++-- .../rust-analyzer/editors/code/src/ctx.ts | 54 ++++++++++++++++++- .../rust-analyzer/editors/code/src/util.ts | 25 +++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index bddf195803d0b..ca5b7e3ec7855 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -1,7 +1,7 @@ import * as vscode from "vscode"; import * as os from "os"; import type { Config } from "./config"; -import { type Env, log, spawnAsync } from "./util"; +import { type Env, log, RUST_TOOLCHAIN_FILES, spawnAsync } from "./util"; import type { PersistentState } from "./persistent_state"; import { exec } from "child_process"; import { TextDecoder } from "node:util"; @@ -59,8 +59,12 @@ async function getServer( // otherwise check if there is a toolchain override for the current vscode workspace // and if the toolchain of this override has a rust-analyzer component // if so, use the rust-analyzer component - const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml"); - if (await hasToolchainFileWithRaDeclared(toolchainUri)) { + // Check both rust-toolchain.toml and rust-toolchain files + for (const toolchainFile of RUST_TOOLCHAIN_FILES) { + const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, toolchainFile); + if (!(await hasToolchainFileWithRaDeclared(toolchainUri))) { + continue; + } const res = await spawnAsync("rustup", ["which", "rust-analyzer"], { env: { ...process.env }, cwd: workspaceFolder.uri.fsPath, @@ -71,6 +75,7 @@ async function getServer( res.stdout.trim(), raVersionResolver, ); + break; } } } diff --git a/src/tools/rust-analyzer/editors/code/src/ctx.ts b/src/tools/rust-analyzer/editors/code/src/ctx.ts index a7b7be03b5d88..e07fbb8bead68 100644 --- a/src/tools/rust-analyzer/editors/code/src/ctx.ts +++ b/src/tools/rust-analyzer/editors/code/src/ctx.ts @@ -1,10 +1,11 @@ import * as vscode from "vscode"; -import type * as lc from "vscode-languageclient/node"; +import * as lc from "vscode-languageclient/node"; import * as ra from "./lsp_ext"; import { Config, prepareVSCodeConfig } from "./config"; import { createClient } from "./client"; import { + findRustToolchainFiles, isCargoTomlEditor, isDocumentInWorkspace, isRustDocument, @@ -266,6 +267,17 @@ export class Ctx implements RustAnalyzerExtensionApi { this.outputChannel!.show(); }), ); + this.pushClientCleanup( + this._client.onNotification( + lc.ShowMessageNotification.type, + async (params: lc.ShowMessageParams) => { + // When an MSRV warning is detected and a rust-toolchain file exists, + // show an additional message with actionable guidance about adding + // the rust-analyzer component. + await handleMsrvWarning(params.message); + }, + ), + ); } return this._client; } @@ -592,3 +604,43 @@ export interface Disposable { // eslint-disable-next-line @typescript-eslint/no-explicit-any export type Cmd = (...args: any[]) => unknown; + +/** + * Pattern to detect MSRV warning messages from the rust-analyzer server. + */ +const MSRV_WARNING_PATTERN = /using an outdated toolchain version.*rust-analyzer only supports/is; + +/** + * Handles the MSRV warning by checking for rust-toolchain files and showing + * an enhanced message if found. + */ +export async function handleMsrvWarning(message: string): Promise { + if (!MSRV_WARNING_PATTERN.test(message)) { + return false; + } + + const toolchainFiles = await findRustToolchainFiles(); + if (toolchainFiles.length === 0) { + return false; + } + + const openFile = "Open rust-toolchain file"; + const result = await vscode.window.showWarningMessage( + "Your workspace uses a rust-toolchain file with a toolchain too old for the extension shipped rust-analyzer to work properly. " + + "Consider adding the rust-analyzer component to the toolchain file to use a compatible rust-analyzer version. " + + "Add the following to your rust-toolchain file's `[toolchain]` section:\n" + + 'components = ["rust-analyzer"]', + { modal: true }, + openFile, + ); + + if (result === openFile) { + const fileToOpen = toolchainFiles[0]; + if (fileToOpen) { + const document = await vscode.workspace.openTextDocument(fileToOpen); + await vscode.window.showTextDocument(document); + } + } + + return true; +} diff --git a/src/tools/rust-analyzer/editors/code/src/util.ts b/src/tools/rust-analyzer/editors/code/src/util.ts index 410b055100a1b..05b475080cbd2 100644 --- a/src/tools/rust-analyzer/editors/code/src/util.ts +++ b/src/tools/rust-analyzer/editors/code/src/util.ts @@ -328,3 +328,28 @@ export function normalizeDriveLetter(path: string, isWindowsOS: boolean = isWind return path; } + +export const RUST_TOOLCHAIN_FILES = ["rust-toolchain.toml", "rust-toolchain"] as const; + +export async function findRustToolchainFiles(): Promise { + const found: vscode.Uri[] = []; + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders) { + return found; + } + + for (const folder of workspaceFolders) { + for (const filename of RUST_TOOLCHAIN_FILES) { + const toolchainUri = vscode.Uri.joinPath(folder.uri, filename); + try { + await vscode.workspace.fs.stat(toolchainUri); + found.push(toolchainUri); + // Only add the first toolchain file found per workspace folder + break; + } catch { + // File doesn't exist, continue + } + } + } + return found; +} From 49b6f677a50ac57051806eef1727b6a40bb2a196 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 29 Dec 2025 04:14:12 +0200 Subject: [PATCH 63/77] Separate the public `tt` API from how they're stored And don't provide any insight into the actual storage (no references, no "cheat" methods like `flat_tokens()`), as we're going to rewrite it to be more memory efficient. --- .../rust-analyzer/crates/cfg/src/cfg_expr.rs | 11 +- .../hir-def/src/nameres/attr_resolution.rs | 2 +- .../crates/hir-def/src/nameres/collector.rs | 4 +- .../crates/hir-def/src/nameres/proc_macro.rs | 51 ++--- .../crates/hir-expand/src/attrs.rs | 19 +- .../crates/hir-expand/src/builtin/fn_macro.rs | 65 +++--- .../crates/hir-expand/src/builtin/quote.rs | 9 +- .../rust-analyzer/crates/hir-expand/src/db.rs | 14 +- .../crates/hir-expand/src/eager.rs | 2 +- .../crates/hir-expand/src/fixup.rs | 95 ++------- .../crates/hir-expand/src/mod_path.rs | 10 +- .../rust-analyzer/crates/mbe/src/benchmark.rs | 2 +- .../rust-analyzer/crates/mbe/src/expander.rs | 2 +- .../crates/mbe/src/expander/matcher.rs | 10 +- .../crates/mbe/src/expander/transcriber.rs | 22 ++- .../rust-analyzer/crates/mbe/src/parser.rs | 8 +- .../src/legacy_protocol/msg/flat.rs | 20 +- .../crates/proc-macro-api/src/lib.rs | 18 +- .../crates/syntax-bridge/src/lib.rs | 14 +- .../crates/syntax-bridge/src/tests.rs | 2 +- .../crates/test-fixture/src/lib.rs | 42 ++-- .../rust-analyzer/crates/tt/src/buffer.rs | 4 +- src/tools/rust-analyzer/crates/tt/src/iter.rs | 32 +-- src/tools/rust-analyzer/crates/tt/src/lib.rs | 185 +++++++++++++++--- 24 files changed, 350 insertions(+), 293 deletions(-) diff --git a/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs b/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs index 2f1fc8ae49e9d..c5da8443a68fc 100644 --- a/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs +++ b/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs @@ -197,17 +197,18 @@ fn next_cfg_expr(it: &mut tt::iter::TtIter<'_>) -> Option { Some(_) => return Some(CfgExpr::Invalid), }; - let ret = match it.peek() { + let mut it_clone = it.clone(); + let ret = match it_clone.next() { Some(TtElement::Leaf(tt::Leaf::Punct(punct))) // Don't consume on e.g. `=>`. if punct.char == '=' && (punct.spacing == tt::Spacing::Alone - || it.remaining().flat_tokens().get(1).is_none_or(|peek2| { - !matches!(peek2, tt::TokenTree::Leaf(tt::Leaf::Punct(_))) + || it_clone.peek().is_none_or(|peek2| { + !matches!(peek2, tt::TtElement::Leaf(tt::Leaf::Punct(_))) })) => { - match it.remaining().flat_tokens().get(1) { - Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => { + match it_clone.next() { + Some(tt::TtElement::Leaf(tt::Leaf::Literal(literal))) => { it.next(); it.next(); CfgAtom::KeyValue { key: name, value: literal.symbol.clone() }.into() diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs index 1cbd2c10b5a1b..062b55fcefdf2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs @@ -114,7 +114,7 @@ pub(super) fn attr_macro_as_call_id( let arg = match macro_attr.input.as_deref() { Some(AttrInput::TokenTree(tt)) => { let mut tt = tt.clone(); - tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Invisible; + tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); Some(tt) } 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 a7f687a3168bc..822da678d7977 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 @@ -2466,8 +2466,8 @@ impl ModCollector<'_, '_> { None => { let explicit_name = attrs.by_key(sym::rustc_builtin_macro).tt_values().next().and_then(|tt| { - match tt.token_trees().flat_tokens().first() { - Some(tt::TokenTree::Leaf(tt::Leaf::Ident(name))) => Some(name), + match tt.token_trees().iter().next() { + Some(tt::TtElement::Leaf(tt::Leaf::Ident(name))) => Some(name), _ => None, } }); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs index cd45afe57d7cd..91d664938bf21 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs @@ -2,10 +2,11 @@ use hir_expand::name::{AsName, Name}; use intern::sym; +use itertools::Itertools; use crate::{ item_tree::Attrs, - tt::{Leaf, TokenTree, TopSubtree, TtElement}, + tt::{Leaf, TopSubtree, TtElement}, }; #[derive(Debug, PartialEq, Eq)] @@ -61,35 +62,35 @@ impl Attrs<'_> { // This fn is intended for `#[proc_macro_derive(..)]` and `#[rustc_builtin_macro(..)]`, which have // the same structure. -#[rustfmt::skip] pub(crate) fn parse_macro_name_and_helper_attrs(tt: &TopSubtree) -> Option<(Name, Box<[Name]>)> { - match tt.token_trees().flat_tokens() { + if let Some([TtElement::Leaf(Leaf::Ident(trait_name))]) = + tt.token_trees().iter().collect_array() + { // `#[proc_macro_derive(Trait)]` // `#[rustc_builtin_macro(Trait)]` - [TokenTree::Leaf(Leaf::Ident(trait_name))] => Some((trait_name.as_name(), Box::new([]))), - + Some((trait_name.as_name(), Box::new([]))) + } else if let Some( + [ + TtElement::Leaf(Leaf::Ident(trait_name)), + TtElement::Leaf(Leaf::Punct(comma)), + TtElement::Leaf(Leaf::Ident(attributes)), + TtElement::Subtree(_, helpers), + ], + ) = tt.token_trees().iter().collect_array() + && comma.char == ',' + && attributes.sym == sym::attributes + { // `#[proc_macro_derive(Trait, attributes(helper1, helper2, ...))]` // `#[rustc_builtin_macro(Trait, attributes(helper1, helper2, ...))]` - [ - TokenTree::Leaf(Leaf::Ident(trait_name)), - TokenTree::Leaf(Leaf::Punct(comma)), - TokenTree::Leaf(Leaf::Ident(attributes)), - TokenTree::Subtree(_), - .. - ] if comma.char == ',' && attributes.sym == sym::attributes => - { - let helpers = tt::TokenTreesView::new(&tt.token_trees().flat_tokens()[3..]).try_into_subtree()?; - let helpers = helpers - .iter() - .filter_map(|tt| match tt { - TtElement::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()), - _ => None, - }) - .collect::>(); - - Some((trait_name.as_name(), helpers)) - } + let helpers = helpers + .filter_map(|tt| match tt { + TtElement::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()), + _ => None, + }) + .collect::>(); - _ => None, + Some((trait_name.as_name(), helpers)) + } else { + None } } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs index e1807cd2e1e9d..5a440187203c9 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs @@ -36,6 +36,7 @@ use base_db::Crate; use cfg::{CfgExpr, CfgOptions}; use either::Either; use intern::{Interned, Symbol}; +use itertools::Itertools; use mbe::{DelimiterKind, Punct}; use parser::T; use smallvec::SmallVec; @@ -453,10 +454,10 @@ impl Attr { } /// #[path(ident)] - pub fn single_ident_value(&self) -> Option<&tt::Ident> { + pub fn single_ident_value(&self) -> Option { match self.input.as_deref()? { - AttrInput::TokenTree(tt) => match tt.token_trees().flat_tokens() { - [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] => Some(ident), + AttrInput::TokenTree(tt) => match tt.token_trees().iter().collect_array() { + Some([tt::TtElement::Leaf(tt::Leaf::Ident(ident))]) => Some(ident), _ => None, }, _ => None, @@ -492,7 +493,7 @@ fn parse_path_comma_token_tree<'a>( args.token_trees() .split(|tt| matches!(tt, tt::TtElement::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))) .filter_map(move |tts| { - let span = tts.flat_tokens().first()?.first_span(); + let span = tts.first_span()?; Some((ModPath::from_tt(db, tts)?, span, tts)) }) } @@ -611,16 +612,12 @@ impl AttrId { else { return derive_attr_range; }; - let (Some(first_tt), Some(last_tt)) = - (derive_tts.flat_tokens().first(), derive_tts.flat_tokens().last()) + let (Some(first_span), Some(last_span)) = (derive_tts.first_span(), derive_tts.last_span()) else { return derive_attr_range; }; - let start = first_tt.first_span().range.start(); - let end = match last_tt { - tt::TokenTree::Leaf(it) => it.span().range.end(), - tt::TokenTree::Subtree(it) => it.delimiter.close.range.end(), - }; + let start = first_span.range.start(); + let end = last_span.range.end(); TextRange::new(start, end) } } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs index 3d630cfc1c8e9..011a9530c4d3a 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs @@ -210,7 +210,7 @@ fn stringify_expand( tt: &tt::TopSubtree, span: Span, ) -> ExpandResult { - let pretty = ::tt::pretty(tt.token_trees().flat_tokens()); + let pretty = ::tt::pretty(tt.token_trees()); let expanded = quote! {span => #pretty @@ -283,7 +283,7 @@ fn format_args_expand( ) -> ExpandResult { let pound = mk_pound(span); let mut tt = tt.clone(); - tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis; + tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Parenthesis); ExpandResult::ok(quote! {span => builtin #pound format_args #tt }) @@ -297,14 +297,14 @@ fn format_args_nl_expand( ) -> ExpandResult { let pound = mk_pound(span); let mut tt = tt.clone(); - tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis; - if let Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - kind: tt::LitKind::Str, - .. - }))) = tt.0.get_mut(1) + tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Parenthesis); + let lit = tt.as_token_trees().iter_flat_tokens().nth(1); + if let Some(tt::TokenTree::Leaf(tt::Leaf::Literal( + mut lit @ tt::Literal { kind: tt::LitKind::Str, .. }, + ))) = lit { - *text = Symbol::intern(&format_smolstr!("{}\\n", text.as_str())); + lit.symbol = Symbol::intern(&format_smolstr!("{}\\n", lit.symbol.as_str())); + tt.set_token(1, lit.into()); } ExpandResult::ok(quote! {span => builtin #pound format_args #tt @@ -318,7 +318,7 @@ fn asm_expand( span: Span, ) -> ExpandResult { let mut tt = tt.clone(); - tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis; + tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Parenthesis); let pound = mk_pound(span); let expanded = quote! {span => builtin #pound asm #tt @@ -333,7 +333,7 @@ fn global_asm_expand( span: Span, ) -> ExpandResult { let mut tt = tt.clone(); - tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis; + tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Parenthesis); let pound = mk_pound(span); let expanded = quote! {span => builtin #pound global_asm #tt @@ -348,7 +348,7 @@ fn naked_asm_expand( span: Span, ) -> ExpandResult { let mut tt = tt.clone(); - tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis; + tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Parenthesis); let pound = mk_pound(span); let expanded = quote! {span => builtin #pound naked_asm #tt @@ -478,11 +478,11 @@ fn unreachable_expand( // Pass the original arguments let mut subtree = tt.clone(); - *subtree.top_subtree_delimiter_mut() = tt::Delimiter { + subtree.set_top_subtree_delimiter_kind(tt::DelimiterKind::Parenthesis); + subtree.set_top_subtree_delimiter_span(tt::DelimSpan { open: call_site_span, close: call_site_span, - kind: tt::DelimiterKind::Parenthesis, - }; + }); // Expand to a macro call `$crate::panic::panic_{edition}` let call = quote!(call_site_span =>#dollar_crate::panic::#mac! #subtree); @@ -518,16 +518,17 @@ fn compile_error_expand( tt: &tt::TopSubtree, span: Span, ) -> ExpandResult { - let err = match &*tt.0 { - [ - _, - tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span: _, - kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), - suffix: _, - })), - ] => ExpandError::other(span, Box::from(unescape_symbol(text).as_str())), + let err = match tt.iter().collect_array() { + Some( + [ + tt::TtElement::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: text, + span: _, + kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), + suffix: _, + })), + ], + ) => ExpandError::other(span, Box::from(unescape_symbol(&text).as_str())), _ => ExpandError::other(span, "`compile_error!` argument must be a string"), }; @@ -556,7 +557,7 @@ fn concat_expand( // to ensure the right parsing order, so skip the parentheses here. Ideally we'd // implement rustc's model. cc https://github.com/rust-lang/rust-analyzer/pull/10623 if let TtElement::Subtree(subtree, subtree_iter) = &t - && let [tt::TokenTree::Leaf(tt)] = subtree_iter.remaining().flat_tokens() + && let Some([tt::TtElement::Leaf(tt)]) = subtree_iter.clone().collect_array() && subtree.delimiter.kind == tt::DelimiterKind::Parenthesis { t = TtElement::Leaf(tt); @@ -663,7 +664,7 @@ fn concat_bytes_expand( kind, suffix: _, })) => { - record_span(*span); + record_span(span); match kind { tt::LitKind::Byte => { if let Ok(b) = unescape_byte(text.as_str()) { @@ -679,7 +680,7 @@ fn concat_bytes_expand( bytes.extend(text.as_str().escape_debug()); } _ => { - err.get_or_insert(ExpandError::other(*span, "unexpected token")); + err.get_or_insert(ExpandError::other(span, "unexpected token")); break; } } @@ -733,7 +734,7 @@ fn concat_bytes_expand_subtree( if let Ok(b) = unescape_byte(text.as_str()) { bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32))); } - record_span(*span); + record_span(span); } TtElement::Leaf(tt::Leaf::Literal(tt::Literal { symbol: text, @@ -741,7 +742,7 @@ fn concat_bytes_expand_subtree( kind: tt::LitKind::Integer, suffix: _, })) => { - record_span(*span); + record_span(span); if let Ok(b) = text.as_str().parse::() { bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32))); } @@ -796,13 +797,13 @@ fn parse_string(tt: &tt::TopSubtree) -> Result<(Symbol, Span), ExpandError> { span, kind: tt::LitKind::Str, suffix: _, - })) => Ok((unescape_symbol(text), *span)), + })) => Ok((unescape_symbol(&text), span)), TtElement::Leaf(tt::Leaf::Literal(tt::Literal { symbol: text, span, kind: tt::LitKind::StrRaw(_), suffix: _, - })) => Ok((text.clone(), *span)), + })) => Ok((text.clone(), span)), TtElement::Leaf(l) => Err(*l.span()), TtElement::Subtree(tt, _) => Err(tt.delimiter.open.cover(tt.delimiter.close)), } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs index b1d46f5c4e76f..4039b6c334eca 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs @@ -163,7 +163,7 @@ impl ToTokenTree for crate::tt::SubtreeView<'_> { impl ToTokenTree for crate::tt::TopSubtree { fn to_tokens(self, _: Span, builder: &mut TopSubtreeBuilder) { - builder.extend_tt_dangerous(self.0); + builder.extend_with_tt(self.as_token_trees()); } } @@ -172,10 +172,9 @@ impl ToTokenTree for crate::tt::TtElement<'_> { match self { crate::tt::TtElement::Leaf(leaf) => builder.push(leaf.clone()), crate::tt::TtElement::Subtree(subtree, subtree_iter) => { - builder.extend_tt_dangerous( - std::iter::once(crate::tt::TokenTree::Subtree(subtree.clone())) - .chain(subtree_iter.remaining().flat_tokens().iter().cloned()), - ); + builder.open(subtree.delimiter.kind, subtree.delimiter.open); + builder.extend_with_tt(subtree_iter.remaining()); + builder.close(subtree.delimiter.close); } } } 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 5c517e671be4d..40d44cd1dba26 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -237,7 +237,8 @@ pub fn expand_speculative( span, DocCommentDesugarMode::ProcMacro, ); - *tree.top_subtree_delimiter_mut() = tt::Delimiter::invisible_spanned(span); + tree.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); + tree.set_top_subtree_delimiter_span(tt::DelimSpan::from_single(span)); tree }, ) @@ -255,7 +256,7 @@ pub fn expand_speculative( span, DocCommentDesugarMode::ProcMacro, ); - attr_arg.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Invisible; + attr_arg.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); Some(attr_arg) } _ => None, @@ -270,7 +271,8 @@ pub fn expand_speculative( let mut speculative_expansion = match loc.def.kind { MacroDefKind::ProcMacro(ast, expander, _) => { let span = db.proc_macro_span(ast); - *tt.top_subtree_delimiter_mut() = tt::Delimiter::invisible_spanned(span); + tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); + tt.set_top_subtree_delimiter_span(tt::DelimSpan::from_single(span)); expander.expand( db, loc.def.krate, @@ -430,7 +432,7 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult { ( Arc::new(tt::TopSubtree::from_token_trees( tt::Delimiter { open: span, close: span, kind }, - tt::TokenTreesView::new(&[]), + tt::TokenTreesView::empty(), )), SyntaxFixupUndoInfo::default(), span, @@ -478,7 +480,7 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult { ); if loc.def.is_proc_macro() { // proc macros expect their inputs without parentheses, MBEs expect it with them included - tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Invisible; + tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); } return (Arc::new(tt), SyntaxFixupUndoInfo::NONE, span); } @@ -512,7 +514,7 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult { if loc.def.is_proc_macro() { // proc macros expect their inputs without parentheses, MBEs expect it with them included - tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Invisible; + tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); } (Arc::new(tt), undo_info, span) 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 9b65bdac65c09..0b6124ebf3598 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs @@ -96,7 +96,7 @@ pub fn expand_eager_macro_input( DocCommentDesugarMode::Mbe, ); - subtree.top_subtree_delimiter_mut().kind = crate::tt::DelimiterKind::Invisible; + subtree.set_top_subtree_delimiter_kind(crate::tt::DelimiterKind::Invisible); let loc = MacroCallLoc { def, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs index 876d870936631..6afc6e5d5e7f2 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs @@ -15,7 +15,7 @@ use syntax::{ }; use syntax_bridge::DocCommentDesugarMode; use triomphe::Arc; -use tt::Spacing; +use tt::{Spacing, TransformTtAction, transform_tt}; use crate::{ span_map::SpanMapRef, @@ -343,93 +343,29 @@ fn has_error_to_handle(node: &SyntaxNode) -> bool { pub(crate) fn reverse_fixups(tt: &mut TopSubtree, undo_info: &SyntaxFixupUndoInfo) { let Some(undo_info) = undo_info.original.as_deref() else { return }; let undo_info = &**undo_info; - let delimiter = tt.top_subtree_delimiter_mut(); + let top_subtree = tt.top_subtree(); + let open_span = top_subtree.delimiter.open; + let close_span = top_subtree.delimiter.close; #[allow(deprecated)] if never!( - delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID - || delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID + close_span.anchor.ast_id == FIXUP_DUMMY_AST_ID + || open_span.anchor.ast_id == FIXUP_DUMMY_AST_ID ) { let span = |file_id| Span { range: TextRange::empty(TextSize::new(0)), anchor: SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID }, ctx: SyntaxContext::root(span::Edition::Edition2015), }; - delimiter.open = span(delimiter.open.anchor.file_id); - delimiter.close = span(delimiter.close.anchor.file_id); + tt.set_top_subtree_delimiter_span(tt::DelimSpan { + open: span(open_span.anchor.file_id), + close: span(close_span.anchor.file_id), + }); } reverse_fixups_(tt, undo_info); } -#[derive(Debug)] -enum TransformTtAction<'a> { - Keep, - ReplaceWith(tt::TokenTreesView<'a>), -} - -impl TransformTtAction<'_> { - fn remove() -> Self { - Self::ReplaceWith(tt::TokenTreesView::new(&[])) - } -} - -/// This function takes a token tree, and calls `callback` with each token tree in it. -/// Then it does what the callback says: keeps the tt or replaces it with a (possibly empty) -/// tts view. -fn transform_tt<'a, 'b>( - tt: &'a mut Vec, - mut callback: impl FnMut(&mut tt::TokenTree) -> TransformTtAction<'b>, -) { - // We need to keep a stack of the currently open subtrees, because we need to update - // them if we change the number of items in them. - let mut subtrees_stack = Vec::new(); - let mut i = 0; - while i < tt.len() { - 'pop_finished_subtrees: while let Some(&subtree_idx) = subtrees_stack.last() { - let tt::TokenTree::Subtree(subtree) = &tt[subtree_idx] else { - unreachable!("non-subtree on subtrees stack"); - }; - if i >= subtree_idx + 1 + subtree.usize_len() { - subtrees_stack.pop(); - } else { - break 'pop_finished_subtrees; - } - } - - let action = callback(&mut tt[i]); - match action { - TransformTtAction::Keep => { - // This cannot be shared with the replaced case, because then we may push the same subtree - // twice, and will update it twice which will lead to errors. - if let tt::TokenTree::Subtree(_) = &tt[i] { - subtrees_stack.push(i); - } - - i += 1; - } - TransformTtAction::ReplaceWith(replacement) => { - let old_len = 1 + match &tt[i] { - tt::TokenTree::Leaf(_) => 0, - tt::TokenTree::Subtree(subtree) => subtree.usize_len(), - }; - let len_diff = replacement.len() as i64 - old_len as i64; - tt.splice(i..i + old_len, replacement.flat_tokens().iter().cloned()); - // Skip the newly inserted replacement, we don't want to visit it. - i += replacement.len(); - - for &subtree_idx in &subtrees_stack { - let tt::TokenTree::Subtree(subtree) = &mut tt[subtree_idx] else { - unreachable!("non-subtree on subtrees stack"); - }; - subtree.len = (i64::from(subtree.len) + len_diff).try_into().unwrap(); - } - } - } - } -} - fn reverse_fixups_(tt: &mut TopSubtree, undo_info: &[TopSubtree]) { - let mut tts = std::mem::take(&mut tt.0).into_vec(); - transform_tt(&mut tts, |tt| match tt { + transform_tt(tt, |tt| match tt { tt::TokenTree::Leaf(leaf) => { let span = leaf.span(); let is_real_leaf = span.anchor.ast_id != FIXUP_DUMMY_AST_ID; @@ -459,7 +395,6 @@ fn reverse_fixups_(tt: &mut TopSubtree, undo_info: &[TopSubtree]) { TransformTtAction::Keep } }); - tt.0 = tts.into_boxed_slice(); } #[cfg(test)] @@ -488,9 +423,9 @@ mod tests { } fn check_subtree_eq(a: &tt::TopSubtree, b: &tt::TopSubtree) -> bool { - let a = a.view().as_token_trees().flat_tokens(); - let b = b.view().as_token_trees().flat_tokens(); - a.len() == b.len() && std::iter::zip(a, b).all(|(a, b)| check_tt_eq(a, b)) + let a = a.view().as_token_trees().iter_flat_tokens(); + let b = b.view().as_token_trees().iter_flat_tokens(); + a.len() == b.len() && std::iter::zip(a, b).all(|(a, b)| check_tt_eq(&a, &b)) } fn check_tt_eq(a: &tt::TokenTree, b: &tt::TokenTree) -> bool { @@ -545,7 +480,7 @@ mod tests { // the fixed-up tree should not contain braces as punct // FIXME: should probably instead check that it's a valid punctuation character - for x in tt.token_trees().flat_tokens() { + for x in tt.token_trees().iter_flat_tokens() { match x { ::tt::TokenTree::Leaf(::tt::Leaf::Punct(punct)) => { assert!(!matches!(punct.char, '{' | '}' | '(' | ')' | '[' | ']')) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs index e9805e3f86b8c..51e449fe50856 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs @@ -355,16 +355,16 @@ fn convert_path_tt(db: &dyn ExpandDatabase, tt: tt::TokenTreesView<'_>) -> Optio tt::Leaf::Punct(tt::Punct { char: ':', .. }) => PathKind::Abs, _ => return None, }, - tt::Leaf::Ident(tt::Ident { sym: text, span, .. }) if *text == sym::dollar_crate => { + tt::Leaf::Ident(tt::Ident { sym: text, span, .. }) if text == sym::dollar_crate => { resolve_crate_root(db, span.ctx).map(PathKind::DollarCrate).unwrap_or(PathKind::Crate) } - tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::self_ => PathKind::SELF, - tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::super_ => { + tt::Leaf::Ident(tt::Ident { sym: text, .. }) if text == sym::self_ => PathKind::SELF, + tt::Leaf::Ident(tt::Ident { sym: text, .. }) if text == sym::super_ => { let mut deg = 1; while let Some(tt::Leaf::Ident(tt::Ident { sym: text, span, is_raw: _ })) = leaves.next() { - if *text != sym::super_ { + if text != sym::super_ { segments.push(Name::new_symbol(text.clone(), span.ctx)); break; } @@ -372,7 +372,7 @@ fn convert_path_tt(db: &dyn ExpandDatabase, tt: tt::TokenTreesView<'_>) -> Optio } PathKind::Super(deg) } - tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::crate_ => PathKind::Crate, + tt::Leaf::Ident(tt::Ident { sym: text, .. }) if text == sym::crate_ => PathKind::Crate, tt::Leaf::Ident(ident) => { segments.push(Name::new_symbol(ident.sym.clone(), ident.span.ctx)); PathKind::Plain diff --git a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs index e0f1e616d851e..6b018510be143 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs @@ -54,7 +54,7 @@ fn benchmark_expand_macro_rules() { .map(|(id, tt)| { let res = rules[&id].expand(&db, &tt, |_| (), MacroCallStyle::FnLike, DUMMY); assert!(res.err.is_none()); - res.value.0.0.len() + res.value.0.as_token_trees().len() }) .sum() }; diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander.rs b/src/tools/rust-analyzer/crates/mbe/src/expander.rs index 36c7871519aa0..25e05df7b710f 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander.rs @@ -162,7 +162,7 @@ impl Fragment<'_> { Fragment::Tokens { tree, .. } => tree.len() == 0, Fragment::Expr(it) => it.len() == 0, Fragment::Path(it) => it.len() == 0, - Fragment::TokensOwned(it) => it.0.is_empty(), + Fragment::TokensOwned(_) => false, // A `TopSubtree` is never empty } } } diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs index c4c1e1f5654f5..e845b1ab8d1f2 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs @@ -517,7 +517,7 @@ fn match_loop_inner<'t>( } OpDelimited::Op(Op::Literal(lhs)) => { if let Ok(rhs) = src.clone().expect_leaf() { - if matches!(rhs, tt::Leaf::Literal(it) if it.symbol == lhs.symbol) { + if matches!(&rhs, tt::Leaf::Literal(it) if it.symbol == lhs.symbol) { item.dot.next(); } else { res.add_err(ExpandError::new( @@ -537,7 +537,7 @@ fn match_loop_inner<'t>( } OpDelimited::Op(Op::Ident(lhs)) => { if let Ok(rhs) = src.clone().expect_leaf() { - if matches!(rhs, tt::Leaf::Ident(it) if it.sym == lhs.sym) { + if matches!(&rhs, tt::Leaf::Ident(it) if it.sym == lhs.sym) { item.dot.next(); } else { res.add_err(ExpandError::new( @@ -701,7 +701,7 @@ fn match_loop<'t>( || !(bb_items.is_empty() || next_items.is_empty()) || bb_items.len() > 1; if has_leftover_tokens { - res.unmatched_tts += src.remaining().flat_tokens().len(); + res.unmatched_tts += src.remaining().len(); res.add_err(ExpandError::new(span.open, ExpandErrorKind::LeftoverTokens)); if let Some(error_recover_item) = error_recover_item { @@ -991,7 +991,7 @@ fn expect_tt(iter: &mut TtIter<'_>) -> Result<(), ()> { Ok(()) } -fn expect_lifetime<'a>(iter: &mut TtIter<'a>) -> Result<&'a tt::Ident, ()> { +fn expect_lifetime<'a>(iter: &mut TtIter<'a>) -> Result { let punct = iter.expect_single_punct()?; if punct.char != '\'' { return Err(()); @@ -1000,7 +1000,7 @@ fn expect_lifetime<'a>(iter: &mut TtIter<'a>) -> Result<&'a tt::Ident, ()> { } fn eat_char(iter: &mut TtIter<'_>, c: char) { - if matches!(iter.peek(), Some(TtElement::Leaf(tt::Leaf::Punct(tt::Punct { char, .. }))) if *char == c) + if matches!(iter.peek(), Some(TtElement::Leaf(tt::Leaf::Punct(tt::Punct { char, .. }))) if char == c) { iter.next().expect("already peeked"); } diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs index a0fd4550ba8c1..38098e2c84da2 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs @@ -3,6 +3,7 @@ use intern::{Symbol, sym}; use span::{Edition, Span}; +use stdx::itertools::Itertools; use tt::{Delimiter, TopSubtreeBuilder, iter::TtElement}; use super::TokensOrigin; @@ -324,7 +325,7 @@ fn expand_subtree( } _ => (None, None), }; - let value = match values { + let value = match &values { (Some(TtElement::Leaf(tt::Leaf::Ident(ident))), None) => { ident.sym.as_str() } @@ -412,15 +413,15 @@ fn expand_var( // Check if this is a simple negative literal (MINUS + LITERAL) // that should not be wrapped in parentheses let is_negative_literal = matches!( - sub.flat_tokens(), - [ - tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '-', .. })), - tt::TokenTree::Leaf(tt::Leaf::Literal(_)) - ] + sub.iter().collect_array(), + Some([ + tt::TtElement::Leaf(tt::Leaf::Punct(tt::Punct { char: '-', .. })), + tt::TtElement::Leaf(tt::Leaf::Literal(_)) + ]) ); let wrap_in_parens = !is_negative_literal - && !matches!(sub.flat_tokens(), [tt::TokenTree::Leaf(_)]) + && !matches!(sub.iter().collect_array(), Some([tt::TtElement::Leaf(_)])) && sub.try_into_subtree().is_none_or(|it| { it.top_subtree().delimiter.kind == tt::DelimiterKind::Invisible }); @@ -560,8 +561,8 @@ fn fix_up_and_push_path_tt( // argument list and thus needs `::` between it and `FnOnce`. However in // today's Rust this type of path *semantically* cannot appear as a // top-level expression-context path, so we can safely ignore it. - if let [tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '<', .. }))] = - tt.flat_tokens() + if let Some([tt::TtElement::Leaf(tt::Leaf::Punct(tt::Punct { char: '<', .. }))]) = + tt.iter().collect_array() { builder.extend([ tt::Leaf::Punct(tt::Punct { @@ -577,7 +578,8 @@ fn fix_up_and_push_path_tt( ]); } } - prev_was_ident = matches!(tt.flat_tokens(), [tt::TokenTree::Leaf(tt::Leaf::Ident(_))]); + prev_was_ident = + matches!(tt.iter().collect_array(), Some([tt::TtElement::Leaf(tt::Leaf::Ident(_))])); builder.extend_with_tt(tt); } } diff --git a/src/tools/rust-analyzer/crates/mbe/src/parser.rs b/src/tools/rust-analyzer/crates/mbe/src/parser.rs index ddf9afbf98ab7..cecdd43a42a11 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/parser.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/parser.rs @@ -224,7 +224,7 @@ fn next_op( None => { return Ok(Op::Punct({ let mut res = ArrayVec::new(); - res.push(*p); + res.push(p); Box::new(res) })); } @@ -268,7 +268,7 @@ fn next_op( let id = ident.span; Op::Var { name, kind, id } } - tt::Leaf::Literal(lit) if is_boolean_literal(lit) => { + tt::Leaf::Literal(lit) if is_boolean_literal(&lit) => { let kind = eat_fragment_kind(edition, src, mode)?; let name = lit.symbol.clone(); let id = lit.span; @@ -282,7 +282,7 @@ fn next_op( } Mode::Template => Op::Punct({ let mut res = ArrayVec::new(); - res.push(*punct); + res.push(punct); Box::new(res) }), }, @@ -400,7 +400,7 @@ fn parse_repeat(src: &mut TtIter<'_>) -> Result<(Option, RepeatKind), '?' => RepeatKind::ZeroOrOne, _ => match &mut separator { Separator::Puncts(puncts) if puncts.len() < 3 => { - puncts.push(*punct); + puncts.push(punct); continue; } _ => return Err(ParseError::InvalidRepeat), 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 8c96b06700a97..ed4b7aff76fcd 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 @@ -489,7 +489,7 @@ struct Writer<'a, 'span, S: SpanTransformer, W> { impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIter<'a>> { fn write_subtree(&mut self, root: tt::SubtreeView<'a>) { let subtree = root.top_subtree(); - self.enqueue(subtree, root.iter()); + self.enqueue(&subtree, root.iter()); while let Some((idx, len, subtree)) = self.work.pop_front() { self.subtree(idx, len, subtree); } @@ -504,7 +504,7 @@ impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIt for child in subtree { let idx_tag = match child { tt::iter::TtElement::Subtree(subtree, subtree_iter) => { - let idx = self.enqueue(subtree, subtree_iter); + let idx = self.enqueue(&subtree, subtree_iter); idx << 2 } tt::iter::TtElement::Leaf(leaf) => match leaf { @@ -513,8 +513,11 @@ impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIt let id = self.token_id_of(lit.span); let (text, suffix) = if self.version >= EXTENDED_LEAF_DATA { ( - self.intern(lit.symbol.as_str()), - lit.suffix.as_ref().map(|s| self.intern(s.as_str())).unwrap_or(!0), + self.intern_owned(lit.symbol.as_str().to_owned()), + lit.suffix + .as_ref() + .map(|s| self.intern_owned(s.as_str().to_owned())) + .unwrap_or(!0), ) } else { (self.intern_owned(format!("{lit}")), !0) @@ -549,11 +552,11 @@ impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIt let idx = self.ident.len() as u32; let id = self.token_id_of(ident.span); let text = if self.version >= EXTENDED_LEAF_DATA { - self.intern(ident.sym.as_str()) + self.intern_owned(ident.sym.as_str().to_owned()) } else if ident.is_raw.yes() { self.intern_owned(format!("r#{}", ident.sym.as_str(),)) } else { - self.intern(ident.sym.as_str()) + self.intern_owned(ident.sym.as_str().to_owned()) }; self.ident.push(IdentRepr { id, text, is_raw: ident.is_raw.yes() }); (idx << 2) | 0b11 @@ -565,7 +568,7 @@ impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIt } } - fn enqueue(&mut self, subtree: &'a tt::Subtree, contents: tt::iter::TtIter<'a>) -> u32 { + fn enqueue(&mut self, subtree: &tt::Subtree, contents: tt::iter::TtIter<'a>) -> u32 { let idx = self.subtree.len(); let open = self.token_id_of(subtree.delimiter.open); let close = self.token_id_of(subtree.delimiter.close); @@ -582,6 +585,7 @@ impl<'a, T: SpanTransformer, U> Writer<'a, '_, T, U> { T::token_id_of(self.span_data_table, span) } + #[cfg(feature = "sysroot-abi")] pub(crate) fn intern(&mut self, text: &'a str) -> u32 { let table = &mut self.text; *self.string_table.entry(text.into()).or_insert_with(|| { @@ -840,7 +844,7 @@ impl> Reader<'_, T> { let (delimiter, mut res) = res[0].take().unwrap(); res.insert(0, tt::TokenTree::Subtree(tt::Subtree { delimiter, len: res.len() as u32 })); - tt::TopSubtree(res.into_boxed_slice()) + tt::TopSubtree::from_serialized(res) } } 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 e0d20c82b9e27..f5fcc99f14a3c 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 @@ -194,28 +194,14 @@ impl ProcMacro { /// On some server versions, the fixup ast id is different than ours. So change it to match. fn change_fixup_to_match_old_server(&self, tt: &mut tt::TopSubtree) { const OLD_FIXUP_AST_ID: ErasedFileAstId = ErasedFileAstId::from_raw(!0 - 1); - let change_ast_id = |ast_id: &mut ErasedFileAstId| { + tt.change_every_ast_id(|ast_id| { if *ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { *ast_id = OLD_FIXUP_AST_ID; } else if *ast_id == OLD_FIXUP_AST_ID { // Swap between them, that means no collision plus the change can be reversed by doing itself. *ast_id = FIXUP_ERASED_FILE_AST_ID_MARKER; } - }; - - for tt in &mut tt.0 { - match tt { - tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { span, .. })) - | tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { span, .. })) - | tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { span, .. })) => { - change_ast_id(&mut span.anchor.ast_id); - } - tt::TokenTree::Subtree(subtree) => { - change_ast_id(&mut subtree.delimiter.open.anchor.ast_id); - change_ast_id(&mut subtree.delimiter.close.anchor.ast_id); - } - } - } + }); } /// Expands the procedural macro by sending an expansion request to the server. diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs index 82cf51f1627a0..79b51a816ebef 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs @@ -223,7 +223,7 @@ where spacing: _, })) => { let found_expected_delimiter = - builder.expected_delimiters().enumerate().find(|(_, delim)| match delim.kind { + builder.expected_delimiters().enumerate().find(|(_, delim)| match delim { tt::DelimiterKind::Parenthesis => char == ')', tt::DelimiterKind::Brace => char == '}', tt::DelimiterKind::Bracket => char == ']', @@ -257,13 +257,11 @@ where } kind if kind.is_punct() && kind != UNDERSCORE => { let found_expected_delimiter = - builder.expected_delimiters().enumerate().find(|(_, delim)| { - match delim.kind { - tt::DelimiterKind::Parenthesis => kind == T![')'], - tt::DelimiterKind::Brace => kind == T!['}'], - tt::DelimiterKind::Bracket => kind == T![']'], - tt::DelimiterKind::Invisible => false, - } + builder.expected_delimiters().enumerate().find(|(_, delim)| match delim { + tt::DelimiterKind::Parenthesis => kind == T![')'], + tt::DelimiterKind::Brace => kind == T!['}'], + tt::DelimiterKind::Bracket => kind == T![']'], + tt::DelimiterKind::Invisible => false, }); // Current token is a closing delimiter that we expect, fix up the closing span diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/tests.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/tests.rs index c8dc3131b59c6..8c28e1c5aaac0 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/tests.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/tests.rs @@ -30,7 +30,7 @@ fn check_punct_spacing(fixture: &str) { }) .collect(); - let mut cursor = Cursor::new(&subtree.0); + let mut cursor = Cursor::new(subtree.as_token_trees()); while !cursor.eof() { while let Some(token_tree) = cursor.token_tree() { if let tt::TokenTree::Leaf(Leaf::Punct(Punct { diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index 67f69d0fa98e3..487171c3e3e8d 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -770,7 +770,7 @@ impl ProcMacroExpander for Issue18089ProcMacroExpander { _: Span, _: String, ) -> Result { - let tt::TokenTree::Leaf(macro_name) = &subtree.0[2] else { + let Some(tt::TtElement::Leaf(macro_name)) = subtree.iter().nth(1) else { return Err(ProcMacroExpansionError::Panic("incorrect input".to_owned())); }; Ok(quote! { call_site => @@ -837,13 +837,14 @@ impl ProcMacroExpander for Issue18840ProcMacroExpander { // ``` // The span that was created by the fixup infra. - let fixed_up_span = fn_.token_trees().flat_tokens()[5].first_span(); + let mut iter = fn_.iter(); + iter.nth(2); + let (_, mut fn_body) = iter.expect_subtree().unwrap(); + let fixed_up_span = fn_body.nth(1).unwrap().first_span(); let mut result = quote! {fixed_up_span => ::core::compile_error! { "my cool compile_error!" } }; // Make it so we won't remove the top subtree when reversing fixups. - let top_subtree_delimiter_mut = result.top_subtree_delimiter_mut(); - top_subtree_delimiter_mut.open = def_site; - top_subtree_delimiter_mut.close = def_site; + result.set_top_subtree_delimiter_span(tt::DelimSpan::from_single(def_site)); Ok(result) } @@ -905,13 +906,14 @@ impl ProcMacroExpander for ShortenProcMacroExpander { _: Span, _: String, ) -> Result { - let mut result = input.0.clone(); - for it in &mut result { - if let TokenTree::Leaf(leaf) = it { - modify_leaf(leaf) + let mut result = input.clone(); + for (idx, it) in input.as_token_trees().iter_flat_tokens().enumerate() { + if let TokenTree::Leaf(mut leaf) = it { + modify_leaf(&mut leaf); + result.set_token(idx, leaf); } } - return Ok(tt::TopSubtree(result)); + return Ok(result); fn modify_leaf(leaf: &mut Leaf) { match leaf { @@ -948,7 +950,8 @@ impl ProcMacroExpander for Issue17479ProcMacroExpander { _: Span, _: String, ) -> Result { - let TokenTree::Leaf(Leaf::Literal(lit)) = &subtree.0[1] else { + let mut iter = subtree.iter(); + let Some(TtElement::Leaf(tt::Leaf::Literal(lit))) = iter.next() else { return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); }; let symbol = &lit.symbol; @@ -980,10 +983,8 @@ impl ProcMacroExpander for Issue18898ProcMacroExpander { ) -> Result { let span = subtree .token_trees() - .flat_tokens() - .last() - .ok_or_else(|| ProcMacroExpansionError::Panic("malformed input".to_owned()))? - .first_span(); + .last_span() + .ok_or_else(|| ProcMacroExpansionError::Panic("malformed input".to_owned()))?; let overly_long_subtree = quote! {span => { let a = 5; @@ -1034,7 +1035,7 @@ impl ProcMacroExpander for DisallowCfgProcMacroExpander { _: Span, _: String, ) -> Result { - for tt in subtree.token_trees().flat_tokens() { + for tt in subtree.token_trees().iter_flat_tokens() { if let tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) = tt && (ident.sym == sym::cfg || ident.sym == sym::cfg_attr) { @@ -1066,20 +1067,23 @@ impl ProcMacroExpander for GenerateSuffixedTypeProcMacroExpander { _mixed_site: Span, _current_dir: String, ) -> Result { - let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[1] else { + let mut iter = subtree.iter(); + let Some(TtElement::Leaf(tt::Leaf::Ident(ident))) = iter.next() else { return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); }; let ident = match ident.sym.as_str() { "struct" => { - let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[2] else { + let Some(TtElement::Leaf(tt::Leaf::Ident(ident))) = iter.next() else { return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); }; ident } "enum" => { - let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[4] else { + iter.next(); + let (_, mut iter) = iter.expect_subtree().unwrap(); + let Some(TtElement::Leaf(tt::Leaf::Ident(ident))) = iter.next() else { return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); }; ident diff --git a/src/tools/rust-analyzer/crates/tt/src/buffer.rs b/src/tools/rust-analyzer/crates/tt/src/buffer.rs index c464e5ece1949..de6379b5cd14b 100644 --- a/src/tools/rust-analyzer/crates/tt/src/buffer.rs +++ b/src/tools/rust-analyzer/crates/tt/src/buffer.rs @@ -10,8 +10,8 @@ pub struct Cursor<'a> { } impl<'a> Cursor<'a> { - pub fn new(buffer: &'a [TokenTree]) -> Self { - Self { buffer, index: 0, subtrees_stack: Vec::new() } + pub fn new(buffer: TokenTreesView<'a>) -> Self { + Self { buffer: buffer.0, index: 0, subtrees_stack: Vec::new() } } /// Check whether it is eof diff --git a/src/tools/rust-analyzer/crates/tt/src/iter.rs b/src/tools/rust-analyzer/crates/tt/src/iter.rs index 88c3c7f52e93d..5ab9f94b63fdf 100644 --- a/src/tools/rust-analyzer/crates/tt/src/iter.rs +++ b/src/tools/rust-analyzer/crates/tt/src/iter.rs @@ -36,28 +36,28 @@ impl<'a> TtIter<'a> { pub fn expect_char(&mut self, char: char) -> Result<(), ()> { match self.next() { - Some(TtElement::Leaf(&Leaf::Punct(Punct { char: c, .. }))) if c == char => Ok(()), + Some(TtElement::Leaf(Leaf::Punct(Punct { char: c, .. }))) if c == char => Ok(()), _ => Err(()), } } pub fn expect_any_char(&mut self, chars: &[char]) -> Result<(), ()> { match self.next() { - Some(TtElement::Leaf(Leaf::Punct(Punct { char: c, .. }))) if chars.contains(c) => { + Some(TtElement::Leaf(Leaf::Punct(Punct { char: c, .. }))) if chars.contains(&c) => { Ok(()) } _ => Err(()), } } - pub fn expect_subtree(&mut self) -> Result<(&'a Subtree, TtIter<'a>), ()> { + pub fn expect_subtree(&mut self) -> Result<(Subtree, TtIter<'a>), ()> { match self.next() { Some(TtElement::Subtree(subtree, iter)) => Ok((subtree, iter)), _ => Err(()), } } - pub fn expect_leaf(&mut self) -> Result<&'a Leaf, ()> { + pub fn expect_leaf(&mut self) -> Result { match self.next() { Some(TtElement::Leaf(it)) => Ok(it), _ => Err(()), @@ -78,30 +78,30 @@ impl<'a> TtIter<'a> { } } - pub fn expect_ident(&mut self) -> Result<&'a Ident, ()> { + pub fn expect_ident(&mut self) -> Result { match self.expect_leaf()? { Leaf::Ident(it) if it.sym != sym::underscore => Ok(it), _ => Err(()), } } - pub fn expect_ident_or_underscore(&mut self) -> Result<&'a Ident, ()> { + pub fn expect_ident_or_underscore(&mut self) -> Result { match self.expect_leaf()? { Leaf::Ident(it) => Ok(it), _ => Err(()), } } - pub fn expect_literal(&mut self) -> Result<&'a Leaf, ()> { + pub fn expect_literal(&mut self) -> Result { let it = self.expect_leaf()?; - match it { + match &it { Leaf::Literal(_) => Ok(it), Leaf::Ident(ident) if ident.sym == sym::true_ || ident.sym == sym::false_ => Ok(it), _ => Err(()), } } - pub fn expect_single_punct(&mut self) -> Result<&'a Punct, ()> { + pub fn expect_single_punct(&mut self) -> Result { match self.expect_leaf()? { Leaf::Punct(it) => Ok(it), _ => Err(()), @@ -113,7 +113,7 @@ impl<'a> TtIter<'a> { /// This method currently may return a single quotation, which is part of lifetime ident and /// conceptually not a punct in the context of mbe. Callers should handle this. pub fn expect_glued_punct(&mut self) -> Result, ()> { - let TtElement::Leaf(&Leaf::Punct(first)) = self.next().ok_or(())? else { + let TtElement::Leaf(Leaf::Punct(first)) = self.next().ok_or(())? else { return Err(()); }; @@ -168,11 +168,11 @@ impl<'a> TtIter<'a> { pub fn peek(&self) -> Option> { match self.inner.as_slice().first()? { - TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf)), + TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf.clone())), TokenTree::Subtree(subtree) => { let nested_iter = TtIter { inner: self.inner.as_slice()[1..][..subtree.usize_len()].iter() }; - Some(TtElement::Subtree(subtree, nested_iter)) + Some(TtElement::Subtree(*subtree, nested_iter)) } } } @@ -214,8 +214,8 @@ impl<'a> TtIter<'a> { #[derive(Clone)] pub enum TtElement<'a> { - Leaf(&'a Leaf), - Subtree(&'a Subtree, TtIter<'a>), + Leaf(Leaf), + Subtree(Subtree, TtIter<'a>), } impl fmt::Debug for TtElement<'_> { @@ -243,12 +243,12 @@ impl<'a> Iterator for TtIter<'a> { type Item = TtElement<'a>; fn next(&mut self) -> Option { match self.inner.next()? { - TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf)), + TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf.clone())), TokenTree::Subtree(subtree) => { let nested_iter = TtIter { inner: self.inner.as_slice()[..subtree.usize_len()].iter() }; self.inner = self.inner.as_slice()[subtree.usize_len()..].iter(); - Some(TtElement::Subtree(subtree, nested_iter)) + Some(TtElement::Subtree(*subtree, nested_iter)) } } } diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs index 636f567f1aa7c..97e0b34ad7b7f 100644 --- a/src/tools/rust-analyzer/crates/tt/src/lib.rs +++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs @@ -111,7 +111,7 @@ impl Leaf { } impl_from!(Literal, Punct, Ident for Leaf); -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Subtree { pub delimiter: Delimiter, /// Number of following token trees that belong to this subtree, excluding this subtree. @@ -125,7 +125,7 @@ impl Subtree { } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct TopSubtree(pub Box<[TokenTree]>); +pub struct TopSubtree(Box<[TokenTree]>); impl TopSubtree { pub fn empty(span: DelimSpan) -> Self { @@ -147,6 +147,10 @@ impl TopSubtree { builder.build() } + pub fn from_serialized(tt: Vec) -> Self { + Self(tt.into_boxed_slice()) + } + pub fn from_subtree(subtree: SubtreeView<'_>) -> Self { Self(subtree.0.into()) } @@ -159,20 +163,55 @@ impl TopSubtree { self.view().iter() } - pub fn top_subtree(&self) -> &Subtree { + pub fn top_subtree(&self) -> Subtree { self.view().top_subtree() } - pub fn top_subtree_delimiter_mut(&mut self) -> &mut Delimiter { + pub fn set_top_subtree_delimiter_kind(&mut self, kind: DelimiterKind) { + self.top_subtree_mut().delimiter.kind = kind; + } + + pub fn set_top_subtree_delimiter_span(&mut self, span: DelimSpan) { + let top_subtree = self.top_subtree_mut(); + top_subtree.delimiter.open = span.open; + top_subtree.delimiter.close = span.close; + } + + fn top_subtree_mut(&mut self) -> &mut Subtree { let TokenTree::Subtree(subtree) = &mut self.0[0] else { unreachable!("the first token tree is always the top subtree"); }; - &mut subtree.delimiter + subtree + } + + pub fn set_token(&mut self, idx: usize, leaf: Leaf) { + assert!(matches!(self.0[idx], TokenTree::Leaf(_)), "cannot replace a subtree by a leaf"); + self.0[idx] = leaf.into(); } pub fn token_trees(&self) -> TokenTreesView<'_> { self.view().token_trees() } + + pub fn as_token_trees(&self) -> TokenTreesView<'_> { + self.view().as_token_trees() + } + + pub fn change_every_ast_id(&mut self, mut callback: impl FnMut(&mut span::ErasedFileAstId)) { + for tt in &mut self.0 { + match tt { + TokenTree::Leaf(Leaf::Ident(Ident { span, .. })) + | TokenTree::Leaf(Leaf::Literal(Literal { span, .. })) + | TokenTree::Leaf(Leaf::Punct(Punct { span, .. })) => { + callback(&mut span.anchor.ast_id); + } + TokenTree::Subtree(subtree) => { + callback(&mut subtree.delimiter.open.anchor.ast_id); + callback(&mut subtree.delimiter.close.anchor.ast_id); + } + } + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -241,11 +280,6 @@ impl TopSubtreeBuilder { self.token_trees.extend(leaves.into_iter().map(TokenTree::Leaf)); } - /// This does not check the token trees are valid, beware! - pub fn extend_tt_dangerous(&mut self, tt: impl IntoIterator) { - self.token_trees.extend(tt); - } - pub fn extend_with_tt(&mut self, tt: TokenTreesView<'_>) { self.token_trees.extend(tt.0.iter().cloned()); } @@ -267,12 +301,12 @@ impl TopSubtreeBuilder { } } - pub fn expected_delimiters(&self) -> impl Iterator { + pub fn expected_delimiters(&self) -> impl Iterator { self.unclosed_subtree_indices.iter().rev().map(|&subtree_idx| { let TokenTree::Subtree(subtree) = &self.token_trees[subtree_idx] else { unreachable!("unclosed token tree is always a subtree") }; - &subtree.delimiter + subtree.delimiter.kind }) } @@ -330,7 +364,7 @@ pub struct SubtreeBuilderRestorePoint { pub struct TokenTreesView<'a>(&'a [TokenTree]); impl<'a> TokenTreesView<'a> { - pub fn new(tts: &'a [TokenTree]) -> Self { + fn new(tts: &'a [TokenTree]) -> Self { if cfg!(debug_assertions) { tts.iter().enumerate().for_each(|(idx, tt)| { if let TokenTree::Subtree(tt) = &tt { @@ -345,12 +379,16 @@ impl<'a> TokenTreesView<'a> { Self(tts) } + pub fn empty() -> Self { + Self(&[]) + } + pub fn iter(&self) -> TtIter<'a> { TtIter::new(self.0) } pub fn cursor(&self) -> Cursor<'a> { - Cursor::new(self.0) + Cursor::new(*self) } pub fn len(&self) -> usize { @@ -374,13 +412,6 @@ impl<'a> TokenTreesView<'a> { self.try_into_subtree().map(|subtree| subtree.strip_invisible()).unwrap_or(self) } - /// This returns a **flat** structure of tokens (subtrees will be represented by a single node - /// preceding their children), so it isn't suited for most use cases, only for matching leaves - /// at the beginning/end with no subtrees before them. If you need a structured pass, use [`TtIter`]. - pub fn flat_tokens(&self) -> &'a [TokenTree] { - self.0 - } - pub fn split( self, mut split_fn: impl FnMut(TtElement<'a>) -> bool, @@ -406,6 +437,21 @@ impl<'a> TokenTreesView<'a> { Some(result) }) } + + pub fn first_span(&self) -> Option { + Some(self.0.first()?.first_span()) + } + + pub fn last_span(&self) -> Option { + Some(match self.0.last()? { + TokenTree::Leaf(it) => *it.span(), + TokenTree::Subtree(it) => it.delimiter.close, + }) + } + + pub fn iter_flat_tokens(&self) -> impl ExactSizeIterator + use<'a> { + self.0.iter().cloned() + } } impl fmt::Debug for TokenTreesView<'_> { @@ -453,11 +499,11 @@ impl fmt::Display for TokenTreesView<'_> { match child { TtElement::Leaf(Leaf::Punct(p)) => { needs_space = p.spacing == Spacing::Alone; - fmt::Display::fmt(p, f)?; + fmt::Display::fmt(&p, f)?; } - TtElement::Leaf(leaf) => fmt::Display::fmt(leaf, f)?, + TtElement::Leaf(leaf) => fmt::Display::fmt(&leaf, f)?, TtElement::Subtree(subtree, subtree_iter) => { - subtree_display(subtree, f, subtree_iter)? + subtree_display(&subtree, f, subtree_iter)? } } } @@ -493,11 +539,11 @@ impl<'a> SubtreeView<'a> { TtIter::new(&self.0[1..]) } - pub fn top_subtree(&self) -> &'a Subtree { + pub fn top_subtree(&self) -> Subtree { let TokenTree::Subtree(subtree) = &self.0[0] else { unreachable!("the first token tree is always the top subtree"); }; - subtree + *subtree } pub fn strip_invisible(&self) -> TokenTreesView<'a> { @@ -791,7 +837,7 @@ fn print_debug_token(f: &mut fmt::Formatter<'_>, level: usize, tt: TtElement<'_> } }, TtElement::Subtree(subtree, subtree_iter) => { - print_debug_subtree(f, subtree, level, subtree_iter)?; + print_debug_subtree(f, &subtree, level, subtree_iter)?; } } @@ -956,7 +1002,7 @@ impl TopSubtree { } } -pub fn pretty(mut tkns: &[TokenTree]) -> String { +pub fn pretty(tkns: TokenTreesView<'_>) -> String { fn tokentree_to_text(tkn: &TokenTree, tkns: &mut &[TokenTree]) -> String { match tkn { TokenTree::Leaf(Leaf::Ident(ident)) => { @@ -966,7 +1012,7 @@ pub fn pretty(mut tkns: &[TokenTree]) -> String { TokenTree::Leaf(Leaf::Punct(punct)) => format!("{}", punct.char), TokenTree::Subtree(subtree) => { let (subtree_content, rest) = tkns.split_at(subtree.usize_len()); - let content = pretty(subtree_content); + let content = pretty(TokenTreesView(subtree_content)); *tkns = rest; let (open, close) = match subtree.delimiter.kind { DelimiterKind::Brace => ("{", "}"), @@ -979,6 +1025,7 @@ pub fn pretty(mut tkns: &[TokenTree]) -> String { } } + let mut tkns = tkns.0; let mut last = String::new(); let mut last_to_joint = true; @@ -994,3 +1041,83 @@ pub fn pretty(mut tkns: &[TokenTree]) -> String { } last } + +#[derive(Debug)] +pub enum TransformTtAction<'a> { + Keep, + ReplaceWith(TokenTreesView<'a>), +} + +impl TransformTtAction<'_> { + #[inline] + pub fn remove() -> Self { + Self::ReplaceWith(TokenTreesView::empty()) + } +} + +/// This function takes a token tree, and calls `callback` with each token tree in it. +/// Then it does what the callback says: keeps the tt or replaces it with a (possibly empty) +/// tts view. +pub fn transform_tt<'b>( + tt: &mut TopSubtree, + mut callback: impl FnMut(TokenTree) -> TransformTtAction<'b>, +) { + let mut tt_vec = std::mem::take(&mut tt.0).into_vec(); + + // We need to keep a stack of the currently open subtrees, because we need to update + // them if we change the number of items in them. + let mut subtrees_stack = Vec::new(); + let mut i = 0; + while i < tt_vec.len() { + 'pop_finished_subtrees: while let Some(&subtree_idx) = subtrees_stack.last() { + let TokenTree::Subtree(subtree) = &tt_vec[subtree_idx] else { + unreachable!("non-subtree on subtrees stack"); + }; + if i >= subtree_idx + 1 + subtree.usize_len() { + subtrees_stack.pop(); + } else { + break 'pop_finished_subtrees; + } + } + + let current = match &tt_vec[i] { + TokenTree::Leaf(leaf) => TokenTree::Leaf(match leaf { + Leaf::Literal(leaf) => Leaf::Literal(leaf.clone()), + Leaf::Punct(leaf) => Leaf::Punct(*leaf), + Leaf::Ident(leaf) => Leaf::Ident(leaf.clone()), + }), + TokenTree::Subtree(subtree) => TokenTree::Subtree(*subtree), + }; + let action = callback(current); + match action { + TransformTtAction::Keep => { + // This cannot be shared with the replaced case, because then we may push the same subtree + // twice, and will update it twice which will lead to errors. + if let TokenTree::Subtree(_) = &tt_vec[i] { + subtrees_stack.push(i); + } + + i += 1; + } + TransformTtAction::ReplaceWith(replacement) => { + let old_len = 1 + match &tt_vec[i] { + TokenTree::Leaf(_) => 0, + TokenTree::Subtree(subtree) => subtree.usize_len(), + }; + let len_diff = replacement.len() as i64 - old_len as i64; + tt_vec.splice(i..i + old_len, replacement.0.iter().cloned()); + // Skip the newly inserted replacement, we don't want to visit it. + i += replacement.len(); + + for &subtree_idx in &subtrees_stack { + let TokenTree::Subtree(subtree) = &mut tt_vec[subtree_idx] else { + unreachable!("non-subtree on subtrees stack"); + }; + subtree.len = (i64::from(subtree.len) + len_diff).try_into().unwrap(); + } + } + } + } + + tt.0 = tt_vec.into_boxed_slice(); +} From 175e297e58198883cc181ad01805c6f3280f2e61 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 29 Dec 2025 05:05:47 +0200 Subject: [PATCH 64/77] Make `tt::Literal` use one `Symbol` for the text and the suffix That shrinks it, which is useless now (it's not the dominant factor), but will become important when we'll implement span compression. --- .../rust-analyzer/crates/cfg/src/cfg_expr.rs | 5 +- .../crates/hir-def/src/item_tree/attrs.rs | 2 +- .../crates/hir-def/src/nameres/collector.rs | 8 +- .../crates/hir-expand/src/attrs.rs | 33 ++--- .../crates/hir-expand/src/builtin/fn_macro.rs | 107 ++++++--------- .../crates/hir-expand/src/builtin/quote.rs | 10 +- .../crates/hir-expand/src/fixup.rs | 2 +- .../rust-analyzer/crates/mbe/src/benchmark.rs | 7 +- .../crates/mbe/src/expander/matcher.rs | 7 +- .../crates/mbe/src/expander/transcriber.rs | 18 ++- .../rust-analyzer/crates/mbe/src/parser.rs | 13 +- .../proc-macro-api/src/legacy_protocol/msg.rs | 25 ++-- .../src/legacy_protocol/msg/flat.rs | 28 ++-- .../crates/syntax-bridge/src/lib.rs | 11 +- .../syntax-bridge/src/to_parser_input.rs | 2 +- .../crates/test-fixture/src/lib.rs | 5 +- src/tools/rust-analyzer/crates/tt/src/lib.rs | 125 +++++++++--------- 17 files changed, 190 insertions(+), 218 deletions(-) diff --git a/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs b/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs index c5da8443a68fc..d253f6f492c7c 100644 --- a/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs +++ b/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs @@ -157,7 +157,8 @@ fn next_cfg_expr_from_ast( }, ctx: span::SyntaxContext::root(span::Edition::Edition2015), }; - let literal = tt::token_to_literal(literal.text(), dummy_span).symbol; + let literal = + Symbol::intern(tt::token_to_literal(literal.text(), dummy_span).text()); it.next(); CfgAtom::KeyValue { key: name, value: literal.clone() }.into() } else { @@ -211,7 +212,7 @@ fn next_cfg_expr(it: &mut tt::iter::TtIter<'_>) -> Option { Some(tt::TtElement::Leaf(tt::Leaf::Literal(literal))) => { it.next(); it.next(); - CfgAtom::KeyValue { key: name, value: literal.symbol.clone() }.into() + CfgAtom::KeyValue { key: name, value: Symbol::intern(literal.text()) }.into() } _ => return Some(CfgExpr::Invalid), } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/attrs.rs index bfebce0013759..79076112847bf 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/attrs.rs @@ -226,7 +226,7 @@ impl<'attr> AttrQuery<'attr> { } #[inline] - pub(crate) fn string_value_with_span(self) -> Option<(&'attr Symbol, span::Span)> { + pub(crate) fn string_value_with_span(self) -> Option<(&'attr str, span::Span)> { self.attrs().find_map(|attr| attr.string_value_with_span()) } 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 822da678d7977..4740e3b99e3de 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 @@ -17,7 +17,7 @@ use hir_expand::{ name::{AsName, Name}, proc_macro::CustomProcMacroExpander, }; -use intern::{Interned, sym}; +use intern::{Interned, Symbol, sym}; use itertools::izip; use la_arena::Idx; use rustc_hash::{FxHashMap, FxHashSet}; @@ -292,13 +292,13 @@ impl<'db> DefCollector<'db> { match () { () if *attr_name == sym::recursion_limit => { if let Some(limit) = attr.string_value() - && let Ok(limit) = limit.as_str().parse() + && let Ok(limit) = limit.parse() { crate_data.recursion_limit = Some(limit); } } () if *attr_name == sym::crate_type => { - if attr.string_value() == Some(&sym::proc_dash_macro) { + if attr.string_value() == Some("proc-macro") { self.is_proc_macro = true; } } @@ -2460,7 +2460,7 @@ impl ModCollector<'_, '_> { let name; let name = match attrs.by_key(sym::rustc_builtin_macro).string_value_with_span() { Some((it, span)) => { - name = Name::new_symbol(it.clone(), span.ctx); + name = Name::new_symbol(Symbol::intern(it), span.ctx); &name } None => { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs index 5a440187203c9..e3f10b2129048 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs @@ -35,7 +35,7 @@ use arrayvec::ArrayVec; use base_db::Crate; use cfg::{CfgExpr, CfgOptions}; use either::Either; -use intern::{Interned, Symbol}; +use intern::Interned; use itertools::Itertools; use mbe::{DelimiterKind, Punct}; use parser::T; @@ -417,37 +417,32 @@ impl fmt::Display for AttrInput { impl Attr { /// #[path = "string"] - pub fn string_value(&self) -> Option<&Symbol> { + pub fn string_value(&self) -> Option<&str> { match self.input.as_deref()? { - AttrInput::Literal(tt::Literal { - symbol: text, - kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), - .. - }) => Some(text), + AttrInput::Literal( + lit @ tt::Literal { kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), .. }, + ) => Some(lit.text()), _ => None, } } /// #[path = "string"] - pub fn string_value_with_span(&self) -> Option<(&Symbol, span::Span)> { + pub fn string_value_with_span(&self) -> Option<(&str, span::Span)> { match self.input.as_deref()? { - AttrInput::Literal(tt::Literal { - symbol: text, - kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), - span, - suffix: _, - }) => Some((text, *span)), + AttrInput::Literal( + lit @ tt::Literal { kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), span, .. }, + ) => Some((lit.text(), *span)), _ => None, } } pub fn string_value_unescape(&self) -> Option> { match self.input.as_deref()? { - AttrInput::Literal(tt::Literal { - symbol: text, kind: tt::LitKind::StrRaw(_), .. - }) => Some(Cow::Borrowed(text.as_str())), - AttrInput::Literal(tt::Literal { symbol: text, kind: tt::LitKind::Str, .. }) => { - unescape(text.as_str()) + AttrInput::Literal(lit @ tt::Literal { kind: tt::LitKind::StrRaw(_), .. }) => { + Some(Cow::Borrowed(lit.text())) + } + AttrInput::Literal(lit @ tt::Literal { kind: tt::LitKind::Str, .. }) => { + unescape(lit.text()) } _ => None, } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs index 011a9530c4d3a..6e4b96b050887 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs @@ -1,5 +1,7 @@ //! Builtin macro +use std::borrow::Cow; + use base_db::AnchoredPath; use cfg::CfgExpr; use either::Either; @@ -13,7 +15,7 @@ use span::{Edition, FileId, Span}; use stdx::format_to; use syntax::{ format_smolstr, - unescape::{unescape_byte, unescape_char, unescape_str}, + unescape::{unescape_byte, unescape_char}, }; use syntax_bridge::syntax_node_to_token_tree; @@ -177,12 +179,7 @@ fn line_expand( // not incremental ExpandResult::ok(tt::TopSubtree::invisible_from_leaves( span, - [tt::Leaf::Literal(tt::Literal { - symbol: sym::INTEGER_0, - span, - kind: tt::LitKind::Integer, - suffix: Some(sym::u32), - })], + [tt::Leaf::Literal(tt::Literal::new("0", span, tt::LitKind::Integer, "u32"))], )) } @@ -303,7 +300,8 @@ fn format_args_nl_expand( mut lit @ tt::Literal { kind: tt::LitKind::Str, .. }, ))) = lit { - lit.symbol = Symbol::intern(&format_smolstr!("{}\\n", lit.symbol.as_str())); + let (text, suffix) = lit.text_and_suffix(); + lit.text_and_suffix = Symbol::intern(&format_smolstr!("{text}\\n{suffix}")); tt.set_token(1, lit.into()); } ExpandResult::ok(quote! {span => @@ -521,14 +519,11 @@ fn compile_error_expand( let err = match tt.iter().collect_array() { Some( [ - tt::TtElement::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span: _, - kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), - suffix: _, - })), + tt::TtElement::Leaf(tt::Leaf::Literal( + lit @ tt::Literal { kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), .. }, + )), ], - ) => ExpandError::other(span, Box::from(unescape_symbol(&text).as_str())), + ) => ExpandError::other(span, Box::from(unescape_str(lit.text()))), _ => ExpandError::other(span, "`compile_error!` argument must be a string"), }; @@ -569,20 +564,20 @@ fn concat_expand( // as-is. match it.kind { tt::LitKind::Char => { - if let Ok(c) = unescape_char(it.symbol.as_str()) { + if let Ok(c) = unescape_char(it.text()) { text.push(c); } record_span(it.span); } tt::LitKind::Integer | tt::LitKind::Float => { - format_to!(text, "{}", it.symbol.as_str()) + format_to!(text, "{}", it.text()) } tt::LitKind::Str => { - text.push_str(unescape_symbol(&it.symbol).as_str()); + text.push_str(&unescape_str(it.text())); record_span(it.span); } tt::LitKind::StrRaw(_) => { - format_to!(text, "{}", it.symbol.as_str()); + format_to!(text, "{}", it.text()); record_span(it.span); } tt::LitKind::Byte @@ -620,7 +615,7 @@ fn concat_expand( TtElement::Leaf(tt::Leaf::Literal(it)) if matches!(it.kind, tt::LitKind::Integer | tt::LitKind::Float) => { - format_to!(text, "-{}", it.symbol.as_str()); + format_to!(text, "-{}", it.text()); record_span(punct.span.cover(it.span)); } _ => { @@ -658,26 +653,22 @@ fn concat_bytes_expand( }; for (i, t) in tt.iter().enumerate() { match t { - TtElement::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span, - kind, - suffix: _, - })) => { + TtElement::Leaf(tt::Leaf::Literal(lit @ tt::Literal { span, kind, .. })) => { + let text = lit.text(); record_span(span); match kind { tt::LitKind::Byte => { - if let Ok(b) = unescape_byte(text.as_str()) { + if let Ok(b) = unescape_byte(text) { bytes.extend( b.escape_ascii().filter_map(|it| char::from_u32(it as u32)), ); } } tt::LitKind::ByteStr => { - bytes.push_str(text.as_str()); + bytes.push_str(text); } tt::LitKind::ByteStrRaw(_) => { - bytes.extend(text.as_str().escape_debug()); + bytes.extend(text.escape_debug()); } _ => { err.get_or_insert(ExpandError::other(span, "unexpected token")); @@ -706,12 +697,7 @@ fn concat_bytes_expand( ExpandResult { value: tt::TopSubtree::invisible_from_leaves( span, - [tt::Leaf::Literal(tt::Literal { - symbol: Symbol::intern(&bytes), - span, - kind: tt::LitKind::ByteStr, - suffix: None, - })], + [tt::Leaf::Literal(tt::Literal::new_no_suffix(&bytes, span, tt::LitKind::ByteStr))], ), err, } @@ -725,25 +711,19 @@ fn concat_bytes_expand_subtree( ) -> Result<(), ExpandError> { for (ti, tt) in tree_iter.enumerate() { match tt { - TtElement::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span, - kind: tt::LitKind::Byte, - suffix: _, - })) => { - if let Ok(b) = unescape_byte(text.as_str()) { + TtElement::Leaf(tt::Leaf::Literal( + lit @ tt::Literal { span, kind: tt::LitKind::Byte, .. }, + )) => { + if let Ok(b) = unescape_byte(lit.text()) { bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32))); } record_span(span); } - TtElement::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span, - kind: tt::LitKind::Integer, - suffix: _, - })) => { + TtElement::Leaf(tt::Leaf::Literal( + lit @ tt::Literal { span, kind: tt::LitKind::Integer, .. }, + )) => { record_span(span); - if let Ok(b) = text.as_str().parse::() { + if let Ok(b) = lit.text().parse::() { bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32))); } } @@ -792,18 +772,16 @@ fn parse_string(tt: &tt::TopSubtree) -> Result<(Symbol, Span), ExpandError> { } match tt { - TtElement::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, + TtElement::Leaf(tt::Leaf::Literal(lit @ tt::Literal { span, kind: tt::LitKind::Str, - suffix: _, - })) => Ok((unescape_symbol(&text), span)), - TtElement::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, + .. + })) => Ok((Symbol::intern(&unescape_str(lit.text())), span)), + TtElement::Leaf(tt::Leaf::Literal(lit @ tt::Literal { span, kind: tt::LitKind::StrRaw(_), - suffix: _, - })) => Ok((text.clone(), span)), + .. + })) => Ok((Symbol::intern(lit.text()), span)), TtElement::Leaf(l) => Err(*l.span()), TtElement::Subtree(tt, _) => Err(tt.delimiter.open.cover(tt.delimiter.close)), } @@ -855,10 +833,10 @@ fn include_bytes_expand( let res = tt::TopSubtree::invisible_from_leaves( span, [tt::Leaf::Literal(tt::Literal { - symbol: Symbol::empty(), + text_and_suffix: Symbol::empty(), span, kind: tt::LitKind::ByteStrRaw(1), - suffix: None, + suffix_len: 0, })], ); ExpandResult::ok(res) @@ -979,17 +957,16 @@ fn quote_expand( ) } -fn unescape_symbol(s: &Symbol) -> Symbol { - if s.as_str().contains('\\') { - let s = s.as_str(); +fn unescape_str(s: &str) -> Cow<'_, str> { + if s.contains('\\') { let mut buf = String::with_capacity(s.len()); - unescape_str(s, |_, c| { + syntax::unescape::unescape_str(s, |_, c| { if let Ok(c) = c { buf.push(c) } }); - Symbol::intern(&buf) + Cow::Owned(buf) } else { - s.clone() + Cow::Borrowed(s) } } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs index 4039b6c334eca..51c4e225168fe 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs @@ -199,16 +199,16 @@ impl ToTokenTree for &T { } impl_to_to_tokentrees! { - span: u32 => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; - span: usize => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; - span: i32 => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; + span: u32 => self { crate::tt::Literal{text_and_suffix: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix_len: 0 } }; + span: usize => self { crate::tt::Literal{text_and_suffix: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix_len: 0 } }; + span: i32 => self { crate::tt::Literal{text_and_suffix: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix_len: 0 } }; span: bool => self { crate::tt::Ident{sym: if self { sym::true_ } else { sym::false_ }, span, is_raw: tt::IdentIsRaw::No } }; _span: crate::tt::Leaf => self { self }; _span: crate::tt::Literal => self { self }; _span: crate::tt::Ident => self { self }; _span: crate::tt::Punct => self { self }; - span: &str => self { crate::tt::Literal{symbol: Symbol::intern(&self.escape_default().to_smolstr()), span, kind: tt::LitKind::Str, suffix: None }}; - span: String => self { crate::tt::Literal{symbol: Symbol::intern(&self.escape_default().to_smolstr()), span, kind: tt::LitKind::Str, suffix: None }}; + span: &str => self { crate::tt::Literal{text_and_suffix: Symbol::intern(&self.escape_default().to_smolstr()), span, kind: tt::LitKind::Str, suffix_len: 0 }}; + span: String => self { crate::tt::Literal{text_and_suffix: Symbol::intern(&self.escape_default().to_smolstr()), span, kind: tt::LitKind::Str, suffix_len: 0 }}; span: Name => self { let (is_raw, s) = IdentIsRaw::split_from_symbol(self.as_str()); crate::tt::Ident{sym: Symbol::intern(s), span, is_raw } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs index 6afc6e5d5e7f2..92ddd7fa8b076 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs @@ -415,7 +415,7 @@ mod tests { // `TokenTree`s, see the last assertion in `check()`. fn check_leaf_eq(a: &tt::Leaf, b: &tt::Leaf) -> bool { match (a, b) { - (tt::Leaf::Literal(a), tt::Leaf::Literal(b)) => a.symbol == b.symbol, + (tt::Leaf::Literal(a), tt::Leaf::Literal(b)) => a.text_and_suffix == b.text_and_suffix, (tt::Leaf::Punct(a), tt::Leaf::Punct(b)) => a.char == b.char, (tt::Leaf::Ident(a), tt::Leaf::Ident(b)) => a.sym == b.sym, _ => false, diff --git a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs index 6b018510be143..603fee73064e7 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs @@ -236,12 +236,7 @@ fn invocation_fixtures( tt::Leaf::Punct(tt::Punct { span: DUMMY, char, spacing: tt::Spacing::Alone }) } fn make_literal(lit: &str) -> tt::Leaf { - tt::Leaf::Literal(tt::Literal { - span: DUMMY, - symbol: Symbol::intern(lit), - kind: tt::LitKind::Str, - suffix: None, - }) + tt::Leaf::Literal(tt::Literal::new_no_suffix(lit, DUMMY, tt::LitKind::Str)) } fn make_subtree(kind: tt::DelimiterKind, builder: &mut tt::TopSubtreeBuilder) { builder.open(kind, DUMMY); diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs index e845b1ab8d1f2..8f6627a60fe63 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs @@ -517,7 +517,8 @@ fn match_loop_inner<'t>( } OpDelimited::Op(Op::Literal(lhs)) => { if let Ok(rhs) = src.clone().expect_leaf() { - if matches!(&rhs, tt::Leaf::Literal(it) if it.symbol == lhs.symbol) { + if matches!(&rhs, tt::Leaf::Literal(it) if it.text_and_suffix == lhs.text_and_suffix) + { item.dot.next(); } else { res.add_err(ExpandError::new( @@ -953,8 +954,8 @@ fn expect_separator(iter: &mut TtIter<'_>, separator: &Separator) -> bool { }, Separator::Literal(lhs) => match fork.expect_literal() { Ok(rhs) => match rhs { - tt::Leaf::Literal(rhs) => rhs.symbol == lhs.symbol, - tt::Leaf::Ident(rhs) => rhs.sym == lhs.symbol, + tt::Leaf::Literal(rhs) => rhs.text_and_suffix == lhs.text_and_suffix, + tt::Leaf::Ident(rhs) => rhs.sym == lhs.text_and_suffix, tt::Leaf::Punct(_) => false, }, Err(_) => false, diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs index 38098e2c84da2..e8e7928c263f0 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs @@ -222,10 +222,10 @@ fn expand_subtree( let index = ctx.nesting.get(ctx.nesting.len() - 1 - depth).map_or(0, |nest| nest.idx); builder.push(tt::Leaf::Literal(tt::Literal { - symbol: Symbol::integer(index), + text_and_suffix: Symbol::integer(index), span: ctx.call_site, kind: tt::LitKind::Integer, - suffix: None, + suffix_len: 0, })); } Op::Len { depth } => { @@ -234,10 +234,10 @@ fn expand_subtree( 0 }); builder.push(tt::Leaf::Literal(tt::Literal { - symbol: Symbol::integer(length), + text_and_suffix: Symbol::integer(length), span: ctx.call_site, kind: tt::LitKind::Integer, - suffix: None, + suffix_len: 0, })); } Op::Count { name, depth } => { @@ -278,9 +278,9 @@ fn expand_subtree( let res = count(binding, 0, depth.unwrap_or(0)); builder.push(tt::Leaf::Literal(tt::Literal { - symbol: Symbol::integer(res), + text_and_suffix: Symbol::integer(res), span: ctx.call_site, - suffix: None, + suffix_len: 0, kind: tt::LitKind::Integer, })); } @@ -294,7 +294,7 @@ fn expand_subtree( ConcatMetaVarExprElem::Literal(lit) => { // FIXME: This isn't really correct wrt. escaping, but that's what rustc does and anyway // escaping is used most of the times for characters that are invalid in identifiers. - concatenated.push_str(lit.symbol.as_str()) + concatenated.push_str(lit.text()) } ConcatMetaVarExprElem::Var(var) => { // Handling of repetitions in `${concat}` isn't fleshed out in rustc, so we currently @@ -329,9 +329,7 @@ fn expand_subtree( (Some(TtElement::Leaf(tt::Leaf::Ident(ident))), None) => { ident.sym.as_str() } - (Some(TtElement::Leaf(tt::Leaf::Literal(lit))), None) => { - lit.symbol.as_str() - } + (Some(TtElement::Leaf(tt::Leaf::Literal(lit))), None) => lit.text(), _ => { if err.is_none() { err = Some(ExpandError::binding_error( diff --git a/src/tools/rust-analyzer/crates/mbe/src/parser.rs b/src/tools/rust-analyzer/crates/mbe/src/parser.rs index cecdd43a42a11..796ee62d48e3a 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/parser.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/parser.rs @@ -192,7 +192,7 @@ impl PartialEq for Separator { match (self, other) { (Ident(a), Ident(b)) => a.sym == b.sym, - (Literal(a), Literal(b)) => a.symbol == b.symbol, + (Literal(a), Literal(b)) => a.text_and_suffix == b.text_and_suffix, (Puncts(a), Puncts(b)) if a.len() == b.len() => { let a_iter = a.iter().map(|a| a.char); let b_iter = b.iter().map(|b| b.char); @@ -270,7 +270,7 @@ fn next_op( } tt::Leaf::Literal(lit) if is_boolean_literal(&lit) => { let kind = eat_fragment_kind(edition, src, mode)?; - let name = lit.symbol.clone(); + let name = lit.text_and_suffix.clone(); let id = lit.span; Op::Var { name, kind, id } } @@ -364,7 +364,7 @@ fn eat_fragment_kind( } fn is_boolean_literal(lit: &tt::Literal) -> bool { - matches!(lit.symbol.as_str(), "true" | "false") + lit.text_and_suffix == sym::true_ || lit.text_and_suffix == sym::false_ } fn parse_repeat(src: &mut TtIter<'_>) -> Result<(Option, RepeatKind), ParseError> { @@ -478,11 +478,12 @@ fn parse_metavar_expr(src: &mut TtIter<'_>) -> Result { fn parse_depth(src: &mut TtIter<'_>) -> Result { if src.is_empty() { Ok(0) - } else if let tt::Leaf::Literal(tt::Literal { symbol: text, suffix: None, .. }) = - src.expect_literal()? + } else if let tt::Leaf::Literal(lit) = src.expect_literal()? + && let (text, suffix) = lit.text_and_suffix() + && suffix.is_empty() { // Suffixes are not allowed. - text.as_str().parse().map_err(|_| ()) + text.parse().map_err(|_| ()) } else { Err(()) } 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 ac627b9232a05..4146b619ec0c4 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 @@ -172,7 +172,7 @@ impl Message for Response {} #[cfg(test)] mod tests { - use intern::{Symbol, sym}; + use intern::Symbol; use span::{ Edition, ROOT_ERASED_FILE_AST_ID, Span, SpanAnchor, SyntaxContext, TextRange, TextSize, }; @@ -232,16 +232,15 @@ mod tests { } .into(), ); - builder.push(Leaf::Literal(Literal { - symbol: Symbol::intern("Foo"), - span: Span { + builder.push(Leaf::Literal(Literal::new_no_suffix( + "Foo", + Span { range: TextRange::at(TextSize::new(10), TextSize::of("\"Foo\"")), anchor, ctx: SyntaxContext::root(Edition::CURRENT), }, - kind: tt::LitKind::Str, - suffix: None, - })); + tt::LitKind::Str, + ))); builder.push(Leaf::Punct(Punct { char: '@', span: Span { @@ -267,16 +266,16 @@ mod tests { ctx: SyntaxContext::root(Edition::CURRENT), }, ); - builder.push(Leaf::Literal(Literal { - symbol: sym::INTEGER_0, - span: Span { + builder.push(Leaf::Literal(Literal::new( + "0", + Span { range: TextRange::at(TextSize::new(16), TextSize::of("0u32")), anchor, ctx: SyntaxContext::root(Edition::CURRENT), }, - kind: tt::LitKind::Integer, - suffix: Some(sym::u32), - })); + tt::LitKind::Integer, + "u32", + ))); builder.close(Span { range: TextRange::at(TextSize::new(20), TextSize::of(']')), anchor, 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 ed4b7aff76fcd..cd8944aa6170a 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 @@ -512,12 +512,14 @@ impl<'a, T: SpanTransformer> Writer<'a, '_, T, tt::iter::TtIt let idx = self.literal.len() as u32; let id = self.token_id_of(lit.span); let (text, suffix) = if self.version >= EXTENDED_LEAF_DATA { + let (text, suffix) = lit.text_and_suffix(); ( - self.intern_owned(lit.symbol.as_str().to_owned()), - lit.suffix - .as_ref() - .map(|s| self.intern_owned(s.as_str().to_owned())) - .unwrap_or(!0), + self.intern_owned(text.to_owned()), + if suffix.is_empty() { + !0 + } else { + self.intern_owned(suffix.to_owned()) + }, ) } else { (self.intern_owned(format!("{lit}")), !0) @@ -774,10 +776,10 @@ impl> Reader<'_, T> { let span = read_span(repr.id); s.push( tt::Leaf::Literal(if self.version >= EXTENDED_LEAF_DATA { - tt::Literal { - symbol: Symbol::intern(text), + tt::Literal::new( + text, span, - kind: match u16::to_le_bytes(repr.kind) { + match u16::to_le_bytes(repr.kind) { [0, _] => Err(()), [1, _] => Byte, [2, _] => Char, @@ -791,14 +793,12 @@ impl> Reader<'_, T> { [10, r] => CStrRaw(r), _ => unreachable!(), }, - suffix: if repr.suffix != !0 { - Some(Symbol::intern( - self.text[repr.suffix as usize].as_str(), - )) + if repr.suffix != !0 { + self.text[repr.suffix as usize].as_str() } else { - None + "" }, - } + ) } else { tt::token_to_literal(text, span) }) diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs index 79b51a816ebef..ce238eb932ae7 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs @@ -442,7 +442,7 @@ fn convert_doc_comment( text = &text[0..text.len() - 2]; } let (text, kind) = desugar_doc_comment_text(text, mode); - let lit = tt::Literal { symbol: text, span, kind, suffix: None }; + let lit = tt::Literal { text_and_suffix: text, span, kind, suffix_len: 0 }; tt::Leaf::from(lit) }; @@ -867,12 +867,9 @@ impl TtTreeSink<'_> { /// This occurs when a float literal is used as a field access. fn float_split(&mut self, has_pseudo_dot: bool) { let (text, span) = match self.cursor.token_tree() { - Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span, - kind: tt::LitKind::Float, - suffix: _, - }))) => (text.as_str(), *span), + Some(tt::TokenTree::Leaf(tt::Leaf::Literal( + lit @ tt::Literal { span, kind: tt::LitKind::Float, .. }, + ))) => (lit.text(), *span), tt => unreachable!("{tt:?}"), }; // FIXME: Span splitting diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/to_parser_input.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/to_parser_input.rs index 6883f71307c81..851a4af864394 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/to_parser_input.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/to_parser_input.rs @@ -52,7 +52,7 @@ pub fn to_parser_input( }; res.push(kind, ctx_edition(lit.span.ctx)); - if kind == FLOAT_NUMBER && !lit.symbol.as_str().ends_with('.') { + if kind == FLOAT_NUMBER && !lit.text().ends_with('.') { // Tag the token as joint if it is float with a fractional part // we use this jointness to inform the parser about what token split // event to emit when we encounter a float literal in a field access diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index 487171c3e3e8d..b9c389c7694e5 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -920,7 +920,8 @@ impl ProcMacroExpander for ShortenProcMacroExpander { Leaf::Literal(it) => { // XXX Currently replaces any literals with an empty string, but supporting // "shortening" other literals would be nice. - it.symbol = Symbol::empty(); + it.text_and_suffix = Symbol::empty(); + it.suffix_len = 0; } Leaf::Punct(_) => {} Leaf::Ident(it) => { @@ -954,7 +955,7 @@ impl ProcMacroExpander for Issue17479ProcMacroExpander { let Some(TtElement::Leaf(tt::Leaf::Literal(lit))) = iter.next() else { return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); }; - let symbol = &lit.symbol; + let symbol = Symbol::intern(lit.text()); let span = lit.span; Ok(quote! { span => #symbol() diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs index 97e0b34ad7b7f..91fcec9327c3e 100644 --- a/src/tools/rust-analyzer/crates/tt/src/lib.rs +++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs @@ -18,6 +18,7 @@ pub mod iter; use std::fmt; +use arrayvec::ArrayString; use buffer::Cursor; use intern::Symbol; use stdx::{impl_from, itertools::Itertools as _}; @@ -617,17 +618,56 @@ pub enum DelimiterKind { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Literal { - // escaped - pub symbol: Symbol, + /// Escaped, text then suffix concatenated. + pub text_and_suffix: Symbol, pub span: Span, pub kind: LitKind, - pub suffix: Option, + pub suffix_len: u8, } -pub fn token_to_literal(text: &str, span: Span) -> Literal -where - Span: Copy, -{ +impl Literal { + #[inline] + pub fn text_and_suffix(&self) -> (&str, &str) { + let text_and_suffix = self.text_and_suffix.as_str(); + text_and_suffix.split_at(text_and_suffix.len() - usize::from(self.suffix_len)) + } + + #[inline] + pub fn text(&self) -> &str { + self.text_and_suffix().0 + } + + #[inline] + pub fn suffix(&self) -> &str { + self.text_and_suffix().1 + } + + pub fn new(text: &str, span: Span, kind: LitKind, suffix: &str) -> Self { + const MAX_INLINE_CAPACITY: usize = 30; + let text_and_suffix = if suffix.is_empty() { + Symbol::intern(text) + } else if (text.len() + suffix.len()) < MAX_INLINE_CAPACITY { + let mut text_and_suffix = ArrayString::::new(); + text_and_suffix.push_str(text); + text_and_suffix.push_str(suffix); + Symbol::intern(&text_and_suffix) + } else { + let mut text_and_suffix = String::with_capacity(text.len() + suffix.len()); + text_and_suffix.push_str(text); + text_and_suffix.push_str(suffix); + Symbol::intern(&text_and_suffix) + }; + + Self { text_and_suffix, span, kind, suffix_len: suffix.len().try_into().unwrap() } + } + + #[inline] + pub fn new_no_suffix(text: &str, span: Span, kind: LitKind) -> Self { + Self { text_and_suffix: Symbol::intern(text), span, kind, suffix_len: 0 } + } +} + +pub fn token_to_literal(text: &str, span: Span) -> Literal { use rustc_lexer::LiteralKind; let token = rustc_lexer::tokenize(text, rustc_lexer::FrontmatterAllowed::No).next_tuple(); @@ -636,12 +676,7 @@ where .. },)) = token else { - return Literal { - span, - symbol: Symbol::intern(text), - kind: LitKind::Err(()), - suffix: None, - }; + return Literal::new_no_suffix(text, span, LitKind::Err(())); }; let (kind, start_offset, end_offset) = match kind { @@ -672,20 +707,15 @@ where let (lit, suffix) = text.split_at(suffix_start as usize); let lit = &lit[start_offset..lit.len() - end_offset]; let suffix = match suffix { - "" | "_" => None, + "" | "_" => "", // ill-suffixed literals _ if !matches!(kind, LitKind::Integer | LitKind::Float | LitKind::Err(_)) => { - return Literal { - span, - symbol: Symbol::intern(text), - kind: LitKind::Err(()), - suffix: None, - }; + return Literal::new_no_suffix(text, span, LitKind::Err(())); } - suffix => Some(Symbol::intern(suffix)), + suffix => suffix, }; - Literal { span, symbol: Symbol::intern(lit), kind, suffix } + Literal::new(lit, span, kind, suffix) } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -805,15 +835,8 @@ fn print_debug_token(f: &mut fmt::Formatter<'_>, level: usize, tt: TtElement<'_> match tt { TtElement::Leaf(leaf) => match leaf { Leaf::Literal(lit) => { - write!( - f, - "{}LITERAL {:?} {}{} {:#?}", - align, - lit.kind, - lit.symbol, - lit.suffix.as_ref().map(|it| it.as_str()).unwrap_or(""), - lit.span - )?; + let (text, suffix) = lit.text_and_suffix(); + write!(f, "{}LITERAL {:?} {}{} {:#?}", align, lit.kind, text, suffix, lit.span)?; } Leaf::Punct(punct) => { write!( @@ -875,44 +898,28 @@ impl fmt::Display for Ident { impl fmt::Display for Literal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (text, suffix) = self.text_and_suffix(); match self.kind { - LitKind::Byte => write!(f, "b'{}'", self.symbol), - LitKind::Char => write!(f, "'{}'", self.symbol), - LitKind::Integer | LitKind::Float | LitKind::Err(_) => write!(f, "{}", self.symbol), - LitKind::Str => write!(f, "\"{}\"", self.symbol), - LitKind::ByteStr => write!(f, "b\"{}\"", self.symbol), - LitKind::CStr => write!(f, "c\"{}\"", self.symbol), + LitKind::Byte => write!(f, "b'{}'", text), + LitKind::Char => write!(f, "'{}'", text), + LitKind::Integer | LitKind::Float | LitKind::Err(_) => write!(f, "{}", text), + LitKind::Str => write!(f, "\"{}\"", text), + LitKind::ByteStr => write!(f, "b\"{}\"", text), + LitKind::CStr => write!(f, "c\"{}\"", text), LitKind::StrRaw(num_of_hashes) => { let num_of_hashes = num_of_hashes as usize; - write!( - f, - r#"r{0:# { let num_of_hashes = num_of_hashes as usize; - write!( - f, - r#"br{0:# { let num_of_hashes = num_of_hashes as usize; - write!( - f, - r#"cr{0:# { let s = match it { - Leaf::Literal(it) => it.symbol.to_string(), + Leaf::Literal(it) => it.text().to_owned(), Leaf::Punct(it) => it.char.to_string(), Leaf::Ident(it) => format!("{}{}", it.is_raw.as_str(), it.sym), }; From 29f9397ad4277c127c7cb4e317cd95f623a27e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Wed, 24 Dec 2025 17:51:39 +0800 Subject: [PATCH 65/77] docs: fix typo in std::io::buffered --- library/std/src/io/buffered/bufreader/buffer.rs | 2 +- library/std/src/io/buffered/linewritershim.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/std/src/io/buffered/bufreader/buffer.rs b/library/std/src/io/buffered/bufreader/buffer.rs index 9b600cd55758b..ad8608bf61908 100644 --- a/library/std/src/io/buffered/bufreader/buffer.rs +++ b/library/std/src/io/buffered/bufreader/buffer.rs @@ -48,7 +48,7 @@ impl Buffer { #[inline] pub fn buffer(&self) -> &[u8] { - // SAFETY: self.pos and self.cap are valid, and self.cap => self.pos, and + // SAFETY: self.pos and self.filled are valid, and self.filled >= self.pos, and // that region is initialized because those are all invariants of this type. unsafe { self.buf.get_unchecked(self.pos..self.filled).assume_init_ref() } } diff --git a/library/std/src/io/buffered/linewritershim.rs b/library/std/src/io/buffered/linewritershim.rs index 5ebeada59bb53..967e24812b9ff 100644 --- a/library/std/src/io/buffered/linewritershim.rs +++ b/library/std/src/io/buffered/linewritershim.rs @@ -52,7 +52,7 @@ impl<'a, W: ?Sized + Write> LineWriterShim<'a, W> { } impl<'a, W: ?Sized + Write> Write for LineWriterShim<'a, W> { - /// Writes some data into this BufReader with line buffering. + /// Writes some data into this BufWriter with line buffering. /// /// This means that, if any newlines are present in the data, the data up to /// the last newline is sent directly to the underlying writer, and data @@ -146,7 +146,7 @@ impl<'a, W: ?Sized + Write> Write for LineWriterShim<'a, W> { self.buffer.flush() } - /// Writes some vectored data into this BufReader with line buffering. + /// Writes some vectored data into this BufWriter with line buffering. /// /// This means that, if any newlines are present in the data, the data up to /// and including the buffer containing the last newline is sent directly to @@ -256,7 +256,7 @@ impl<'a, W: ?Sized + Write> Write for LineWriterShim<'a, W> { self.inner().is_write_vectored() } - /// Writes some data into this BufReader with line buffering. + /// Writes some data into this BufWriter with line buffering. /// /// This means that, if any newlines are present in the data, the data up to /// the last newline is sent directly to the underlying writer, and data From b0ff5733bb2e06ba967e54a4637b62475244063f Mon Sep 17 00:00:00 2001 From: dfireBird Date: Sat, 20 Dec 2025 23:27:00 +0530 Subject: [PATCH 66/77] feat: implement configuration to change subcommand for test and bench --- .../crates/rust-analyzer/src/config.rs | 37 ++++++++++ .../crates/rust-analyzer/src/lsp/to_proto.rs | 53 ++++++++++----- .../crates/rust-analyzer/src/target_spec.rs | 53 +++++++++++++-- .../docs/book/src/configuration_generated.md | 47 +++++++++++++ .../rust-analyzer/editors/code/package.json | 68 +++++++++++++++++++ 5 files changed, 236 insertions(+), 22 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 1b2d8c8d14fa3..3ca2e0d300b70 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -904,8 +904,22 @@ config_data! { /// This config takes a map of crate names with the exported proc-macro names to ignore as values. procMacro_ignored: FxHashMap, Box<[Box]>> = FxHashMap::default(), + /// Subcommand used for bench runnables instead of `bench`. + runnables_bench_command: String = "bench".to_owned(), + /// Override the command used for bench runnables. + /// The first element of the array should be the program to execute (for example, `cargo`). + /// + /// Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, + /// target option (such as `--bin` or `--example`), and the target name. + runnables_bench_overrideCommand: Option> = None, /// Command to be executed instead of 'cargo' for runnables. runnables_command: Option = None, + /// Override the command used for bench runnables. + /// The first element of the array should be the program to execute (for example, `cargo`). + /// + /// Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, + /// target option (such as `--bin` or `--example`), and the target name. + runnables_doctest_overrideCommand: Option> = None, /// Additional arguments to be passed to cargo for runnables such as /// tests or binaries. For example, it may be `--release`. runnables_extraArgs: Vec = vec![], @@ -917,6 +931,14 @@ config_data! { /// they will end up being interpreted as options to /// [`rustc`’s built-in test harness (“libtest”)](https://doc.rust-lang.org/rustc/tests/index.html#cli-arguments). runnables_extraTestBinaryArgs: Vec = vec!["--nocapture".to_owned()], + /// Subcommand used for test runnables instead of `test`. + runnables_test_command: String = "test".to_owned(), + /// Override the command used for test runnables. + /// The first element of the array should be the program to execute (for example, `cargo`). + /// + /// Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, + /// target option (such as `--bin` or `--example`), and the target name. + runnables_test_overrideCommand: Option> = None, /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private /// projects, or "discover" to try to automatically find it if the `rustc-dev` component @@ -1568,6 +1590,16 @@ pub struct RunnablesConfig { pub cargo_extra_args: Vec, /// Additional arguments for the binary being run, if it is a test or benchmark. pub extra_test_binary_args: Vec, + /// Subcommand used for doctest runnables instead of `test`. + pub test_command: String, + /// Override the command used for test runnables. + pub test_override_command: Option>, + /// Subcommand used for doctest runnables instead of `bench`. + pub bench_command: String, + /// Override the command used for bench runnables. + pub bench_override_command: Option>, + /// Override the command used for doctest runnables. + pub doc_test_override_command: Option>, } /// Configuration for workspace symbol search requests. @@ -2494,6 +2526,11 @@ impl Config { override_cargo: self.runnables_command(source_root).clone(), cargo_extra_args: self.runnables_extraArgs(source_root).clone(), extra_test_binary_args: self.runnables_extraTestBinaryArgs(source_root).clone(), + test_command: self.runnables_test_command(source_root).clone(), + test_override_command: self.runnables_test_overrideCommand(source_root).clone(), + bench_command: self.runnables_bench_command(source_root).clone(), + bench_override_command: self.runnables_bench_overrideCommand(source_root).clone(), + doc_test_override_command: self.runnables_doctest_overrideCommand(source_root).clone(), } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 86a35c7d116cd..ae1861985e552 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -1561,6 +1561,9 @@ pub(crate) fn runnable( let target = spec.target.clone(); + let override_command = + CargoTargetSpec::override_command(snap, Some(spec.clone()), &runnable.kind); + let (cargo_args, executable_args) = CargoTargetSpec::runnable_args( snap, Some(spec.clone()), @@ -1576,23 +1579,41 @@ pub(crate) fn runnable( let label = runnable.label(Some(&target)); let location = location_link(snap, None, runnable.nav)?; - Ok(Some(lsp_ext::Runnable { - label, - location: Some(location), - kind: lsp_ext::RunnableKind::Cargo, - args: lsp_ext::RunnableArgs::Cargo(lsp_ext::CargoRunnableArgs { - workspace_root: Some(workspace_root.into()), - override_cargo: config.override_cargo, - cargo_args, - cwd: cwd.into(), - executable_args, - environment: spec - .sysroot_root - .map(|root| ("RUSTC_TOOLCHAIN".to_owned(), root.to_string())) - .into_iter() - .collect(), + let environment = spec + .sysroot_root + .map(|root| ("RUSTC_TOOLCHAIN".to_owned(), root.to_string())) + .into_iter() + .collect(); + + Ok(match override_command { + Some(override_command) => match override_command.split_first() { + Some((program, args)) => Some(lsp_ext::Runnable { + label, + location: Some(location), + kind: lsp_ext::RunnableKind::Shell, + args: lsp_ext::RunnableArgs::Shell(lsp_ext::ShellRunnableArgs { + environment, + cwd: cwd.into(), + program: program.to_string(), + args: args.to_vec().into_iter().chain(executable_args).collect(), + }), + }), + _ => None, + }, + None => Some(lsp_ext::Runnable { + label, + location: Some(location), + kind: lsp_ext::RunnableKind::Cargo, + args: lsp_ext::RunnableArgs::Cargo(lsp_ext::CargoRunnableArgs { + workspace_root: Some(workspace_root.into()), + override_cargo: config.override_cargo, + cargo_args, + cwd: cwd.into(), + executable_args, + environment, + }), }), - })) + }) } Some(TargetSpec::ProjectJson(spec)) => { let label = runnable.label(Some(&spec.label)); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index e532d155536c1..dbe48f313440a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -123,7 +123,7 @@ impl CargoTargetSpec { match kind { RunnableKind::Test { test_id, attr } => { - cargo_args.push("test".to_owned()); + cargo_args.push(config.test_command); executable_args.push(test_id.to_string()); if let TestId::Path(_) = test_id { executable_args.push("--exact".to_owned()); @@ -134,12 +134,12 @@ impl CargoTargetSpec { } } RunnableKind::TestMod { path } => { - cargo_args.push("test".to_owned()); + cargo_args.push(config.test_command); executable_args.push(path.clone()); executable_args.extend(extra_test_binary_args); } RunnableKind::Bench { test_id } => { - cargo_args.push("bench".to_owned()); + cargo_args.push(config.bench_command); executable_args.push(test_id.to_string()); if let TestId::Path(_) = test_id { executable_args.push("--exact".to_owned()); @@ -154,10 +154,12 @@ impl CargoTargetSpec { } RunnableKind::Bin => { let subcommand = match spec { - Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => "test", - _ => "run", + Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => { + config.test_command + } + _ => "run".to_owned(), }; - cargo_args.push(subcommand.to_owned()); + cargo_args.push(subcommand); } } @@ -206,6 +208,45 @@ impl CargoTargetSpec { (cargo_args, executable_args) } + pub(crate) fn override_command( + snap: &GlobalStateSnapshot, + spec: Option, + kind: &RunnableKind, + ) -> Option> { + let config = snap.config.runnables(None); + let args = match kind { + RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { + config.test_override_command + } + RunnableKind::Bench { .. } => config.bench_override_command, + RunnableKind::DocTest { .. } => config.doc_test_override_command, + RunnableKind::Bin => match spec { + Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => { + config.test_override_command + } + _ => None, + }, + }; + + let target_arg = |kind| match kind { + TargetKind::Bin => "--bin", + TargetKind::Test => "--test", + TargetKind::Bench => "--bench", + TargetKind::Example => "--example", + TargetKind::Lib { .. } => "--lib", + TargetKind::BuildScript | TargetKind::Other => "", + }; + + let replace_placeholders = |arg| match &spec { + Some(spec) if arg == "${package}" => spec.package.clone(), + Some(spec) if arg == "${target_arg}" => target_arg(spec.target_kind).to_string(), + Some(spec) if arg == "${target}" => spec.target.clone(), + _ => arg, + }; + + args.map(|args| args.into_iter().map(replace_placeholders).collect()) + } + pub(crate) fn push_to(self, buf: &mut Vec, kind: &RunnableKind) { buf.push("--package".to_owned()); buf.push(self.package); diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index e208dbadea2bc..e824e18854abd 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -1348,6 +1348,24 @@ Default: `true` Whether to warn when a rename will cause conflicts (change the meaning of the code). +## rust-analyzer.runnables.bench.command {#runnables.bench.command} + +Default: `"bench"` + +Subcommand used for bench runnables instead of `bench`. + + +## rust-analyzer.runnables.bench.overrideCommand {#runnables.bench.overrideCommand} + +Default: `null` + +Override the command used for bench runnables. +The first element of the array should be the program to execute (for example, `cargo`). + +Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, +target option (such as `--bin` or `--example`), and the target name. + + ## rust-analyzer.runnables.command {#runnables.command} Default: `null` @@ -1355,6 +1373,17 @@ Default: `null` Command to be executed instead of 'cargo' for runnables. +## rust-analyzer.runnables.doctest.overrideCommand {#runnables.doctest.overrideCommand} + +Default: `null` + +Override the command used for bench runnables. +The first element of the array should be the program to execute (for example, `cargo`). + +Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, +target option (such as `--bin` or `--example`), and the target name. + + ## rust-analyzer.runnables.extraArgs {#runnables.extraArgs} Default: `[]` @@ -1381,6 +1410,24 @@ they will end up being interpreted as options to [`rustc`’s built-in test harness (“libtest”)](https://doc.rust-lang.org/rustc/tests/index.html#cli-arguments). +## rust-analyzer.runnables.test.command {#runnables.test.command} + +Default: `"test"` + +Subcommand used for test runnables instead of `test`. + + +## rust-analyzer.runnables.test.overrideCommand {#runnables.test.overrideCommand} + +Default: `null` + +Override the command used for test runnables. +The first element of the array should be the program to execute (for example, `cargo`). + +Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, +target option (such as `--bin` or `--example`), and the target name. + + ## rust-analyzer.rustc.source {#rustc.source} Default: `null` diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 481721896227d..39404458095fa 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -2826,6 +2826,32 @@ } } }, + { + "title": "Runnables", + "properties": { + "rust-analyzer.runnables.bench.command": { + "markdownDescription": "Subcommand used for bench runnables instead of `bench`.", + "default": "bench", + "type": "string" + } + } + }, + { + "title": "Runnables", + "properties": { + "rust-analyzer.runnables.bench.overrideCommand": { + "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name,\ntarget option (such as `--bin` or `--example`), and the target name.", + "default": null, + "type": [ + "null", + "array" + ], + "items": { + "type": "string" + } + } + } + }, { "title": "Runnables", "properties": { @@ -2839,6 +2865,22 @@ } } }, + { + "title": "Runnables", + "properties": { + "rust-analyzer.runnables.doctest.overrideCommand": { + "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name,\ntarget option (such as `--bin` or `--example`), and the target name.", + "default": null, + "type": [ + "null", + "array" + ], + "items": { + "type": "string" + } + } + } + }, { "title": "Runnables", "properties": { @@ -2867,6 +2909,32 @@ } } }, + { + "title": "Runnables", + "properties": { + "rust-analyzer.runnables.test.command": { + "markdownDescription": "Subcommand used for test runnables instead of `test`.", + "default": "test", + "type": "string" + } + } + }, + { + "title": "Runnables", + "properties": { + "rust-analyzer.runnables.test.overrideCommand": { + "markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name,\ntarget option (such as `--bin` or `--example`), and the target name.", + "default": null, + "type": [ + "null", + "array" + ], + "items": { + "type": "string" + } + } + } + }, { "title": "Rustc", "properties": { From bb4d75f123ffef443a65fbb26f65bd15e1d82895 Mon Sep 17 00:00:00 2001 From: dfireBird Date: Mon, 29 Dec 2025 20:24:24 +0530 Subject: [PATCH 67/77] fix clippy errors --- .../rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs | 2 +- src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index ae1861985e552..64476338b51e1 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -1595,7 +1595,7 @@ pub(crate) fn runnable( environment, cwd: cwd.into(), program: program.to_string(), - args: args.to_vec().into_iter().chain(executable_args).collect(), + args: args.iter().cloned().chain(executable_args).collect(), }), }), _ => None, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index dbe48f313440a..6d05efd75beab 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -239,7 +239,7 @@ impl CargoTargetSpec { let replace_placeholders = |arg| match &spec { Some(spec) if arg == "${package}" => spec.package.clone(), - Some(spec) if arg == "${target_arg}" => target_arg(spec.target_kind).to_string(), + Some(spec) if arg == "${target_arg}" => target_arg(spec.target_kind).to_owned(), Some(spec) if arg == "${target}" => spec.target.clone(), _ => arg, }; From cd2c209a1c158d37d1db840c3a00aa5bf57577be Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 29 Dec 2025 18:58:25 +0200 Subject: [PATCH 68/77] Fix Clippy --- src/tools/rust-analyzer/crates/span/src/ast_id.rs | 2 +- src/tools/rust-analyzer/crates/span/src/map.rs | 2 +- src/tools/rust-analyzer/crates/syntax/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/span/src/ast_id.rs b/src/tools/rust-analyzer/crates/span/src/ast_id.rs index da537ba9900ca..599b3c7175228 100644 --- a/src/tools/rust-analyzer/crates/span/src/ast_id.rs +++ b/src/tools/rust-analyzer/crates/span/src/ast_id.rs @@ -804,7 +804,7 @@ impl Drop for AstIdMap { // block on a receive _ = receiver.recv(); // then drain the entire channel - while let Ok(_) = receiver.try_recv() {} + while receiver.try_recv().is_ok() {} // and sleep for a bit std::thread::sleep(std::time::Duration::from_millis(100)); } diff --git a/src/tools/rust-analyzer/crates/span/src/map.rs b/src/tools/rust-analyzer/crates/span/src/map.rs index 75a1b0029cbc0..dc7d471aa03ac 100644 --- a/src/tools/rust-analyzer/crates/span/src/map.rs +++ b/src/tools/rust-analyzer/crates/span/src/map.rs @@ -168,7 +168,7 @@ impl Drop for SpanMap { // block on a receive _ = receiver.recv(); // then drain the entire channel - while let Ok(_) = receiver.try_recv() {} + while receiver.try_recv().is_ok() {} // and sleep for a bit std::thread::sleep(std::time::Duration::from_millis(100)); } diff --git a/src/tools/rust-analyzer/crates/syntax/src/lib.rs b/src/tools/rust-analyzer/crates/syntax/src/lib.rs index d36019514016f..c510b2831e12b 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/lib.rs @@ -223,7 +223,7 @@ impl Drop for Parse { // block on a receive _ = receiver.recv(); // then drain the entire channel - while let Ok(_) = receiver.try_recv() {} + while receiver.try_recv().is_ok() {} // and sleep for a bit std::thread::sleep(std::time::Duration::from_millis(100)); } From 7069629ee92c2aeb911f7db6a73c02b98882903d Mon Sep 17 00:00:00 2001 From: dfireBird Date: Mon, 29 Dec 2025 22:25:00 +0530 Subject: [PATCH 69/77] chore: add `test_name` placeholder in config --- .../crates/rust-analyzer/src/config.rs | 15 ++++++---- .../crates/rust-analyzer/src/lsp/to_proto.rs | 2 +- .../crates/rust-analyzer/src/target_spec.rs | 30 ++++++++++++------- .../docs/book/src/configuration_generated.md | 15 ++++++---- .../rust-analyzer/editors/code/package.json | 6 ++-- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 3ca2e0d300b70..e39569e108de4 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -909,16 +909,18 @@ config_data! { /// Override the command used for bench runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, - /// target option (such as `--bin` or `--example`), and the target name. + /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically + /// replace the package name, target option (such as `--bin` or `--example`), the target name and + /// the test name (name of test function or test mod path). runnables_bench_overrideCommand: Option> = None, /// Command to be executed instead of 'cargo' for runnables. runnables_command: Option = None, /// Override the command used for bench runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, - /// target option (such as `--bin` or `--example`), and the target name. + /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically + /// replace the package name, target option (such as `--bin` or `--example`), the target name and + /// the test name (name of test function or test mod path). runnables_doctest_overrideCommand: Option> = None, /// Additional arguments to be passed to cargo for runnables such as /// tests or binaries. For example, it may be `--release`. @@ -936,8 +938,9 @@ config_data! { /// Override the command used for test runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, - /// target option (such as `--bin` or `--example`), and the target name. + /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically + /// replace the package name, target option (such as `--bin` or `--example`), the target name and + /// the test name (name of test function or test mod path). runnables_test_overrideCommand: Option> = None, /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 64476338b51e1..6f0f57725fc7a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -1595,7 +1595,7 @@ pub(crate) fn runnable( environment, cwd: cwd.into(), program: program.to_string(), - args: args.iter().cloned().chain(executable_args).collect(), + args: args.to_vec(), }), }), _ => None, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index 6d05efd75beab..e0f95a7830ea8 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -214,19 +214,25 @@ impl CargoTargetSpec { kind: &RunnableKind, ) -> Option> { let config = snap.config.runnables(None); - let args = match kind { - RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { - config.test_override_command + let (args, test_name) = match kind { + RunnableKind::Test { test_id, .. } => { + (config.test_override_command, Some(test_id.to_string())) + } + RunnableKind::TestMod { path } => (config.test_override_command, Some(path.clone())), + RunnableKind::Bench { test_id } => { + (config.bench_override_command, Some(test_id.to_string())) + } + RunnableKind::DocTest { test_id } => { + (config.doc_test_override_command, Some(test_id.to_string())) } - RunnableKind::Bench { .. } => config.bench_override_command, - RunnableKind::DocTest { .. } => config.doc_test_override_command, RunnableKind::Bin => match spec { Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => { - config.test_override_command + (config.test_override_command, None) } - _ => None, + _ => (None, None), }, }; + let test_name = test_name.unwrap_or_default(); let target_arg = |kind| match kind { TargetKind::Bin => "--bin", @@ -237,10 +243,12 @@ impl CargoTargetSpec { TargetKind::BuildScript | TargetKind::Other => "", }; - let replace_placeholders = |arg| match &spec { - Some(spec) if arg == "${package}" => spec.package.clone(), - Some(spec) if arg == "${target_arg}" => target_arg(spec.target_kind).to_owned(), - Some(spec) if arg == "${target}" => spec.target.clone(), + let replace_placeholders = |arg: String| match &spec { + Some(spec) => arg + .replace("${package}", &spec.package) + .replace("${target_arg}", target_arg(spec.target_kind)) + .replace("${target}", &spec.target) + .replace("${test_name}", &test_name), _ => arg, }; diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index e824e18854abd..58b6363345279 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -1362,8 +1362,9 @@ Default: `null` Override the command used for bench runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, -target option (such as `--bin` or `--example`), and the target name. +Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically +replace the package name, target option (such as `--bin` or `--example`), the target name and +the test name (name of test function or test mod path). ## rust-analyzer.runnables.command {#runnables.command} @@ -1380,8 +1381,9 @@ Default: `null` Override the command used for bench runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, -target option (such as `--bin` or `--example`), and the target name. +Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically +replace the package name, target option (such as `--bin` or `--example`), the target name and +the test name (name of test function or test mod path). ## rust-analyzer.runnables.extraArgs {#runnables.extraArgs} @@ -1424,8 +1426,9 @@ Default: `null` Override the command used for test runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name, -target option (such as `--bin` or `--example`), and the target name. +Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically +replace the package name, target option (such as `--bin` or `--example`), the target name and +the test name (name of test function or test mod path). ## rust-analyzer.rustc.source {#rustc.source} diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 39404458095fa..2157cbd486535 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -2840,7 +2840,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.bench.overrideCommand": { - "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name,\ntarget option (such as `--bin` or `--example`), and the target name.", + "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).", "default": null, "type": [ "null", @@ -2869,7 +2869,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.doctest.overrideCommand": { - "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name,\ntarget option (such as `--bin` or `--example`), and the target name.", + "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).", "default": null, "type": [ "null", @@ -2923,7 +2923,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.test.overrideCommand": { - "markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}` to dynamically replace the package name,\ntarget option (such as `--bin` or `--example`), and the target name.", + "markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).", "default": null, "type": [ "null", From 192bbd1d4ba14c9b0599d63938dcd11cd809d7d9 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 29 Dec 2025 18:56:07 +0200 Subject: [PATCH 70/77] Have an `upvars_mentioned()` query that only computes what upvars a closure captures It's required for coercion, where we now use it, as well as for other things. --- .../crates/hir-ty/src/infer/coerce.rs | 39 +-- .../rust-analyzer/crates/hir-ty/src/lib.rs | 1 + .../crates/hir-ty/src/tests/simple.rs | 23 ++ .../rust-analyzer/crates/hir-ty/src/upvars.rs | 319 ++++++++++++++++++ 4 files changed, 359 insertions(+), 23 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-ty/src/upvars.rs diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs index ec7dad0fa08ca..e79868f4ae6b3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs @@ -56,7 +56,7 @@ use tracing::{debug, instrument}; use crate::{ Adjust, Adjustment, AutoBorrow, ParamEnvAndCrate, PointerCast, TargetFeatures, autoderef::Autoderef, - db::{HirDatabase, InternedClosureId}, + db::{HirDatabase, InternedClosure, InternedClosureId}, infer::{ AllowTwoPhase, AutoBorrowMutability, InferenceContext, TypeMismatch, expr::ExprIsRead, }, @@ -74,6 +74,7 @@ use crate::{ }, obligation_ctxt::ObligationCtxt, }, + upvars::upvars_mentioned, utils::TargetFeatureIsSafeInTarget, }; @@ -896,7 +897,7 @@ where fn coerce_closure_to_fn( &mut self, a: Ty<'db>, - _closure_def_id_a: InternedClosureId, + closure_def_id_a: InternedClosureId, args_a: GenericArgs<'db>, b: Ty<'db>, ) -> CoerceResult<'db> { @@ -904,19 +905,7 @@ where debug_assert!(self.infcx().shallow_resolve(b) == b); match b.kind() { - // FIXME: We need to have an `upvars_mentioned()` query: - // At this point we haven't done capture analysis, which means - // that the ClosureArgs just contains an inference variable instead - // of tuple of captured types. - // - // All we care here is if any variable is being captured and not the exact paths, - // so we check `upvars_mentioned` for root variables being captured. - TyKind::FnPtr(_, hdr) => - // if self - // .db - // .upvars_mentioned(closure_def_id_a.expect_local()) - // .is_none_or(|u| u.is_empty()) => - { + TyKind::FnPtr(_, hdr) if !is_capturing_closure(self.db(), closure_def_id_a) => { // We coerce the closure, which has fn type // `extern "rust-call" fn((arg0,arg1,...)) -> _` // to @@ -1089,14 +1078,12 @@ impl<'db> InferenceContext<'_, 'db> { // Special-case that coercion alone cannot handle: // Function items or non-capturing closures of differing IDs or GenericArgs. let (a_sig, b_sig) = { - let is_capturing_closure = |_ty: Ty<'db>| { - // FIXME: - // if let TyKind::Closure(closure_def_id, _args) = ty.kind() { - // self.db.upvars_mentioned(closure_def_id.expect_local()).is_some() - // } else { - // false - // } - false + let is_capturing_closure = |ty: Ty<'db>| { + if let TyKind::Closure(closure_def_id, _args) = ty.kind() { + is_capturing_closure(self.db, closure_def_id.0) + } else { + false + } }; if is_capturing_closure(prev_ty) || is_capturing_closure(new_ty) { (None, None) @@ -1728,3 +1715,9 @@ fn coerce<'db>( .collect(); Ok((adjustments, ty)) } + +fn is_capturing_closure(db: &dyn HirDatabase, closure: InternedClosureId) -> bool { + let InternedClosure(owner, expr) = closure.loc(db); + upvars_mentioned(db, owner) + .is_some_and(|upvars| upvars.get(&expr).is_some_and(|upvars| !upvars.is_empty())) +} 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 373862229bd19..41c381220cfd1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -50,6 +50,7 @@ pub mod method_resolution; pub mod mir; pub mod primitive; pub mod traits; +pub mod upvars; #[cfg(test)] mod test_db; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index 2e107b2c59416..db557b75071f6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -1,5 +1,7 @@ use expect_test::expect; +use crate::tests::check_infer_with_mismatches; + use super::{check, check_infer, check_no_mismatches, check_types}; #[test] @@ -3956,3 +3958,24 @@ fn bar() { "#, ); } + +#[test] +fn cannot_coerce_capturing_closure_to_fn_ptr() { + check_infer_with_mismatches( + r#" +fn foo() { + let a = 1; + let _: fn() -> i32 = || a; +} + "#, + expect![[r#" + 9..58 '{ ...| a; }': () + 19..20 'a': i32 + 23..24 '1': i32 + 34..35 '_': fn() -> i32 + 51..55 '|| a': impl Fn() -> i32 + 54..55 'a': i32 + 51..55: expected fn() -> i32, got impl Fn() -> i32 + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/upvars.rs b/src/tools/rust-analyzer/crates/hir-ty/src/upvars.rs new file mode 100644 index 0000000000000..ee864ab068d2d --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/upvars.rs @@ -0,0 +1,319 @@ +//! A simple query to collect tall locals (upvars) a closure use. + +use hir_def::{ + DefWithBodyId, + expr_store::{Body, path::Path}, + hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat}, + resolver::{HasResolver, Resolver, ValueNs}, +}; +use hir_expand::mod_path::PathKind; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::db::HirDatabase; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +// Kept sorted. +pub struct Upvars(Box<[BindingId]>); + +impl Upvars { + fn new(upvars: &FxHashSet) -> Upvars { + let mut upvars = upvars.iter().copied().collect::>(); + upvars.sort_unstable(); + Upvars(upvars) + } + + #[inline] + pub fn contains(&self, local: BindingId) -> bool { + self.0.binary_search(&local).is_ok() + } + + #[inline] + pub fn iter(&self) -> impl ExactSizeIterator { + self.0.iter().copied() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +/// Returns a map from `Expr::Closure` to its upvars. +#[salsa::tracked(returns(as_deref))] +pub fn upvars_mentioned( + db: &dyn HirDatabase, + owner: DefWithBodyId, +) -> Option>> { + let body = db.body(owner); + let mut resolver = owner.resolver(db); + let mut result = FxHashMap::default(); + handle_expr_outside_closure(db, &mut resolver, owner, &body, body.body_expr, &mut result); + return if result.is_empty() { + None + } else { + result.shrink_to_fit(); + Some(Box::new(result)) + }; + + fn handle_expr_outside_closure<'db>( + db: &'db dyn HirDatabase, + resolver: &mut Resolver<'db>, + owner: DefWithBodyId, + body: &Body, + expr: ExprId, + closures_map: &mut FxHashMap, + ) { + match &body[expr] { + &Expr::Closure { body: body_expr, .. } => { + let mut upvars = FxHashSet::default(); + handle_expr_inside_closure( + db, + resolver, + owner, + body, + expr, + body_expr, + &mut upvars, + closures_map, + ); + if !upvars.is_empty() { + closures_map.insert(expr, Upvars::new(&upvars)); + } + } + _ => body.walk_child_exprs(expr, |expr| { + handle_expr_outside_closure(db, resolver, owner, body, expr, closures_map) + }), + } + } + + fn handle_expr_inside_closure<'db>( + db: &'db dyn HirDatabase, + resolver: &mut Resolver<'db>, + owner: DefWithBodyId, + body: &Body, + current_closure: ExprId, + expr: ExprId, + upvars: &mut FxHashSet, + closures_map: &mut FxHashMap, + ) { + match &body[expr] { + Expr::Path(path) => { + resolve_maybe_upvar( + db, + resolver, + owner, + body, + current_closure, + expr, + expr.into(), + upvars, + path, + ); + } + &Expr::Assignment { target, .. } => { + body.walk_pats(target, &mut |pat| { + let Pat::Path(path) = &body[pat] else { return }; + resolve_maybe_upvar( + db, + resolver, + owner, + body, + current_closure, + expr, + pat.into(), + upvars, + path, + ); + }); + } + &Expr::Closure { body: body_expr, .. } => { + let mut closure_upvars = FxHashSet::default(); + handle_expr_inside_closure( + db, + resolver, + owner, + body, + expr, + body_expr, + &mut closure_upvars, + closures_map, + ); + if !closure_upvars.is_empty() { + closures_map.insert(expr, Upvars::new(&closure_upvars)); + // All nested closure's upvars are also upvars of the parent closure. + upvars.extend( + closure_upvars + .iter() + .copied() + .filter(|local| body.binding_owner(*local) != Some(current_closure)), + ); + } + return; + } + _ => {} + } + body.walk_child_exprs(expr, |expr| { + handle_expr_inside_closure( + db, + resolver, + owner, + body, + current_closure, + expr, + upvars, + closures_map, + ) + }); + } +} + +fn resolve_maybe_upvar<'db>( + db: &'db dyn HirDatabase, + resolver: &mut Resolver<'db>, + owner: DefWithBodyId, + body: &Body, + current_closure: ExprId, + expr: ExprId, + id: ExprOrPatId, + upvars: &mut FxHashSet, + path: &Path, +) { + if let Path::BarePath(mod_path) = path + && matches!(mod_path.kind, PathKind::Plain) + && mod_path.segments().len() == 1 + { + // Could be a variable. + let guard = resolver.update_to_inner_scope(db, owner, expr); + let resolution = + resolver.resolve_path_in_value_ns_fully(db, path, body.expr_or_pat_path_hygiene(id)); + if let Some(ValueNs::LocalBinding(local)) = resolution + && body.binding_owner(local) != Some(current_closure) + { + upvars.insert(local); + } + resolver.reset_to_guard(guard); + } +} + +#[cfg(test)] +mod tests { + use expect_test::{Expect, expect}; + use hir_def::{ModuleDefId, db::DefDatabase, nameres::crate_def_map}; + use itertools::Itertools; + use span::Edition; + use test_fixture::WithFixture; + + use crate::{test_db::TestDB, upvars::upvars_mentioned}; + + #[track_caller] + fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expectation: Expect) { + let db = TestDB::with_files(ra_fixture); + crate::attach_db(&db, || { + let def_map = crate_def_map(&db, db.test_crate()); + let func = def_map + .modules() + .flat_map(|(_, module)| module.scope.declarations()) + .filter_map(|decl| match decl { + ModuleDefId::FunctionId(func) => Some(func), + _ => None, + }) + .exactly_one() + .unwrap_or_else(|_| panic!("expected one function")); + let (body, source_map) = db.body_with_source_map(func.into()); + let Some(upvars) = upvars_mentioned(&db, func.into()) else { + expectation.assert_eq(""); + return; + }; + let mut closures = Vec::new(); + for (&closure, upvars) in upvars { + let closure_range = source_map.expr_syntax(closure).unwrap().value.text_range(); + let upvars = upvars + .iter() + .map(|local| body[local].name.display(&db, Edition::CURRENT)) + .join(", "); + closures.push((closure_range, upvars)); + } + closures.sort_unstable_by_key(|(range, _)| (range.start(), range.end())); + let closures = closures + .into_iter() + .map(|(range, upvars)| format!("{range:?}: {upvars}")) + .join("\n"); + expectation.assert_eq(&closures); + }); + } + + #[test] + fn simple() { + check( + r#" +struct foo; +fn foo(param: i32) { + let local = "boo"; + || { param; foo }; + || local; + || { param; local; param; local; }; + || 0xDEAFBEAF; +} + "#, + expect![[r#" + 60..77: param + 83..91: local + 97..131: param, local"#]], + ); + } + + #[test] + fn nested() { + check( + r#" +fn foo() { + let (a, b); + || { + || a; + || b; + }; +} + "#, + expect![[r#" + 31..69: a, b + 44..48: a + 58..62: b"#]], + ); + } + + #[test] + fn closure_var() { + check( + r#" +fn foo() { + let upvar = 1; + |closure_param: i32| { + let closure_local = closure_param; + closure_local + upvar + }; +} + "#, + expect!["34..135: upvar"], + ); + } + + #[test] + fn closure_var_nested() { + check( + r#" +fn foo() { + let a = 1; + |b: i32| { + || { + let c = 123; + a + b + c + } + }; +} + "#, + expect![[r#" + 30..116: a + 49..110: a, b"#]], + ); + } +} From 9508cd1cb7cc3f329e79da69839b144eabfe0d91 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 29 Dec 2025 10:54:10 +0200 Subject: [PATCH 71/77] Compress token trees for best memory usage --- src/tools/rust-analyzer/Cargo.lock | 2 + .../crates/syntax-bridge/src/lib.rs | 13 +- .../crates/syntax-bridge/src/tests.rs | 4 +- src/tools/rust-analyzer/crates/tt/Cargo.toml | 2 + .../rust-analyzer/crates/tt/src/buffer.rs | 37 +- src/tools/rust-analyzer/crates/tt/src/iter.rs | 94 +- src/tools/rust-analyzer/crates/tt/src/lib.rs | 542 +++------- .../rust-analyzer/crates/tt/src/storage.rs | 992 ++++++++++++++++++ 8 files changed, 1254 insertions(+), 432 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/tt/src/storage.rs diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 10927728a105f..42eaeb01f1f2a 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -3085,8 +3085,10 @@ name = "tt" version = "0.0.0" dependencies = [ "arrayvec", + "indexmap", "intern", "ra-ap-rustc_lexer", + "rustc-hash 2.1.1", "span", "stdx", "text-size 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs index ce238eb932ae7..0dcf18a4ad97a 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs @@ -866,7 +866,8 @@ impl TtTreeSink<'_> { /// Parses a float literal as if it was a one to two name ref nodes with a dot inbetween. /// This occurs when a float literal is used as a field access. fn float_split(&mut self, has_pseudo_dot: bool) { - let (text, span) = match self.cursor.token_tree() { + let token_tree = self.cursor.token_tree(); + let (text, span) = match &token_tree { Some(tt::TokenTree::Leaf(tt::Leaf::Literal( lit @ tt::Literal { span, kind: tt::LitKind::Float, .. }, ))) => (lit.text(), *span), @@ -928,9 +929,15 @@ impl TtTreeSink<'_> { self.buf.push_str("r#"); self.text_pos += TextSize::of("r#"); } - let r = (ident.sym.as_str(), ident.span); + let text = ident.sym.as_str(); + self.buf += text; + self.text_pos += TextSize::of(text); + combined_span = match combined_span { + None => Some(ident.span), + Some(prev_span) => Some(Self::merge_spans(prev_span, ident.span)), + }; self.cursor.bump(); - r + continue 'tokens; } tt::Leaf::Punct(punct) => { assert!(punct.char.is_ascii()); diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/tests.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/tests.rs index 8c28e1c5aaac0..16f2498bf357c 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/tests.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/tests.rs @@ -36,9 +36,9 @@ fn check_punct_spacing(fixture: &str) { if let tt::TokenTree::Leaf(Leaf::Punct(Punct { spacing, span: Span { range, .. }, .. })) = token_tree - && let Some(expected) = annotations.remove(range) + && let Some(expected) = annotations.remove(&range) { - assert_eq!(expected, *spacing); + assert_eq!(expected, spacing); } cursor.bump(); } diff --git a/src/tools/rust-analyzer/crates/tt/Cargo.toml b/src/tools/rust-analyzer/crates/tt/Cargo.toml index 4b38bfb1e5360..6cfb76400e3cc 100644 --- a/src/tools/rust-analyzer/crates/tt/Cargo.toml +++ b/src/tools/rust-analyzer/crates/tt/Cargo.toml @@ -15,6 +15,8 @@ doctest = false [dependencies] arrayvec.workspace = true text-size.workspace = true +rustc-hash.workspace = true +indexmap.workspace = true span = { path = "../span", version = "0.0", default-features = false } stdx.workspace = true diff --git a/src/tools/rust-analyzer/crates/tt/src/buffer.rs b/src/tools/rust-analyzer/crates/tt/src/buffer.rs index de6379b5cd14b..78cf4b956d0ce 100644 --- a/src/tools/rust-analyzer/crates/tt/src/buffer.rs +++ b/src/tools/rust-analyzer/crates/tt/src/buffer.rs @@ -1,17 +1,17 @@ //! Stateful iteration over token trees. //! //! We use this as the source of tokens for parser. -use crate::{Leaf, Subtree, TokenTree, TokenTreesView}; +use crate::{Leaf, Subtree, TokenTree, TokenTreesView, dispatch_ref}; pub struct Cursor<'a> { - buffer: &'a [TokenTree], + buffer: TokenTreesView<'a>, index: usize, subtrees_stack: Vec, } impl<'a> Cursor<'a> { pub fn new(buffer: TokenTreesView<'a>) -> Self { - Self { buffer: buffer.0, index: 0, subtrees_stack: Vec::new() } + Self { buffer, index: 0, subtrees_stack: Vec::new() } } /// Check whether it is eof @@ -23,16 +23,22 @@ impl<'a> Cursor<'a> { self.subtrees_stack.is_empty() } - fn last_subtree(&self) -> Option<(usize, &'a Subtree)> { + fn at(&self, idx: usize) -> Option { + dispatch_ref! { + match self.buffer.repr => tt => Some(tt.get(idx)?.to_api(self.buffer.span_parts)) + } + } + + fn last_subtree(&self) -> Option<(usize, Subtree)> { self.subtrees_stack.last().map(|&subtree_idx| { - let TokenTree::Subtree(subtree) = &self.buffer[subtree_idx] else { + let Some(TokenTree::Subtree(subtree)) = self.at(subtree_idx) else { panic!("subtree pointing to non-subtree"); }; (subtree_idx, subtree) }) } - pub fn end(&mut self) -> &'a Subtree { + pub fn end(&mut self) -> Subtree { let (last_subtree_idx, last_subtree) = self.last_subtree().expect("called `Cursor::end()` without an open subtree"); // +1 because `Subtree.len` excludes the subtree itself. @@ -46,14 +52,14 @@ impl<'a> Cursor<'a> { } /// Returns the `TokenTree` at the cursor if it is not at the end of a subtree. - pub fn token_tree(&self) -> Option<&'a TokenTree> { + pub fn token_tree(&self) -> Option { if let Some((last_subtree_idx, last_subtree)) = self.last_subtree() { // +1 because `Subtree.len` excludes the subtree itself. if last_subtree_idx + last_subtree.usize_len() + 1 == self.index { return None; } } - self.buffer.get(self.index) + self.at(self.index) } /// Bump the cursor, and enters a subtree if it is on one. @@ -66,7 +72,7 @@ impl<'a> Cursor<'a> { "called `Cursor::bump()` when at the end of a subtree" ); } - if let TokenTree::Subtree(_) = self.buffer[self.index] { + if let Some(TokenTree::Subtree(_)) = self.at(self.index) { self.subtrees_stack.push(self.index); } self.index += 1; @@ -81,13 +87,13 @@ impl<'a> Cursor<'a> { } } // +1 because `Subtree.len` excludes the subtree itself. - if let TokenTree::Subtree(_) = self.buffer[self.index] { + if let Some(TokenTree::Subtree(_)) = self.at(self.index) { self.subtrees_stack.push(self.index); } self.index += 1; } - pub fn peek_two_leaves(&self) -> Option<[&'a Leaf; 2]> { + pub fn peek_two_leaves(&self) -> Option<[Leaf; 2]> { if let Some((last_subtree_idx, last_subtree)) = self.last_subtree() { // +1 because `Subtree.len` excludes the subtree itself. let last_end = last_subtree_idx + last_subtree.usize_len() + 1; @@ -95,14 +101,17 @@ impl<'a> Cursor<'a> { return None; } } - self.buffer.get(self.index..self.index + 2).and_then(|it| match it { - [TokenTree::Leaf(a), TokenTree::Leaf(b)] => Some([a, b]), + self.at(self.index).zip(self.at(self.index + 1)).and_then(|it| match it { + (TokenTree::Leaf(a), TokenTree::Leaf(b)) => Some([a, b]), _ => None, }) } pub fn crossed(&self) -> TokenTreesView<'a> { assert!(self.is_root()); - TokenTreesView::new(&self.buffer[..self.index]) + TokenTreesView { + repr: self.buffer.repr.get(..self.index).unwrap(), + span_parts: self.buffer.span_parts, + } } } diff --git a/src/tools/rust-analyzer/crates/tt/src/iter.rs b/src/tools/rust-analyzer/crates/tt/src/iter.rs index 5ab9f94b63fdf..7caacd40dd7e3 100644 --- a/src/tools/rust-analyzer/crates/tt/src/iter.rs +++ b/src/tools/rust-analyzer/crates/tt/src/iter.rs @@ -7,11 +7,14 @@ use arrayvec::ArrayVec; use intern::sym; use span::Span; -use crate::{Ident, Leaf, MAX_GLUED_PUNCT_LEN, Punct, Spacing, Subtree, TokenTree, TokenTreesView}; +use crate::{ + Ident, Leaf, MAX_GLUED_PUNCT_LEN, Punct, Spacing, Subtree, TokenTree, TokenTreesReprRef, + TokenTreesView, dispatch_ref, +}; #[derive(Clone)] pub struct TtIter<'a> { - inner: std::slice::Iter<'a, TokenTree>, + inner: TokenTreesView<'a>, } impl fmt::Debug for TtIter<'_> { @@ -21,17 +24,17 @@ impl fmt::Debug for TtIter<'_> { } #[derive(Clone, Copy)] -pub struct TtIterSavepoint<'a>(&'a [TokenTree]); +pub struct TtIterSavepoint<'a>(TokenTreesView<'a>); impl<'a> TtIterSavepoint<'a> { pub fn remaining(self) -> TokenTreesView<'a> { - TokenTreesView::new(self.0) + self.0 } } impl<'a> TtIter<'a> { - pub(crate) fn new(tt: &'a [TokenTree]) -> TtIter<'a> { - TtIter { inner: tt.iter() } + pub(crate) fn new(tt: TokenTreesView<'a>) -> TtIter<'a> { + TtIter { inner: tt } } pub fn expect_char(&mut self, char: char) -> Result<(), ()> { @@ -141,8 +144,8 @@ impl<'a> TtIter<'a> { let _ = self.next().unwrap(); let _ = self.next().unwrap(); res.push(first); - res.push(*second); - res.push(*third.unwrap()); + res.push(second); + res.push(third.unwrap()); } ('-' | '!' | '*' | '/' | '&' | '%' | '^' | '+' | '<' | '=' | '>' | '|', '=', _) | ('-' | '=' | '>', '>', _) @@ -154,7 +157,7 @@ impl<'a> TtIter<'a> { | ('|', '|', _) => { let _ = self.next().unwrap(); res.push(first); - res.push(*second); + res.push(second); } _ => res.push(first), } @@ -162,17 +165,21 @@ impl<'a> TtIter<'a> { } /// This method won't check for subtrees, so the nth token tree may not be the nth sibling of the current tree. - fn peek_n(&self, n: usize) -> Option<&'a TokenTree> { - self.inner.as_slice().get(n) + fn peek_n(&self, n: usize) -> Option { + dispatch_ref! { + match self.inner.repr => tt => Some(tt.get(n)?.to_api(self.inner.span_parts)) + } } pub fn peek(&self) -> Option> { - match self.inner.as_slice().first()? { - TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf.clone())), + match self.peek_n(0)? { + TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf)), TokenTree::Subtree(subtree) => { - let nested_iter = - TtIter { inner: self.inner.as_slice()[1..][..subtree.usize_len()].iter() }; - Some(TtElement::Subtree(*subtree, nested_iter)) + let nested_repr = self.inner.repr.get(1..subtree.usize_len() + 1).unwrap(); + let nested_iter = TtIter { + inner: TokenTreesView { repr: nested_repr, span_parts: self.inner.span_parts }, + }; + Some(TtElement::Subtree(subtree, nested_iter)) } } } @@ -183,26 +190,51 @@ impl<'a> TtIter<'a> { } pub fn next_span(&self) -> Option { - Some(self.inner.as_slice().first()?.first_span()) + Some(self.peek()?.first_span()) } pub fn remaining(&self) -> TokenTreesView<'a> { - TokenTreesView::new(self.inner.as_slice()) + self.inner } /// **Warning**: This advances `skip` **flat** token trees, subtrees account for children+1! pub fn flat_advance(&mut self, skip: usize) { - self.inner = self.inner.as_slice()[skip..].iter(); + self.inner.repr = self.inner.repr.get(skip..).unwrap(); } pub fn savepoint(&self) -> TtIterSavepoint<'a> { - TtIterSavepoint(self.inner.as_slice()) + TtIterSavepoint(self.inner) } pub fn from_savepoint(&self, savepoint: TtIterSavepoint<'a>) -> TokenTreesView<'a> { - let len = (self.inner.as_slice().as_ptr() as usize - savepoint.0.as_ptr() as usize) - / size_of::(); - TokenTreesView::new(&savepoint.0[..len]) + let len = match (self.inner.repr, savepoint.0.repr) { + ( + TokenTreesReprRef::SpanStorage32(this), + TokenTreesReprRef::SpanStorage32(savepoint), + ) => { + (this.as_ptr() as usize - savepoint.as_ptr() as usize) + / size_of::>() + } + ( + TokenTreesReprRef::SpanStorage64(this), + TokenTreesReprRef::SpanStorage64(savepoint), + ) => { + (this.as_ptr() as usize - savepoint.as_ptr() as usize) + / size_of::>() + } + ( + TokenTreesReprRef::SpanStorage96(this), + TokenTreesReprRef::SpanStorage96(savepoint), + ) => { + (this.as_ptr() as usize - savepoint.as_ptr() as usize) + / size_of::>() + } + _ => panic!("savepoint did not originate from this TtIter"), + }; + TokenTreesView { + repr: savepoint.0.repr.get(..len).unwrap(), + span_parts: savepoint.0.span_parts, + } } pub fn next_as_view(&mut self) -> Option> { @@ -242,14 +274,12 @@ impl TtElement<'_> { impl<'a> Iterator for TtIter<'a> { type Item = TtElement<'a>; fn next(&mut self) -> Option { - match self.inner.next()? { - TokenTree::Leaf(leaf) => Some(TtElement::Leaf(leaf.clone())), - TokenTree::Subtree(subtree) => { - let nested_iter = - TtIter { inner: self.inner.as_slice()[..subtree.usize_len()].iter() }; - self.inner = self.inner.as_slice()[subtree.usize_len()..].iter(); - Some(TtElement::Subtree(*subtree, nested_iter)) - } - } + let result = self.peek()?; + let skip = match &result { + TtElement::Leaf(_) => 1, + TtElement::Subtree(subtree, _) => subtree.usize_len() + 1, + }; + self.inner.repr = self.inner.repr.get(skip..).unwrap(); + Some(result) } } diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs index 91fcec9327c3e..a59fc2e089827 100644 --- a/src/tools/rust-analyzer/crates/tt/src/lib.rs +++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs @@ -15,8 +15,9 @@ extern crate rustc_lexer; pub mod buffer; pub mod iter; +mod storage; -use std::fmt; +use std::{fmt, slice::SliceIndex}; use arrayvec::ArrayString; use buffer::Cursor; @@ -26,7 +27,10 @@ use stdx::{impl_from, itertools::Itertools as _}; pub use span::Span; pub use text_size::{TextRange, TextSize}; +use crate::storage::{CompressedSpanPart, SpanStorage}; + pub use self::iter::{TtElement, TtIter}; +pub use self::storage::{TopSubtree, TopSubtreeBuilder}; pub const MAX_GLUED_PUNCT_LEN: usize = 3; @@ -125,267 +129,71 @@ impl Subtree { } } -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct TopSubtree(Box<[TokenTree]>); - -impl TopSubtree { - pub fn empty(span: DelimSpan) -> Self { - Self(Box::new([TokenTree::Subtree(Subtree { - delimiter: Delimiter::invisible_delim_spanned(span), - len: 0, - })])) - } - - pub fn invisible_from_leaves(delim_span: Span, leaves: [Leaf; N]) -> Self { - let mut builder = TopSubtreeBuilder::new(Delimiter::invisible_spanned(delim_span)); - builder.extend(leaves); - builder.build() - } - - pub fn from_token_trees(delimiter: Delimiter, token_trees: TokenTreesView<'_>) -> Self { - let mut builder = TopSubtreeBuilder::new(delimiter); - builder.extend_with_tt(token_trees); - builder.build() - } - - pub fn from_serialized(tt: Vec) -> Self { - Self(tt.into_boxed_slice()) - } - - pub fn from_subtree(subtree: SubtreeView<'_>) -> Self { - Self(subtree.0.into()) - } - - pub fn view(&self) -> SubtreeView<'_> { - SubtreeView::new(&self.0) - } - - pub fn iter(&self) -> TtIter<'_> { - self.view().iter() - } - - pub fn top_subtree(&self) -> Subtree { - self.view().top_subtree() - } - - pub fn set_top_subtree_delimiter_kind(&mut self, kind: DelimiterKind) { - self.top_subtree_mut().delimiter.kind = kind; - } - - pub fn set_top_subtree_delimiter_span(&mut self, span: DelimSpan) { - let top_subtree = self.top_subtree_mut(); - top_subtree.delimiter.open = span.open; - top_subtree.delimiter.close = span.close; - } - - fn top_subtree_mut(&mut self) -> &mut Subtree { - let TokenTree::Subtree(subtree) = &mut self.0[0] else { - unreachable!("the first token tree is always the top subtree"); - }; - subtree - } - - pub fn set_token(&mut self, idx: usize, leaf: Leaf) { - assert!(matches!(self.0[idx], TokenTree::Leaf(_)), "cannot replace a subtree by a leaf"); - self.0[idx] = leaf.into(); - } - - pub fn token_trees(&self) -> TokenTreesView<'_> { - self.view().token_trees() - } - - pub fn as_token_trees(&self) -> TokenTreesView<'_> { - self.view().as_token_trees() - } - - pub fn change_every_ast_id(&mut self, mut callback: impl FnMut(&mut span::ErasedFileAstId)) { - for tt in &mut self.0 { - match tt { - TokenTree::Leaf(Leaf::Ident(Ident { span, .. })) - | TokenTree::Leaf(Leaf::Literal(Literal { span, .. })) - | TokenTree::Leaf(Leaf::Punct(Punct { span, .. })) => { - callback(&mut span.anchor.ast_id); - } - TokenTree::Subtree(subtree) => { - callback(&mut subtree.delimiter.open.anchor.ast_id); - callback(&mut subtree.delimiter.close.anchor.ast_id); - } - } +/// `dispatch_ref! {}` +macro_rules! dispatch_ref { + ( + match $scrutinee:expr => $tt:ident => $body:expr + ) => { + match $scrutinee { + $crate::TokenTreesReprRef::SpanStorage32($tt) => $body, + $crate::TokenTreesReprRef::SpanStorage64($tt) => $body, + $crate::TokenTreesReprRef::SpanStorage96($tt) => $body, } - } + }; } +use dispatch_ref; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TopSubtreeBuilder { - unclosed_subtree_indices: Vec, - token_trees: Vec, - last_closed_subtree: Option, +#[derive(Clone, Copy)] +enum TokenTreesReprRef<'a> { + SpanStorage32(&'a [crate::storage::TokenTree]), + SpanStorage64(&'a [crate::storage::TokenTree]), + SpanStorage96(&'a [crate::storage::TokenTree]), } -impl TopSubtreeBuilder { - pub fn new(top_delimiter: Delimiter) -> Self { - let mut result = Self { - unclosed_subtree_indices: Vec::new(), - token_trees: Vec::new(), - last_closed_subtree: None, - }; - let top_subtree = TokenTree::Subtree(Subtree { delimiter: top_delimiter, len: 0 }); - result.token_trees.push(top_subtree); - result - } - - pub fn open(&mut self, delimiter_kind: DelimiterKind, open_span: Span) { - self.unclosed_subtree_indices.push(self.token_trees.len()); - self.token_trees.push(TokenTree::Subtree(Subtree { - delimiter: Delimiter { - open: open_span, - close: open_span, // Will be overwritten on close. - kind: delimiter_kind, - }, - len: 0, - })); - } - - pub fn close(&mut self, close_span: Span) { - let last_unclosed_index = self - .unclosed_subtree_indices - .pop() - .expect("attempt to close a `tt::Subtree` when none is open"); - let subtree_len = (self.token_trees.len() - last_unclosed_index - 1) as u32; - let TokenTree::Subtree(subtree) = &mut self.token_trees[last_unclosed_index] else { - unreachable!("unclosed token tree is always a subtree"); - }; - subtree.len = subtree_len; - subtree.delimiter.close = close_span; - self.last_closed_subtree = Some(last_unclosed_index); - } - - /// You cannot call this consecutively, it will only work once after close. - pub fn remove_last_subtree_if_invisible(&mut self) { - let Some(last_subtree_idx) = self.last_closed_subtree else { return }; - if let TokenTree::Subtree(Subtree { - delimiter: Delimiter { kind: DelimiterKind::Invisible, .. }, - .. - }) = self.token_trees[last_subtree_idx] - { - self.token_trees.remove(last_subtree_idx); - self.last_closed_subtree = None; - } - } - - pub fn push(&mut self, leaf: Leaf) { - self.token_trees.push(TokenTree::Leaf(leaf)); - } - - pub fn extend(&mut self, leaves: impl IntoIterator) { - self.token_trees.extend(leaves.into_iter().map(TokenTree::Leaf)); - } - - pub fn extend_with_tt(&mut self, tt: TokenTreesView<'_>) { - self.token_trees.extend(tt.0.iter().cloned()); - } - - /// Like [`Self::extend_with_tt()`], but makes sure the new tokens will never be - /// joint with whatever comes after them. - pub fn extend_with_tt_alone(&mut self, tt: TokenTreesView<'_>) { - if let Some((last, before_last)) = tt.0.split_last() { - self.token_trees.reserve(tt.0.len()); - self.token_trees.extend(before_last.iter().cloned()); - let last = if let TokenTree::Leaf(Leaf::Punct(last)) = last { - let mut last = *last; - last.spacing = Spacing::Alone; - TokenTree::Leaf(Leaf::Punct(last)) - } else { - last.clone() - }; - self.token_trees.push(last); - } - } - - pub fn expected_delimiters(&self) -> impl Iterator { - self.unclosed_subtree_indices.iter().rev().map(|&subtree_idx| { - let TokenTree::Subtree(subtree) = &self.token_trees[subtree_idx] else { - unreachable!("unclosed token tree is always a subtree") - }; - subtree.delimiter.kind - }) - } - - /// Builds, and remove the top subtree if it has only one subtree child. - pub fn build_skip_top_subtree(mut self) -> TopSubtree { - let top_tts = TokenTreesView::new(&self.token_trees[1..]); - match top_tts.try_into_subtree() { - Some(_) => { - assert!( - self.unclosed_subtree_indices.is_empty(), - "attempt to build an unbalanced `TopSubtreeBuilder`" - ); - TopSubtree(self.token_trees.drain(1..).collect()) +impl<'a> TokenTreesReprRef<'a> { + #[inline] + fn get(&self, index: I) -> Option + where + I: SliceIndex< + [crate::storage::TokenTree], + Output = [crate::storage::TokenTree], + >, + I: SliceIndex< + [crate::storage::TokenTree], + Output = [crate::storage::TokenTree], + >, + I: SliceIndex< + [crate::storage::TokenTree], + Output = [crate::storage::TokenTree], + >, + { + Some(match self { + TokenTreesReprRef::SpanStorage32(tt) => { + TokenTreesReprRef::SpanStorage32(tt.get(index)?) } - None => self.build(), - } - } - - pub fn build(mut self) -> TopSubtree { - assert!( - self.unclosed_subtree_indices.is_empty(), - "attempt to build an unbalanced `TopSubtreeBuilder`" - ); - let total_len = self.token_trees.len() as u32; - let TokenTree::Subtree(top_subtree) = &mut self.token_trees[0] else { - unreachable!("first token tree is always a subtree"); - }; - top_subtree.len = total_len - 1; - TopSubtree(self.token_trees.into_boxed_slice()) - } - - pub fn restore_point(&self) -> SubtreeBuilderRestorePoint { - SubtreeBuilderRestorePoint { - unclosed_subtree_indices_len: self.unclosed_subtree_indices.len(), - token_trees_len: self.token_trees.len(), - last_closed_subtree: self.last_closed_subtree, - } - } - - pub fn restore(&mut self, restore_point: SubtreeBuilderRestorePoint) { - self.unclosed_subtree_indices.truncate(restore_point.unclosed_subtree_indices_len); - self.token_trees.truncate(restore_point.token_trees_len); - self.last_closed_subtree = restore_point.last_closed_subtree; + TokenTreesReprRef::SpanStorage64(tt) => { + TokenTreesReprRef::SpanStorage64(tt.get(index)?) + } + TokenTreesReprRef::SpanStorage96(tt) => { + TokenTreesReprRef::SpanStorage96(tt.get(index)?) + } + }) } } #[derive(Clone, Copy)] -pub struct SubtreeBuilderRestorePoint { - unclosed_subtree_indices_len: usize, - token_trees_len: usize, - last_closed_subtree: Option, +pub struct TokenTreesView<'a> { + repr: TokenTreesReprRef<'a>, + span_parts: &'a [CompressedSpanPart], } -#[derive(Clone, Copy)] -pub struct TokenTreesView<'a>(&'a [TokenTree]); - impl<'a> TokenTreesView<'a> { - fn new(tts: &'a [TokenTree]) -> Self { - if cfg!(debug_assertions) { - tts.iter().enumerate().for_each(|(idx, tt)| { - if let TokenTree::Subtree(tt) = &tt { - // `<` and not `<=` because `Subtree.len` does not include the subtree node itself. - debug_assert!( - idx + tt.usize_len() < tts.len(), - "`TokenTreeView::new()` was given a cut-in-half list" - ); - } - }); - } - Self(tts) - } - pub fn empty() -> Self { - Self(&[]) + Self { repr: TokenTreesReprRef::SpanStorage32(&[]), span_parts: &[] } } pub fn iter(&self) -> TtIter<'a> { - TtIter::new(self.0) + TtIter::new(*self) } pub fn cursor(&self) -> Cursor<'a> { @@ -393,20 +201,23 @@ impl<'a> TokenTreesView<'a> { } pub fn len(&self) -> usize { - self.0.len() + dispatch_ref! { + match self.repr => tt => tt.len() + } } pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.len() == 0 } pub fn try_into_subtree(self) -> Option> { - if let Some(TokenTree::Subtree(subtree)) = self.0.first() - && subtree.usize_len() == (self.0.len() - 1) - { - return Some(SubtreeView::new(self.0)); - } - None + let is_subtree = dispatch_ref! { + match self.repr => tt => matches!( + tt.first(), + Some(crate::storage::TokenTree::Subtree { len, .. }) if (*len as usize) == (tt.len() - 1) + ) + }; + if is_subtree { Some(SubtreeView(self)) } else { None } } pub fn strip_invisible(self) -> TokenTreesView<'a> { @@ -440,18 +251,23 @@ impl<'a> TokenTreesView<'a> { } pub fn first_span(&self) -> Option { - Some(self.0.first()?.first_span()) + Some(dispatch_ref! { + match self.repr => tt => tt.first()?.first_span().span(self.span_parts) + }) } pub fn last_span(&self) -> Option { - Some(match self.0.last()? { - TokenTree::Leaf(it) => *it.span(), - TokenTree::Subtree(it) => it.delimiter.close, + Some(dispatch_ref! { + match self.repr => tt => tt.last()?.last_span().span(self.span_parts) }) } - pub fn iter_flat_tokens(&self) -> impl ExactSizeIterator + use<'a> { - self.0.iter().cloned() + pub fn iter_flat_tokens(self) -> impl ExactSizeIterator + use<'a> { + (0..self.len()).map(move |idx| { + dispatch_ref! { + match self.repr => tt => tt[idx].to_api(self.span_parts) + } + }) } } @@ -515,60 +331,70 @@ impl fmt::Display for TokenTreesView<'_> { #[derive(Clone, Copy)] // Invariant: always starts with `Subtree` that covers the entire thing. -pub struct SubtreeView<'a>(&'a [TokenTree]); +pub struct SubtreeView<'a>(TokenTreesView<'a>); impl<'a> SubtreeView<'a> { - pub fn new(tts: &'a [TokenTree]) -> Self { - if cfg!(debug_assertions) { - let TokenTree::Subtree(subtree) = &tts[0] else { - panic!("first token tree must be a subtree in `SubtreeView`"); - }; - assert_eq!( - subtree.usize_len(), - tts.len() - 1, - "subtree must cover the entire `SubtreeView`" - ); - } - Self(tts) - } - pub fn as_token_trees(self) -> TokenTreesView<'a> { - TokenTreesView::new(self.0) + self.0 } pub fn iter(&self) -> TtIter<'a> { - TtIter::new(&self.0[1..]) + self.token_trees().iter() } pub fn top_subtree(&self) -> Subtree { - let TokenTree::Subtree(subtree) = &self.0[0] else { - unreachable!("the first token tree is always the top subtree"); - }; - *subtree + dispatch_ref! { + match self.0.repr => tt => { + let crate::storage::TokenTree::Subtree { len, delim_kind, open_span, close_span } = + &tt[0] + else { + unreachable!("the first token tree is always the top subtree"); + }; + Subtree { + delimiter: Delimiter { + open: open_span.span(self.0.span_parts), + close: close_span.span(self.0.span_parts), + kind: *delim_kind, + }, + len: *len, + } + } + } } pub fn strip_invisible(&self) -> TokenTreesView<'a> { if self.top_subtree().delimiter.kind == DelimiterKind::Invisible { - TokenTreesView::new(&self.0[1..]) + self.token_trees() } else { - TokenTreesView::new(self.0) + self.0 } } pub fn token_trees(&self) -> TokenTreesView<'a> { - TokenTreesView::new(&self.0[1..]) + let repr = match self.0.repr { + TokenTreesReprRef::SpanStorage32(token_trees) => { + TokenTreesReprRef::SpanStorage32(&token_trees[1..]) + } + TokenTreesReprRef::SpanStorage64(token_trees) => { + TokenTreesReprRef::SpanStorage64(&token_trees[1..]) + } + TokenTreesReprRef::SpanStorage96(token_trees) => { + TokenTreesReprRef::SpanStorage96(&token_trees[1..]) + } + }; + TokenTreesView { repr, ..self.0 } } } impl fmt::Debug for SubtreeView<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&TokenTreesView(self.0), f) + fmt::Debug::fmt(&self.0, f) } } impl fmt::Display for SubtreeView<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&TokenTreesView(self.0), f) + fmt::Display::fmt(&self.0, f) } } @@ -937,91 +763,40 @@ impl Subtree { } } -impl TopSubtree { - /// A simple line string used for debugging - pub fn subtree_as_debug_string(&self, subtree_idx: usize) -> String { - fn debug_subtree( - output: &mut String, - subtree: &Subtree, - iter: &mut std::slice::Iter<'_, TokenTree>, - ) { - let delim = match subtree.delimiter.kind { - DelimiterKind::Brace => ("{", "}"), - DelimiterKind::Bracket => ("[", "]"), - DelimiterKind::Parenthesis => ("(", ")"), - DelimiterKind::Invisible => ("$", "$"), - }; - - output.push_str(delim.0); - let mut last = None; - let mut idx = 0; - while idx < subtree.len { - let child = iter.next().unwrap(); - debug_token_tree(output, child, last, iter); - last = Some(child); - idx += 1; - } - - output.push_str(delim.1); - } - - fn debug_token_tree( - output: &mut String, - tt: &TokenTree, - last: Option<&TokenTree>, - iter: &mut std::slice::Iter<'_, TokenTree>, - ) { - match tt { - TokenTree::Leaf(it) => { - let s = match it { - Leaf::Literal(it) => it.text().to_owned(), - Leaf::Punct(it) => it.char.to_string(), - Leaf::Ident(it) => format!("{}{}", it.is_raw.as_str(), it.sym), - }; - match (it, last) { - (Leaf::Ident(_), Some(&TokenTree::Leaf(Leaf::Ident(_)))) => { - output.push(' '); - output.push_str(&s); - } - (Leaf::Punct(_), Some(TokenTree::Leaf(Leaf::Punct(punct)))) => { - if punct.spacing == Spacing::Alone { - output.push(' '); - output.push_str(&s); - } else { - output.push_str(&s); - } - } - _ => output.push_str(&s), - } - } - TokenTree::Subtree(it) => debug_subtree(output, it, iter), - } - } +pub fn pretty(tkns: TokenTreesView<'_>) -> String { + return dispatch_ref! { + match tkns.repr => tt => pretty_impl(tt) + }; - let mut res = String::new(); - debug_token_tree( - &mut res, - &self.0[subtree_idx], - None, - &mut self.0[subtree_idx + 1..].iter(), - ); - res - } -} + use crate::storage::TokenTree; -pub fn pretty(tkns: TokenTreesView<'_>) -> String { - fn tokentree_to_text(tkn: &TokenTree, tkns: &mut &[TokenTree]) -> String { + fn tokentree_to_text(tkn: &TokenTree, tkns: &mut &[TokenTree]) -> String { match tkn { - TokenTree::Leaf(Leaf::Ident(ident)) => { - format!("{}{}", ident.is_raw.as_str(), ident.sym) + TokenTree::Ident { sym, is_raw, .. } => format!("{}{}", is_raw.as_str(), sym), + &TokenTree::Literal { ref text_and_suffix, kind, suffix_len, span: _ } => { + format!( + "{}", + Literal { + text_and_suffix: text_and_suffix.clone(), + span: Span { + range: TextRange::empty(TextSize::new(0)), + anchor: span::SpanAnchor { + file_id: span::EditionedFileId::from_raw(0), + ast_id: span::FIXUP_ERASED_FILE_AST_ID_MARKER + }, + ctx: span::SyntaxContext::root(span::Edition::Edition2015) + }, + kind, + suffix_len + } + ) } - TokenTree::Leaf(Leaf::Literal(literal)) => format!("{literal}"), - TokenTree::Leaf(Leaf::Punct(punct)) => format!("{}", punct.char), - TokenTree::Subtree(subtree) => { - let (subtree_content, rest) = tkns.split_at(subtree.usize_len()); - let content = pretty(TokenTreesView(subtree_content)); + TokenTree::Punct { char, .. } => format!("{}", char), + TokenTree::Subtree { len, delim_kind, .. } => { + let (subtree_content, rest) = tkns.split_at(*len as usize); + let content = pretty_impl(subtree_content); *tkns = rest; - let (open, close) = match subtree.delimiter.kind { + let (open, close) = match *delim_kind { DelimiterKind::Brace => ("{", "}"), DelimiterKind::Bracket => ("[", "]"), DelimiterKind::Parenthesis => ("(", ")"), @@ -1032,21 +807,26 @@ pub fn pretty(tkns: TokenTreesView<'_>) -> String { } } - let mut tkns = tkns.0; - let mut last = String::new(); - let mut last_to_joint = true; - - while let Some((tkn, rest)) = tkns.split_first() { - tkns = rest; - last = [last, tokentree_to_text(tkn, &mut tkns)].join(if last_to_joint { "" } else { " " }); - last_to_joint = false; - if let TokenTree::Leaf(Leaf::Punct(punct)) = tkn - && punct.spacing == Spacing::Joint - { - last_to_joint = true; + fn pretty_impl(mut tkns: &[TokenTree]) -> String { + let mut last = String::new(); + let mut last_to_joint = true; + + while let Some((tkn, rest)) = tkns.split_first() { + tkns = rest; + last = [last, tokentree_to_text(tkn, &mut tkns)].join(if last_to_joint { + "" + } else { + " " + }); + last_to_joint = false; + if let TokenTree::Punct { spacing, .. } = tkn + && *spacing == Spacing::Joint + { + last_to_joint = true; + } } + last } - last } #[derive(Debug)] @@ -1069,7 +849,7 @@ pub fn transform_tt<'b>( tt: &mut TopSubtree, mut callback: impl FnMut(TokenTree) -> TransformTtAction<'b>, ) { - let mut tt_vec = std::mem::take(&mut tt.0).into_vec(); + let mut tt_vec = tt.as_token_trees().iter_flat_tokens().collect::>(); // We need to keep a stack of the currently open subtrees, because we need to update // them if we change the number of items in them. @@ -1112,7 +892,7 @@ pub fn transform_tt<'b>( TokenTree::Subtree(subtree) => subtree.usize_len(), }; let len_diff = replacement.len() as i64 - old_len as i64; - tt_vec.splice(i..i + old_len, replacement.0.iter().cloned()); + tt_vec.splice(i..i + old_len, replacement.iter_flat_tokens()); // Skip the newly inserted replacement, we don't want to visit it. i += replacement.len(); @@ -1126,5 +906,5 @@ pub fn transform_tt<'b>( } } - tt.0 = tt_vec.into_boxed_slice(); + *tt = TopSubtree::from_serialized(tt_vec); } diff --git a/src/tools/rust-analyzer/crates/tt/src/storage.rs b/src/tools/rust-analyzer/crates/tt/src/storage.rs new file mode 100644 index 0000000000000..62d2e20016ac3 --- /dev/null +++ b/src/tools/rust-analyzer/crates/tt/src/storage.rs @@ -0,0 +1,992 @@ +//! Spans are memory heavy, and we have a lot of token trees. Storing them straight +//! will waste a lot of memory. So instead we implement a clever compression mechanism: +//! +//! A `TopSubtree` has a list of [`CompressedSpanPart`], which are the parts of a span +//! that tend to be shared between tokens - namely, without the range. The main list +//! of token trees is kept in one of three versions, where we use the smallest version +//! we can for this tree: +//! +//! 1. In the most common version a span is just a `u32`. The bits are divided as follows: +//! there are 4 bits that index into the [`CompressedSpanPart`] list. 20 bits +//! store the range start, and 8 bits store the range length. In experiments, +//! this accounts for 75%-85% of the spans. +//! 2. In the second version a span is 64 bits. 32 bits for the range start, 16 bits +//! for the range length, and 16 bits for the span parts index. This is used in +//! less than 2% of all `TopSubtree`s, but they account for 15%-25% of the spans: +//! those are mostly token tree munchers, that generate a lot of `SyntaxContext`s +//! (because they recurse a lot), which is why they can't fit in the first version, +//! and tend to generate a lot of code. +//! 3. The third version is practically unused; 65,535 bytes for a token and 65,535 +//! unique span parts is more than enough for everybody. However, someone may still +//! create a macro that requires more, therefore we have this version as a backup: +//! it uses 96 bits, 32 for each of the range start, length and span parts index. + +use std::fmt; + +use intern::Symbol; +use rustc_hash::FxBuildHasher; +use span::{Span, SpanAnchor, SyntaxContext, TextRange, TextSize}; + +use crate::{ + DelimSpan, DelimiterKind, IdentIsRaw, LitKind, Spacing, SubtreeView, TokenTreesReprRef, + TokenTreesView, TtIter, dispatch_ref, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct CompressedSpanPart { + pub(crate) anchor: SpanAnchor, + pub(crate) ctx: SyntaxContext, +} + +impl CompressedSpanPart { + #[inline] + fn from_span(span: &Span) -> Self { + Self { anchor: span.anchor, ctx: span.ctx } + } + + #[inline] + fn recombine(&self, range: TextRange) -> Span { + Span { range, anchor: self.anchor, ctx: self.ctx } + } +} + +pub(crate) trait SpanStorage: Copy { + fn can_hold(text_range: TextRange, span_parts_index: usize) -> bool; + + fn new(text_range: TextRange, span_parts_index: usize) -> Self; + + fn text_range(&self) -> TextRange; + + fn span_parts_index(&self) -> usize; + + #[inline] + fn span(&self, span_parts: &[CompressedSpanPart]) -> Span { + span_parts[self.span_parts_index()].recombine(self.text_range()) + } +} + +#[inline] +const fn n_bits_mask(n: u32) -> u32 { + (1 << n) - 1 +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SpanStorage32(u32); + +impl SpanStorage32 { + const SPAN_PARTS_BIT: u32 = 4; + const LEN_BITS: u32 = 8; + const OFFSET_BITS: u32 = 20; +} + +const _: () = assert!( + (SpanStorage32::SPAN_PARTS_BIT + SpanStorage32::LEN_BITS + SpanStorage32::OFFSET_BITS) + == u32::BITS +); + +impl SpanStorage for SpanStorage32 { + #[inline] + fn can_hold(text_range: TextRange, span_parts_index: usize) -> bool { + let offset = u32::from(text_range.start()); + let len = u32::from(text_range.len()); + let span_parts_index = span_parts_index as u32; + + offset <= n_bits_mask(Self::OFFSET_BITS) + && len <= n_bits_mask(Self::LEN_BITS) + && span_parts_index <= n_bits_mask(Self::SPAN_PARTS_BIT) + } + + #[inline] + fn new(text_range: TextRange, span_parts_index: usize) -> Self { + let offset = u32::from(text_range.start()); + let len = u32::from(text_range.len()); + let span_parts_index = span_parts_index as u32; + + debug_assert!(offset <= n_bits_mask(Self::OFFSET_BITS)); + debug_assert!(len <= n_bits_mask(Self::LEN_BITS)); + debug_assert!(span_parts_index <= n_bits_mask(Self::SPAN_PARTS_BIT)); + + Self( + (offset << (Self::LEN_BITS + Self::SPAN_PARTS_BIT)) + | (len << Self::SPAN_PARTS_BIT) + | span_parts_index, + ) + } + + #[inline] + fn text_range(&self) -> TextRange { + let offset = TextSize::new(self.0 >> (Self::SPAN_PARTS_BIT + Self::LEN_BITS)); + let len = TextSize::new((self.0 >> Self::SPAN_PARTS_BIT) & n_bits_mask(Self::LEN_BITS)); + TextRange::at(offset, len) + } + + #[inline] + fn span_parts_index(&self) -> usize { + (self.0 & n_bits_mask(Self::SPAN_PARTS_BIT)) as usize + } +} + +impl fmt::Debug for SpanStorage32 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SpanStorage32") + .field("text_range", &self.text_range()) + .field("span_parts_index", &self.span_parts_index()) + .finish() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SpanStorage64 { + offset: u32, + len_and_parts: u32, +} + +impl SpanStorage64 { + const SPAN_PARTS_BIT: u32 = 16; + const LEN_BITS: u32 = 16; +} + +const _: () = assert!((SpanStorage64::SPAN_PARTS_BIT + SpanStorage64::LEN_BITS) == u32::BITS); + +impl SpanStorage for SpanStorage64 { + #[inline] + fn can_hold(text_range: TextRange, span_parts_index: usize) -> bool { + let len = u32::from(text_range.len()); + let span_parts_index = span_parts_index as u32; + + len <= n_bits_mask(Self::LEN_BITS) && span_parts_index <= n_bits_mask(Self::SPAN_PARTS_BIT) + } + + #[inline] + fn new(text_range: TextRange, span_parts_index: usize) -> Self { + let offset = u32::from(text_range.start()); + let len = u32::from(text_range.len()); + let span_parts_index = span_parts_index as u32; + + debug_assert!(len <= n_bits_mask(Self::LEN_BITS)); + debug_assert!(span_parts_index <= n_bits_mask(Self::SPAN_PARTS_BIT)); + + Self { offset, len_and_parts: (len << Self::SPAN_PARTS_BIT) | span_parts_index } + } + + #[inline] + fn text_range(&self) -> TextRange { + let offset = TextSize::new(self.offset); + let len = TextSize::new(self.len_and_parts >> Self::SPAN_PARTS_BIT); + TextRange::at(offset, len) + } + + #[inline] + fn span_parts_index(&self) -> usize { + (self.len_and_parts & n_bits_mask(Self::SPAN_PARTS_BIT)) as usize + } +} + +impl fmt::Debug for SpanStorage64 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SpanStorage64") + .field("text_range", &self.text_range()) + .field("span_parts_index", &self.span_parts_index()) + .finish() + } +} + +impl From for SpanStorage64 { + #[inline] + fn from(value: SpanStorage32) -> Self { + SpanStorage64::new(value.text_range(), value.span_parts_index()) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SpanStorage96 { + offset: u32, + len: u32, + parts: u32, +} + +impl SpanStorage for SpanStorage96 { + #[inline] + fn can_hold(_text_range: TextRange, _span_parts_index: usize) -> bool { + true + } + + #[inline] + fn new(text_range: TextRange, span_parts_index: usize) -> Self { + let offset = u32::from(text_range.start()); + let len = u32::from(text_range.len()); + let span_parts_index = span_parts_index as u32; + + Self { offset, len, parts: span_parts_index } + } + + #[inline] + fn text_range(&self) -> TextRange { + let offset = TextSize::new(self.offset); + let len = TextSize::new(self.len); + TextRange::at(offset, len) + } + + #[inline] + fn span_parts_index(&self) -> usize { + self.parts as usize + } +} + +impl fmt::Debug for SpanStorage96 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SpanStorage96") + .field("text_range", &self.text_range()) + .field("span_parts_index", &self.span_parts_index()) + .finish() + } +} + +impl From for SpanStorage96 { + #[inline] + fn from(value: SpanStorage32) -> Self { + SpanStorage96::new(value.text_range(), value.span_parts_index()) + } +} + +impl From for SpanStorage96 { + #[inline] + fn from(value: SpanStorage64) -> Self { + SpanStorage96::new(value.text_range(), value.span_parts_index()) + } +} + +// We don't use structs or enum nesting here to save padding. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) enum TokenTree { + Literal { text_and_suffix: Symbol, span: S, kind: LitKind, suffix_len: u8 }, + Punct { char: char, spacing: Spacing, span: S }, + Ident { sym: Symbol, span: S, is_raw: IdentIsRaw }, + Subtree { len: u32, delim_kind: DelimiterKind, open_span: S, close_span: S }, +} + +impl TokenTree { + #[inline] + pub(crate) fn first_span(&self) -> &S { + match self { + TokenTree::Literal { span, .. } => span, + TokenTree::Punct { span, .. } => span, + TokenTree::Ident { span, .. } => span, + TokenTree::Subtree { open_span, .. } => open_span, + } + } + + #[inline] + pub(crate) fn last_span(&self) -> &S { + match self { + TokenTree::Literal { span, .. } => span, + TokenTree::Punct { span, .. } => span, + TokenTree::Ident { span, .. } => span, + TokenTree::Subtree { close_span, .. } => close_span, + } + } + + #[inline] + pub(crate) fn to_api(&self, span_parts: &[CompressedSpanPart]) -> crate::TokenTree { + match self { + TokenTree::Literal { text_and_suffix, span, kind, suffix_len } => { + crate::TokenTree::Leaf(crate::Leaf::Literal(crate::Literal { + text_and_suffix: text_and_suffix.clone(), + span: span.span(span_parts), + kind: *kind, + suffix_len: *suffix_len, + })) + } + TokenTree::Punct { char, spacing, span } => { + crate::TokenTree::Leaf(crate::Leaf::Punct(crate::Punct { + char: *char, + spacing: *spacing, + span: span.span(span_parts), + })) + } + TokenTree::Ident { sym, span, is_raw } => { + crate::TokenTree::Leaf(crate::Leaf::Ident(crate::Ident { + sym: sym.clone(), + span: span.span(span_parts), + is_raw: *is_raw, + })) + } + TokenTree::Subtree { len, delim_kind, open_span, close_span } => { + crate::TokenTree::Subtree(crate::Subtree { + delimiter: crate::Delimiter { + open: open_span.span(span_parts), + close: close_span.span(span_parts), + kind: *delim_kind, + }, + len: *len, + }) + } + } + } + + #[inline] + fn convert>(self) -> TokenTree { + match self { + TokenTree::Literal { text_and_suffix, span, kind, suffix_len } => { + TokenTree::Literal { text_and_suffix, span: span.into(), kind, suffix_len } + } + TokenTree::Punct { char, spacing, span } => { + TokenTree::Punct { char, spacing, span: span.into() } + } + TokenTree::Ident { sym, span, is_raw } => { + TokenTree::Ident { sym, span: span.into(), is_raw } + } + TokenTree::Subtree { len, delim_kind, open_span, close_span } => TokenTree::Subtree { + len, + delim_kind, + open_span: open_span.into(), + close_span: close_span.into(), + }, + } + } +} + +// This is used a lot, make sure it doesn't grow unintentionally. +const _: () = { + assert!(size_of::>() == 16); + assert!(size_of::>() == 24); + assert!(size_of::>() == 32); +}; + +/// `dispatch! {}` +macro_rules! dispatch { + ( + match $scrutinee:expr => $tt:ident => $body:expr + ) => { + match $scrutinee { + TopSubtreeRepr::SpanStorage32($tt) => $body, + TopSubtreeRepr::SpanStorage64($tt) => $body, + TopSubtreeRepr::SpanStorage96($tt) => $body, + } + }; +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) enum TopSubtreeRepr { + SpanStorage32(Box<[TokenTree]>), + SpanStorage64(Box<[TokenTree]>), + SpanStorage96(Box<[TokenTree]>), +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct TopSubtree { + repr: TopSubtreeRepr, + span_parts: Box<[CompressedSpanPart]>, +} + +impl TopSubtree { + pub fn empty(span: DelimSpan) -> Self { + Self { + repr: TopSubtreeRepr::SpanStorage96(Box::new([TokenTree::Subtree { + len: 0, + delim_kind: DelimiterKind::Invisible, + open_span: SpanStorage96::new(span.open.range, 0), + close_span: SpanStorage96::new(span.close.range, 1), + }])), + span_parts: Box::new([ + CompressedSpanPart::from_span(&span.open), + CompressedSpanPart::from_span(&span.close), + ]), + } + } + + pub fn invisible_from_leaves( + delim_span: Span, + leaves: [crate::Leaf; N], + ) -> Self { + let mut builder = TopSubtreeBuilder::new(crate::Delimiter::invisible_spanned(delim_span)); + builder.extend(leaves); + builder.build() + } + + pub fn from_token_trees(delimiter: crate::Delimiter, token_trees: TokenTreesView<'_>) -> Self { + let mut builder = TopSubtreeBuilder::new(delimiter); + builder.extend_with_tt(token_trees); + builder.build() + } + + pub fn from_serialized(tt: Vec) -> Self { + let mut tt = tt.into_iter(); + let Some(crate::TokenTree::Subtree(top_subtree)) = tt.next() else { + panic!("first must always come the top subtree") + }; + let mut builder = TopSubtreeBuilder::new(top_subtree.delimiter); + for tt in tt { + builder.push_token_tree(tt); + } + builder.build() + } + + pub fn from_subtree(subtree: SubtreeView<'_>) -> Self { + let mut builder = TopSubtreeBuilder::new(subtree.top_subtree().delimiter); + builder.extend_with_tt(subtree.token_trees()); + builder.build() + } + + pub fn view(&self) -> SubtreeView<'_> { + let repr = match &self.repr { + TopSubtreeRepr::SpanStorage32(token_trees) => { + TokenTreesReprRef::SpanStorage32(token_trees) + } + TopSubtreeRepr::SpanStorage64(token_trees) => { + TokenTreesReprRef::SpanStorage64(token_trees) + } + TopSubtreeRepr::SpanStorage96(token_trees) => { + TokenTreesReprRef::SpanStorage96(token_trees) + } + }; + SubtreeView(TokenTreesView { repr, span_parts: &self.span_parts }) + } + + pub fn iter(&self) -> TtIter<'_> { + self.view().iter() + } + + pub fn top_subtree(&self) -> crate::Subtree { + self.view().top_subtree() + } + + pub fn set_top_subtree_delimiter_kind(&mut self, kind: DelimiterKind) { + dispatch! { + match &mut self.repr => tt => { + let TokenTree::Subtree { delim_kind, .. } = &mut tt[0] else { + unreachable!("the first token tree is always the top subtree"); + }; + *delim_kind = kind; + } + } + } + + fn ensure_can_hold(&mut self, range: TextRange) { + fn can_hold(_: &[TokenTree], range: TextRange) -> bool { + S::can_hold(range, 0) + } + let can_hold = dispatch! { + match &self.repr => tt => can_hold(tt, range) + }; + if can_hold { + return; + } + + // Otherwise, we do something very junky: recreate the entire tree. Hopefully this should be rare. + let mut builder = TopSubtreeBuilder::new(self.top_subtree().delimiter); + builder.extend_with_tt(self.token_trees()); + builder.ensure_can_hold(range, 0); + *self = builder.build(); + } + + pub fn set_top_subtree_delimiter_span(&mut self, span: DelimSpan) { + self.ensure_can_hold(span.open.range); + self.ensure_can_hold(span.close.range); + fn do_it(tt: &mut [TokenTree], span: DelimSpan) { + let TokenTree::Subtree { open_span, close_span, .. } = &mut tt[0] else { + unreachable!() + }; + *open_span = S::new(span.open.range, 0); + *close_span = S::new(span.close.range, 0); + } + dispatch! { + match &mut self.repr => tt => do_it(tt, span) + } + self.span_parts[0] = CompressedSpanPart::from_span(&span.open); + self.span_parts[1] = CompressedSpanPart::from_span(&span.close); + } + + /// Note: this cannot change spans. + pub fn set_token(&mut self, idx: usize, leaf: crate::Leaf) { + fn do_it( + tt: &mut [TokenTree], + idx: usize, + span_parts: &[CompressedSpanPart], + leaf: crate::Leaf, + ) { + assert!( + !matches!(tt[idx], TokenTree::Subtree { .. }), + "`TopSubtree::set_token()` must be called on a leaf" + ); + let existing_span_compressed = *tt[idx].first_span(); + let existing_span = existing_span_compressed.span(span_parts); + assert_eq!( + *leaf.span(), + existing_span, + "`TopSubtree::set_token()` cannot change spans" + ); + match leaf { + crate::Leaf::Literal(leaf) => { + tt[idx] = TokenTree::Literal { + text_and_suffix: leaf.text_and_suffix, + span: existing_span_compressed, + kind: leaf.kind, + suffix_len: leaf.suffix_len, + } + } + crate::Leaf::Punct(leaf) => { + tt[idx] = TokenTree::Punct { + char: leaf.char, + spacing: leaf.spacing, + span: existing_span_compressed, + } + } + crate::Leaf::Ident(leaf) => { + tt[idx] = TokenTree::Ident { + sym: leaf.sym, + span: existing_span_compressed, + is_raw: leaf.is_raw, + } + } + } + } + dispatch! { + match &mut self.repr => tt => do_it(tt, idx, &self.span_parts, leaf) + } + } + + pub fn token_trees(&self) -> TokenTreesView<'_> { + self.view().token_trees() + } + + pub fn as_token_trees(&self) -> TokenTreesView<'_> { + self.view().as_token_trees() + } + + pub fn change_every_ast_id(&mut self, mut callback: impl FnMut(&mut span::ErasedFileAstId)) { + for span_part in &mut self.span_parts { + callback(&mut span_part.anchor.ast_id); + } + } +} + +/// `dispatch_builder! {}` +macro_rules! dispatch_builder { + ( + match $scrutinee:expr => $tt:ident => $body:expr + ) => { + match $scrutinee { + TopSubtreeBuilderRepr::SpanStorage32($tt) => $body, + TopSubtreeBuilderRepr::SpanStorage64($tt) => $body, + TopSubtreeBuilderRepr::SpanStorage96($tt) => $body, + } + }; +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum TopSubtreeBuilderRepr { + SpanStorage32(Vec>), + SpanStorage64(Vec>), + SpanStorage96(Vec>), +} + +type FxIndexSet = indexmap::IndexSet; + +/// In any tree, the first two subtree parts are reserved for the top subtree. +/// +/// We do it because `TopSubtree` exposes an API to modify the top subtree, therefore it's more convenient +/// this way, and it's unlikely to affect memory usage. +const RESERVED_SPAN_PARTS_LEN: usize = 2; + +#[derive(Debug, Clone)] +pub struct TopSubtreeBuilder { + unclosed_subtree_indices: Vec, + token_trees: TopSubtreeBuilderRepr, + span_parts: FxIndexSet, + last_closed_subtree: Option, + /// We need to keep those because they are not inside `span_parts`, see [`RESERVED_SPAN_PARTS_LEN`]. + top_subtree_spans: DelimSpan, +} + +impl TopSubtreeBuilder { + pub fn new(top_delimiter: crate::Delimiter) -> Self { + let mut result = Self { + unclosed_subtree_indices: Vec::new(), + token_trees: TopSubtreeBuilderRepr::SpanStorage32(Vec::new()), + span_parts: FxIndexSet::default(), + last_closed_subtree: None, + top_subtree_spans: top_delimiter.delim_span(), + }; + result.ensure_can_hold(top_delimiter.open.range, 0); + result.ensure_can_hold(top_delimiter.close.range, 1); + fn push_first(tt: &mut Vec>, top_delimiter: crate::Delimiter) { + tt.push(TokenTree::Subtree { + len: 0, + delim_kind: top_delimiter.kind, + open_span: S::new(top_delimiter.open.range, 0), + close_span: S::new(top_delimiter.close.range, 1), + }); + } + dispatch_builder! { + match &mut result.token_trees => tt => push_first(tt, top_delimiter) + } + result + } + + fn span_part_index(&mut self, part: CompressedSpanPart) -> usize { + self.span_parts.insert_full(part).0 + RESERVED_SPAN_PARTS_LEN + } + + fn switch_repr>(repr: &mut Vec>) -> Vec> { + let repr = std::mem::take(repr); + repr.into_iter().map(|tt| tt.convert()).collect() + } + + /// Ensures we have a representation that can hold these values. + fn ensure_can_hold(&mut self, text_range: TextRange, span_parts_index: usize) { + match &mut self.token_trees { + TopSubtreeBuilderRepr::SpanStorage32(token_trees) => { + if SpanStorage32::can_hold(text_range, span_parts_index) { + // Can hold. + } else if SpanStorage64::can_hold(text_range, span_parts_index) { + self.token_trees = + TopSubtreeBuilderRepr::SpanStorage64(Self::switch_repr(token_trees)); + } else { + self.token_trees = + TopSubtreeBuilderRepr::SpanStorage96(Self::switch_repr(token_trees)); + } + } + TopSubtreeBuilderRepr::SpanStorage64(token_trees) => { + if SpanStorage64::can_hold(text_range, span_parts_index) { + // Can hold. + } else { + self.token_trees = + TopSubtreeBuilderRepr::SpanStorage96(Self::switch_repr(token_trees)); + } + } + TopSubtreeBuilderRepr::SpanStorage96(_) => { + // Can hold anything. + } + } + } + + /// Not to be exposed, this assumes the subtree's children will be filled in immediately. + fn push_subtree(&mut self, subtree: crate::Subtree) { + let open_span_parts_index = + self.span_part_index(CompressedSpanPart::from_span(&subtree.delimiter.open)); + self.ensure_can_hold(subtree.delimiter.open.range, open_span_parts_index); + let close_span_parts_index = + self.span_part_index(CompressedSpanPart::from_span(&subtree.delimiter.close)); + self.ensure_can_hold(subtree.delimiter.close.range, close_span_parts_index); + fn do_it( + tt: &mut Vec>, + open_span_parts_index: usize, + close_span_parts_index: usize, + subtree: crate::Subtree, + ) { + let open_span = S::new(subtree.delimiter.open.range, open_span_parts_index); + let close_span = S::new(subtree.delimiter.close.range, close_span_parts_index); + tt.push(TokenTree::Subtree { + len: subtree.len, + delim_kind: subtree.delimiter.kind, + open_span, + close_span, + }); + } + dispatch_builder! { + match &mut self.token_trees => tt => do_it(tt, open_span_parts_index, close_span_parts_index, subtree) + } + } + + pub fn open(&mut self, delimiter_kind: DelimiterKind, open_span: Span) { + let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&open_span)); + self.ensure_can_hold(open_span.range, span_parts_index); + fn do_it( + token_trees: &mut Vec>, + delimiter_kind: DelimiterKind, + range: TextRange, + span_parts_index: usize, + ) -> usize { + let open_span = S::new(range, span_parts_index); + token_trees.push(TokenTree::Subtree { + len: 0, + delim_kind: delimiter_kind, + open_span, + close_span: open_span, // Will be overwritten on close. + }); + token_trees.len() - 1 + } + let subtree_idx = dispatch_builder! { + match &mut self.token_trees => tt => do_it(tt, delimiter_kind, open_span.range, span_parts_index) + }; + self.unclosed_subtree_indices.push(subtree_idx); + } + + pub fn close(&mut self, close_span: Span) { + let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&close_span)); + let range = close_span.range; + self.ensure_can_hold(range, span_parts_index); + + let last_unclosed_index = self + .unclosed_subtree_indices + .pop() + .expect("attempt to close a `tt::Subtree` when none is open"); + fn do_it( + token_trees: &mut [TokenTree], + last_unclosed_index: usize, + range: TextRange, + span_parts_index: usize, + ) { + let token_trees_len = token_trees.len(); + let TokenTree::Subtree { len, delim_kind: _, open_span: _, close_span } = + &mut token_trees[last_unclosed_index] + else { + unreachable!("unclosed token tree is always a subtree"); + }; + *len = (token_trees_len - last_unclosed_index - 1) as u32; + *close_span = S::new(range, span_parts_index); + } + dispatch_builder! { + match &mut self.token_trees => tt => do_it(tt, last_unclosed_index, range, span_parts_index) + } + self.last_closed_subtree = Some(last_unclosed_index); + } + + /// You cannot call this consecutively, it will only work once after close. + pub fn remove_last_subtree_if_invisible(&mut self) { + let Some(last_subtree_idx) = self.last_closed_subtree else { return }; + fn do_it(tt: &mut Vec>, last_subtree_idx: usize) { + if let TokenTree::Subtree { delim_kind: DelimiterKind::Invisible, .. } = + tt[last_subtree_idx] + { + tt.remove(last_subtree_idx); + } + } + dispatch_builder! { + match &mut self.token_trees => tt => do_it(tt, last_subtree_idx) + } + self.last_closed_subtree = None; + } + + fn push_literal(&mut self, leaf: crate::Literal) { + let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&leaf.span)); + let range = leaf.span.range; + self.ensure_can_hold(range, span_parts_index); + fn do_it( + tt: &mut Vec>, + range: TextRange, + span_parts_index: usize, + leaf: crate::Literal, + ) { + tt.push(TokenTree::Literal { + text_and_suffix: leaf.text_and_suffix, + span: S::new(range, span_parts_index), + kind: leaf.kind, + suffix_len: leaf.suffix_len, + }) + } + dispatch_builder! { + match &mut self.token_trees => tt => do_it(tt, range, span_parts_index, leaf) + } + } + + fn push_punct(&mut self, leaf: crate::Punct) { + let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&leaf.span)); + let range = leaf.span.range; + self.ensure_can_hold(range, span_parts_index); + fn do_it( + tt: &mut Vec>, + range: TextRange, + span_parts_index: usize, + leaf: crate::Punct, + ) { + tt.push(TokenTree::Punct { + char: leaf.char, + spacing: leaf.spacing, + span: S::new(range, span_parts_index), + }) + } + dispatch_builder! { + match &mut self.token_trees => tt => do_it(tt, range, span_parts_index, leaf) + } + } + + fn push_ident(&mut self, leaf: crate::Ident) { + let span_parts_index = self.span_part_index(CompressedSpanPart::from_span(&leaf.span)); + let range = leaf.span.range; + self.ensure_can_hold(range, span_parts_index); + fn do_it( + tt: &mut Vec>, + range: TextRange, + span_parts_index: usize, + leaf: crate::Ident, + ) { + tt.push(TokenTree::Ident { + sym: leaf.sym, + span: S::new(range, span_parts_index), + is_raw: leaf.is_raw, + }) + } + dispatch_builder! { + match &mut self.token_trees => tt => do_it(tt, range, span_parts_index, leaf) + } + } + + pub fn push(&mut self, leaf: crate::Leaf) { + match leaf { + crate::Leaf::Literal(leaf) => self.push_literal(leaf), + crate::Leaf::Punct(leaf) => self.push_punct(leaf), + crate::Leaf::Ident(leaf) => self.push_ident(leaf), + } + } + + fn push_token_tree(&mut self, tt: crate::TokenTree) { + match tt { + crate::TokenTree::Leaf(leaf) => self.push(leaf), + crate::TokenTree::Subtree(subtree) => self.push_subtree(subtree), + } + } + + pub fn extend(&mut self, leaves: impl IntoIterator) { + leaves.into_iter().for_each(|leaf| self.push(leaf)); + } + + pub fn extend_with_tt(&mut self, tt: TokenTreesView<'_>) { + fn do_it( + this: &mut TopSubtreeBuilder, + tt: &[TokenTree], + span_parts: &[CompressedSpanPart], + ) { + for tt in tt { + this.push_token_tree(tt.to_api(span_parts)); + } + } + dispatch_ref! { + match tt.repr => tt_repr => do_it(self, tt_repr, tt.span_parts) + } + } + + /// Like [`Self::extend_with_tt()`], but makes sure the new tokens will never be + /// joint with whatever comes after them. + pub fn extend_with_tt_alone(&mut self, tt: TokenTreesView<'_>) { + self.extend_with_tt(tt); + fn do_it(tt: &mut [TokenTree]) { + if let Some(TokenTree::Punct { spacing, .. }) = tt.last_mut() { + *spacing = Spacing::Alone; + } + } + if !tt.is_empty() { + dispatch_builder! { + match &mut self.token_trees => tt => do_it(tt) + } + } + } + + pub fn expected_delimiters(&self) -> impl Iterator { + self.unclosed_subtree_indices.iter().rev().map(|&subtree_idx| { + dispatch_builder! { + match &self.token_trees => tt => { + let TokenTree::Subtree { delim_kind, .. } = tt[subtree_idx] else { + unreachable!("unclosed token tree is always a subtree") + }; + delim_kind + } + } + }) + } + + /// Builds, and remove the top subtree if it has only one subtree child. + pub fn build_skip_top_subtree(mut self) -> TopSubtree { + fn remove_first_if_needed( + tt: &mut Vec>, + top_delim_span: &mut DelimSpan, + span_parts: &FxIndexSet, + ) { + let tt_len = tt.len(); + let Some(TokenTree::Subtree { len, open_span, close_span, .. }) = tt.get_mut(1) else { + return; + }; + if (*len as usize) != (tt_len - 2) { + // Subtree does not cover the whole tree (minus 2; itself, and the top span). + return; + } + + // Now we need to adjust the spans, because we assume that the first two spans are always reserved. + let top_open_span = span_parts + .get_index(open_span.span_parts_index() - RESERVED_SPAN_PARTS_LEN) + .unwrap() + .recombine(open_span.text_range()); + let top_close_span = span_parts + .get_index(close_span.span_parts_index() - RESERVED_SPAN_PARTS_LEN) + .unwrap() + .recombine(close_span.text_range()); + *top_delim_span = DelimSpan { open: top_open_span, close: top_close_span }; + // Can't remove the top spans from the map, as maybe they're used by other things as well. + // Now we need to reencode the spans, because their parts index changed: + *open_span = S::new(open_span.text_range(), 0); + *close_span = S::new(close_span.text_range(), 1); + + tt.remove(0); + } + dispatch_builder! { + match &mut self.token_trees => tt => remove_first_if_needed(tt, &mut self.top_subtree_spans, &self.span_parts) + } + self.build() + } + + pub fn build(mut self) -> TopSubtree { + assert!( + self.unclosed_subtree_indices.is_empty(), + "attempt to build an unbalanced `TopSubtreeBuilder`" + ); + fn finish_top_len(tt: &mut [TokenTree]) { + let total_len = tt.len() as u32; + let TokenTree::Subtree { len, .. } = &mut tt[0] else { + unreachable!("first token tree is always a subtree"); + }; + *len = total_len - 1; + } + dispatch_builder! { + match &mut self.token_trees => tt => finish_top_len(tt) + } + + let span_parts = [ + CompressedSpanPart::from_span(&self.top_subtree_spans.open), + CompressedSpanPart::from_span(&self.top_subtree_spans.close), + ] + .into_iter() + .chain(self.span_parts.iter().copied()) + .collect(); + + let repr = match self.token_trees { + TopSubtreeBuilderRepr::SpanStorage32(tt) => { + TopSubtreeRepr::SpanStorage32(tt.into_boxed_slice()) + } + TopSubtreeBuilderRepr::SpanStorage64(tt) => { + TopSubtreeRepr::SpanStorage64(tt.into_boxed_slice()) + } + TopSubtreeBuilderRepr::SpanStorage96(tt) => { + TopSubtreeRepr::SpanStorage96(tt.into_boxed_slice()) + } + }; + + TopSubtree { repr, span_parts } + } + + pub fn restore_point(&self) -> SubtreeBuilderRestorePoint { + let token_trees_len = dispatch_builder! { + match &self.token_trees => tt => tt.len() + }; + SubtreeBuilderRestorePoint { + unclosed_subtree_indices_len: self.unclosed_subtree_indices.len(), + token_trees_len, + last_closed_subtree: self.last_closed_subtree, + } + } + + pub fn restore(&mut self, restore_point: SubtreeBuilderRestorePoint) { + self.unclosed_subtree_indices.truncate(restore_point.unclosed_subtree_indices_len); + dispatch_builder! { + match &mut self.token_trees => tt => tt.truncate(restore_point.token_trees_len) + } + self.last_closed_subtree = restore_point.last_closed_subtree; + } +} + +#[derive(Clone, Copy)] +pub struct SubtreeBuilderRestorePoint { + unclosed_subtree_indices_len: usize, + token_trees_len: usize, + last_closed_subtree: Option, +} From 975aa0dc8a535165a5415ca057a738626339be03 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 30 Dec 2025 19:39:13 +0200 Subject: [PATCH 72/77] Add `#[rust_analyzer::macro_style()]` attribute to control macro completion brace style --- .../rust-analyzer/crates/hir-def/src/attrs.rs | 19 ++++ src/tools/rust-analyzer/crates/hir/src/lib.rs | 44 ++++++++++ .../ide-completion/src/render/macro_.rs | 87 ++++++++++++++++--- .../ide-completion/src/tests/flyimport.rs | 1 + 4 files changed, 138 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index e91d72a701cff..83df11f2d2a45 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -99,6 +99,20 @@ fn extract_ra_completions(attr_flags: &mut AttrFlags, tt: ast::TokenTree) { } } +fn extract_ra_macro_style(attr_flags: &mut AttrFlags, tt: ast::TokenTree) { + let tt = TokenTreeChildren::new(&tt); + if let Ok(NodeOrToken::Token(option)) = Itertools::exactly_one(tt) + && option.kind().is_any_identifier() + { + match option.text() { + "braces" => attr_flags.insert(AttrFlags::MACRO_STYLE_BRACES), + "brackets" => attr_flags.insert(AttrFlags::MACRO_STYLE_BRACKETS), + "parentheses" => attr_flags.insert(AttrFlags::MACRO_STYLE_PARENTHESES), + _ => {} + } + } +} + fn extract_rustc_skip_during_method_dispatch(attr_flags: &mut AttrFlags, tt: ast::TokenTree) { let iter = TokenTreeChildren::new(&tt); for kind in iter { @@ -163,6 +177,7 @@ fn match_attr_flags(attr_flags: &mut AttrFlags, attr: Meta) -> ControlFlow match path.segments[0].text() { "rust_analyzer" => match path.segments[1].text() { "completions" => extract_ra_completions(attr_flags, tt), + "macro_style" => extract_ra_macro_style(attr_flags, tt), _ => {} }, _ => {} @@ -291,6 +306,10 @@ bitflags::bitflags! { const RUSTC_COINDUCTIVE = 1 << 43; const RUSTC_FORCE_INLINE = 1 << 44; const IS_POINTEE = 1 << 45; + + const MACRO_STYLE_BRACES = 1 << 46; + const MACRO_STYLE_BRACKETS = 1 << 47; + const MACRO_STYLE_PARENTHESES = 1 << 48; } } diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 6a19603923c30..1ab57c4489cfb 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -3429,6 +3429,50 @@ impl Macro { pub fn is_derive(&self, db: &dyn HirDatabase) -> bool { matches!(self.kind(db), MacroKind::Derive | MacroKind::DeriveBuiltIn) } + + pub fn preferred_brace_style(&self, db: &dyn HirDatabase) -> Option { + let attrs = self.attrs(db); + MacroBraces::extract(attrs.attrs) + } +} + +// Feature: Macro Brace Style Attribute +// Crate authors can declare the preferred brace style for their macro. This will affect how completion +// insert calls to it. +// +// This is only supported on function-like macros. +// +// To do that, insert the `#[rust_analyzer::macro_style(style)]` attribute on the macro (for proc macros, +// insert it for the macro's function). `style` can be one of: +// +// - `braces` for `{...}` style. +// - `brackets` for `[...]` style. +// - `parentheses` for `(...)` style. +// +// Malformed attributes will be ignored without warnings. +// +// Note that users have no way to override this attribute, so be careful and only include things +// users definitely do not want to be completed! + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MacroBraces { + Braces, + Brackets, + Parentheses, +} + +impl MacroBraces { + fn extract(attrs: AttrFlags) -> Option { + if attrs.contains(AttrFlags::MACRO_STYLE_BRACES) { + Some(Self::Braces) + } else if attrs.contains(AttrFlags::MACRO_STYLE_BRACKETS) { + Some(Self::Brackets) + } else if attrs.contains(AttrFlags::MACRO_STYLE_PARENTHESES) { + Some(Self::Parentheses) + } else { + None + } + } } #[derive(Clone, Copy, PartialEq, Eq, Hash)] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs index 6efa8a84553e5..8cdeb8abbff77 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs @@ -1,6 +1,6 @@ //! Renderer for macro invocations. -use hir::HirDisplay; +use hir::{HirDisplay, db::HirDatabase}; use ide_db::{SymbolKind, documentation::Documentation}; use syntax::{SmolStr, ToSmolStr, format_smolstr}; @@ -46,17 +46,15 @@ fn render( ctx.source_range() }; - let orig_name = macro_.name(ctx.db()); - let (name, orig_name, escaped_name) = ( - name.as_str(), - orig_name.as_str(), - name.display(ctx.db(), completion.edition).to_smolstr(), - ); + let (name, escaped_name) = + (name.as_str(), name.display(ctx.db(), completion.edition).to_smolstr()); let docs = ctx.docs(macro_); - let docs_str = docs.as_ref().map(Documentation::as_str).unwrap_or_default(); let is_fn_like = macro_.is_fn_like(completion.db); - let (bra, ket) = - if is_fn_like { guess_macro_braces(name, orig_name, docs_str) } else { ("", "") }; + let (bra, ket) = if is_fn_like { + guess_macro_braces(ctx.db(), macro_, name, docs.as_ref()) + } else { + ("", "") + }; let needs_bang = is_fn_like && !is_use_path && !has_macro_bang; @@ -115,12 +113,24 @@ fn banged_name(name: &str) -> SmolStr { } fn guess_macro_braces( + db: &dyn HirDatabase, + macro_: hir::Macro, macro_name: &str, - orig_name: &str, - docs: &str, + docs: Option<&Documentation<'_>>, ) -> (&'static str, &'static str) { + if let Some(style) = macro_.preferred_brace_style(db) { + return match style { + hir::MacroBraces::Braces => (" {", "}"), + hir::MacroBraces::Brackets => ("[", "]"), + hir::MacroBraces::Parentheses => ("(", ")"), + }; + } + + let orig_name = macro_.name(db); + let docs = docs.map(Documentation::as_str).unwrap_or_default(); + let mut votes = [0, 0, 0]; - for (idx, s) in docs.match_indices(macro_name).chain(docs.match_indices(orig_name)) { + for (idx, s) in docs.match_indices(macro_name).chain(docs.match_indices(orig_name.as_str())) { let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); // Ensure to match the full word if after.starts_with('!') @@ -199,6 +209,57 @@ fn main() { ); } + #[test] + fn preferred_macro_braces() { + check_edit( + "vec!", + r#" +#[rust_analyzer::macro_style(brackets)] +macro_rules! vec { () => {} } + +fn main() { v$0 } +"#, + r#" +#[rust_analyzer::macro_style(brackets)] +macro_rules! vec { () => {} } + +fn main() { vec![$0] } +"#, + ); + + check_edit( + "foo!", + r#" +#[rust_analyzer::macro_style(braces)] +macro_rules! foo { () => {} } +fn main() { $0 } +"#, + r#" +#[rust_analyzer::macro_style(braces)] +macro_rules! foo { () => {} } +fn main() { foo! {$0} } +"#, + ); + + check_edit( + "bar!", + r#" +#[macro_export] +#[rust_analyzer::macro_style(brackets)] +macro_rules! foo { () => {} } +pub use crate::foo as bar; +fn main() { $0 } +"#, + r#" +#[macro_export] +#[rust_analyzer::macro_style(brackets)] +macro_rules! foo { () => {} } +pub use crate::foo as bar; +fn main() { bar![$0] } +"#, + ); + } + #[test] fn guesses_macro_braces() { check_edit( diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs index c9755525a5ded..aad881f8ce4f1 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs @@ -79,6 +79,7 @@ fn macro_fuzzy_completion() { r#" //- /lib.rs crate:dep /// Please call me as macro_with_curlies! {} +#[rust_analyzer::macro_style(braces)] #[macro_export] macro_rules! macro_with_curlies { () => {} From df998811cee1c2d0567a04020708c1534185a980 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 30 Dec 2025 19:48:04 +0200 Subject: [PATCH 73/77] Adapt macros in rust-analyzer to new attribute `match_ast!` cannot be ported because of a weird compiler bug: https://github.com/rust-lang/rust/issues/150518. --- src/tools/rust-analyzer/crates/tt/src/lib.rs | 2 +- src/tools/rust-analyzer/crates/tt/src/storage.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs index a59fc2e089827..72b0d762ef62d 100644 --- a/src/tools/rust-analyzer/crates/tt/src/lib.rs +++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs @@ -129,7 +129,7 @@ impl Subtree { } } -/// `dispatch_ref! {}` +#[rust_analyzer::macro_style(braces)] macro_rules! dispatch_ref { ( match $scrutinee:expr => $tt:ident => $body:expr diff --git a/src/tools/rust-analyzer/crates/tt/src/storage.rs b/src/tools/rust-analyzer/crates/tt/src/storage.rs index 62d2e20016ac3..4dd02d875a29e 100644 --- a/src/tools/rust-analyzer/crates/tt/src/storage.rs +++ b/src/tools/rust-analyzer/crates/tt/src/storage.rs @@ -353,7 +353,7 @@ const _: () = { assert!(size_of::>() == 32); }; -/// `dispatch! {}` +#[rust_analyzer::macro_style(braces)] macro_rules! dispatch { ( match $scrutinee:expr => $tt:ident => $body:expr @@ -561,7 +561,7 @@ impl TopSubtree { } } -/// `dispatch_builder! {}` +#[rust_analyzer::macro_style(braces)] macro_rules! dispatch_builder { ( match $scrutinee:expr => $tt:ident => $body:expr From ef8d943ecddc978169cbee1f47f34fb7e8450804 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 31 Dec 2025 11:10:29 +1100 Subject: [PATCH 74/77] Remove `feature(string_deref_patterns)` --- compiler/rustc_feature/src/removed.rs | 2 + compiler/rustc_feature/src/unstable.rs | 2 - compiler/rustc_hir_typeck/src/pat.rs | 14 ------ compiler/rustc_middle/src/thir.rs | 1 - .../src/builder/matches/test.rs | 30 +----------- .../rustc_mir_build/src/thir/pattern/mod.rs | 18 +------ .../src/language-features/deref-patterns.md | 3 +- .../string-deref-patterns.md | 48 ------------------- .../string.foo.PreCodegen.after.mir | 20 ++++---- .../building/match/deref-patterns/string.rs | 3 +- tests/ui/README.md | 6 --- tests/ui/deref-patterns/gate.rs | 7 --- tests/ui/deref-patterns/gate.stderr | 11 ----- .../ui/{ => pattern}/deref-patterns/basic.rs | 3 +- .../deref-patterns/basic.run.stdout | 0 .../deref-patterns/default-infer.rs | 3 +- tests/ui/{ => pattern}/deref-patterns/refs.rs | 3 +- 17 files changed, 24 insertions(+), 150 deletions(-) delete mode 100644 src/doc/unstable-book/src/language-features/string-deref-patterns.md delete mode 100644 tests/ui/deref-patterns/gate.rs delete mode 100644 tests/ui/deref-patterns/gate.stderr rename tests/ui/{ => pattern}/deref-patterns/basic.rs (83%) rename tests/ui/{ => pattern}/deref-patterns/basic.run.stdout (100%) rename tests/ui/{ => pattern}/deref-patterns/default-infer.rs (66%) rename tests/ui/{ => pattern}/deref-patterns/refs.rs (79%) diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index e5d66364c2a6e..9d8a84c52e980 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -271,6 +271,8 @@ declare_features! ( /// Allows `#[link(kind = "static-nobundle", ...)]`. (removed, static_nobundle, "1.63.0", Some(37403), Some(r#"subsumed by `#[link(kind = "static", modifiers = "-bundle", ...)]`"#), 95818), + /// Allows string patterns to dereference values to match them. + (removed, string_deref_patterns, "CURRENT_RUSTC_VERSION", Some(87121), Some("superseded by `deref_patterns`"), 150530), (removed, struct_inherit, "1.0.0", None, None), (removed, test_removed_feature, "1.0.0", None, None), /// Allows using items which are missing stability attributes diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 692cba8035c49..34b7cac5c941f 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -647,8 +647,6 @@ declare_features! ( (unstable, stmt_expr_attributes, "1.6.0", Some(15701)), /// Allows lints part of the strict provenance effort. (unstable, strict_provenance_lints, "1.61.0", Some(130351)), - /// Allows string patterns to dereference values to match them. - (unstable, string_deref_patterns, "1.67.0", Some(87121)), /// Allows `super let` statements. (unstable, super_let, "1.88.0", Some(139076)), /// Allows subtrait items to shadow supertrait items. diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 1a579c4c6fa4a..90e22b2cd381a 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -996,20 +996,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pat_ty = self.tcx.types.str_; } - if self.tcx.features().string_deref_patterns() - && let hir::PatExprKind::Lit { - lit: Spanned { node: ast::LitKind::Str(..), .. }, .. - } = lt.kind - { - let tcx = self.tcx; - let expected = self.resolve_vars_if_possible(expected); - pat_ty = match expected.kind() { - ty::Adt(def, _) if tcx.is_lang_item(def.did(), LangItem::String) => expected, - ty::Str => Ty::new_static_str(tcx), - _ => pat_ty, - }; - } - // Somewhat surprising: in this case, the subtyping relation goes the // opposite way as the other cases. Actually what we really want is not // a subtyping relation at all but rather that there exists a LUB diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index 65bfc7d362b61..3683c1cfb7ddb 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -827,7 +827,6 @@ pub enum PatKind<'tcx> { /// much simpler. /// * raw pointers derived from integers, other raw pointers will have already resulted in an /// error. - /// * `String`, if `string_deref_patterns` is enabled. Constant { value: ty::Value<'tcx>, }, diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index cac4f7b7ab4f8..972d9f66faddc 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -150,7 +150,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let mut expect = self.literal_operand(test.span, Const::from_ty_value(tcx, value)); let mut place = place; - let mut block = block; + match cast_ty.kind() { ty::Str => { // String literal patterns may have type `str` if `deref_patterns` is @@ -175,34 +175,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { place = ref_place; cast_ty = ref_str_ty; } - ty::Adt(def, _) if tcx.is_lang_item(def.did(), LangItem::String) => { - if !tcx.features().string_deref_patterns() { - span_bug!( - test.span, - "matching on `String` went through without enabling string_deref_patterns" - ); - } - let re_erased = tcx.lifetimes.re_erased; - let ref_str_ty = Ty::new_imm_ref(tcx, re_erased, tcx.types.str_); - let ref_str = self.temp(ref_str_ty, test.span); - let eq_block = self.cfg.start_new_block(); - // `let ref_str: &str = ::deref(&place);` - self.call_deref( - block, - eq_block, - place, - Mutability::Not, - cast_ty, - ref_str, - test.span, - ); - // Since we generated a `ref_str = ::deref(&place) -> eq_block` terminator, - // we need to add all further statements to `eq_block`. - // Similarly, the normal test code should be generated for the `&str`, instead of the `String`. - block = eq_block; - place = ref_str; - cast_ty = ref_str_ty; - } &ty::Pat(base, _) => { assert_eq!(cast_ty, value.ty); assert!(base.is_trivially_pure_clone_copy()); diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index 063bb4a3d461a..0310003e7d58e 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -11,7 +11,7 @@ use rustc_abi::{FieldIdx, Integer}; use rustc_errors::codes::*; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::pat_util::EnumerateAndAdjustIterator; -use rustc_hir::{self as hir, LangItem, RangeEnd}; +use rustc_hir::{self as hir, RangeEnd}; use rustc_index::Idx; use rustc_middle::mir::interpret::LitToConstInput; use rustc_middle::thir::{ @@ -626,23 +626,7 @@ impl<'tcx> PatCtxt<'tcx> { // the pattern's type will be `&[u8]` whereas the literal's type is `&[u8; 3]`; using the // pattern's type means we'll properly translate it to a slice reference pattern. This works // because slices and arrays have the same valtree representation. - // HACK: As an exception, use the literal's type if `pat_ty` is `String`; this can happen if - // `string_deref_patterns` is enabled. There's a special case for that when lowering to MIR. - // FIXME(deref_patterns): This hack won't be necessary once `string_deref_patterns` is - // superseded by a more general implementation of deref patterns. let ct_ty = match pat_ty { - Some(pat_ty) - if let ty::Adt(def, _) = *pat_ty.kind() - && self.tcx.is_lang_item(def.did(), LangItem::String) => - { - if !self.tcx.features().string_deref_patterns() { - span_bug!( - expr.span, - "matching on `String` went through without enabling string_deref_patterns" - ); - } - self.typeck_results.node_type(expr.hir_id) - } Some(pat_ty) => pat_ty, None => self.typeck_results.node_type(expr.hir_id), }; diff --git a/src/doc/unstable-book/src/language-features/deref-patterns.md b/src/doc/unstable-book/src/language-features/deref-patterns.md index 4c3d456b9af80..23c2dc688400b 100644 --- a/src/doc/unstable-book/src/language-features/deref-patterns.md +++ b/src/doc/unstable-book/src/language-features/deref-patterns.md @@ -7,7 +7,7 @@ The tracking issue for this feature is: [#87121] ------------------------ > **Note**: This feature is incomplete. In the future, it is meant to supersede -> [`box_patterns`] and [`string_deref_patterns`]. +> [`box_patterns`]. This feature permits pattern matching on [smart pointers in the standard library] through their `Deref` target types, either implicitly or with explicit `deref!(_)` patterns (the syntax of which @@ -103,5 +103,4 @@ match *(b"test" as &[u8]) { ``` [`box_patterns`]: ./box-patterns.md -[`string_deref_patterns`]: ./string-deref-patterns.md [smart pointers in the standard library]: https://doc.rust-lang.org/std/ops/trait.DerefPure.html#implementors diff --git a/src/doc/unstable-book/src/language-features/string-deref-patterns.md b/src/doc/unstable-book/src/language-features/string-deref-patterns.md deleted file mode 100644 index 366bb15d4ea86..0000000000000 --- a/src/doc/unstable-book/src/language-features/string-deref-patterns.md +++ /dev/null @@ -1,48 +0,0 @@ -# `string_deref_patterns` - -The tracking issue for this feature is: [#87121] - -[#87121]: https://github.com/rust-lang/rust/issues/87121 - ------------------------- - -> **Note**: This feature will be superseded by [`deref_patterns`] in the future. - -This feature permits pattern matching `String` to `&str` through [its `Deref` implementation]. - -```rust -#![feature(string_deref_patterns)] - -pub enum Value { - String(String), - Number(u32), -} - -pub fn is_it_the_answer(value: Value) -> bool { - match value { - Value::String("42") => true, - Value::Number(42) => true, - _ => false, - } -} -``` - -Without this feature other constructs such as match guards have to be used. - -```rust -# pub enum Value { -# String(String), -# Number(u32), -# } -# -pub fn is_it_the_answer(value: Value) -> bool { - match value { - Value::String(s) if s == "42" => true, - Value::Number(42) => true, - _ => false, - } -} -``` - -[`deref_patterns`]: ./deref-patterns.md -[its `Deref` implementation]: https://doc.rust-lang.org/std/string/struct.String.html#impl-Deref-for-String diff --git a/tests/mir-opt/building/match/deref-patterns/string.foo.PreCodegen.after.mir b/tests/mir-opt/building/match/deref-patterns/string.foo.PreCodegen.after.mir index 9f52a8cd1e55e..c7c26d6351ad2 100644 --- a/tests/mir-opt/building/match/deref-patterns/string.foo.PreCodegen.after.mir +++ b/tests/mir-opt/building/match/deref-patterns/string.foo.PreCodegen.after.mir @@ -7,10 +7,11 @@ fn foo(_1: Option) -> i32 { let mut _3: isize; let mut _4: &std::string::String; let mut _5: &str; - let mut _6: bool; - let _7: std::option::Option; + let mut _6: &str; + let mut _7: bool; + let _8: std::option::Option; scope 1 { - debug s => _7; + debug s => _8; } bb0: { @@ -26,11 +27,12 @@ fn foo(_1: Option) -> i32 { } bb2: { - _6 = ::eq(copy _5, const "a") -> [return: bb3, unwind unreachable]; + _6 = &(*_5); + _7 = ::eq(copy _6, const "a") -> [return: bb3, unwind unreachable]; } bb3: { - switchInt(move _6) -> [0: bb5, otherwise: bb4]; + switchInt(move _7) -> [0: bb5, otherwise: bb4]; } bb4: { @@ -39,15 +41,15 @@ fn foo(_1: Option) -> i32 { } bb5: { - StorageLive(_7); + StorageLive(_8); _2 = const false; - _7 = move _1; + _8 = move _1; _0 = const 4321_i32; - drop(_7) -> [return: bb6, unwind unreachable]; + drop(_8) -> [return: bb6, unwind unreachable]; } bb6: { - StorageDead(_7); + StorageDead(_8); goto -> bb7; } diff --git a/tests/mir-opt/building/match/deref-patterns/string.rs b/tests/mir-opt/building/match/deref-patterns/string.rs index bb4b5379b2751..1f8d6fbb0bd25 100644 --- a/tests/mir-opt/building/match/deref-patterns/string.rs +++ b/tests/mir-opt/building/match/deref-patterns/string.rs @@ -1,7 +1,8 @@ // skip-filecheck //@ compile-flags: -Z mir-opt-level=0 -C panic=abort -#![feature(string_deref_patterns)] +#![feature(deref_patterns)] +#![expect(incomplete_features)] #![crate_type = "lib"] // EMIT_MIR string.foo.PreCodegen.after.mir diff --git a/tests/ui/README.md b/tests/ui/README.md index b1c813cce93fb..4c91f313a7351 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -380,12 +380,6 @@ These tests use the unstable command line option `query-dep-graph` to examine th Tests for `#[deprecated]` attribute and `deprecated_in_future` internal lint. -## `tests/ui/deref-patterns/`: `#![feature(deref_patterns)]` and `#![feature(string_deref_patterns)]` - -Tests for `#![feature(deref_patterns)]` and `#![feature(string_deref_patterns)]`. See [Deref patterns | The Unstable book](https://doc.rust-lang.org/nightly/unstable-book/language-features/deref-patterns.html). - -**FIXME**: May have some overlap with `tests/ui/pattern/deref-patterns`. - ## `tests/ui/derived-errors/`: Derived Error Messages Tests for quality of diagnostics involving suppression of cascading errors in some cases to avoid overwhelming the user. diff --git a/tests/ui/deref-patterns/gate.rs b/tests/ui/deref-patterns/gate.rs deleted file mode 100644 index 835fdf854d2c4..0000000000000 --- a/tests/ui/deref-patterns/gate.rs +++ /dev/null @@ -1,7 +0,0 @@ -// gate-test-string_deref_patterns -fn main() { - match String::new() { - "" | _ => {} - //~^ ERROR mismatched types - } -} diff --git a/tests/ui/deref-patterns/gate.stderr b/tests/ui/deref-patterns/gate.stderr deleted file mode 100644 index e3cbded339d7f..0000000000000 --- a/tests/ui/deref-patterns/gate.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error[E0308]: mismatched types - --> $DIR/gate.rs:4:9 - | -LL | match String::new() { - | ------------- this expression has type `String` -LL | "" | _ => {} - | ^^ expected `String`, found `&str` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/deref-patterns/basic.rs b/tests/ui/pattern/deref-patterns/basic.rs similarity index 83% rename from tests/ui/deref-patterns/basic.rs rename to tests/ui/pattern/deref-patterns/basic.rs index d76fb697f4062..dee4521e1f95c 100644 --- a/tests/ui/deref-patterns/basic.rs +++ b/tests/ui/pattern/deref-patterns/basic.rs @@ -1,6 +1,7 @@ //@ run-pass //@ check-run-results -#![feature(string_deref_patterns)] +#![feature(deref_patterns)] +#![expect(incomplete_features)] fn main() { test(Some(String::from("42"))); diff --git a/tests/ui/deref-patterns/basic.run.stdout b/tests/ui/pattern/deref-patterns/basic.run.stdout similarity index 100% rename from tests/ui/deref-patterns/basic.run.stdout rename to tests/ui/pattern/deref-patterns/basic.run.stdout diff --git a/tests/ui/deref-patterns/default-infer.rs b/tests/ui/pattern/deref-patterns/default-infer.rs similarity index 66% rename from tests/ui/deref-patterns/default-infer.rs rename to tests/ui/pattern/deref-patterns/default-infer.rs index 4f926175bd335..fb0b2add132a3 100644 --- a/tests/ui/deref-patterns/default-infer.rs +++ b/tests/ui/pattern/deref-patterns/default-infer.rs @@ -1,5 +1,6 @@ //@ check-pass -#![feature(string_deref_patterns)] +#![feature(deref_patterns)] +#![expect(incomplete_features)] fn main() { match <_ as Default>::default() { diff --git a/tests/ui/deref-patterns/refs.rs b/tests/ui/pattern/deref-patterns/refs.rs similarity index 79% rename from tests/ui/deref-patterns/refs.rs rename to tests/ui/pattern/deref-patterns/refs.rs index c93e579bfd804..51826225856bd 100644 --- a/tests/ui/deref-patterns/refs.rs +++ b/tests/ui/pattern/deref-patterns/refs.rs @@ -1,5 +1,6 @@ //@ check-pass -#![feature(string_deref_patterns)] +#![feature(deref_patterns)] +#![expect(incomplete_features)] fn foo(s: &String) -> i32 { match *s { From 935e3cbb86c81b88777e2de8ab45e0363e95412d Mon Sep 17 00:00:00 2001 From: Jefffrey Date: Wed, 31 Dec 2025 15:29:51 +0900 Subject: [PATCH 75/77] fix: don't fire `non_camel_case_types` lint for structs/enums marked with `repr(C)` --- .../crates/hir-def/src/signatures.rs | 11 ++++++ .../hir-ty/src/diagnostics/decl_check.rs | 37 ++++++++++++------- .../src/handlers/incorrect_case.rs | 15 ++++++++ .../crates/ide-diagnostics/src/lib.rs | 1 - 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs index a13ef484baa50..0dd88edbfb087 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs @@ -185,6 +185,9 @@ impl UnionSignature { bitflags! { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct EnumFlags: u8 { + /// Indicates whether this enum has `#[repr]`. + const HAS_REPR = 1 << 0; + /// Indicates whether the enum has a `#[rustc_has_incoherent_inherent_impls]` attribute. const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS = 1 << 1; } } @@ -205,6 +208,9 @@ impl EnumSignature { if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) { flags |= EnumFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS; } + if attrs.contains(AttrFlags::HAS_REPR) { + flags |= EnumFlags::HAS_REPR; + } let InFile { file_id, value: source } = loc.source(db); let (store, generic_params, source_map) = lower_generic_params( @@ -233,6 +239,11 @@ impl EnumSignature { _ => IntegerType::Pointer(true), } } + + #[inline] + pub fn repr(&self, db: &dyn DefDatabase, id: EnumId) -> Option { + if self.flags.contains(EnumFlags::HAS_REPR) { AttrFlags::repr(db, id.into()) } else { None } + } } bitflags::bitflags! { #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs index a6852b87f601f..29da1b0c513b7 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs @@ -293,12 +293,18 @@ impl<'a> DeclValidator<'a> { fn validate_struct(&mut self, struct_id: StructId) { // Check the structure name. let data = self.db.struct_signature(struct_id); - self.create_incorrect_case_diagnostic_for_item_name( - struct_id, - &data.name, - CaseType::UpperCamelCase, - IdentType::Structure, - ); + + // rustc implementation excuses repr(C) since C structs predominantly don't + // use camel case. + let has_repr_c = data.repr(self.db, struct_id).is_some_and(|repr| repr.c()); + if !has_repr_c { + self.create_incorrect_case_diagnostic_for_item_name( + struct_id, + &data.name, + CaseType::UpperCamelCase, + IdentType::Structure, + ); + } // Check the field names. self.validate_struct_fields(struct_id); @@ -378,15 +384,20 @@ impl<'a> DeclValidator<'a> { } fn validate_enum(&mut self, enum_id: EnumId) { + // Check the enum name. let data = self.db.enum_signature(enum_id); - // Check the enum name. - self.create_incorrect_case_diagnostic_for_item_name( - enum_id, - &data.name, - CaseType::UpperCamelCase, - IdentType::Enum, - ); + // rustc implementation excuses repr(C) since C structs predominantly don't + // use camel case. + let has_repr_c = data.repr(self.db, enum_id).is_some_and(|repr| repr.c()); + if !has_repr_c { + self.create_incorrect_case_diagnostic_for_item_name( + enum_id, + &data.name, + CaseType::UpperCamelCase, + IdentType::Enum, + ); + } // Check the variant names. self.validate_enum_variants(enum_id) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs index 8f68312b40f86..c47449f2593db 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -1059,4 +1059,19 @@ fn foo(_HelloWorld: ()) {} "#, ); } + + #[test] + fn allow_with_repr_c() { + check_diagnostics( + r#" +#[repr(C)] +struct FFI_Struct; + +#[repr(C)] +enum FFI_Enum { + Field, +} + "#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs index 360ded1c0be4a..0c6953419f7d5 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -108,7 +108,6 @@ use syntax::{ ast::{self, AstNode}, }; -// FIXME: Make this an enum #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum DiagnosticCode { RustcHardError(&'static str), From c19e9ecdd912cfe55a407275bb04eaf334eb3264 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 31 Dec 2025 16:18:33 +0800 Subject: [PATCH 76/77] Fix incorrect dyn hint in `impl Trait for` Example --- ```rust trait T {} impl T for {} ``` **Before this PR** ```rust trait T {} impl T for {} // ^ dyn ``` **After this PR** ```rust trait T {} impl T for {} ``` --- .../ide/src/inlay_hints/implied_dyn_trait.rs | 1 + .../crates/syntax/src/ast/node_ext.rs | 23 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs index 4fbc88a210cf2..ac91da9a3cecb 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs @@ -105,6 +105,7 @@ impl T {} // ^ dyn impl T for (T) {} // ^ dyn +impl T for {} impl T "#, ); diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs index 3357b250115c3..76cfea9d5bc64 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs @@ -447,24 +447,23 @@ impl ast::UseTreeList { impl ast::Impl { pub fn self_ty(&self) -> Option { - match self.target() { - (Some(t), None) | (_, Some(t)) => Some(t), - _ => None, - } + self.target().1 } pub fn trait_(&self) -> Option { - match self.target() { - (Some(t), Some(_)) => Some(t), - _ => None, - } + self.target().0 } fn target(&self) -> (Option, Option) { - let mut types = support::children(self.syntax()); - let first = types.next(); - let second = types.next(); - (first, second) + let mut types = support::children(self.syntax()).peekable(); + let for_kw = self.for_token(); + let trait_ = types.next_if(|trait_: &ast::Type| { + for_kw.is_some_and(|for_kw| { + trait_.syntax().text_range().start() < for_kw.text_range().start() + }) + }); + let self_ty = types.next(); + (trait_, self_ty) } pub fn for_trait_name_ref(name_ref: &ast::NameRef) -> Option { From 1a4e7f9aa9830901d02c1c04b8a511a969f68536 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 31 Dec 2025 15:14:36 +0000 Subject: [PATCH 77/77] Use --print target-libdir in run-make tests This makes the tests more robust against sysroot layout changes and slightly simplifies them. --- tests/run-make/apple-deployment-target/rmake.rs | 5 ++--- tests/run-make/cdylib-dylib-linkage/rmake.rs | 11 +++++------ tests/run-make/libstd-no-protected/rmake.rs | 6 ++---- tests/run-make/sysroot-crates-are-unstable/rmake.rs | 4 ++-- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/tests/run-make/apple-deployment-target/rmake.rs b/tests/run-make/apple-deployment-target/rmake.rs index 5d4512843d534..fb46c554c94cd 100644 --- a/tests/run-make/apple-deployment-target/rmake.rs +++ b/tests/run-make/apple-deployment-target/rmake.rs @@ -169,9 +169,8 @@ fn main() { // Test that all binaries in rlibs produced by `rustc` have the same version. // Regression test for https://github.com/rust-lang/rust/issues/128419. - let sysroot = rustc().print("sysroot").run().stdout_utf8(); - let target_sysroot = path(sysroot.trim()).join("lib/rustlib").join(target()).join("lib"); - let rlibs = shallow_find_files(&target_sysroot, |path| has_extension(path, "rlib")); + let sysroot_libs_dir = rustc().print("target-libdir").target(target()).run().stdout_utf8(); + let rlibs = shallow_find_files(sysroot_libs_dir.trim(), |path| has_extension(path, "rlib")); let output = cmd("otool").arg("-l").args(rlibs).run().stdout_utf8(); let re = regex::Regex::new(r"(minos|version) ([0-9.]*)").unwrap(); diff --git a/tests/run-make/cdylib-dylib-linkage/rmake.rs b/tests/run-make/cdylib-dylib-linkage/rmake.rs index 3c145d9f99cf3..59d8d0337daff 100644 --- a/tests/run-make/cdylib-dylib-linkage/rmake.rs +++ b/tests/run-make/cdylib-dylib-linkage/rmake.rs @@ -16,24 +16,23 @@ use run_make_support::{ fn main() { rustc().arg("-Cprefer-dynamic").input("bar.rs").run(); rustc().input("foo.rs").run(); - let sysroot = rustc().print("sysroot").run().stdout_utf8(); - let sysroot = sysroot.trim(); - let target_sysroot = path(sysroot).join("lib/rustlib").join(target()).join("lib"); + let sysroot_libs_dir = rustc().print("target-libdir").target(target()).run().stdout_utf8(); + let sysroot_libs_dir = sysroot_libs_dir.trim(); if is_windows_msvc() { - let mut libs = shallow_find_files(&target_sysroot, |path| { + let mut libs = shallow_find_files(sysroot_libs_dir, |path| { has_prefix(path, "libstd-") && has_suffix(path, ".dll.lib") }); libs.push(path(msvc_import_dynamic_lib_name("foo"))); libs.push(path(msvc_import_dynamic_lib_name("bar"))); cc().input("foo.c").args(&libs).out_exe("foo").run(); } else { - let stdlibs = shallow_find_files(&target_sysroot, |path| { + let stdlibs = shallow_find_files(sysroot_libs_dir, |path| { has_extension(path, dynamic_lib_extension()) && filename_contains(path, "std") }); cc().input("foo.c") .args(&[dynamic_lib_name("foo"), dynamic_lib_name("bar")]) .arg(stdlibs.get(0).unwrap()) - .library_search_path(&target_sysroot) + .library_search_path(sysroot_libs_dir) .output(bin_name("foo")) .run(); } diff --git a/tests/run-make/libstd-no-protected/rmake.rs b/tests/run-make/libstd-no-protected/rmake.rs index 4091406d46e29..75a1dcc4a7d0f 100644 --- a/tests/run-make/libstd-no-protected/rmake.rs +++ b/tests/run-make/libstd-no-protected/rmake.rs @@ -15,10 +15,8 @@ type SymbolTable<'data> = run_make_support::object::read::elf::SymbolTable<'data fn main() { // Find libstd-...rlib - let sysroot = rustc().print("sysroot").run().stdout_utf8(); - let sysroot = sysroot.trim(); - let target_sysroot = path(sysroot).join("lib/rustlib").join(target()).join("lib"); - let mut libs = shallow_find_files(&target_sysroot, |path| { + let sysroot_libs_dir = rustc().print("target-libdir").target(target()).run().stdout_utf8(); + let mut libs = shallow_find_files(sysroot_libs_dir.trim(), |path| { has_prefix(path, "libstd-") && has_suffix(path, ".rlib") }); assert_eq!(libs.len(), 1); diff --git a/tests/run-make/sysroot-crates-are-unstable/rmake.rs b/tests/run-make/sysroot-crates-are-unstable/rmake.rs index 20ad01bef61d6..623b9650771cb 100644 --- a/tests/run-make/sysroot-crates-are-unstable/rmake.rs +++ b/tests/run-make/sysroot-crates-are-unstable/rmake.rs @@ -44,8 +44,8 @@ fn check_crate_is_unstable(cr: &Crate) { } fn get_unstable_sysroot_crates() -> Vec { - let sysroot = PathBuf::from(rustc().print("sysroot").run().stdout_utf8().trim()); - let sysroot_libs_dir = sysroot.join("lib").join("rustlib").join(target()).join("lib"); + let sysroot_libs_dir = + PathBuf::from(rustc().print("target-libdir").target(target()).run().stdout_utf8().trim()); println!("Sysroot libs dir: {sysroot_libs_dir:?}"); // Generate a list of all library crates in the sysroot.