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