• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! A set of high-level utility fixture methods to use in tests.
2 use std::{mem, str::FromStr, sync};
3 
4 use cfg::CfgOptions;
5 use rustc_hash::FxHashMap;
6 use test_utils::{
7     extract_range_or_offset, Fixture, FixtureWithProjectMeta, RangeOrOffset, CURSOR_MARKER,
8     ESCAPED_CURSOR_MARKER,
9 };
10 use triomphe::Arc;
11 use tt::token_id::{Leaf, Subtree, TokenTree};
12 use vfs::{file_set::FileSet, VfsPath};
13 
14 use crate::{
15     input::{CrateName, CrateOrigin, LangCrateOrigin},
16     Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition,
17     FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros, ReleaseChannel,
18     SourceDatabaseExt, SourceRoot, SourceRootId,
19 };
20 
21 pub const WORKSPACE: SourceRootId = SourceRootId(0);
22 
23 pub trait WithFixture: Default + SourceDatabaseExt + 'static {
24     #[track_caller]
with_single_file(ra_fixture: &str) -> (Self, FileId)25     fn with_single_file(ra_fixture: &str) -> (Self, FileId) {
26         let fixture = ChangeFixture::parse(ra_fixture);
27         let mut db = Self::default();
28         fixture.change.apply(&mut db);
29         assert_eq!(fixture.files.len(), 1);
30         (db, fixture.files[0])
31     }
32 
33     #[track_caller]
with_many_files(ra_fixture: &str) -> (Self, Vec<FileId>)34     fn with_many_files(ra_fixture: &str) -> (Self, Vec<FileId>) {
35         let fixture = ChangeFixture::parse(ra_fixture);
36         let mut db = Self::default();
37         fixture.change.apply(&mut db);
38         assert!(fixture.file_position.is_none());
39         (db, fixture.files)
40     }
41 
42     #[track_caller]
with_files(ra_fixture: &str) -> Self43     fn with_files(ra_fixture: &str) -> Self {
44         let fixture = ChangeFixture::parse(ra_fixture);
45         let mut db = Self::default();
46         fixture.change.apply(&mut db);
47         assert!(fixture.file_position.is_none());
48         db
49     }
50 
51     #[track_caller]
with_files_extra_proc_macros( ra_fixture: &str, proc_macros: Vec<(String, ProcMacro)>, ) -> Self52     fn with_files_extra_proc_macros(
53         ra_fixture: &str,
54         proc_macros: Vec<(String, ProcMacro)>,
55     ) -> Self {
56         let fixture = ChangeFixture::parse_with_proc_macros(ra_fixture, proc_macros);
57         let mut db = Self::default();
58         fixture.change.apply(&mut db);
59         assert!(fixture.file_position.is_none());
60         db
61     }
62 
63     #[track_caller]
with_position(ra_fixture: &str) -> (Self, FilePosition)64     fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
65         let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
66         let offset = range_or_offset.expect_offset();
67         (db, FilePosition { file_id, offset })
68     }
69 
70     #[track_caller]
with_range(ra_fixture: &str) -> (Self, FileRange)71     fn with_range(ra_fixture: &str) -> (Self, FileRange) {
72         let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
73         let range = range_or_offset.expect_range();
74         (db, FileRange { file_id, range })
75     }
76 
77     #[track_caller]
with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset)78     fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) {
79         let fixture = ChangeFixture::parse(ra_fixture);
80         let mut db = Self::default();
81         fixture.change.apply(&mut db);
82         let (file_id, range_or_offset) = fixture
83             .file_position
84             .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
85         (db, file_id, range_or_offset)
86     }
87 
test_crate(&self) -> CrateId88     fn test_crate(&self) -> CrateId {
89         let crate_graph = self.crate_graph();
90         let mut it = crate_graph.iter();
91         let res = it.next().unwrap();
92         assert!(it.next().is_none());
93         res
94     }
95 }
96 
97 impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
98 
99 pub struct ChangeFixture {
100     pub file_position: Option<(FileId, RangeOrOffset)>,
101     pub files: Vec<FileId>,
102     pub change: Change,
103 }
104 
105 impl ChangeFixture {
parse(ra_fixture: &str) -> ChangeFixture106     pub fn parse(ra_fixture: &str) -> ChangeFixture {
107         Self::parse_with_proc_macros(ra_fixture, Vec::new())
108     }
109 
parse_with_proc_macros( ra_fixture: &str, mut proc_macro_defs: Vec<(String, ProcMacro)>, ) -> ChangeFixture110     pub fn parse_with_proc_macros(
111         ra_fixture: &str,
112         mut proc_macro_defs: Vec<(String, ProcMacro)>,
113     ) -> ChangeFixture {
114         let FixtureWithProjectMeta { fixture, mini_core, proc_macro_names, toolchain } =
115             FixtureWithProjectMeta::parse(ra_fixture);
116         let toolchain = toolchain
117             .map(|it| {
118                 ReleaseChannel::from_str(&it)
119                     .unwrap_or_else(|| panic!("unknown release channel found: {it}"))
120             })
121             .unwrap_or(ReleaseChannel::Stable);
122         let mut change = Change::new();
123 
124         let mut files = Vec::new();
125         let mut crate_graph = CrateGraph::default();
126         let mut crates = FxHashMap::default();
127         let mut crate_deps = Vec::new();
128         let mut default_crate_root: Option<FileId> = None;
129         let mut default_target_data_layout: Option<String> = None;
130         let mut default_cfg = CfgOptions::default();
131 
132         let mut file_set = FileSet::default();
133         let mut current_source_root_kind = SourceRootKind::Local;
134         let source_root_prefix = "/".to_string();
135         let mut file_id = FileId(0);
136         let mut roots = Vec::new();
137 
138         let mut file_position = None;
139 
140         for entry in fixture {
141             let text = if entry.text.contains(CURSOR_MARKER) {
142                 if entry.text.contains(ESCAPED_CURSOR_MARKER) {
143                     entry.text.replace(ESCAPED_CURSOR_MARKER, CURSOR_MARKER)
144                 } else {
145                     let (range_or_offset, text) = extract_range_or_offset(&entry.text);
146                     assert!(file_position.is_none());
147                     file_position = Some((file_id, range_or_offset));
148                     text
149                 }
150             } else {
151                 entry.text.clone()
152             };
153 
154             let meta = FileMeta::from(entry);
155             assert!(meta.path.starts_with(&source_root_prefix));
156             if !meta.deps.is_empty() {
157                 assert!(meta.krate.is_some(), "can't specify deps without naming the crate")
158             }
159 
160             if let Some(kind) = &meta.introduce_new_source_root {
161                 let root = match current_source_root_kind {
162                     SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
163                     SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
164                 };
165                 roots.push(root);
166                 current_source_root_kind = *kind;
167             }
168 
169             if let Some((krate, origin, version)) = meta.krate {
170                 let crate_name = CrateName::normalize_dashes(&krate);
171                 let crate_id = crate_graph.add_crate_root(
172                     file_id,
173                     meta.edition,
174                     Some(crate_name.clone().into()),
175                     version,
176                     meta.cfg,
177                     Default::default(),
178                     meta.env,
179                     false,
180                     origin,
181                     meta.target_data_layout
182                         .as_deref()
183                         .map(Arc::from)
184                         .ok_or_else(|| "target_data_layout unset".into()),
185                     Some(toolchain),
186                 );
187                 let prev = crates.insert(crate_name.clone(), crate_id);
188                 assert!(prev.is_none());
189                 for dep in meta.deps {
190                     let prelude = meta.extern_prelude.contains(&dep);
191                     let dep = CrateName::normalize_dashes(&dep);
192                     crate_deps.push((crate_name.clone(), dep, prelude))
193                 }
194             } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
195                 assert!(default_crate_root.is_none());
196                 default_crate_root = Some(file_id);
197                 default_cfg = meta.cfg;
198                 default_target_data_layout = meta.target_data_layout;
199             }
200 
201             change.change_file(file_id, Some(Arc::from(text)));
202             let path = VfsPath::new_virtual_path(meta.path);
203             file_set.insert(file_id, path);
204             files.push(file_id);
205             file_id.0 += 1;
206         }
207 
208         if crates.is_empty() {
209             let crate_root = default_crate_root
210                 .expect("missing default crate root, specify a main.rs or lib.rs");
211             crate_graph.add_crate_root(
212                 crate_root,
213                 Edition::CURRENT,
214                 Some(CrateName::new("test").unwrap().into()),
215                 None,
216                 default_cfg,
217                 Default::default(),
218                 Env::new_for_test_fixture(),
219                 false,
220                 CrateOrigin::Local { repo: None, name: None },
221                 default_target_data_layout
222                     .map(|x| x.into())
223                     .ok_or_else(|| "target_data_layout unset".into()),
224                 Some(toolchain),
225             );
226         } else {
227             for (from, to, prelude) in crate_deps {
228                 let from_id = crates[&from];
229                 let to_id = crates[&to];
230                 crate_graph
231                     .add_dep(
232                         from_id,
233                         Dependency::with_prelude(CrateName::new(&to).unwrap(), to_id, prelude),
234                     )
235                     .unwrap();
236             }
237         }
238         let target_layout = crate_graph.iter().next().map_or_else(
239             || Err("target_data_layout unset".into()),
240             |it| crate_graph[it].target_layout.clone(),
241         );
242 
243         if let Some(mini_core) = mini_core {
244             let core_file = file_id;
245             file_id.0 += 1;
246 
247             let mut fs = FileSet::default();
248             fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_string()));
249             roots.push(SourceRoot::new_library(fs));
250 
251             change.change_file(core_file, Some(Arc::from(mini_core.source_code())));
252 
253             let all_crates = crate_graph.crates_in_topological_order();
254 
255             let core_crate = crate_graph.add_crate_root(
256                 core_file,
257                 Edition::Edition2021,
258                 Some(CrateDisplayName::from_canonical_name("core".to_string())),
259                 None,
260                 Default::default(),
261                 Default::default(),
262                 Env::new_for_test_fixture(),
263                 false,
264                 CrateOrigin::Lang(LangCrateOrigin::Core),
265                 target_layout.clone(),
266                 Some(toolchain),
267             );
268 
269             for krate in all_crates {
270                 crate_graph
271                     .add_dep(krate, Dependency::new(CrateName::new("core").unwrap(), core_crate))
272                     .unwrap();
273             }
274         }
275 
276         let mut proc_macros = ProcMacros::default();
277         if !proc_macro_names.is_empty() {
278             let proc_lib_file = file_id;
279             file_id.0 += 1;
280 
281             proc_macro_defs.extend(default_test_proc_macros());
282             let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macro_defs);
283             let mut fs = FileSet::default();
284             fs.insert(
285                 proc_lib_file,
286                 VfsPath::new_virtual_path("/sysroot/proc_macros/lib.rs".to_string()),
287             );
288             roots.push(SourceRoot::new_library(fs));
289 
290             change.change_file(proc_lib_file, Some(Arc::from(source)));
291 
292             let all_crates = crate_graph.crates_in_topological_order();
293 
294             let proc_macros_crate = crate_graph.add_crate_root(
295                 proc_lib_file,
296                 Edition::Edition2021,
297                 Some(CrateDisplayName::from_canonical_name("proc_macros".to_string())),
298                 None,
299                 Default::default(),
300                 Default::default(),
301                 Env::new_for_test_fixture(),
302                 true,
303                 CrateOrigin::Local { repo: None, name: None },
304                 target_layout,
305                 Some(toolchain),
306             );
307             proc_macros.insert(proc_macros_crate, Ok(proc_macro));
308 
309             for krate in all_crates {
310                 crate_graph
311                     .add_dep(
312                         krate,
313                         Dependency::new(CrateName::new("proc_macros").unwrap(), proc_macros_crate),
314                     )
315                     .unwrap();
316             }
317         }
318 
319         let root = match current_source_root_kind {
320             SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
321             SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
322         };
323         roots.push(root);
324         change.set_roots(roots);
325         change.set_crate_graph(crate_graph);
326         change.set_proc_macros(proc_macros);
327 
328         ChangeFixture { file_position, files, change }
329     }
330 }
331 
default_test_proc_macros() -> [(String, ProcMacro); 5]332 fn default_test_proc_macros() -> [(String, ProcMacro); 5] {
333     [
334         (
335             r#"
336 #[proc_macro_attribute]
337 pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
338     item
339 }
340 "#
341             .into(),
342             ProcMacro {
343                 name: "identity".into(),
344                 kind: crate::ProcMacroKind::Attr,
345                 expander: sync::Arc::new(IdentityProcMacroExpander),
346             },
347         ),
348         (
349             r#"
350 #[proc_macro_derive(DeriveIdentity)]
351 pub fn derive_identity(item: TokenStream) -> TokenStream {
352     item
353 }
354 "#
355             .into(),
356             ProcMacro {
357                 name: "DeriveIdentity".into(),
358                 kind: crate::ProcMacroKind::CustomDerive,
359                 expander: sync::Arc::new(IdentityProcMacroExpander),
360             },
361         ),
362         (
363             r#"
364 #[proc_macro_attribute]
365 pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
366     attr
367 }
368 "#
369             .into(),
370             ProcMacro {
371                 name: "input_replace".into(),
372                 kind: crate::ProcMacroKind::Attr,
373                 expander: sync::Arc::new(AttributeInputReplaceProcMacroExpander),
374             },
375         ),
376         (
377             r#"
378 #[proc_macro]
379 pub fn mirror(input: TokenStream) -> TokenStream {
380     input
381 }
382 "#
383             .into(),
384             ProcMacro {
385                 name: "mirror".into(),
386                 kind: crate::ProcMacroKind::FuncLike,
387                 expander: sync::Arc::new(MirrorProcMacroExpander),
388             },
389         ),
390         (
391             r#"
392 #[proc_macro]
393 pub fn shorten(input: TokenStream) -> TokenStream {
394     loop {}
395 }
396 "#
397             .into(),
398             ProcMacro {
399                 name: "shorten".into(),
400                 kind: crate::ProcMacroKind::FuncLike,
401                 expander: sync::Arc::new(ShortenProcMacroExpander),
402             },
403         ),
404     ]
405 }
406 
filter_test_proc_macros( proc_macro_names: &[String], proc_macro_defs: Vec<(String, ProcMacro)>, ) -> (Vec<ProcMacro>, String)407 fn filter_test_proc_macros(
408     proc_macro_names: &[String],
409     proc_macro_defs: Vec<(String, ProcMacro)>,
410 ) -> (Vec<ProcMacro>, String) {
411     // The source here is only required so that paths to the macros exist and are resolvable.
412     let mut source = String::new();
413     let mut proc_macros = Vec::new();
414 
415     for (c, p) in proc_macro_defs {
416         if !proc_macro_names.iter().any(|name| name == &stdx::to_lower_snake_case(&p.name)) {
417             continue;
418         }
419         proc_macros.push(p);
420         source += &c;
421     }
422 
423     (proc_macros, source)
424 }
425 
426 #[derive(Debug, Clone, Copy)]
427 enum SourceRootKind {
428     Local,
429     Library,
430 }
431 
432 #[derive(Debug)]
433 struct FileMeta {
434     path: String,
435     krate: Option<(String, CrateOrigin, Option<String>)>,
436     deps: Vec<String>,
437     extern_prelude: Vec<String>,
438     cfg: CfgOptions,
439     edition: Edition,
440     env: Env,
441     introduce_new_source_root: Option<SourceRootKind>,
442     target_data_layout: Option<String>,
443 }
444 
parse_crate(crate_str: String) -> (String, CrateOrigin, Option<String>)445 fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option<String>) {
446     if let Some((a, b)) = crate_str.split_once('@') {
447         let (version, origin) = match b.split_once(':') {
448             Some(("CratesIo", data)) => match data.split_once(',') {
449                 Some((version, url)) => {
450                     (version, CrateOrigin::Local { repo: Some(url.to_owned()), name: None })
451                 }
452                 _ => panic!("Bad crates.io parameter: {data}"),
453             },
454             _ => panic!("Bad string for crate origin: {b}"),
455         };
456         (a.to_owned(), origin, Some(version.to_string()))
457     } else {
458         let crate_origin = match LangCrateOrigin::from(&*crate_str) {
459             LangCrateOrigin::Other => CrateOrigin::Local { repo: None, name: None },
460             origin => CrateOrigin::Lang(origin),
461         };
462         (crate_str, crate_origin, None)
463     }
464 }
465 
466 impl From<Fixture> for FileMeta {
from(f: Fixture) -> FileMeta467     fn from(f: Fixture) -> FileMeta {
468         let mut cfg = CfgOptions::default();
469         f.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
470         f.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
471         let deps = f.deps;
472         FileMeta {
473             path: f.path,
474             krate: f.krate.map(parse_crate),
475             extern_prelude: f.extern_prelude.unwrap_or_else(|| deps.clone()),
476             deps,
477             cfg,
478             edition: f.edition.as_ref().map_or(Edition::CURRENT, |v| Edition::from_str(v).unwrap()),
479             env: f.env.into_iter().collect(),
480             introduce_new_source_root: f.introduce_new_source_root.map(|kind| match &*kind {
481                 "local" => SourceRootKind::Local,
482                 "library" => SourceRootKind::Library,
483                 invalid => panic!("invalid source root kind '{invalid}'"),
484             }),
485             target_data_layout: f.target_data_layout,
486         }
487     }
488 }
489 
490 // Identity mapping
491 #[derive(Debug)]
492 struct IdentityProcMacroExpander;
493 impl ProcMacroExpander for IdentityProcMacroExpander {
expand( &self, subtree: &Subtree, _: Option<&Subtree>, _: &Env, ) -> Result<Subtree, ProcMacroExpansionError>494     fn expand(
495         &self,
496         subtree: &Subtree,
497         _: Option<&Subtree>,
498         _: &Env,
499     ) -> Result<Subtree, ProcMacroExpansionError> {
500         Ok(subtree.clone())
501     }
502 }
503 
504 // Pastes the attribute input as its output
505 #[derive(Debug)]
506 struct AttributeInputReplaceProcMacroExpander;
507 impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander {
expand( &self, _: &Subtree, attrs: Option<&Subtree>, _: &Env, ) -> Result<Subtree, ProcMacroExpansionError>508     fn expand(
509         &self,
510         _: &Subtree,
511         attrs: Option<&Subtree>,
512         _: &Env,
513     ) -> Result<Subtree, ProcMacroExpansionError> {
514         attrs
515             .cloned()
516             .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into()))
517     }
518 }
519 
520 #[derive(Debug)]
521 struct MirrorProcMacroExpander;
522 impl ProcMacroExpander for MirrorProcMacroExpander {
expand( &self, input: &Subtree, _: Option<&Subtree>, _: &Env, ) -> Result<Subtree, ProcMacroExpansionError>523     fn expand(
524         &self,
525         input: &Subtree,
526         _: Option<&Subtree>,
527         _: &Env,
528     ) -> Result<Subtree, ProcMacroExpansionError> {
529         fn traverse(input: &Subtree) -> Subtree {
530             let mut token_trees = vec![];
531             for tt in input.token_trees.iter().rev() {
532                 let tt = match tt {
533                     tt::TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(leaf.clone()),
534                     tt::TokenTree::Subtree(sub) => tt::TokenTree::Subtree(traverse(sub)),
535                 };
536                 token_trees.push(tt);
537             }
538             Subtree { delimiter: input.delimiter, token_trees }
539         }
540         Ok(traverse(input))
541     }
542 }
543 
544 // Replaces every literal with an empty string literal and every identifier with its first letter,
545 // but retains all tokens' span. Useful for testing we don't assume token hasn't been modified by
546 // macros even if it retains its span.
547 #[derive(Debug)]
548 struct ShortenProcMacroExpander;
549 impl ProcMacroExpander for ShortenProcMacroExpander {
expand( &self, input: &Subtree, _: Option<&Subtree>, _: &Env, ) -> Result<Subtree, ProcMacroExpansionError>550     fn expand(
551         &self,
552         input: &Subtree,
553         _: Option<&Subtree>,
554         _: &Env,
555     ) -> Result<Subtree, ProcMacroExpansionError> {
556         return Ok(traverse(input));
557 
558         fn traverse(input: &Subtree) -> Subtree {
559             let token_trees = input
560                 .token_trees
561                 .iter()
562                 .map(|it| match it {
563                     TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(modify_leaf(leaf)),
564                     TokenTree::Subtree(subtree) => tt::TokenTree::Subtree(traverse(subtree)),
565                 })
566                 .collect();
567             Subtree { delimiter: input.delimiter, token_trees }
568         }
569 
570         fn modify_leaf(leaf: &Leaf) -> Leaf {
571             let mut leaf = leaf.clone();
572             match &mut leaf {
573                 Leaf::Literal(it) => {
574                     // XXX Currently replaces any literals with an empty string, but supporting
575                     // "shortening" other literals would be nice.
576                     it.text = "\"\"".into();
577                 }
578                 Leaf::Punct(_) => {}
579                 Leaf::Ident(it) => {
580                     it.text = it.text.chars().take(1).collect();
581                 }
582             }
583             leaf
584         }
585     }
586 }
587