test_fixture/
lib.rs

1//! A set of high-level utility fixture methods to use in tests.
2
3#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
4
5#[cfg(feature = "in-rust-tree")]
6extern crate rustc_driver as _;
7
8use std::{any::TypeId, mem, str::FromStr, sync};
9
10use base_db::target::TargetData;
11use base_db::{
12    Crate, CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CrateWorkspaceData,
13    DependencyBuilder, Env, FileChange, FileSet, FxIndexMap, LangCrateOrigin, SourceDatabase,
14    SourceRoot, Version, VfsPath,
15};
16use cfg::CfgOptions;
17use hir_expand::{
18    EditionedFileId, FileRange,
19    change::ChangeWithProcMacros,
20    db::ExpandDatabase,
21    files::FilePosition,
22    proc_macro::{
23        ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, ProcMacrosBuilder,
24    },
25    quote,
26    tt::{Leaf, TokenTree, TopSubtree, TopSubtreeBuilder, TtElement, TtIter},
27};
28use intern::{Symbol, sym};
29use paths::AbsPathBuf;
30use span::{Edition, FileId, Span};
31use stdx::itertools::Itertools;
32use test_utils::{
33    CURSOR_MARKER, ESCAPED_CURSOR_MARKER, Fixture, FixtureWithProjectMeta, MiniCore, RangeOrOffset,
34    extract_range_or_offset,
35};
36use triomphe::Arc;
37
38pub const WORKSPACE: base_db::SourceRootId = base_db::SourceRootId(0);
39
40/// A trait for setting up test databases from fixture strings.
41///
42/// Fixtures are strings containing Rust source code with optional metadata that describe
43/// a project setup. This is the primary way to write tests for rust-analyzer without
44/// having to depend on the entire sysroot.
45///
46/// # Fixture Syntax
47///
48/// ## Basic Structure
49///
50/// A fixture without metadata is parsed into a single source file (`/main.rs`).
51/// Metadata is added after a `//-` comment prefix.
52///
53/// ```text
54/// //- /main.rs
55/// fn main() {
56///     println!("Hello");
57/// }
58/// ```
59///
60/// Note that the fixture syntax is optional and can be omitted if the test only requires
61/// a simple single file.
62///
63/// ## File Metadata
64///
65/// Each file can have the following metadata after `//-`:
66///
67/// - **Path** (required): Must start with `/`, e.g., `/main.rs`, `/lib.rs`, `/foo/bar.rs`
68/// - **`crate:<name>`**: Defines a new crate with this file as its root
69///   - Optional version: `crate:foo@0.1.0,https://example.com/repo.git`
70/// - **`deps:<crate1>,<crate2>`**: Dependencies (requires `crate:`)
71/// - **`extern-prelude:<crate1>,<crate2>`**: Limits extern prelude to specified crates
72/// - **`edition:<year>`**: Rust edition (2015, 2018, 2021, 2024). Defaults to current.
73/// - **`cfg:<key>=<value>,<flag>`**: Configuration options, e.g., `cfg:test,feature="foo"`
74/// - **`env:<KEY>=<value>`**: Environment variables
75/// - **`crate-attr:<attr>`**: Crate-level attributes, e.g., `crate-attr:no_std`
76/// - **`new_source_root:local|library`**: Starts a new source root
77/// - **`library`**: Marks crate as external library (not workspace member)
78///
79/// ## Global Meta (must appear at the top, in order)
80///
81/// - **`//- toolchain: nightly|stable`**: Sets the Rust toolchain (default: stable)
82/// - **`//- target_data_layout: <layout>`**: LLVM data layout string
83/// - **`//- target_arch: <arch>`**: Target architecture (default: x86_64)
84/// - **`//- proc_macros: <name1>,<name2>`**: Enables predefined test proc macros
85/// - **`//- minicore: <flag1>, <flag2>`**: Includes subset of libcore
86///
87/// ## Cursor Markers
88///
89/// Use `$0` to mark cursor position(s) in the fixture:
90/// - Single `$0`: marks a position (use with [`with_position`](Self::with_position))
91/// - Two `$0` markers: marks a range (use with [`with_range`](Self::with_range))
92/// - Escape as `\$0` if you need a literal `$0`
93///
94/// # Examples
95///
96/// ## Single file with cursor position
97/// ```text
98/// r#"
99/// fn main() {
100///     let x$0 = 42;
101/// }
102/// "#
103/// ```
104///
105/// ## Multiple crates with dependencies
106/// ```text
107/// r#"
108/// //- /main.rs crate:main deps:helper
109/// use helper::greet;
110/// fn main() { greet(); }
111///
112/// //- /lib.rs crate:helper
113/// pub fn greet() {}
114/// "#
115/// ```
116///
117/// ## Using minicore for lang items
118/// ```text
119/// r#"
120/// //- minicore: option, result, iterator
121/// //- /main.rs
122/// fn foo() -> Option<i32> { Some(42) }
123/// "#
124/// ```
125///
126/// The available minicore flags are listed at the top of crates\test-utils\src\minicore.rs.
127///
128/// ## Using test proc macros
129/// ```text
130/// r#"
131/// //- proc_macros: identity, mirror
132/// //- /main.rs crate:main deps:proc_macros
133/// use proc_macros::identity;
134///
135/// #[identity]
136/// fn foo() {}
137/// "#
138/// ```
139///
140/// Available proc macros: `identity` (attr), `DeriveIdentity` (derive), `input_replace` (attr),
141/// `mirror` (bang), `shorten` (bang)
142pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static {
143    /// See the trait documentation for more information on fixtures.
144    #[track_caller]
145    fn with_single_file(
146        #[rust_analyzer::rust_fixture] ra_fixture: &str,
147    ) -> (Self, EditionedFileId) {
148        let mut db = Self::default();
149        let fixture = ChangeFixture::parse(ra_fixture);
150        fixture.change.apply(&mut db);
151        assert_eq!(fixture.files.len(), 1, "Multiple file found in the fixture");
152        let file = EditionedFileId::from_span_guess_origin(&db, fixture.files[0]);
153        (db, file)
154    }
155
156    /// See the trait documentation for more information on fixtures.
157    #[track_caller]
158    fn with_many_files(
159        #[rust_analyzer::rust_fixture] ra_fixture: &str,
160    ) -> (Self, Vec<EditionedFileId>) {
161        let mut db = Self::default();
162        let fixture = ChangeFixture::parse(ra_fixture);
163        fixture.change.apply(&mut db);
164        assert!(fixture.file_position.is_none());
165        let files = fixture
166            .files
167            .into_iter()
168            .map(|file| EditionedFileId::from_span_guess_origin(&db, file))
169            .collect();
170        (db, files)
171    }
172
173    /// See the trait documentation for more information on fixtures.
174    #[track_caller]
175    fn with_files(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> Self {
176        let mut db = Self::default();
177        let fixture = ChangeFixture::parse(ra_fixture);
178        fixture.change.apply(&mut db);
179        assert!(fixture.file_position.is_none());
180        db
181    }
182
183    /// See the trait documentation for more information on fixtures.
184    #[track_caller]
185    fn with_files_extra_proc_macros(
186        #[rust_analyzer::rust_fixture] ra_fixture: &str,
187        proc_macros: Vec<(String, ProcMacro)>,
188    ) -> Self {
189        let mut db = Self::default();
190        let fixture =
191            ChangeFixture::parse_with_proc_macros(ra_fixture, MiniCore::RAW_SOURCE, proc_macros);
192        fixture.change.apply(&mut db);
193        assert!(fixture.file_position.is_none());
194        db
195    }
196
197    /// See the trait documentation for more information on fixtures.
198    #[track_caller]
199    fn with_position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Self, FilePosition) {
200        let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
201        let offset = range_or_offset.expect_offset();
202        (db, FilePosition { file_id, offset })
203    }
204
205    /// See the trait documentation for more information on fixtures.
206    #[track_caller]
207    fn with_range(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Self, FileRange) {
208        let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
209        let range = range_or_offset.expect_range();
210        (db, FileRange { file_id, range })
211    }
212
213    /// See the trait documentation for more information on fixtures.
214    #[track_caller]
215    fn with_range_or_offset(
216        #[rust_analyzer::rust_fixture] ra_fixture: &str,
217    ) -> (Self, EditionedFileId, RangeOrOffset) {
218        let mut db = Self::default();
219        let fixture = ChangeFixture::parse(ra_fixture);
220        fixture.change.apply(&mut db);
221
222        let (file_id, range_or_offset) = fixture
223            .file_position
224            .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
225        let file_id = EditionedFileId::from_span_guess_origin(&db, file_id);
226        (db, file_id, range_or_offset)
227    }
228
229    fn test_crate(&self) -> Crate {
230        self.all_crates().iter().copied().find(|&krate| !krate.data(self).origin.is_lang()).unwrap()
231    }
232}
233
234impl<DB: ExpandDatabase + SourceDatabase + Default + 'static> WithFixture for DB {}
235
236pub struct ChangeFixture {
237    pub file_position: Option<(span::EditionedFileId, RangeOrOffset)>,
238    pub file_lines: Vec<usize>,
239    pub files: Vec<span::EditionedFileId>,
240    pub change: ChangeWithProcMacros,
241    pub sysroot_files: Vec<FileId>,
242}
243
244const SOURCE_ROOT_PREFIX: &str = "/";
245
246impl ChangeFixture {
247    pub fn parse(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> ChangeFixture {
248        Self::parse_with_proc_macros(ra_fixture, MiniCore::RAW_SOURCE, Vec::new())
249    }
250
251    pub fn parse_with_proc_macros(
252        #[rust_analyzer::rust_fixture] ra_fixture: &str,
253        minicore_raw: &str,
254        mut proc_macro_defs: Vec<(String, ProcMacro)>,
255    ) -> ChangeFixture {
256        let FixtureWithProjectMeta {
257            fixture,
258            mini_core,
259            proc_macro_names,
260            toolchain,
261            target_data_layout,
262            target_arch,
263        } = FixtureWithProjectMeta::parse(ra_fixture);
264        let target_data_layout = target_data_layout.into();
265        let target_arch = parse_target_arch(&target_arch);
266        let target = Ok(TargetData { arch: target_arch, data_layout: target_data_layout });
267        let toolchain = Some({
268            let channel = toolchain.as_deref().unwrap_or("stable");
269            Version::parse(&format!("1.76.0-{channel}")).unwrap()
270        });
271        let mut source_change = FileChange::default();
272
273        let mut files = Vec::new();
274        let mut sysroot_files = Vec::new();
275        let mut file_lines = Vec::new();
276        let mut crate_graph = CrateGraphBuilder::default();
277        let mut crates = FxIndexMap::default();
278        let mut crate_deps = Vec::new();
279        let mut default_crate_root: Option<FileId> = None;
280        let mut default_edition = Edition::CURRENT;
281        let mut default_cfg = CfgOptions::default();
282        let mut default_env = Env::from_iter([(
283            String::from("__ra_is_test_fixture"),
284            String::from("__ra_is_test_fixture"),
285        )]);
286
287        let mut file_set = FileSet::default();
288        let mut current_source_root_kind = SourceRootKind::Local;
289        let mut file_id = FileId::from_raw(0);
290        let mut roots = Vec::new();
291
292        let mut file_position = None;
293
294        let crate_ws_data = Arc::new(CrateWorkspaceData { target, toolchain });
295
296        // FIXME: This is less than ideal
297        let proc_macro_cwd = Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap()));
298
299        for entry in fixture {
300            file_lines.push(entry.line);
301
302            let mut range_or_offset = None;
303            let text = if entry.text.contains(CURSOR_MARKER) {
304                if entry.text.contains(ESCAPED_CURSOR_MARKER) {
305                    entry.text.replace(ESCAPED_CURSOR_MARKER, CURSOR_MARKER)
306                } else {
307                    let (roo, text) = extract_range_or_offset(&entry.text);
308                    assert!(file_position.is_none());
309                    range_or_offset = Some(roo);
310                    text
311                }
312            } else {
313                entry.text.as_str().into()
314            };
315
316            let meta = FileMeta::from_fixture(entry, current_source_root_kind);
317            if let Some(range_or_offset) = range_or_offset {
318                file_position =
319                    Some((span::EditionedFileId::new(file_id, meta.edition), range_or_offset));
320            }
321
322            assert!(meta.path.starts_with(SOURCE_ROOT_PREFIX));
323            if !meta.deps.is_empty() {
324                assert!(meta.krate.is_some(), "can't specify deps without naming the crate")
325            }
326
327            if let Some(kind) = meta.introduce_new_source_root {
328                assert!(
329                    meta.krate.is_some(),
330                    "new_source_root meta doesn't make sense without crate meta"
331                );
332                let prev_kind = mem::replace(&mut current_source_root_kind, kind);
333                let prev_root = match prev_kind {
334                    SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
335                    SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
336                };
337                roots.push(prev_root);
338            }
339
340            if let Some((krate, origin, version)) = meta.krate {
341                let crate_name = CrateName::normalize_dashes(&krate);
342                let crate_id = crate_graph.add_crate_root(
343                    file_id,
344                    meta.edition,
345                    Some(crate_name.clone().into()),
346                    version,
347                    meta.cfg.clone(),
348                    Some(meta.cfg),
349                    meta.env,
350                    origin,
351                    meta.crate_attrs,
352                    false,
353                    proc_macro_cwd.clone(),
354                    crate_ws_data.clone(),
355                );
356                let prev = crates.insert(crate_name.clone(), crate_id);
357                assert!(prev.is_none(), "multiple crates with same name: {crate_name}");
358                for dep in meta.deps {
359                    let prelude = match &meta.extern_prelude {
360                        Some(v) => v.contains(&dep),
361                        None => true,
362                    };
363                    let dep = CrateName::normalize_dashes(&dep);
364                    crate_deps.push((crate_name.clone(), dep, prelude))
365                }
366            } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
367                assert!(default_crate_root.is_none());
368                default_crate_root = Some(file_id);
369                default_edition = meta.edition;
370                default_cfg.append(meta.cfg);
371                default_env.extend_from_other(&meta.env);
372            }
373
374            source_change.change_file(file_id, Some(text));
375            let path = VfsPath::new_virtual_path(meta.path);
376            file_set.insert(file_id, path);
377            files.push(span::EditionedFileId::new(file_id, meta.edition));
378            file_id = FileId::from_raw(file_id.index() + 1);
379        }
380
381        let mini_core = mini_core.map(|mini_core| {
382            let core_file = file_id;
383            file_id = FileId::from_raw(file_id.index() + 1);
384
385            let mut fs = FileSet::default();
386            fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_owned()));
387            roots.push(SourceRoot::new_library(fs));
388
389            sysroot_files.push(core_file);
390
391            source_change.change_file(core_file, Some(mini_core.source_code(minicore_raw)));
392
393            let core_crate = crate_graph.add_crate_root(
394                core_file,
395                Edition::CURRENT,
396                Some(CrateDisplayName::from_canonical_name("core")),
397                None,
398                Default::default(),
399                Default::default(),
400                Env::from_iter([(
401                    String::from("__ra_is_test_fixture"),
402                    String::from("__ra_is_test_fixture"),
403                )]),
404                CrateOrigin::Lang(LangCrateOrigin::Core),
405                Vec::new(),
406                false,
407                proc_macro_cwd.clone(),
408                crate_ws_data.clone(),
409            );
410
411            (
412                move || {
413                    DependencyBuilder::with_prelude(
414                        CrateName::new("core").unwrap(),
415                        core_crate,
416                        true,
417                        true,
418                    )
419                },
420                core_crate,
421            )
422        });
423
424        if crates.is_empty() {
425            let crate_root = default_crate_root
426                .expect("missing default crate root, specify a main.rs or lib.rs");
427            let root = crate_graph.add_crate_root(
428                crate_root,
429                default_edition,
430                Some(CrateName::new("ra_test_fixture").unwrap().into()),
431                None,
432                default_cfg.clone(),
433                Some(default_cfg),
434                default_env,
435                CrateOrigin::Local { repo: None, name: None },
436                Vec::new(),
437                false,
438                proc_macro_cwd.clone(),
439                crate_ws_data.clone(),
440            );
441            if let Some((mini_core, _)) = mini_core {
442                crate_graph.add_dep(root, mini_core()).unwrap();
443            }
444        } else {
445            // Insert minicore first to match with `project-model::workspace`
446            if let Some((mini_core, core_crate)) = mini_core {
447                let all_crates = crate_graph.iter().collect::<Vec<_>>();
448                for krate in all_crates {
449                    if krate == core_crate {
450                        continue;
451                    }
452                    crate_graph.add_dep(krate, mini_core()).unwrap();
453                }
454            }
455
456            for (from, to, prelude) in crate_deps {
457                let from_id = crates[&from];
458                let to_id = crates[&to];
459                let sysroot = crate_graph[to_id].basic.origin.is_lang();
460                crate_graph
461                    .add_dep(
462                        from_id,
463                        DependencyBuilder::with_prelude(to.clone(), to_id, prelude, sysroot),
464                    )
465                    .unwrap();
466            }
467        }
468
469        let mut proc_macros = ProcMacrosBuilder::default();
470        if !proc_macro_names.is_empty() {
471            let proc_lib_file = file_id;
472
473            proc_macro_defs.extend(default_test_proc_macros());
474            let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macro_defs);
475            let mut fs = FileSet::default();
476            fs.insert(
477                proc_lib_file,
478                VfsPath::new_virtual_path("/sysroot/proc_macros/lib.rs".to_owned()),
479            );
480            roots.push(SourceRoot::new_library(fs));
481
482            sysroot_files.push(proc_lib_file);
483
484            source_change.change_file(proc_lib_file, Some(source));
485
486            let all_crates = crate_graph.iter().collect::<Vec<_>>();
487
488            let proc_macros_crate = crate_graph.add_crate_root(
489                proc_lib_file,
490                Edition::CURRENT,
491                Some(CrateDisplayName::from_canonical_name("proc_macros")),
492                None,
493                Default::default(),
494                Default::default(),
495                Env::from_iter([(
496                    String::from("__ra_is_test_fixture"),
497                    String::from("__ra_is_test_fixture"),
498                )]),
499                CrateOrigin::Local { repo: None, name: None },
500                Vec::new(),
501                true,
502                proc_macro_cwd,
503                crate_ws_data,
504            );
505            proc_macros.insert(proc_macros_crate, Ok(proc_macro));
506
507            for krate in all_crates {
508                crate_graph
509                    .add_dep(
510                        krate,
511                        DependencyBuilder::new(
512                            CrateName::new("proc_macros").unwrap(),
513                            proc_macros_crate,
514                        ),
515                    )
516                    .unwrap();
517            }
518        }
519
520        let _ = file_id;
521
522        let root = match current_source_root_kind {
523            SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
524            SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
525        };
526        roots.push(root);
527
528        let mut change = ChangeWithProcMacros { source_change, proc_macros: Some(proc_macros) };
529
530        change.source_change.set_roots(roots);
531        change.source_change.set_crate_graph(crate_graph);
532
533        ChangeFixture { file_position, file_lines, files, change, sysroot_files }
534    }
535}
536
537fn parse_target_arch(arch: &str) -> base_db::target::Arch {
538    use base_db::target::Arch::*;
539    match arch {
540        "wasm32" => Wasm32,
541        "wasm64" => Wasm64,
542        _ => Other,
543    }
544}
545
546fn default_test_proc_macros() -> Box<[(String, ProcMacro)]> {
547    Box::new([
548        (
549            r#"
550#[proc_macro_attribute]
551pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
552    item
553}
554"#
555            .into(),
556            ProcMacro {
557                name: Symbol::intern("identity"),
558                kind: ProcMacroKind::Attr,
559                expander: sync::Arc::new(IdentityProcMacroExpander),
560                disabled: false,
561            },
562        ),
563        (
564            r#"
565#[proc_macro_derive(DeriveIdentity)]
566pub fn derive_identity(item: TokenStream) -> TokenStream {
567    item
568}
569"#
570            .into(),
571            ProcMacro {
572                name: Symbol::intern("DeriveIdentity"),
573                kind: ProcMacroKind::CustomDerive,
574                expander: sync::Arc::new(IdentityProcMacroExpander),
575                disabled: false,
576            },
577        ),
578        (
579            r#"
580#[proc_macro_attribute]
581pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
582    attr
583}
584"#
585            .into(),
586            ProcMacro {
587                name: Symbol::intern("input_replace"),
588                kind: ProcMacroKind::Attr,
589                expander: sync::Arc::new(AttributeInputReplaceProcMacroExpander),
590                disabled: false,
591            },
592        ),
593        (
594            r#"
595#[proc_macro]
596pub fn mirror(input: TokenStream) -> TokenStream {
597    input
598}
599"#
600            .into(),
601            ProcMacro {
602                name: Symbol::intern("mirror"),
603                kind: ProcMacroKind::Bang,
604                expander: sync::Arc::new(MirrorProcMacroExpander),
605                disabled: false,
606            },
607        ),
608        (
609            r#"
610#[proc_macro]
611pub fn shorten(input: TokenStream) -> TokenStream {
612    loop {}
613}
614"#
615            .into(),
616            ProcMacro {
617                name: Symbol::intern("shorten"),
618                kind: ProcMacroKind::Bang,
619                expander: sync::Arc::new(ShortenProcMacroExpander),
620                disabled: false,
621            },
622        ),
623        (
624            r#"
625#[proc_macro_attribute]
626pub fn issue_18089(_attr: TokenStream, _item: TokenStream) -> TokenStream {
627    loop {}
628}
629"#
630            .into(),
631            ProcMacro {
632                name: Symbol::intern("issue_18089"),
633                kind: ProcMacroKind::Attr,
634                expander: sync::Arc::new(Issue18089ProcMacroExpander),
635                disabled: false,
636            },
637        ),
638        (
639            r#"
640#[proc_macro_attribute]
641pub fn issue_18840(_attr: TokenStream, _item: TokenStream) -> TokenStream {
642    loop {}
643}
644"#
645            .into(),
646            ProcMacro {
647                name: Symbol::intern("issue_18840"),
648                kind: ProcMacroKind::Attr,
649                expander: sync::Arc::new(Issue18840ProcMacroExpander),
650                disabled: false,
651            },
652        ),
653        (
654            r#"
655#[proc_macro]
656pub fn issue_17479(input: TokenStream) -> TokenStream {
657    input
658}
659"#
660            .into(),
661            ProcMacro {
662                name: Symbol::intern("issue_17479"),
663                kind: ProcMacroKind::Bang,
664                expander: sync::Arc::new(Issue17479ProcMacroExpander),
665                disabled: false,
666            },
667        ),
668        (
669            r#"
670#[proc_macro_attribute]
671pub fn issue_18898(_attr: TokenStream, input: TokenStream) -> TokenStream {
672    input
673}
674"#
675            .into(),
676            ProcMacro {
677                name: Symbol::intern("issue_18898"),
678                kind: ProcMacroKind::Bang,
679                expander: sync::Arc::new(Issue18898ProcMacroExpander),
680                disabled: false,
681            },
682        ),
683        (
684            r#"
685#[proc_macro_attribute]
686pub fn disallow_cfg(_attr: TokenStream, input: TokenStream) -> TokenStream {
687    input
688}
689"#
690            .into(),
691            ProcMacro {
692                name: Symbol::intern("disallow_cfg"),
693                kind: ProcMacroKind::Attr,
694                expander: sync::Arc::new(DisallowCfgProcMacroExpander),
695                disabled: false,
696            },
697        ),
698        (
699            r#"
700#[proc_macro_attribute]
701pub fn generate_suffixed_type(_attr: TokenStream, input: TokenStream) -> TokenStream {
702    input
703}
704"#
705            .into(),
706            ProcMacro {
707                name: Symbol::intern("generate_suffixed_type"),
708                kind: ProcMacroKind::Attr,
709                expander: sync::Arc::new(GenerateSuffixedTypeProcMacroExpander),
710                disabled: false,
711            },
712        ),
713    ])
714}
715
716fn filter_test_proc_macros(
717    proc_macro_names: &[String],
718    proc_macro_defs: Vec<(String, ProcMacro)>,
719) -> (Vec<ProcMacro>, String) {
720    // The source here is only required so that paths to the macros exist and are resolvable.
721    let mut source = String::new();
722    let mut proc_macros = Vec::new();
723
724    for (c, p) in proc_macro_defs {
725        if !proc_macro_names.iter().any(|name| name == &stdx::to_lower_snake_case(p.name.as_str()))
726        {
727            continue;
728        }
729        proc_macros.push(p);
730        source += &c;
731    }
732
733    (proc_macros, source)
734}
735
736#[derive(Debug, Clone, Copy)]
737enum SourceRootKind {
738    Local,
739    Library,
740}
741
742#[derive(Debug)]
743struct FileMeta {
744    path: String,
745    krate: Option<(String, CrateOrigin, Option<String>)>,
746    deps: Vec<String>,
747    extern_prelude: Option<Vec<String>>,
748    cfg: CfgOptions,
749    edition: Edition,
750    env: Env,
751    crate_attrs: Vec<String>,
752    introduce_new_source_root: Option<SourceRootKind>,
753}
754
755impl FileMeta {
756    fn from_fixture(f: Fixture, current_source_root_kind: SourceRootKind) -> Self {
757        let mut cfg = CfgOptions::default();
758        for (k, v) in f.cfgs {
759            if let Some(v) = v {
760                cfg.insert_key_value(Symbol::intern(&k), Symbol::intern(&v));
761            } else {
762                cfg.insert_atom(Symbol::intern(&k));
763            }
764        }
765
766        let introduce_new_source_root = f.introduce_new_source_root.map(|kind| match &*kind {
767            "local" => SourceRootKind::Local,
768            "library" => SourceRootKind::Library,
769            invalid => panic!("invalid source root kind '{invalid}'"),
770        });
771        let current_source_root_kind =
772            introduce_new_source_root.unwrap_or(current_source_root_kind);
773
774        let deps = f.deps;
775        Self {
776            path: f.path,
777            krate: f.krate.map(|it| parse_crate(it, current_source_root_kind, f.library)),
778            extern_prelude: f.extern_prelude,
779            deps,
780            cfg,
781            edition: f.edition.map_or(Edition::CURRENT, |v| Edition::from_str(&v).unwrap()),
782            env: f.env.into_iter().collect(),
783            crate_attrs: f.crate_attrs,
784            introduce_new_source_root,
785        }
786    }
787}
788
789#[derive(Debug, Clone, Copy, PartialEq, Eq)]
790enum ForceNoneLangOrigin {
791    Yes,
792    No,
793}
794
795fn parse_crate(
796    crate_str: String,
797    current_source_root_kind: SourceRootKind,
798    explicit_non_workspace_member: bool,
799) -> (String, CrateOrigin, Option<String>) {
800    let (crate_str, force_non_lang_origin) = if let Some(s) = crate_str.strip_prefix("r#") {
801        (s.to_owned(), ForceNoneLangOrigin::Yes)
802    } else {
803        (crate_str, ForceNoneLangOrigin::No)
804    };
805
806    // syntax:
807    //   "my_awesome_crate"
808    //   "my_awesome_crate@0.0.1,http://example.com"
809    let (name, repo, version) = if let Some((name, remain)) = crate_str.split_once('@') {
810        let (version, repo) =
811            remain.split_once(',').expect("crate meta: found '@' without version and url");
812        (name.to_owned(), Some(repo.to_owned()), Some(version.to_owned()))
813    } else {
814        (crate_str, None, None)
815    };
816
817    let non_workspace_member = explicit_non_workspace_member
818        || matches!(current_source_root_kind, SourceRootKind::Library);
819
820    let origin = if force_non_lang_origin == ForceNoneLangOrigin::Yes {
821        let name = Symbol::intern(&name);
822        if non_workspace_member {
823            CrateOrigin::Library { repo, name }
824        } else {
825            CrateOrigin::Local { repo, name: Some(name) }
826        }
827    } else {
828        match LangCrateOrigin::from(&*name) {
829            LangCrateOrigin::Other => {
830                let name = Symbol::intern(&name);
831                if non_workspace_member {
832                    CrateOrigin::Library { repo, name }
833                } else {
834                    CrateOrigin::Local { repo, name: Some(name) }
835                }
836            }
837            origin => CrateOrigin::Lang(origin),
838        }
839    };
840
841    (name, origin, version)
842}
843
844// Identity mapping
845#[derive(Debug)]
846struct IdentityProcMacroExpander;
847impl ProcMacroExpander for IdentityProcMacroExpander {
848    fn expand(
849        &self,
850        _: &dyn ExpandDatabase,
851        subtree: &TopSubtree,
852        _: Option<&TopSubtree>,
853        _: &Env,
854        _: Span,
855        _: Span,
856        _: Span,
857        _: String,
858    ) -> Result<TopSubtree, ProcMacroExpansionError> {
859        Ok(subtree.clone())
860    }
861
862    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
863        other.type_id() == TypeId::of::<Self>()
864    }
865}
866
867// Expands to a macro_rules! macro, for issue #18089.
868#[derive(Debug)]
869struct Issue18089ProcMacroExpander;
870impl ProcMacroExpander for Issue18089ProcMacroExpander {
871    fn expand(
872        &self,
873        _: &dyn ExpandDatabase,
874        subtree: &TopSubtree,
875        _: Option<&TopSubtree>,
876        _: &Env,
877        _: Span,
878        call_site: Span,
879        _: Span,
880        _: String,
881    ) -> Result<TopSubtree, ProcMacroExpansionError> {
882        let Some(tt::TtElement::Leaf(macro_name)) = subtree.iter().nth(1) else {
883            return Err(ProcMacroExpansionError::Panic("incorrect input".to_owned()));
884        };
885        Ok(quote! { call_site =>
886            #[macro_export]
887            macro_rules! my_macro___ {
888                ($($token:tt)*) => {{
889                }};
890            }
891
892            pub use my_macro___ as #macro_name;
893
894            #subtree
895        })
896    }
897
898    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
899        other.type_id() == TypeId::of::<Self>()
900    }
901}
902
903// Pastes the attribute input as its output
904#[derive(Debug)]
905struct AttributeInputReplaceProcMacroExpander;
906impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander {
907    fn expand(
908        &self,
909        _: &dyn ExpandDatabase,
910        _: &TopSubtree,
911        attrs: Option<&TopSubtree>,
912        _: &Env,
913        _: Span,
914        _: Span,
915        _: Span,
916        _: String,
917    ) -> Result<TopSubtree, ProcMacroExpansionError> {
918        attrs
919            .cloned()
920            .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into()))
921    }
922
923    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
924        other.type_id() == TypeId::of::<Self>()
925    }
926}
927
928#[derive(Debug)]
929struct Issue18840ProcMacroExpander;
930impl ProcMacroExpander for Issue18840ProcMacroExpander {
931    fn expand(
932        &self,
933        _: &dyn ExpandDatabase,
934        fn_: &TopSubtree,
935        _: Option<&TopSubtree>,
936        _: &Env,
937        def_site: Span,
938        _: Span,
939        _: Span,
940        _: String,
941    ) -> Result<TopSubtree, ProcMacroExpansionError> {
942        // Input:
943        // ```
944        // #[issue_18840]
945        // fn foo() { let loop {} }
946        // ```
947
948        // The span that was created by the fixup infra.
949        let mut iter = fn_.iter();
950        iter.nth(2);
951        let (_, mut fn_body) = iter.expect_subtree().unwrap();
952        let fixed_up_span = fn_body.nth(1).unwrap().first_span();
953        let mut result =
954            quote! {fixed_up_span => ::core::compile_error! { "my cool compile_error!" } };
955        // Make it so we won't remove the top subtree when reversing fixups.
956        result.set_top_subtree_delimiter_span(tt::DelimSpan::from_single(def_site));
957        Ok(result)
958    }
959
960    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
961        other.type_id() == TypeId::of::<Self>()
962    }
963}
964
965#[derive(Debug)]
966struct MirrorProcMacroExpander;
967impl ProcMacroExpander for MirrorProcMacroExpander {
968    fn expand(
969        &self,
970        _: &dyn ExpandDatabase,
971        input: &TopSubtree,
972        _: Option<&TopSubtree>,
973        _: &Env,
974        _: Span,
975        _: Span,
976        _: Span,
977        _: String,
978    ) -> Result<TopSubtree, ProcMacroExpansionError> {
979        fn traverse(builder: &mut TopSubtreeBuilder, iter: TtIter<'_>) {
980            for tt in iter.collect_vec().into_iter().rev() {
981                match tt {
982                    TtElement::Leaf(leaf) => builder.push(leaf.clone()),
983                    TtElement::Subtree(subtree, subtree_iter) => {
984                        builder.open(subtree.delimiter.kind, subtree.delimiter.open);
985                        traverse(builder, subtree_iter);
986                        builder.close(subtree.delimiter.close);
987                    }
988                }
989            }
990        }
991        let mut builder = TopSubtreeBuilder::new(input.top_subtree().delimiter);
992        traverse(&mut builder, input.iter());
993        Ok(builder.build())
994    }
995
996    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
997        other.type_id() == TypeId::of::<Self>()
998    }
999}
1000
1001// Replaces every literal with an empty string literal and every identifier with its first letter,
1002// but retains all tokens' span. Useful for testing we don't assume token hasn't been modified by
1003// macros even if it retains its span.
1004#[derive(Debug)]
1005struct ShortenProcMacroExpander;
1006impl ProcMacroExpander for ShortenProcMacroExpander {
1007    fn expand(
1008        &self,
1009        _: &dyn ExpandDatabase,
1010        input: &TopSubtree,
1011        _: Option<&TopSubtree>,
1012        _: &Env,
1013        _: Span,
1014        _: Span,
1015        _: Span,
1016        _: String,
1017    ) -> Result<TopSubtree, ProcMacroExpansionError> {
1018        let mut result = input.clone();
1019        for (idx, it) in input.as_token_trees().iter_flat_tokens().enumerate() {
1020            if let TokenTree::Leaf(mut leaf) = it {
1021                modify_leaf(&mut leaf);
1022                result.set_token(idx, leaf);
1023            }
1024        }
1025        return Ok(result);
1026
1027        fn modify_leaf(leaf: &mut Leaf) {
1028            match leaf {
1029                Leaf::Literal(it) => {
1030                    // XXX Currently replaces any literals with an empty string, but supporting
1031                    // "shortening" other literals would be nice.
1032                    it.text_and_suffix = Symbol::empty();
1033                    it.suffix_len = 0;
1034                }
1035                Leaf::Punct(_) => {}
1036                Leaf::Ident(it) => {
1037                    it.sym = Symbol::intern(&it.sym.as_str().chars().take(1).collect::<String>());
1038                }
1039            }
1040        }
1041    }
1042
1043    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
1044        other.type_id() == TypeId::of::<Self>()
1045    }
1046}
1047
1048// Reads ident type within string quotes, for issue #17479.
1049#[derive(Debug)]
1050struct Issue17479ProcMacroExpander;
1051impl ProcMacroExpander for Issue17479ProcMacroExpander {
1052    fn expand(
1053        &self,
1054        _: &dyn ExpandDatabase,
1055        subtree: &TopSubtree,
1056        _: Option<&TopSubtree>,
1057        _: &Env,
1058        _: Span,
1059        _: Span,
1060        _: Span,
1061        _: String,
1062    ) -> Result<TopSubtree, ProcMacroExpansionError> {
1063        let mut iter = subtree.iter();
1064        let Some(TtElement::Leaf(tt::Leaf::Literal(lit))) = iter.next() else {
1065            return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
1066        };
1067        let symbol = Symbol::intern(lit.text());
1068        let span = lit.span;
1069        Ok(quote! { span =>
1070            #symbol()
1071        })
1072    }
1073
1074    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
1075        other.type_id() == TypeId::of::<Self>()
1076    }
1077}
1078
1079// Reads ident type within string quotes, for issue #17479.
1080#[derive(Debug)]
1081struct Issue18898ProcMacroExpander;
1082impl ProcMacroExpander for Issue18898ProcMacroExpander {
1083    fn expand(
1084        &self,
1085        _: &dyn ExpandDatabase,
1086        subtree: &TopSubtree,
1087        _: Option<&TopSubtree>,
1088        _: &Env,
1089        def_site: Span,
1090        _: Span,
1091        _: Span,
1092        _: String,
1093    ) -> Result<TopSubtree, ProcMacroExpansionError> {
1094        let span = subtree
1095            .token_trees()
1096            .last_span()
1097            .ok_or_else(|| ProcMacroExpansionError::Panic("malformed input".to_owned()))?;
1098        let overly_long_subtree = quote! {span =>
1099            {
1100                let a = 5;
1101                let a = 5;
1102                let a = 5;
1103                let a = 5;
1104                let a = 5;
1105                let a = 5;
1106                let a = 5;
1107                let a = 5;
1108                let a = 5;
1109                let a = 5;
1110                let a = 5;
1111                let a = 5;
1112                let a = 5;
1113                let a = 5;
1114                let a = 5;
1115                let a = 5;
1116                let a = 5;
1117                let a = 5;
1118                let a = 5;
1119            }
1120        };
1121        Ok(quote! { def_site =>
1122            fn foo() {
1123                #overly_long_subtree
1124            }
1125        })
1126    }
1127
1128    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
1129        other.type_id() == TypeId::of::<Self>()
1130    }
1131}
1132
1133// Reads ident type within string quotes, for issue #17479.
1134#[derive(Debug)]
1135struct DisallowCfgProcMacroExpander;
1136impl ProcMacroExpander for DisallowCfgProcMacroExpander {
1137    fn expand(
1138        &self,
1139        _: &dyn ExpandDatabase,
1140        subtree: &TopSubtree,
1141        _: Option<&TopSubtree>,
1142        _: &Env,
1143        _: Span,
1144        _: Span,
1145        _: Span,
1146        _: String,
1147    ) -> Result<TopSubtree, ProcMacroExpansionError> {
1148        for tt in subtree.token_trees().iter_flat_tokens() {
1149            if let tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) = tt
1150                && (ident.sym == sym::cfg || ident.sym == sym::cfg_attr)
1151            {
1152                return Err(ProcMacroExpansionError::Panic(
1153                    "cfg or cfg_attr found in DisallowCfgProcMacroExpander".to_owned(),
1154                ));
1155            }
1156        }
1157        Ok(subtree.clone())
1158    }
1159
1160    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
1161        other.type_id() == TypeId::of::<Self>()
1162    }
1163}
1164
1165// Generates a new type by adding a suffix to the original name
1166#[derive(Debug)]
1167struct GenerateSuffixedTypeProcMacroExpander;
1168impl ProcMacroExpander for GenerateSuffixedTypeProcMacroExpander {
1169    fn expand(
1170        &self,
1171        _: &dyn ExpandDatabase,
1172        subtree: &TopSubtree,
1173        _attrs: Option<&TopSubtree>,
1174        _env: &Env,
1175        _def_site: Span,
1176        call_site: Span,
1177        _mixed_site: Span,
1178        _current_dir: String,
1179    ) -> Result<TopSubtree, ProcMacroExpansionError> {
1180        let mut iter = subtree.iter();
1181        let Some(TtElement::Leaf(tt::Leaf::Ident(ident))) = iter.next() else {
1182            return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
1183        };
1184
1185        let ident = match ident.sym.as_str() {
1186            "struct" => {
1187                let Some(TtElement::Leaf(tt::Leaf::Ident(ident))) = iter.next() else {
1188                    return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
1189                };
1190                ident
1191            }
1192
1193            "enum" => {
1194                iter.next();
1195                let (_, mut iter) = iter.expect_subtree().unwrap();
1196                let Some(TtElement::Leaf(tt::Leaf::Ident(ident))) = iter.next() else {
1197                    return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
1198                };
1199                ident
1200            }
1201
1202            _ => {
1203                return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
1204            }
1205        };
1206
1207        let generated_ident = tt::Ident {
1208            sym: Symbol::intern(&format!("{}Suffix", ident.sym)),
1209            span: ident.span,
1210            is_raw: tt::IdentIsRaw::No,
1211        };
1212
1213        let ret = quote! { call_site =>
1214            #subtree
1215
1216            struct #generated_ident;
1217        };
1218
1219        Ok(ret)
1220    }
1221
1222    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
1223        other.type_id() == TypeId::of::<Self>()
1224    }
1225}