Skip to main content

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