ra_ap_test_fixture/
lib.rs

1//! A set of high-level utility fixture methods to use in tests.
2use std::{any::TypeId, mem, str::FromStr, sync};
3
4use base_db::{
5    Crate, CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CrateWorkspaceData,
6    DependencyBuilder, Env, FileChange, FileSet, LangCrateOrigin, SourceDatabase, SourceRoot,
7    Version, VfsPath, salsa,
8};
9use cfg::CfgOptions;
10use hir_expand::{
11    EditionedFileId, FileRange,
12    change::ChangeWithProcMacros,
13    db::ExpandDatabase,
14    files::FilePosition,
15    proc_macro::{
16        ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, ProcMacrosBuilder,
17    },
18    quote,
19    tt::{Leaf, TokenTree, TopSubtree, TopSubtreeBuilder, TtElement, TtIter},
20};
21use intern::{Symbol, sym};
22use paths::AbsPathBuf;
23use rustc_hash::FxHashMap;
24use span::{Edition, FileId, Span};
25use stdx::itertools::Itertools;
26use test_utils::{
27    CURSOR_MARKER, ESCAPED_CURSOR_MARKER, Fixture, FixtureWithProjectMeta, RangeOrOffset,
28    extract_range_or_offset,
29};
30use triomphe::Arc;
31
32pub const WORKSPACE: base_db::SourceRootId = base_db::SourceRootId(0);
33
34pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static {
35    #[track_caller]
36    fn with_single_file(
37        #[rust_analyzer::rust_fixture] ra_fixture: &str,
38    ) -> (Self, EditionedFileId) {
39        let mut db = Self::default();
40        let fixture = ChangeFixture::parse(&db, ra_fixture);
41        fixture.change.apply(&mut db);
42        assert_eq!(fixture.files.len(), 1, "Multiple file found in the fixture");
43        (db, fixture.files[0])
44    }
45
46    #[track_caller]
47    fn with_many_files(
48        #[rust_analyzer::rust_fixture] ra_fixture: &str,
49    ) -> (Self, Vec<EditionedFileId>) {
50        let mut db = Self::default();
51        let fixture = ChangeFixture::parse(&db, ra_fixture);
52        fixture.change.apply(&mut db);
53        assert!(fixture.file_position.is_none());
54        (db, fixture.files)
55    }
56
57    #[track_caller]
58    fn with_files(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> Self {
59        let mut db = Self::default();
60        let fixture = ChangeFixture::parse(&db, ra_fixture);
61        fixture.change.apply(&mut db);
62        assert!(fixture.file_position.is_none());
63        db
64    }
65
66    #[track_caller]
67    fn with_files_extra_proc_macros(
68        #[rust_analyzer::rust_fixture] ra_fixture: &str,
69        proc_macros: Vec<(String, ProcMacro)>,
70    ) -> Self {
71        let mut db = Self::default();
72        let fixture = ChangeFixture::parse_with_proc_macros(&db, ra_fixture, proc_macros);
73        fixture.change.apply(&mut db);
74        assert!(fixture.file_position.is_none());
75        db
76    }
77
78    #[track_caller]
79    fn with_position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Self, FilePosition) {
80        let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
81        let offset = range_or_offset.expect_offset();
82        (db, FilePosition { file_id, offset })
83    }
84
85    #[track_caller]
86    fn with_range(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (Self, FileRange) {
87        let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
88        let range = range_or_offset.expect_range();
89        (db, FileRange { file_id, range })
90    }
91
92    #[track_caller]
93    fn with_range_or_offset(
94        #[rust_analyzer::rust_fixture] ra_fixture: &str,
95    ) -> (Self, EditionedFileId, RangeOrOffset) {
96        let mut db = Self::default();
97        let fixture = ChangeFixture::parse(&db, ra_fixture);
98        fixture.change.apply(&mut db);
99
100        let (file_id, range_or_offset) = fixture
101            .file_position
102            .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
103        (db, file_id, range_or_offset)
104    }
105
106    fn test_crate(&self) -> Crate {
107        self.all_crates().iter().copied().find(|&krate| !krate.data(self).origin.is_lang()).unwrap()
108    }
109}
110
111impl<DB: ExpandDatabase + SourceDatabase + Default + 'static> WithFixture for DB {}
112
113pub struct ChangeFixture {
114    pub file_position: Option<(EditionedFileId, RangeOrOffset)>,
115    pub files: Vec<EditionedFileId>,
116    pub change: ChangeWithProcMacros,
117}
118
119const SOURCE_ROOT_PREFIX: &str = "/";
120
121impl ChangeFixture {
122    pub fn parse(
123        db: &dyn salsa::Database,
124        #[rust_analyzer::rust_fixture] ra_fixture: &str,
125    ) -> ChangeFixture {
126        Self::parse_with_proc_macros(db, ra_fixture, Vec::new())
127    }
128
129    pub fn parse_with_proc_macros(
130        db: &dyn salsa::Database,
131        #[rust_analyzer::rust_fixture] ra_fixture: &str,
132        mut proc_macro_defs: Vec<(String, ProcMacro)>,
133    ) -> ChangeFixture {
134        let FixtureWithProjectMeta {
135            fixture,
136            mini_core,
137            proc_macro_names,
138            toolchain,
139            target_data_layout,
140        } = FixtureWithProjectMeta::parse(ra_fixture);
141        let target_data_layout = Ok(target_data_layout.into());
142        let toolchain = Some({
143            let channel = toolchain.as_deref().unwrap_or("stable");
144            Version::parse(&format!("1.76.0-{channel}")).unwrap()
145        });
146        let mut source_change = FileChange::default();
147
148        let mut files = Vec::new();
149        let mut crate_graph = CrateGraphBuilder::default();
150        let mut crates = FxHashMap::default();
151        let mut crate_deps = Vec::new();
152        let mut default_crate_root: Option<FileId> = None;
153        let mut default_edition = Edition::CURRENT;
154        let mut default_cfg = CfgOptions::default();
155        let mut default_env = Env::from_iter([(
156            String::from("__ra_is_test_fixture"),
157            String::from("__ra_is_test_fixture"),
158        )]);
159
160        let mut file_set = FileSet::default();
161        let mut current_source_root_kind = SourceRootKind::Local;
162        let mut file_id = FileId::from_raw(0);
163        let mut roots = Vec::new();
164
165        let mut file_position = None;
166
167        let crate_ws_data =
168            Arc::new(CrateWorkspaceData { data_layout: target_data_layout, toolchain });
169
170        // FIXME: This is less than ideal
171        let proc_macro_cwd = Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap()));
172
173        for entry in fixture {
174            let mut range_or_offset = None;
175            let text = if entry.text.contains(CURSOR_MARKER) {
176                if entry.text.contains(ESCAPED_CURSOR_MARKER) {
177                    entry.text.replace(ESCAPED_CURSOR_MARKER, CURSOR_MARKER)
178                } else {
179                    let (roo, text) = extract_range_or_offset(&entry.text);
180                    assert!(file_position.is_none());
181                    range_or_offset = Some(roo);
182                    text
183                }
184            } else {
185                entry.text.as_str().into()
186            };
187
188            let meta = FileMeta::from_fixture(entry, current_source_root_kind);
189            if let Some(range_or_offset) = range_or_offset {
190                file_position =
191                    Some((EditionedFileId::new(db, file_id, meta.edition), range_or_offset));
192            }
193
194            assert!(meta.path.starts_with(SOURCE_ROOT_PREFIX));
195            if !meta.deps.is_empty() {
196                assert!(meta.krate.is_some(), "can't specify deps without naming the crate")
197            }
198
199            if let Some(kind) = meta.introduce_new_source_root {
200                assert!(
201                    meta.krate.is_some(),
202                    "new_source_root meta doesn't make sense without crate meta"
203                );
204                let prev_kind = mem::replace(&mut current_source_root_kind, kind);
205                let prev_root = match prev_kind {
206                    SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
207                    SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
208                };
209                roots.push(prev_root);
210            }
211
212            if let Some((krate, origin, version)) = meta.krate {
213                let crate_name = CrateName::normalize_dashes(&krate);
214                let crate_id = crate_graph.add_crate_root(
215                    file_id,
216                    meta.edition,
217                    Some(crate_name.clone().into()),
218                    version,
219                    meta.cfg.clone(),
220                    Some(meta.cfg),
221                    meta.env,
222                    origin,
223                    false,
224                    proc_macro_cwd.clone(),
225                    crate_ws_data.clone(),
226                );
227                let prev = crates.insert(crate_name.clone(), crate_id);
228                assert!(prev.is_none(), "multiple crates with same name: {crate_name}");
229                for dep in meta.deps {
230                    let prelude = match &meta.extern_prelude {
231                        Some(v) => v.contains(&dep),
232                        None => true,
233                    };
234                    let dep = CrateName::normalize_dashes(&dep);
235                    crate_deps.push((crate_name.clone(), dep, prelude))
236                }
237            } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
238                assert!(default_crate_root.is_none());
239                default_crate_root = Some(file_id);
240                default_edition = meta.edition;
241                default_cfg.extend(meta.cfg.into_iter());
242                default_env.extend_from_other(&meta.env);
243            }
244
245            source_change.change_file(file_id, Some(text));
246            let path = VfsPath::new_virtual_path(meta.path);
247            file_set.insert(file_id, path);
248            files.push(EditionedFileId::new(db, file_id, meta.edition));
249            file_id = FileId::from_raw(file_id.index() + 1);
250        }
251
252        if crates.is_empty() {
253            let crate_root = default_crate_root
254                .expect("missing default crate root, specify a main.rs or lib.rs");
255            crate_graph.add_crate_root(
256                crate_root,
257                default_edition,
258                Some(CrateName::new("ra_test_fixture").unwrap().into()),
259                None,
260                default_cfg.clone(),
261                Some(default_cfg),
262                default_env,
263                CrateOrigin::Local { repo: None, name: None },
264                false,
265                proc_macro_cwd.clone(),
266                crate_ws_data.clone(),
267            );
268        } else {
269            for (from, to, prelude) in crate_deps {
270                let from_id = crates[&from];
271                let to_id = crates[&to];
272                let sysroot = crate_graph[to_id].basic.origin.is_lang();
273                crate_graph
274                    .add_dep(
275                        from_id,
276                        DependencyBuilder::with_prelude(to.clone(), to_id, prelude, sysroot),
277                    )
278                    .unwrap();
279            }
280        }
281
282        if let Some(mini_core) = mini_core {
283            let core_file = file_id;
284            file_id = FileId::from_raw(file_id.index() + 1);
285
286            let mut fs = FileSet::default();
287            fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_owned()));
288            roots.push(SourceRoot::new_library(fs));
289
290            source_change.change_file(core_file, Some(mini_core.source_code()));
291
292            let all_crates = crate_graph.iter().collect::<Vec<_>>();
293
294            let core_crate = crate_graph.add_crate_root(
295                core_file,
296                Edition::CURRENT,
297                Some(CrateDisplayName::from_canonical_name("core")),
298                None,
299                Default::default(),
300                Default::default(),
301                Env::from_iter([(
302                    String::from("__ra_is_test_fixture"),
303                    String::from("__ra_is_test_fixture"),
304                )]),
305                CrateOrigin::Lang(LangCrateOrigin::Core),
306                false,
307                proc_macro_cwd.clone(),
308                crate_ws_data.clone(),
309            );
310
311            for krate in all_crates {
312                crate_graph
313                    .add_dep(
314                        krate,
315                        DependencyBuilder::with_prelude(
316                            CrateName::new("core").unwrap(),
317                            core_crate,
318                            true,
319                            true,
320                        ),
321                    )
322                    .unwrap();
323            }
324        }
325
326        let mut proc_macros = ProcMacrosBuilder::default();
327        if !proc_macro_names.is_empty() {
328            let proc_lib_file = file_id;
329
330            proc_macro_defs.extend(default_test_proc_macros());
331            let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macro_defs);
332            let mut fs = FileSet::default();
333            fs.insert(
334                proc_lib_file,
335                VfsPath::new_virtual_path("/sysroot/proc_macros/lib.rs".to_owned()),
336            );
337            roots.push(SourceRoot::new_library(fs));
338
339            source_change.change_file(proc_lib_file, Some(source));
340
341            let all_crates = crate_graph.iter().collect::<Vec<_>>();
342
343            let proc_macros_crate = crate_graph.add_crate_root(
344                proc_lib_file,
345                Edition::CURRENT,
346                Some(CrateDisplayName::from_canonical_name("proc_macros")),
347                None,
348                Default::default(),
349                Default::default(),
350                Env::from_iter([(
351                    String::from("__ra_is_test_fixture"),
352                    String::from("__ra_is_test_fixture"),
353                )]),
354                CrateOrigin::Local { repo: None, name: None },
355                true,
356                proc_macro_cwd,
357                crate_ws_data,
358            );
359            proc_macros.insert(proc_macros_crate, Ok(proc_macro));
360
361            for krate in all_crates {
362                crate_graph
363                    .add_dep(
364                        krate,
365                        DependencyBuilder::new(
366                            CrateName::new("proc_macros").unwrap(),
367                            proc_macros_crate,
368                        ),
369                    )
370                    .unwrap();
371            }
372        }
373
374        let root = match current_source_root_kind {
375            SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
376            SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
377        };
378        roots.push(root);
379
380        let mut change = ChangeWithProcMacros { source_change, proc_macros: Some(proc_macros) };
381
382        change.source_change.set_roots(roots);
383        change.source_change.set_crate_graph(crate_graph);
384
385        ChangeFixture { file_position, files, change }
386    }
387}
388
389fn default_test_proc_macros() -> Box<[(String, ProcMacro)]> {
390    Box::new([
391        (
392            r#"
393#[proc_macro_attribute]
394pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
395    item
396}
397"#
398            .into(),
399            ProcMacro {
400                name: Symbol::intern("identity"),
401                kind: ProcMacroKind::Attr,
402                expander: sync::Arc::new(IdentityProcMacroExpander),
403                disabled: false,
404            },
405        ),
406        (
407            r#"
408#[proc_macro_derive(DeriveIdentity)]
409pub fn derive_identity(item: TokenStream) -> TokenStream {
410    item
411}
412"#
413            .into(),
414            ProcMacro {
415                name: Symbol::intern("DeriveIdentity"),
416                kind: ProcMacroKind::CustomDerive,
417                expander: sync::Arc::new(IdentityProcMacroExpander),
418                disabled: false,
419            },
420        ),
421        (
422            r#"
423#[proc_macro_attribute]
424pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
425    attr
426}
427"#
428            .into(),
429            ProcMacro {
430                name: Symbol::intern("input_replace"),
431                kind: ProcMacroKind::Attr,
432                expander: sync::Arc::new(AttributeInputReplaceProcMacroExpander),
433                disabled: false,
434            },
435        ),
436        (
437            r#"
438#[proc_macro]
439pub fn mirror(input: TokenStream) -> TokenStream {
440    input
441}
442"#
443            .into(),
444            ProcMacro {
445                name: Symbol::intern("mirror"),
446                kind: ProcMacroKind::Bang,
447                expander: sync::Arc::new(MirrorProcMacroExpander),
448                disabled: false,
449            },
450        ),
451        (
452            r#"
453#[proc_macro]
454pub fn shorten(input: TokenStream) -> TokenStream {
455    loop {}
456}
457"#
458            .into(),
459            ProcMacro {
460                name: Symbol::intern("shorten"),
461                kind: ProcMacroKind::Bang,
462                expander: sync::Arc::new(ShortenProcMacroExpander),
463                disabled: false,
464            },
465        ),
466        (
467            r#"
468#[proc_macro_attribute]
469pub fn issue_18089(_attr: TokenStream, _item: TokenStream) -> TokenStream {
470    loop {}
471}
472"#
473            .into(),
474            ProcMacro {
475                name: Symbol::intern("issue_18089"),
476                kind: ProcMacroKind::Attr,
477                expander: sync::Arc::new(Issue18089ProcMacroExpander),
478                disabled: false,
479            },
480        ),
481        (
482            r#"
483#[proc_macro_attribute]
484pub fn issue_18840(_attr: TokenStream, _item: TokenStream) -> TokenStream {
485    loop {}
486}
487"#
488            .into(),
489            ProcMacro {
490                name: Symbol::intern("issue_18840"),
491                kind: ProcMacroKind::Attr,
492                expander: sync::Arc::new(Issue18840ProcMacroExpander),
493                disabled: false,
494            },
495        ),
496        (
497            r#"
498#[proc_macro]
499pub fn issue_17479(input: TokenStream) -> TokenStream {
500    input
501}
502"#
503            .into(),
504            ProcMacro {
505                name: Symbol::intern("issue_17479"),
506                kind: ProcMacroKind::Bang,
507                expander: sync::Arc::new(Issue17479ProcMacroExpander),
508                disabled: false,
509            },
510        ),
511        (
512            r#"
513#[proc_macro_attribute]
514pub fn issue_18898(_attr: TokenStream, input: TokenStream) -> TokenStream {
515    input
516}
517"#
518            .into(),
519            ProcMacro {
520                name: Symbol::intern("issue_18898"),
521                kind: ProcMacroKind::Bang,
522                expander: sync::Arc::new(Issue18898ProcMacroExpander),
523                disabled: false,
524            },
525        ),
526        (
527            r#"
528#[proc_macro_attribute]
529pub fn disallow_cfg(_attr: TokenStream, input: TokenStream) -> TokenStream {
530    input
531}
532"#
533            .into(),
534            ProcMacro {
535                name: Symbol::intern("disallow_cfg"),
536                kind: ProcMacroKind::Attr,
537                expander: sync::Arc::new(DisallowCfgProcMacroExpander),
538                disabled: false,
539            },
540        ),
541        (
542            r#"
543#[proc_macro_attribute]
544pub fn generate_suffixed_type(_attr: TokenStream, input: TokenStream) -> TokenStream {
545    input
546}
547"#
548            .into(),
549            ProcMacro {
550                name: Symbol::intern("generate_suffixed_type"),
551                kind: ProcMacroKind::Attr,
552                expander: sync::Arc::new(GenerateSuffixedTypeProcMacroExpander),
553                disabled: false,
554            },
555        ),
556    ])
557}
558
559fn filter_test_proc_macros(
560    proc_macro_names: &[String],
561    proc_macro_defs: Vec<(String, ProcMacro)>,
562) -> (Vec<ProcMacro>, String) {
563    // The source here is only required so that paths to the macros exist and are resolvable.
564    let mut source = String::new();
565    let mut proc_macros = Vec::new();
566
567    for (c, p) in proc_macro_defs {
568        if !proc_macro_names.iter().any(|name| name == &stdx::to_lower_snake_case(p.name.as_str()))
569        {
570            continue;
571        }
572        proc_macros.push(p);
573        source += &c;
574    }
575
576    (proc_macros, source)
577}
578
579#[derive(Debug, Clone, Copy)]
580enum SourceRootKind {
581    Local,
582    Library,
583}
584
585#[derive(Debug)]
586struct FileMeta {
587    path: String,
588    krate: Option<(String, CrateOrigin, Option<String>)>,
589    deps: Vec<String>,
590    extern_prelude: Option<Vec<String>>,
591    cfg: CfgOptions,
592    edition: Edition,
593    env: Env,
594    introduce_new_source_root: Option<SourceRootKind>,
595}
596
597impl FileMeta {
598    fn from_fixture(f: Fixture, current_source_root_kind: SourceRootKind) -> Self {
599        let mut cfg = CfgOptions::default();
600        for (k, v) in f.cfgs {
601            if let Some(v) = v {
602                cfg.insert_key_value(Symbol::intern(&k), Symbol::intern(&v));
603            } else {
604                cfg.insert_atom(Symbol::intern(&k));
605            }
606        }
607
608        let introduce_new_source_root = f.introduce_new_source_root.map(|kind| match &*kind {
609            "local" => SourceRootKind::Local,
610            "library" => SourceRootKind::Library,
611            invalid => panic!("invalid source root kind '{invalid}'"),
612        });
613        let current_source_root_kind =
614            introduce_new_source_root.unwrap_or(current_source_root_kind);
615
616        let deps = f.deps;
617        Self {
618            path: f.path,
619            krate: f.krate.map(|it| parse_crate(it, current_source_root_kind, f.library)),
620            extern_prelude: f.extern_prelude,
621            deps,
622            cfg,
623            edition: f.edition.map_or(Edition::CURRENT, |v| Edition::from_str(&v).unwrap()),
624            env: f.env.into_iter().collect(),
625            introduce_new_source_root,
626        }
627    }
628}
629
630fn parse_crate(
631    crate_str: String,
632    current_source_root_kind: SourceRootKind,
633    explicit_non_workspace_member: bool,
634) -> (String, CrateOrigin, Option<String>) {
635    // syntax:
636    //   "my_awesome_crate"
637    //   "my_awesome_crate@0.0.1,http://example.com"
638    let (name, repo, version) = if let Some((name, remain)) = crate_str.split_once('@') {
639        let (version, repo) =
640            remain.split_once(',').expect("crate meta: found '@' without version and url");
641        (name.to_owned(), Some(repo.to_owned()), Some(version.to_owned()))
642    } else {
643        (crate_str, None, None)
644    };
645
646    let non_workspace_member = explicit_non_workspace_member
647        || matches!(current_source_root_kind, SourceRootKind::Library);
648
649    let origin = match LangCrateOrigin::from(&*name) {
650        LangCrateOrigin::Other => {
651            let name = Symbol::intern(&name);
652            if non_workspace_member {
653                CrateOrigin::Library { repo, name }
654            } else {
655                CrateOrigin::Local { repo, name: Some(name) }
656            }
657        }
658        origin => CrateOrigin::Lang(origin),
659    };
660
661    (name, origin, version)
662}
663
664// Identity mapping
665#[derive(Debug)]
666struct IdentityProcMacroExpander;
667impl ProcMacroExpander for IdentityProcMacroExpander {
668    fn expand(
669        &self,
670        subtree: &TopSubtree,
671        _: Option<&TopSubtree>,
672        _: &Env,
673        _: Span,
674        _: Span,
675        _: Span,
676        _: String,
677    ) -> Result<TopSubtree, ProcMacroExpansionError> {
678        Ok(subtree.clone())
679    }
680
681    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
682        other.type_id() == TypeId::of::<Self>()
683    }
684}
685
686// Expands to a macro_rules! macro, for issue #18089.
687#[derive(Debug)]
688struct Issue18089ProcMacroExpander;
689impl ProcMacroExpander for Issue18089ProcMacroExpander {
690    fn expand(
691        &self,
692        subtree: &TopSubtree,
693        _: Option<&TopSubtree>,
694        _: &Env,
695        _: Span,
696        call_site: Span,
697        _: Span,
698        _: String,
699    ) -> Result<TopSubtree, ProcMacroExpansionError> {
700        let tt::TokenTree::Leaf(macro_name) = &subtree.0[2] else {
701            return Err(ProcMacroExpansionError::Panic("incorrect input".to_owned()));
702        };
703        Ok(quote! { call_site =>
704            #[macro_export]
705            macro_rules! my_macro___ {
706                ($($token:tt)*) => {{
707                }};
708            }
709
710            pub use my_macro___ as #macro_name;
711
712            #subtree
713        })
714    }
715
716    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
717        other.type_id() == TypeId::of::<Self>()
718    }
719}
720
721// Pastes the attribute input as its output
722#[derive(Debug)]
723struct AttributeInputReplaceProcMacroExpander;
724impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander {
725    fn expand(
726        &self,
727        _: &TopSubtree,
728        attrs: Option<&TopSubtree>,
729        _: &Env,
730        _: Span,
731        _: Span,
732        _: Span,
733        _: String,
734    ) -> Result<TopSubtree, ProcMacroExpansionError> {
735        attrs
736            .cloned()
737            .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into()))
738    }
739
740    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
741        other.type_id() == TypeId::of::<Self>()
742    }
743}
744
745#[derive(Debug)]
746struct Issue18840ProcMacroExpander;
747impl ProcMacroExpander for Issue18840ProcMacroExpander {
748    fn expand(
749        &self,
750        fn_: &TopSubtree,
751        _: Option<&TopSubtree>,
752        _: &Env,
753        def_site: Span,
754        _: Span,
755        _: Span,
756        _: String,
757    ) -> Result<TopSubtree, ProcMacroExpansionError> {
758        // Input:
759        // ```
760        // #[issue_18840]
761        // fn foo() { let loop {} }
762        // ```
763
764        // The span that was created by the fixup infra.
765        let fixed_up_span = fn_.token_trees().flat_tokens()[5].first_span();
766        let mut result =
767            quote! {fixed_up_span => ::core::compile_error! { "my cool compile_error!" } };
768        // Make it so we won't remove the top subtree when reversing fixups.
769        let top_subtree_delimiter_mut = result.top_subtree_delimiter_mut();
770        top_subtree_delimiter_mut.open = def_site;
771        top_subtree_delimiter_mut.close = def_site;
772        Ok(result)
773    }
774
775    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
776        other.type_id() == TypeId::of::<Self>()
777    }
778}
779
780#[derive(Debug)]
781struct MirrorProcMacroExpander;
782impl ProcMacroExpander for MirrorProcMacroExpander {
783    fn expand(
784        &self,
785        input: &TopSubtree,
786        _: Option<&TopSubtree>,
787        _: &Env,
788        _: Span,
789        _: Span,
790        _: Span,
791        _: String,
792    ) -> Result<TopSubtree, ProcMacroExpansionError> {
793        fn traverse(builder: &mut TopSubtreeBuilder, iter: TtIter<'_>) {
794            for tt in iter.collect_vec().into_iter().rev() {
795                match tt {
796                    TtElement::Leaf(leaf) => builder.push(leaf.clone()),
797                    TtElement::Subtree(subtree, subtree_iter) => {
798                        builder.open(subtree.delimiter.kind, subtree.delimiter.open);
799                        traverse(builder, subtree_iter);
800                        builder.close(subtree.delimiter.close);
801                    }
802                }
803            }
804        }
805        let mut builder = TopSubtreeBuilder::new(input.top_subtree().delimiter);
806        traverse(&mut builder, input.iter());
807        Ok(builder.build())
808    }
809
810    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
811        other.type_id() == TypeId::of::<Self>()
812    }
813}
814
815// Replaces every literal with an empty string literal and every identifier with its first letter,
816// but retains all tokens' span. Useful for testing we don't assume token hasn't been modified by
817// macros even if it retains its span.
818#[derive(Debug)]
819struct ShortenProcMacroExpander;
820impl ProcMacroExpander for ShortenProcMacroExpander {
821    fn expand(
822        &self,
823        input: &TopSubtree,
824        _: Option<&TopSubtree>,
825        _: &Env,
826        _: Span,
827        _: Span,
828        _: Span,
829        _: String,
830    ) -> Result<TopSubtree, ProcMacroExpansionError> {
831        let mut result = input.0.clone();
832        for it in &mut result {
833            if let TokenTree::Leaf(leaf) = it {
834                modify_leaf(leaf)
835            }
836        }
837        return Ok(tt::TopSubtree(result));
838
839        fn modify_leaf(leaf: &mut Leaf) {
840            match leaf {
841                Leaf::Literal(it) => {
842                    // XXX Currently replaces any literals with an empty string, but supporting
843                    // "shortening" other literals would be nice.
844                    it.symbol = Symbol::empty();
845                }
846                Leaf::Punct(_) => {}
847                Leaf::Ident(it) => {
848                    it.sym = Symbol::intern(&it.sym.as_str().chars().take(1).collect::<String>());
849                }
850            }
851        }
852    }
853
854    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
855        other.type_id() == TypeId::of::<Self>()
856    }
857}
858
859// Reads ident type within string quotes, for issue #17479.
860#[derive(Debug)]
861struct Issue17479ProcMacroExpander;
862impl ProcMacroExpander for Issue17479ProcMacroExpander {
863    fn expand(
864        &self,
865        subtree: &TopSubtree,
866        _: Option<&TopSubtree>,
867        _: &Env,
868        _: Span,
869        _: Span,
870        _: Span,
871        _: String,
872    ) -> Result<TopSubtree, ProcMacroExpansionError> {
873        let TokenTree::Leaf(Leaf::Literal(lit)) = &subtree.0[1] else {
874            return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
875        };
876        let symbol = &lit.symbol;
877        let span = lit.span;
878        Ok(quote! { span =>
879            #symbol()
880        })
881    }
882
883    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
884        other.type_id() == TypeId::of::<Self>()
885    }
886}
887
888// Reads ident type within string quotes, for issue #17479.
889#[derive(Debug)]
890struct Issue18898ProcMacroExpander;
891impl ProcMacroExpander for Issue18898ProcMacroExpander {
892    fn expand(
893        &self,
894        subtree: &TopSubtree,
895        _: Option<&TopSubtree>,
896        _: &Env,
897        def_site: Span,
898        _: Span,
899        _: Span,
900        _: String,
901    ) -> Result<TopSubtree, ProcMacroExpansionError> {
902        let span = subtree
903            .token_trees()
904            .flat_tokens()
905            .last()
906            .ok_or_else(|| ProcMacroExpansionError::Panic("malformed input".to_owned()))?
907            .first_span();
908        let overly_long_subtree = quote! {span =>
909            {
910                let a = 5;
911                let a = 5;
912                let a = 5;
913                let a = 5;
914                let a = 5;
915                let a = 5;
916                let a = 5;
917                let a = 5;
918                let a = 5;
919                let a = 5;
920                let a = 5;
921                let a = 5;
922                let a = 5;
923                let a = 5;
924                let a = 5;
925                let a = 5;
926                let a = 5;
927                let a = 5;
928                let a = 5;
929            }
930        };
931        Ok(quote! { def_site =>
932            fn foo() {
933                #overly_long_subtree
934            }
935        })
936    }
937
938    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
939        other.type_id() == TypeId::of::<Self>()
940    }
941}
942
943// Reads ident type within string quotes, for issue #17479.
944#[derive(Debug)]
945struct DisallowCfgProcMacroExpander;
946impl ProcMacroExpander for DisallowCfgProcMacroExpander {
947    fn expand(
948        &self,
949        subtree: &TopSubtree,
950        _: Option<&TopSubtree>,
951        _: &Env,
952        _: Span,
953        _: Span,
954        _: Span,
955        _: String,
956    ) -> Result<TopSubtree, ProcMacroExpansionError> {
957        for tt in subtree.token_trees().flat_tokens() {
958            if let tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) = tt {
959                if ident.sym == sym::cfg || ident.sym == sym::cfg_attr {
960                    return Err(ProcMacroExpansionError::Panic(
961                        "cfg or cfg_attr found in DisallowCfgProcMacroExpander".to_owned(),
962                    ));
963                }
964            }
965        }
966        Ok(subtree.clone())
967    }
968
969    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
970        other.type_id() == TypeId::of::<Self>()
971    }
972}
973
974// Generates a new type by adding a suffix to the original name
975#[derive(Debug)]
976struct GenerateSuffixedTypeProcMacroExpander;
977impl ProcMacroExpander for GenerateSuffixedTypeProcMacroExpander {
978    fn expand(
979        &self,
980        subtree: &TopSubtree,
981        _attrs: Option<&TopSubtree>,
982        _env: &Env,
983        _def_site: Span,
984        call_site: Span,
985        _mixed_site: Span,
986        _current_dir: String,
987    ) -> Result<TopSubtree, ProcMacroExpansionError> {
988        let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[1] else {
989            return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
990        };
991
992        let ident = match ident.sym.as_str() {
993            "struct" => {
994                let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[2] else {
995                    return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
996                };
997                ident
998            }
999
1000            "enum" => {
1001                let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[4] else {
1002                    return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
1003                };
1004                ident
1005            }
1006
1007            _ => {
1008                return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
1009            }
1010        };
1011
1012        let generated_ident = tt::Ident {
1013            sym: Symbol::intern(&format!("{}Suffix", ident.sym)),
1014            span: ident.span,
1015            is_raw: tt::IdentIsRaw::No,
1016        };
1017
1018        let ret = quote! { call_site =>
1019            #subtree
1020
1021            struct #generated_ident;
1022        };
1023
1024        Ok(ret)
1025    }
1026
1027    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool {
1028        other.type_id() == TypeId::of::<Self>()
1029    }
1030}