1use std::iter;
66
67use rustc_hash::FxHashMap;
68use stdx::trim_indent;
69
70#[derive(Debug, Eq, PartialEq)]
71pub struct Fixture {
72 pub path: String,
74 pub krate: Option<String>,
83 pub deps: Vec<String>,
87 pub extern_prelude: Option<Vec<String>>,
94 pub cfgs: Vec<(String, Option<String>)>,
99 pub edition: Option<String>,
106 pub env: FxHashMap<String, String>,
110 pub introduce_new_source_root: Option<String>,
124 pub library: bool,
134 pub text: String,
136 pub line: usize,
138}
139
140#[derive(Debug)]
141pub struct MiniCore {
142 activated_flags: Vec<String>,
143 valid_flags: Vec<String>,
144}
145
146#[derive(Debug)]
147pub struct FixtureWithProjectMeta {
148 pub fixture: Vec<Fixture>,
149 pub mini_core: Option<MiniCore>,
150 pub proc_macro_names: Vec<String>,
151 pub toolchain: Option<String>,
152 pub target_data_layout: String,
157 pub target_arch: String,
159}
160
161impl FixtureWithProjectMeta {
162 pub fn parse(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> Self {
183 let fixture = trim_indent(ra_fixture);
184 let mut fixture = fixture.as_str();
185 let mut toolchain = None;
186 let mut target_data_layout =
187 "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128".to_owned();
188 let mut target_arch = "x86_64".to_owned();
189 let mut mini_core = None;
190 let mut res: Vec<Fixture> = Vec::new();
191 let mut proc_macro_names = vec![];
192 let mut first_row = 0;
193
194 if let Some(meta) = fixture.strip_prefix("//- toolchain:") {
195 first_row += 1;
196 let (meta, remain) = meta.split_once('\n').unwrap();
197 toolchain = Some(meta.trim().to_owned());
198 fixture = remain;
199 }
200
201 if let Some(meta) = fixture.strip_prefix("//- target_data_layout:") {
202 first_row += 1;
203 let (meta, remain) = meta.split_once('\n').unwrap();
204 meta.trim().clone_into(&mut target_data_layout);
205 fixture = remain;
206 }
207
208 if let Some(meta) = fixture.strip_prefix("//- target_arch:") {
209 first_row += 1;
210 let (meta, remain) = meta.split_once('\n').unwrap();
211 meta.trim().clone_into(&mut target_arch);
212 fixture = remain;
213 }
214
215 if let Some(meta) = fixture.strip_prefix("//- proc_macros:") {
216 first_row += 1;
217 let (meta, remain) = meta.split_once('\n').unwrap();
218 proc_macro_names = meta.split(',').map(|it| it.trim().to_owned()).collect();
219 fixture = remain;
220 }
221
222 if let Some(meta) = fixture.strip_prefix("//- minicore:") {
223 first_row += 1;
224 let (meta, remain) = meta.split_once('\n').unwrap();
225 mini_core = Some(MiniCore::parse(meta));
226 fixture = remain;
227 }
228
229 let default =
230 if fixture.contains("//- /") { None } else { Some((first_row - 1, "//- /main.rs")) };
231
232 for (ix, line) in
233 default.into_iter().chain((first_row..).zip(fixture.split_inclusive('\n')))
234 {
235 if line.contains("//-") {
236 assert!(
237 line.starts_with("//-"),
238 "Metadata line {ix} has invalid indentation. \
239 All metadata lines need to have the same indentation.\n\
240 The offending line: {line:?}"
241 );
242 }
243
244 if let Some(line) = line.strip_prefix("//-") {
245 let meta = Self::parse_meta_line(line, (ix + 1).try_into().unwrap());
246 res.push(meta);
247 } else {
248 if matches!(line.strip_prefix("// "), Some(l) if l.trim().starts_with('/')) {
249 panic!("looks like invalid metadata line: {line:?}");
250 }
251
252 if let Some(entry) = res.last_mut() {
253 entry.text.push_str(line);
254 }
255 }
256 }
257
258 Self {
259 fixture: res,
260 mini_core,
261 proc_macro_names,
262 toolchain,
263 target_data_layout,
264 target_arch,
265 }
266 }
267
268 fn parse_meta_line(meta: &str, line: usize) -> Fixture {
270 let meta = meta.trim();
271 let mut components = meta.split_ascii_whitespace();
272
273 let path = components.next().expect("fixture meta must start with a path").to_owned();
274 assert!(path.starts_with('/'), "fixture path does not start with `/`: {path:?}");
275
276 let mut krate = None;
277 let mut deps = Vec::new();
278 let mut extern_prelude = None;
279 let mut edition = None;
280 let mut cfgs = Vec::new();
281 let mut env = FxHashMap::default();
282 let mut introduce_new_source_root = None;
283 let mut library = false;
284 for component in components {
285 if component == "library" {
286 library = true;
287 continue;
288 }
289
290 let (key, value) =
291 component.split_once(':').unwrap_or_else(|| panic!("invalid meta line: {meta:?}"));
292 match key {
293 "crate" => krate = Some(value.to_owned()),
294 "deps" => deps = value.split(',').map(|it| it.to_owned()).collect(),
295 "extern-prelude" => {
296 if value.is_empty() {
297 extern_prelude = Some(Vec::new());
298 } else {
299 extern_prelude =
300 Some(value.split(',').map(|it| it.to_owned()).collect::<Vec<_>>());
301 }
302 }
303 "edition" => edition = Some(value.to_owned()),
304 "cfg" => {
305 for entry in value.split(',') {
306 match entry.split_once('=') {
307 Some((k, v)) => cfgs.push((k.to_owned(), Some(v.to_owned()))),
308 None => cfgs.push((entry.to_owned(), None)),
309 }
310 }
311 }
312 "env" => {
313 for key in value.split(',') {
314 if let Some((k, v)) = key.split_once('=') {
315 env.insert(k.into(), v.into());
316 }
317 }
318 }
319 "new_source_root" => introduce_new_source_root = Some(value.to_owned()),
320 _ => panic!("bad component: {component:?}"),
321 }
322 }
323
324 for prelude_dep in extern_prelude.iter().flatten() {
325 assert!(
326 deps.contains(prelude_dep),
327 "extern-prelude {extern_prelude:?} must be a subset of deps {deps:?}"
328 );
329 }
330
331 Fixture {
332 path,
333 text: String::new(),
334 line,
335 krate,
336 deps,
337 extern_prelude,
338 cfgs,
339 edition,
340 env,
341 introduce_new_source_root,
342 library,
343 }
344 }
345}
346
347impl MiniCore {
348 pub const RAW_SOURCE: &'static str = include_str!("./minicore.rs");
349
350 fn has_flag(&self, flag: &str) -> bool {
351 self.activated_flags.iter().any(|it| it == flag)
352 }
353
354 pub fn from_flags<'a>(flags: impl IntoIterator<Item = &'a str>) -> Self {
355 MiniCore {
356 activated_flags: flags.into_iter().map(|x| x.to_owned()).collect(),
357 valid_flags: Vec::new(),
358 }
359 }
360
361 #[track_caller]
362 fn assert_valid_flag(&self, flag: &str) {
363 if !self.valid_flags.iter().any(|it| it == flag) {
364 panic!("invalid flag: {flag:?}, valid flags: {:?}", self.valid_flags);
365 }
366 }
367
368 fn parse(line: &str) -> MiniCore {
369 let mut res = MiniCore { activated_flags: Vec::new(), valid_flags: Vec::new() };
370
371 for entry in line.trim().split(", ") {
372 if res.has_flag(entry) {
373 panic!("duplicate minicore flag: {entry:?}");
374 }
375 res.activated_flags.push(entry.to_owned());
376 }
377
378 res
379 }
380
381 pub fn available_flags(raw_source: &str) -> impl Iterator<Item = &str> {
382 let lines = raw_source.split_inclusive('\n');
383 lines
384 .map_while(|x| x.strip_prefix("//!"))
385 .skip_while(|line| !line.contains("Available flags:"))
386 .skip(1)
387 .map(|x| x.split_once(':').unwrap().0.trim())
388 }
389
390 pub fn source_code(mut self, raw_source: &str) -> String {
394 let mut buf = String::new();
395 let mut lines = raw_source.split_inclusive('\n');
396
397 let mut implications = Vec::new();
398
399 let trim_doc: fn(&str) -> Option<&str> = |line| match line.strip_prefix("//!") {
401 Some(it) => Some(it),
402 None => {
403 assert!(line.trim().is_empty(), "expected empty line after minicore header");
404 None
405 }
406 };
407 for line in lines
408 .by_ref()
409 .map_while(trim_doc)
410 .skip_while(|line| !line.contains("Available flags:"))
411 .skip(1)
412 {
413 let (flag, deps) = line.split_once(':').unwrap();
414 let flag = flag.trim();
415
416 self.valid_flags.push(flag.to_owned());
417 implications.extend(
418 iter::repeat(flag)
419 .zip(deps.split(", ").map(str::trim).filter(|dep| !dep.is_empty())),
420 );
421 }
422
423 for (_, dep) in &implications {
424 self.assert_valid_flag(dep);
425 }
426
427 for flag in &self.activated_flags {
428 self.assert_valid_flag(flag);
429 }
430
431 loop {
433 let mut changed = false;
434 for &(u, v) in &implications {
435 if self.has_flag(u) && !self.has_flag(v) {
436 self.activated_flags.push(v.to_owned());
437 changed = true;
438 }
439 }
440 if !changed {
441 break;
442 }
443 }
444
445 let mut active_regions = Vec::new();
446 let mut inactive_regions = Vec::new();
447 let mut seen_regions = Vec::new();
448 for line in lines {
449 let trimmed = line.trim();
450 if let Some(region) = trimmed.strip_prefix("// region:") {
451 if let Some(region) = region.strip_prefix('!') {
452 inactive_regions.push(region);
453 continue;
454 } else {
455 active_regions.push(region);
456 continue;
457 }
458 }
459 if let Some(region) = trimmed.strip_prefix("// endregion:") {
460 let (prev, region) = if let Some(region) = region.strip_prefix('!') {
461 (inactive_regions.pop().unwrap(), region)
462 } else {
463 (active_regions.pop().unwrap(), region)
464 };
465 assert_eq!(prev, region, "unbalanced region pairs");
466 continue;
467 }
468
469 let mut active_line_region = 0;
470 let mut inactive_line_region = 0;
471 if let Some(idx) = trimmed.find("// :!") {
472 let regions = trimmed[idx + "// :!".len()..].split(", ");
473 inactive_line_region += regions.clone().count();
474 inactive_regions.extend(regions);
475 } else if let Some(idx) = trimmed.find("// :") {
476 let regions = trimmed[idx + "// :".len()..].split(", ");
477 active_line_region += regions.clone().count();
478 active_regions.extend(regions);
479 }
480
481 let mut keep = true;
482 for ®ion in &active_regions {
483 assert!(!region.starts_with(' '), "region marker starts with a space: {region:?}");
484 self.assert_valid_flag(region);
485 seen_regions.push(region);
486 keep &= self.has_flag(region);
487 }
488 for ®ion in &inactive_regions {
489 assert!(!region.starts_with(' '), "region marker starts with a space: {region:?}");
490 self.assert_valid_flag(region);
491 seen_regions.push(region);
492 keep &= !self.has_flag(region);
493 }
494
495 if keep {
496 buf.push_str(line);
497 }
498 if active_line_region > 0 {
499 active_regions.drain(active_regions.len() - active_line_region..);
500 }
501 if inactive_line_region > 0 {
502 inactive_regions.drain(inactive_regions.len() - active_line_region..);
503 }
504 }
505
506 if !active_regions.is_empty() {
507 panic!("unclosed regions: {active_regions:?} Add an `endregion` comment");
508 }
509 if !inactive_regions.is_empty() {
510 panic!("unclosed regions: {inactive_regions:?} Add an `endregion` comment");
511 }
512
513 for flag in &self.valid_flags {
514 if !seen_regions.iter().any(|it| it == flag) {
515 panic!("unused minicore flag: {flag:?}");
516 }
517 }
518 buf
519 }
520}
521
522#[test]
523#[should_panic]
524fn parse_fixture_checks_further_indented_metadata() {
525 FixtureWithProjectMeta::parse(
526 r"
527 //- /lib.rs
528 mod bar;
529
530 fn foo() {}
531 //- /bar.rs
532 pub fn baz() {}
533 ",
534 );
535}
536
537#[test]
538fn parse_fixture_gets_full_meta() {
539 let FixtureWithProjectMeta {
540 fixture: parsed,
541 mini_core,
542 proc_macro_names,
543 toolchain,
544 target_data_layout: _,
545 target_arch: _,
546 } = FixtureWithProjectMeta::parse(
547 r#"
548//- toolchain: nightly
549//- proc_macros: identity
550//- minicore: coerce_unsized
551//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo
552mod m;
553"#,
554 );
555 assert_eq!(toolchain, Some("nightly".to_owned()));
556 assert_eq!(proc_macro_names, vec!["identity".to_owned()]);
557 assert_eq!(mini_core.unwrap().activated_flags, vec!["coerce_unsized".to_owned()]);
558 assert_eq!(1, parsed.len());
559
560 let meta = &parsed[0];
561 assert_eq!("mod m;\n", meta.text);
562
563 assert_eq!("foo", meta.krate.as_ref().unwrap());
564 assert_eq!("/lib.rs", meta.path);
565 assert_eq!(2, meta.env.len());
566}