//! A set of high-level utility fixture methods to use in tests. #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #[cfg(feature = "in-rust-tree")] extern crate rustc_driver as _; use std::{any::TypeId, mem, str::FromStr, sync}; use base_db::target::TargetData; use base_db::{ Crate, CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CrateWorkspaceData, DependencyBuilder, Env, FileChange, FileSet, FxIndexMap, LangCrateOrigin, SourceDatabase, SourceRoot, Version, VfsPath, }; use cfg::CfgOptions; use hir_expand::{ EditionedFileId, FileRange, change::ChangeWithProcMacros, db::ExpandDatabase, files::FilePosition, proc_macro::{ ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, ProcMacrosBuilder, }, quote, tt::{Leaf, TokenTree, TopSubtree, TopSubtreeBuilder, TtElement, TtIter}, }; use intern::{Symbol, sym}; use paths::AbsPathBuf; use span::{Edition, FileId, Span}; use stdx::itertools::Itertools; use test_utils::{ CURSOR_MARKER, ESCAPED_CURSOR_MARKER, Fixture, FixtureWithProjectMeta, MiniCore, RangeOrOffset, extract_range_or_offset, }; use triomphe::Arc; pub const WORKSPACE: base_db::SourceRootId = base_db::SourceRootId(0); /// A trait for setting up test databases from fixture strings. /// /// Fixtures are strings containing Rust source code with optional metadata that describe /// a project setup. This is the primary way to write tests for rust-analyzer without /// having to depend on the entire sysroot. /// /// # Fixture Syntax /// /// ## Basic Structure /// /// A fixture without metadata is parsed into a single source file (`/main.rs`). /// Metadata is added after a `//-` comment prefix. /// /// ```text /// //- /main.rs /// fn main() { /// println!("Hello"); /// } /// ``` /// /// Note that the fixture syntax is optional and can be omitted if the test only requires /// a simple single file. /// /// ## File Metadata /// /// Each file can have the following metadata after `//-`: /// /// - **Path** (required): Must start with `/`, e.g., `/main.rs`, `/lib.rs`, `/foo/bar.rs` /// - **`crate:`**: Defines a new crate with this file as its root /// - Optional version: `crate:foo@0.1.0,https://example.com/repo.git` /// - **`deps:,`**: Dependencies (requires `crate:`) /// - **`extern-prelude:,`**: Limits extern prelude to specified crates /// - **`edition:`**: Rust edition (2015, 2018, 2021, 2024). Defaults to current. /// - **`cfg:=,`**: Configuration options, e.g., `cfg:test,feature="foo"` /// - **`env:=`**: Environment variables /// - **`crate-attr:`**: Crate-level attributes, e.g., `crate-attr:no_std` /// - **`new_source_root:local|library`**: Starts a new source root /// - **`library`**: Marks crate as external library (not workspace member) /// /// ## Global Meta (must appear at the top, in order) /// /// - **`//- toolchain: nightly|stable`**: Sets the Rust toolchain (default: stable) /// - **`//- target_data_layout: `**: LLVM data layout string /// - **`//- target_arch: `**: Target architecture (default: x86_64) /// - **`//- proc_macros: ,`**: Enables predefined test proc macros /// - **`//- minicore: , `**: Includes subset of libcore /// /// ## Cursor Markers /// /// Use `$0` to mark cursor position(s) in the fixture: /// - Single `$0`: marks a position (use with [`with_position`](Self::with_position)) /// - Two `$0` markers: marks a range (use with [`with_range`](Self::with_range)) /// - Escape as `\$0` if you need a literal `$0` /// /// # Examples /// /// ## Single file with cursor position /// ```text /// r#" /// fn main() { /// let x$0 = 42; /// } /// "# /// ``` /// /// ## Multiple crates with dependencies /// ```text /// r#" /// //- /main.rs crate:main deps:helper /// use helper::greet; /// fn main() { greet(); } /// /// //- /lib.rs crate:helper /// pub fn greet() {} /// "# /// ``` /// /// ## Using minicore for lang items /// ```text /// r#" /// //- minicore: option, result, iterator /// //- /main.rs /// fn foo() -> Option { Some(42) } /// "# /// ``` /// /// The available minicore flags are listed at the top of crates\test-utils\src\minicore.rs. /// /// ## Using test proc macros /// ```text /// r#" /// //- proc_macros: identity, mirror /// //- /main.rs crate:main deps:proc_macros /// use proc_macros::identity; /// /// #[identity] /// fn foo() {} /// "# /// ``` /// /// Available proc macros: `identity` (attr), `DeriveIdentity` (derive), `input_replace` (attr), /// `mirror` (bang), `shorten` (bang) pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { /// See the trait documentation for more information on fixtures. #[track_caller] fn with_single_file( #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (Self, EditionedFileId) { let mut db = Self::default(); let fixture = ChangeFixture::parse(ra_fixture); fixture.change.apply(&mut db); assert_eq!(fixture.files.len(), 1, "Multiple file found in the fixture"); let file = EditionedFileId::from_span_guess_origin(&db, fixture.files[0]); (db, file) } /// See the trait documentation for more information on fixtures. #[track_caller] fn with_many_files( #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (Self, Vec) { let mut db = Self::default(); let fixture = ChangeFixture::parse(ra_fixture); fixture.change.apply(&mut db); assert!(fixture.file_position.is_none()); let files = fixture .files .into_iter() .map(|file| EditionedFileId::from_span_guess_origin(&db, file)) .collect(); (db, files) } /// See the trait documentation for more information on fixtures. #[track_caller] fn with_files(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> Self { let mut db = Self::default(); let fixture = ChangeFixture::parse(ra_fixture); fixture.change.apply(&mut db); assert!(fixture.file_position.is_none()); db } /// See the trait documentation for more information on fixtures. #[track_caller] fn with_files_extra_proc_macros( #[rust_analyzer::rust_fixture] ra_fixture: &str, proc_macros: Vec<(String, ProcMacro)>, ) -> Self { let mut db = Self::default(); let fixture = ChangeFixture::parse_with_proc_macros(ra_fixture, MiniCore::RAW_SOURCE, proc_macros); fixture.change.apply(&mut db); assert!(fixture.file_position.is_none()); db } /// See the trait documentation for more information on fixtures. #[track_caller] fn with_position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Self, FilePosition) { let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); let offset = range_or_offset.expect_offset(); (db, FilePosition { file_id, offset }) } /// See the trait documentation for more information on fixtures. #[track_caller] fn with_range(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Self, FileRange) { let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); let range = range_or_offset.expect_range(); (db, FileRange { file_id, range }) } /// See the trait documentation for more information on fixtures. #[track_caller] fn with_range_or_offset( #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (Self, EditionedFileId, RangeOrOffset) { let mut db = Self::default(); let fixture = ChangeFixture::parse(ra_fixture); fixture.change.apply(&mut db); let (file_id, range_or_offset) = fixture .file_position .expect("Could not find file position in fixture. Did you forget to add an `$0`?"); let file_id = EditionedFileId::from_span_guess_origin(&db, file_id); (db, file_id, range_or_offset) } fn test_crate(&self) -> Crate { self.all_crates().iter().copied().find(|&krate| !krate.data(self).origin.is_lang()).unwrap() } } impl WithFixture for DB {} pub struct ChangeFixture { pub file_position: Option<(span::EditionedFileId, RangeOrOffset)>, pub file_lines: Vec, pub files: Vec, pub change: ChangeWithProcMacros, pub sysroot_files: Vec, } const SOURCE_ROOT_PREFIX: &str = "/"; impl ChangeFixture { pub fn parse(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> ChangeFixture { Self::parse_with_proc_macros(ra_fixture, MiniCore::RAW_SOURCE, Vec::new()) } pub fn parse_with_proc_macros( #[rust_analyzer::rust_fixture] ra_fixture: &str, minicore_raw: &str, mut proc_macro_defs: Vec<(String, ProcMacro)>, ) -> ChangeFixture { let FixtureWithProjectMeta { fixture, mini_core, proc_macro_names, toolchain, target_data_layout, target_arch, } = FixtureWithProjectMeta::parse(ra_fixture); let target_data_layout = target_data_layout.into(); let target_arch = parse_target_arch(&target_arch); let target = Ok(TargetData { arch: target_arch, data_layout: target_data_layout }); let toolchain = Some({ let channel = toolchain.as_deref().unwrap_or("stable"); Version::parse(&format!("1.76.0-{channel}")).unwrap() }); let mut source_change = FileChange::default(); let mut files = Vec::new(); let mut sysroot_files = Vec::new(); let mut file_lines = Vec::new(); let mut crate_graph = CrateGraphBuilder::default(); let mut crates = FxIndexMap::default(); let mut crate_deps = Vec::new(); let mut default_crate_root: Option = None; let mut default_edition = Edition::CURRENT; let mut default_cfg = CfgOptions::default(); let mut default_env = Env::from_iter([( String::from("__ra_is_test_fixture"), String::from("__ra_is_test_fixture"), )]); let mut file_set = FileSet::default(); let mut current_source_root_kind = SourceRootKind::Local; let mut file_id = FileId::from_raw(0); let mut roots = Vec::new(); let mut file_position = None; let crate_ws_data = Arc::new(CrateWorkspaceData { target, toolchain }); // FIXME: This is less than ideal let proc_macro_cwd = Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())); for entry in fixture { file_lines.push(entry.line); let mut range_or_offset = None; let text = if entry.text.contains(CURSOR_MARKER) { if entry.text.contains(ESCAPED_CURSOR_MARKER) { entry.text.replace(ESCAPED_CURSOR_MARKER, CURSOR_MARKER) } else { let (roo, text) = extract_range_or_offset(&entry.text); assert!(file_position.is_none()); range_or_offset = Some(roo); text } } else { entry.text.as_str().into() }; let meta = FileMeta::from_fixture(entry, current_source_root_kind); if let Some(range_or_offset) = range_or_offset { file_position = Some((span::EditionedFileId::new(file_id, meta.edition), range_or_offset)); } assert!(meta.path.starts_with(SOURCE_ROOT_PREFIX)); if !meta.deps.is_empty() { assert!(meta.krate.is_some(), "can't specify deps without naming the crate") } if let Some(kind) = meta.introduce_new_source_root { assert!( meta.krate.is_some(), "new_source_root meta doesn't make sense without crate meta" ); let prev_kind = mem::replace(&mut current_source_root_kind, kind); let prev_root = match prev_kind { SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)), SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)), }; roots.push(prev_root); } if let Some((krate, origin, version)) = meta.krate { let crate_name = CrateName::normalize_dashes(&krate); let crate_id = crate_graph.add_crate_root( file_id, meta.edition, Some(crate_name.clone().into()), version, meta.cfg.clone(), Some(meta.cfg), meta.env, origin, meta.crate_attrs, false, proc_macro_cwd.clone(), crate_ws_data.clone(), ); let prev = crates.insert(crate_name.clone(), crate_id); assert!(prev.is_none(), "multiple crates with same name: {crate_name}"); for dep in meta.deps { let prelude = match &meta.extern_prelude { Some(v) => v.contains(&dep), None => true, }; let dep = CrateName::normalize_dashes(&dep); crate_deps.push((crate_name.clone(), dep, prelude)) } } else if meta.path == "/main.rs" || meta.path == "/lib.rs" { assert!(default_crate_root.is_none()); default_crate_root = Some(file_id); default_edition = meta.edition; default_cfg.append(meta.cfg); default_env.extend_from_other(&meta.env); } source_change.change_file(file_id, Some(text)); let path = VfsPath::new_virtual_path(meta.path); file_set.insert(file_id, path); files.push(span::EditionedFileId::new(file_id, meta.edition)); file_id = FileId::from_raw(file_id.index() + 1); } let mini_core = mini_core.map(|mini_core| { let core_file = file_id; file_id = FileId::from_raw(file_id.index() + 1); let mut fs = FileSet::default(); fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_owned())); roots.push(SourceRoot::new_library(fs)); sysroot_files.push(core_file); source_change.change_file(core_file, Some(mini_core.source_code(minicore_raw))); let core_crate = crate_graph.add_crate_root( core_file, Edition::CURRENT, Some(CrateDisplayName::from_canonical_name("core")), None, Default::default(), Default::default(), Env::from_iter([( String::from("__ra_is_test_fixture"), String::from("__ra_is_test_fixture"), )]), CrateOrigin::Lang(LangCrateOrigin::Core), Vec::new(), false, proc_macro_cwd.clone(), crate_ws_data.clone(), ); ( move || { DependencyBuilder::with_prelude( CrateName::new("core").unwrap(), core_crate, true, true, ) }, core_crate, ) }); if crates.is_empty() { let crate_root = default_crate_root .expect("missing default crate root, specify a main.rs or lib.rs"); let root = crate_graph.add_crate_root( crate_root, default_edition, Some(CrateName::new("ra_test_fixture").unwrap().into()), None, default_cfg.clone(), Some(default_cfg), default_env, CrateOrigin::Local { repo: None, name: None }, Vec::new(), false, proc_macro_cwd.clone(), crate_ws_data.clone(), ); if let Some((mini_core, _)) = mini_core { crate_graph.add_dep(root, mini_core()).unwrap(); } } else { // Insert minicore first to match with `project-model::workspace` if let Some((mini_core, core_crate)) = mini_core { let all_crates = crate_graph.iter().collect::>(); for krate in all_crates { if krate == core_crate { continue; } crate_graph.add_dep(krate, mini_core()).unwrap(); } } for (from, to, prelude) in crate_deps { let from_id = crates[&from]; let to_id = crates[&to]; let sysroot = crate_graph[to_id].basic.origin.is_lang(); crate_graph .add_dep( from_id, DependencyBuilder::with_prelude(to.clone(), to_id, prelude, sysroot), ) .unwrap(); } } let mut proc_macros = ProcMacrosBuilder::default(); if !proc_macro_names.is_empty() { let proc_lib_file = file_id; proc_macro_defs.extend(default_test_proc_macros()); let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macro_defs); let mut fs = FileSet::default(); fs.insert( proc_lib_file, VfsPath::new_virtual_path("/sysroot/proc_macros/lib.rs".to_owned()), ); roots.push(SourceRoot::new_library(fs)); sysroot_files.push(proc_lib_file); source_change.change_file(proc_lib_file, Some(source)); let all_crates = crate_graph.iter().collect::>(); let proc_macros_crate = crate_graph.add_crate_root( proc_lib_file, Edition::CURRENT, Some(CrateDisplayName::from_canonical_name("proc_macros")), None, Default::default(), Default::default(), Env::from_iter([( String::from("__ra_is_test_fixture"), String::from("__ra_is_test_fixture"), )]), CrateOrigin::Local { repo: None, name: None }, Vec::new(), true, proc_macro_cwd, crate_ws_data, ); proc_macros.insert(proc_macros_crate, Ok(proc_macro)); for krate in all_crates { crate_graph .add_dep( krate, DependencyBuilder::new( CrateName::new("proc_macros").unwrap(), proc_macros_crate, ), ) .unwrap(); } } let _ = file_id; let root = match current_source_root_kind { SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)), SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)), }; roots.push(root); let mut change = ChangeWithProcMacros { source_change, proc_macros: Some(proc_macros) }; change.source_change.set_roots(roots); change.source_change.set_crate_graph(crate_graph); ChangeFixture { file_position, file_lines, files, change, sysroot_files } } } fn parse_target_arch(arch: &str) -> base_db::target::Arch { use base_db::target::Arch::*; match arch { "wasm32" => Wasm32, "wasm64" => Wasm64, _ => Other, } } fn default_test_proc_macros() -> Box<[(String, ProcMacro)]> { Box::new([ ( r#" #[proc_macro_attribute] pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream { item } "# .into(), ProcMacro { name: Symbol::intern("identity"), kind: ProcMacroKind::Attr, expander: sync::Arc::new(IdentityProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro_derive(DeriveIdentity)] pub fn derive_identity(item: TokenStream) -> TokenStream { item } "# .into(), ProcMacro { name: Symbol::intern("DeriveIdentity"), kind: ProcMacroKind::CustomDerive, expander: sync::Arc::new(IdentityProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro_attribute] pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream { attr } "# .into(), ProcMacro { name: Symbol::intern("input_replace"), kind: ProcMacroKind::Attr, expander: sync::Arc::new(AttributeInputReplaceProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro] pub fn mirror(input: TokenStream) -> TokenStream { input } "# .into(), ProcMacro { name: Symbol::intern("mirror"), kind: ProcMacroKind::Bang, expander: sync::Arc::new(MirrorProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro] pub fn shorten(input: TokenStream) -> TokenStream { loop {} } "# .into(), ProcMacro { name: Symbol::intern("shorten"), kind: ProcMacroKind::Bang, expander: sync::Arc::new(ShortenProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro_attribute] pub fn issue_18089(_attr: TokenStream, _item: TokenStream) -> TokenStream { loop {} } "# .into(), ProcMacro { name: Symbol::intern("issue_18089"), kind: ProcMacroKind::Attr, expander: sync::Arc::new(Issue18089ProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro_attribute] pub fn issue_18840(_attr: TokenStream, _item: TokenStream) -> TokenStream { loop {} } "# .into(), ProcMacro { name: Symbol::intern("issue_18840"), kind: ProcMacroKind::Attr, expander: sync::Arc::new(Issue18840ProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro] pub fn issue_17479(input: TokenStream) -> TokenStream { input } "# .into(), ProcMacro { name: Symbol::intern("issue_17479"), kind: ProcMacroKind::Bang, expander: sync::Arc::new(Issue17479ProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro_attribute] pub fn issue_18898(_attr: TokenStream, input: TokenStream) -> TokenStream { input } "# .into(), ProcMacro { name: Symbol::intern("issue_18898"), kind: ProcMacroKind::Bang, expander: sync::Arc::new(Issue18898ProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro_attribute] pub fn disallow_cfg(_attr: TokenStream, input: TokenStream) -> TokenStream { input } "# .into(), ProcMacro { name: Symbol::intern("disallow_cfg"), kind: ProcMacroKind::Attr, expander: sync::Arc::new(DisallowCfgProcMacroExpander), disabled: false, }, ), ( r#" #[proc_macro_attribute] pub fn generate_suffixed_type(_attr: TokenStream, input: TokenStream) -> TokenStream { input } "# .into(), ProcMacro { name: Symbol::intern("generate_suffixed_type"), kind: ProcMacroKind::Attr, expander: sync::Arc::new(GenerateSuffixedTypeProcMacroExpander), disabled: false, }, ), ]) } fn filter_test_proc_macros( proc_macro_names: &[String], proc_macro_defs: Vec<(String, ProcMacro)>, ) -> (Vec, String) { // The source here is only required so that paths to the macros exist and are resolvable. let mut source = String::new(); let mut proc_macros = Vec::new(); for (c, p) in proc_macro_defs { if !proc_macro_names.iter().any(|name| name == &stdx::to_lower_snake_case(p.name.as_str())) { continue; } proc_macros.push(p); source += &c; } (proc_macros, source) } #[derive(Debug, Clone, Copy)] enum SourceRootKind { Local, Library, } #[derive(Debug)] struct FileMeta { path: String, krate: Option<(String, CrateOrigin, Option)>, deps: Vec, extern_prelude: Option>, cfg: CfgOptions, edition: Edition, env: Env, crate_attrs: Vec, introduce_new_source_root: Option, } impl FileMeta { fn from_fixture(f: Fixture, current_source_root_kind: SourceRootKind) -> Self { let mut cfg = CfgOptions::default(); for (k, v) in f.cfgs { if let Some(v) = v { cfg.insert_key_value(Symbol::intern(&k), Symbol::intern(&v)); } else { cfg.insert_atom(Symbol::intern(&k)); } } let introduce_new_source_root = f.introduce_new_source_root.map(|kind| match &*kind { "local" => SourceRootKind::Local, "library" => SourceRootKind::Library, invalid => panic!("invalid source root kind '{invalid}'"), }); let current_source_root_kind = introduce_new_source_root.unwrap_or(current_source_root_kind); let deps = f.deps; Self { path: f.path, krate: f.krate.map(|it| parse_crate(it, current_source_root_kind, f.library)), extern_prelude: f.extern_prelude, deps, cfg, edition: f.edition.map_or(Edition::CURRENT, |v| Edition::from_str(&v).unwrap()), env: f.env.into_iter().collect(), crate_attrs: f.crate_attrs, introduce_new_source_root, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ForceNoneLangOrigin { Yes, No, } fn parse_crate( crate_str: String, current_source_root_kind: SourceRootKind, explicit_non_workspace_member: bool, ) -> (String, CrateOrigin, Option) { let (crate_str, force_non_lang_origin) = if let Some(s) = crate_str.strip_prefix("r#") { (s.to_owned(), ForceNoneLangOrigin::Yes) } else { (crate_str, ForceNoneLangOrigin::No) }; // syntax: // "my_awesome_crate" // "my_awesome_crate@0.0.1,http://example.com" let (name, repo, version) = if let Some((name, remain)) = crate_str.split_once('@') { let (version, repo) = remain.split_once(',').expect("crate meta: found '@' without version and url"); (name.to_owned(), Some(repo.to_owned()), Some(version.to_owned())) } else { (crate_str, None, None) }; let non_workspace_member = explicit_non_workspace_member || matches!(current_source_root_kind, SourceRootKind::Library); let origin = if force_non_lang_origin == ForceNoneLangOrigin::Yes { let name = Symbol::intern(&name); if non_workspace_member { CrateOrigin::Library { repo, name } } else { CrateOrigin::Local { repo, name: Some(name) } } } else { match LangCrateOrigin::from(&*name) { LangCrateOrigin::Other => { let name = Symbol::intern(&name); if non_workspace_member { CrateOrigin::Library { repo, name } } else { CrateOrigin::Local { repo, name: Some(name) } } } origin => CrateOrigin::Lang(origin), } }; (name, origin, version) } // Identity mapping #[derive(Debug)] struct IdentityProcMacroExpander; impl ProcMacroExpander for IdentityProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, _: Span, _: Span, _: Span, _: String, ) -> Result { Ok(subtree.clone()) } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } } // Expands to a macro_rules! macro, for issue #18089. #[derive(Debug)] struct Issue18089ProcMacroExpander; impl ProcMacroExpander for Issue18089ProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, _: Span, call_site: Span, _: Span, _: String, ) -> Result { let Some(tt::TtElement::Leaf(macro_name)) = subtree.iter().nth(1) else { return Err(ProcMacroExpansionError::Panic("incorrect input".to_owned())); }; Ok(quote! { call_site => #[macro_export] macro_rules! my_macro___ { ($($token:tt)*) => {{ }}; } pub use my_macro___ as #macro_name; #subtree }) } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } } // Pastes the attribute input as its output #[derive(Debug)] struct AttributeInputReplaceProcMacroExpander; impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, _: &TopSubtree, attrs: Option<&TopSubtree>, _: &Env, _: Span, _: Span, _: Span, _: String, ) -> Result { attrs .cloned() .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into())) } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } } #[derive(Debug)] struct Issue18840ProcMacroExpander; impl ProcMacroExpander for Issue18840ProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, fn_: &TopSubtree, _: Option<&TopSubtree>, _: &Env, def_site: Span, _: Span, _: Span, _: String, ) -> Result { // Input: // ``` // #[issue_18840] // fn foo() { let loop {} } // ``` // The span that was created by the fixup infra. 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. result.set_top_subtree_delimiter_span(tt::DelimSpan::from_single(def_site)); Ok(result) } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } } #[derive(Debug)] struct MirrorProcMacroExpander; impl ProcMacroExpander for MirrorProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, input: &TopSubtree, _: Option<&TopSubtree>, _: &Env, _: Span, _: Span, _: Span, _: String, ) -> Result { fn traverse(builder: &mut TopSubtreeBuilder, iter: TtIter<'_>) { for tt in iter.collect_vec().into_iter().rev() { match tt { TtElement::Leaf(leaf) => builder.push(leaf.clone()), TtElement::Subtree(subtree, subtree_iter) => { builder.open(subtree.delimiter.kind, subtree.delimiter.open); traverse(builder, subtree_iter); builder.close(subtree.delimiter.close); } } } } let mut builder = TopSubtreeBuilder::new(input.top_subtree().delimiter); traverse(&mut builder, input.iter()); Ok(builder.build()) } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } } // Replaces every literal with an empty string literal and every identifier with its first letter, // but retains all tokens' span. Useful for testing we don't assume token hasn't been modified by // macros even if it retains its span. #[derive(Debug)] struct ShortenProcMacroExpander; impl ProcMacroExpander for ShortenProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, input: &TopSubtree, _: Option<&TopSubtree>, _: &Env, _: Span, _: Span, _: Span, _: String, ) -> Result { 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(result); fn modify_leaf(leaf: &mut Leaf) { match leaf { Leaf::Literal(it) => { // XXX Currently replaces any literals with an empty string, but supporting // "shortening" other literals would be nice. it.text_and_suffix = Symbol::empty(); it.suffix_len = 0; } Leaf::Punct(_) => {} Leaf::Ident(it) => { it.sym = Symbol::intern(&it.sym.as_str().chars().take(1).collect::()); } } } } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } } // Reads ident type within string quotes, for issue #17479. #[derive(Debug)] struct Issue17479ProcMacroExpander; impl ProcMacroExpander for Issue17479ProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, _: Span, _: Span, _: Span, _: String, ) -> Result { 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 = Symbol::intern(lit.text()); let span = lit.span; Ok(quote! { span => #symbol() }) } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } } // Reads ident type within string quotes, for issue #17479. #[derive(Debug)] struct Issue18898ProcMacroExpander; impl ProcMacroExpander for Issue18898ProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, def_site: Span, _: Span, _: Span, _: String, ) -> Result { let span = subtree .token_trees() .last_span() .ok_or_else(|| ProcMacroExpansionError::Panic("malformed input".to_owned()))?; let overly_long_subtree = quote! {span => { let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; let a = 5; } }; Ok(quote! { def_site => fn foo() { #overly_long_subtree } }) } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } } // Reads ident type within string quotes, for issue #17479. #[derive(Debug)] struct DisallowCfgProcMacroExpander; impl ProcMacroExpander for DisallowCfgProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, subtree: &TopSubtree, _: Option<&TopSubtree>, _: &Env, _: Span, _: Span, _: Span, _: String, ) -> Result { 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) { return Err(ProcMacroExpansionError::Panic( "cfg or cfg_attr found in DisallowCfgProcMacroExpander".to_owned(), )); } } Ok(subtree.clone()) } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } } // Generates a new type by adding a suffix to the original name #[derive(Debug)] struct GenerateSuffixedTypeProcMacroExpander; impl ProcMacroExpander for GenerateSuffixedTypeProcMacroExpander { fn expand( &self, _: &dyn ExpandDatabase, subtree: &TopSubtree, _attrs: Option<&TopSubtree>, _env: &Env, _def_site: Span, call_site: Span, _mixed_site: Span, _current_dir: String, ) -> Result { 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 Some(TtElement::Leaf(tt::Leaf::Ident(ident))) = iter.next() else { return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); }; ident } "enum" => { 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 } _ => { return Err(ProcMacroExpansionError::Panic("incorrect Input".into())); } }; let generated_ident = tt::Ident { sym: Symbol::intern(&format!("{}Suffix", ident.sym)), span: ident.span, is_raw: tt::IdentIsRaw::No, }; let ret = quote! { call_site => #subtree struct #generated_ident; }; Ok(ret) } fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool { other.type_id() == TypeId::of::() } }