litcheck_filecheck/check/mod.rs
1mod program;
2
3pub use self::program::{CheckGroup, CheckProgram, CheckSection, CheckTree};
4
5use crate::{common::*, pattern::matcher::MatchAny};
6
7pub struct Checker<'a> {
8 config: &'a Config,
9 interner: &'a mut StringInterner,
10 program: CheckProgram<'a>,
11 match_file: ArcSource,
12}
13impl<'a> Checker<'a> {
14 pub fn new(
15 config: &'a Config,
16 interner: &'a mut StringInterner,
17 program: CheckProgram<'a>,
18 match_file: ArcSource,
19 ) -> Self {
20 Self {
21 config,
22 interner,
23 program,
24 match_file,
25 }
26 }
27
28 /// Check `source` against the rules in this [Checker]
29 pub fn check<S>(&mut self, source: &S) -> TestResult
30 where
31 S: NamedSourceFile + ?Sized,
32 {
33 let source = ArcSource::new(Source::new(source.name(), source.source().to_string()));
34 self.check_input(source)
35 }
36
37 /// Check `input` against the rules in this [Checker]
38 pub fn check_str<S>(&mut self, input: &S) -> TestResult
39 where
40 S: AsRef<str>,
41 {
42 let source = ArcSource::new(Source::from(input.as_ref().to_string()));
43 self.check_input(source)
44 }
45
46 /// Check `source` against the rules in this [Checker]
47 pub fn check_input(&mut self, source: ArcSource) -> TestResult {
48 let buffer = source.source_bytes();
49 let mut context = MatchContext::new(
50 self.config,
51 self.interner,
52 self.match_file.clone(),
53 source.clone(),
54 buffer,
55 );
56
57 if !self.config.allow_empty && buffer.is_empty() {
58 return TestResult::from_error(TestFailed::new(
59 vec![CheckFailedError::EmptyInput],
60 &context,
61 ));
62 }
63
64 match discover_blocks(&self.program, &mut context) {
65 Ok(blocks) => check_blocks(blocks, &self.program, &mut context),
66 Err(failed) => TestResult::from_error(failed),
67 }
68 }
69}
70
71/// Analyze the input to identify regions of input corresponding to
72/// block sections in `program`, i.e. regions delimited by CHECK-LABEL
73/// directives.
74///
75/// This will evaluate the CHECK-LABEL patterns, and construct block
76/// metadata for each region trailing a CHECK-LABEL directive.
77pub fn discover_blocks<'input, 'context: 'input>(
78 program: &CheckProgram<'context>,
79 context: &mut MatchContext<'input, 'context>,
80) -> Result<SmallVec<[BlockInfo; 2]>, TestFailed> {
81 // Traverse the code of the program and try to identify
82 // the byte ranges corresponding to block starts of the
83 // program. Once the block starts are recorded, we can
84 // properly constrain the search bounds of the matchers
85 let mut blocks = SmallVec::<[BlockInfo; 2]>::default();
86 let mut errors = Vec::<CheckFailedError>::new();
87 // We must push an implicit block for ops that come before the first CHECK-LABEL,
88 // if such ops exist
89 let eof = context.cursor().end_of_file();
90 if !matches!(program.sections.first(), Some(CheckSection::Block { .. })) {
91 blocks.push(BlockInfo {
92 label_info: None,
93 sections: Some(Range::new(
94 0,
95 program
96 .sections
97 .iter()
98 .take_while(|s| !matches!(s, CheckSection::Block { .. }))
99 .count(),
100 )),
101 start: Some(0),
102 end: None,
103 eof,
104 });
105 }
106
107 for (i, section) in program.sections.iter().enumerate() {
108 if let CheckSection::Block { ref label, .. } = section {
109 // Find the starting index of this pattern
110 //
111 // If no match is found, record the error,
112 // and skip ahead in the instruction stream
113 let buffer = context.search_to_eof();
114 match label.try_match(buffer, context) {
115 Ok(result) => {
116 match result.info {
117 Some(info) => {
118 // We must compute the indices for the end of the previous block,
119 // and the start of the current block, by looking forwards/backwards
120 // for the nearest newlines in those directions.
121 let match_start = info.span.offset();
122 let cursor = context.cursor_mut();
123 let eol = cursor.next_newline_from(match_start).unwrap_or(eof);
124 let prev_block_end = cursor.prev_newline_from(match_start).unwrap_or(0);
125 // Start subsequent searches at the newline, to ensure that rules which
126 // match on next lines can eat the first newline, but prevent any further
127 // matching of rules on this line
128 cursor.set_start(eol);
129 // Create the block info for this CHECK-LABEL to record the start index
130 let block_id = blocks.len();
131 blocks.push(BlockInfo {
132 label_info: Some(info.into_static()),
133 sections: Some(Range::new(i, i + 1)),
134 start: Some(cursor.start()),
135 end: None,
136 eof,
137 });
138 // Update the block info for the most recent CHECK-LABEL to record its end index,
139 // if one is present
140 if let Some(prev_block) = blocks[..block_id]
141 .iter_mut()
142 .rev()
143 .find(|bi| bi.start.is_some())
144 {
145 prev_block.end = Some(prev_block_end);
146 }
147 }
148 None => {
149 // The current block could not be found, so record an error
150 blocks.push(BlockInfo {
151 label_info: None,
152 sections: Some(Range::new(i, i + 1)),
153 start: None,
154 end: None,
155 eof,
156 });
157 let span = label.span();
158 let msg = format!(
159 "Unable to find a match for this pattern in the input.\
160 Search started at byte {}, ending at {eof}",
161 context.cursor().start()
162 );
163 errors.push(CheckFailedError::MatchNoneButExpected {
164 span,
165 match_file: context.match_file(),
166 note: Some(msg),
167 });
168 }
169 }
170 }
171 Err(err) => {
172 blocks.push(BlockInfo {
173 label_info: None,
174 sections: Some(Range::new(i, i + 1)),
175 start: None,
176 end: None,
177 eof,
178 });
179 errors.push(CheckFailedError::MatchNoneForInvalidPattern {
180 span: label.span(),
181 match_file: context.match_file(),
182 error: Some(RelatedError::new(err)),
183 });
184 }
185 }
186 }
187 }
188
189 // Reset the context bounds
190 context.cursor_mut().reset();
191
192 if errors.is_empty() {
193 Ok(blocks)
194 } else {
195 Err(TestFailed::new(errors, context))
196 }
197}
198
199/// Evaluate all check operations in the given blocks
200pub fn check_blocks<'input, 'a: 'input, I>(
201 blocks: I,
202 program: &'input CheckProgram<'a>,
203 context: &mut MatchContext<'input, 'a>,
204) -> TestResult
205where
206 I: IntoIterator<Item = BlockInfo>,
207{
208 // Execute compiled check program now that we have the blocks identified.
209 //
210 // If we encounter a block which was not found in the check file, all ops
211 // up to the next CHECK-LABEL are skipped.
212 //
213 // Each time a CHECK-LABEL is found, we set the search bounds of the match
214 // context to stay within that region. If --enable-var-scopes is set, the
215 // locals bound so far will be cleared
216 let mut test_result = TestResult::new(context);
217 for mut block in blocks.into_iter() {
218 if let Some(label_info) = block.label_info.take() {
219 test_result.matched(label_info);
220 }
221 if block.start.is_none() {
222 continue;
223 }
224 if block.sections.is_none() {
225 continue;
226 }
227
228 // Update the match context cursor
229 context.enter_block(block.range());
230 let section_range = unsafe { block.sections.unwrap_unchecked() };
231 if section_range.len() > 1 {
232 // No CHECK-LABEL
233 check_group_sections(
234 &program.sections[section_range.start..section_range.end],
235 &mut test_result,
236 context,
237 );
238 } else {
239 match &program.sections[section_range.start] {
240 CheckSection::Block { ref body, .. } => {
241 // CHECK-LABEL
242 check_block_section(body, &mut test_result, context);
243 }
244 section @ CheckSection::Group { .. } => {
245 // Single-group, no blocks
246 check_group_sections(core::slice::from_ref(section), &mut test_result, context);
247 }
248 }
249 }
250 }
251
252 test_result
253}
254
255/// Evaluate a section of check operations with the given context,
256/// gathering any errors encountered into `errors`.
257pub fn check_block_section<'input, 'a: 'input>(
258 body: &[CheckGroup<'a>],
259 test_result: &mut TestResult,
260 context: &mut MatchContext<'input, 'a>,
261) {
262 for group in body.iter() {
263 match check_group(group, test_result, context) {
264 Ok(Ok(matched) | Err(matched)) => {
265 matched
266 .into_iter()
267 .for_each(|matches| test_result.append(matches));
268 }
269 Err(err) => {
270 test_result.failed(err);
271 }
272 }
273 }
274}
275
276pub fn check_group_sections<'input, 'a: 'input>(
277 body: &[CheckSection<'a>],
278 test_result: &mut TestResult,
279 context: &mut MatchContext<'input, 'a>,
280) {
281 for section in body.iter() {
282 let CheckSection::Group { body: ref group } = section else {
283 unreachable!()
284 };
285 match check_group(group, test_result, context) {
286 Ok(Ok(matched) | Err(matched)) => {
287 matched
288 .into_iter()
289 .for_each(|matches| test_result.append(matches));
290 }
291 Err(err) => {
292 test_result.failed(err);
293 }
294 }
295 }
296}
297
298pub fn check_group<'section, 'input, 'a: 'input>(
299 group: &'section CheckGroup<'a>,
300 test_result: &mut TestResult,
301 context: &mut MatchContext<'input, 'a>,
302) -> Result<Result<Vec<Matches<'input>>, Vec<Matches<'input>>>, CheckFailedError> {
303 match group {
304 CheckGroup::Never(ref pattern) => {
305 // The given pattern should not match any of
306 // the remaining input in this block
307 let input = context.search_block();
308 match check_not(pattern, input, context) {
309 Ok(Ok(num_patterns)) => {
310 (0..num_patterns).for_each(|_| test_result.passed());
311 Ok(Ok(vec![]))
312 }
313 Ok(Err(info)) => Ok(Err(vec![Matches::from_iter([MatchResult::failed(
314 CheckFailedError::MatchFoundButExcluded {
315 span: info.span,
316 input_file: context.input_file(),
317 labels: vec![RelatedLabel::error(
318 Label::new(info.pattern_span, "by this pattern"),
319 context.match_file(),
320 )],
321 },
322 )])])),
323 Err(err) => {
324 // Something went wrong with this pattern
325 Err(CheckFailedError::MatchNoneErrorNote {
326 span: pattern.span(),
327 match_file: context.match_file(),
328 error: Some(RelatedError::new(err)),
329 })
330 }
331 }
332 }
333 CheckGroup::Ordered(rules) => {
334 let mut matched = vec![];
335 for rule in rules.iter() {
336 match rule.apply(context) {
337 Ok(matches) => {
338 matched.push(matches);
339 }
340 Err(err) => {
341 test_result.failed(CheckFailedError::MatchNoneErrorNote {
342 span: rule.span(),
343 match_file: context.match_file(),
344 error: Some(RelatedError::new(err)),
345 });
346 }
347 }
348 }
349 if matched.is_empty() || matched.iter().all(|m| m.range().is_none()) {
350 Ok(Err(matched))
351 } else {
352 Ok(Ok(matched))
353 }
354 }
355 CheckGroup::Repeated { rule, count } => {
356 let mut matched = vec![];
357 for _ in 0..*count {
358 match rule.apply(context) {
359 Ok(matches) => {
360 matched.push(matches);
361 }
362 Err(err) => {
363 test_result.failed(CheckFailedError::MatchNoneErrorNote {
364 span: rule.span(),
365 match_file: context.match_file(),
366 error: Some(RelatedError::new(err)),
367 });
368 break;
369 }
370 }
371 }
372 if matched.is_empty() || matched.iter().all(|m| m.range().is_none()) {
373 Ok(Err(matched))
374 } else {
375 Ok(Ok(matched))
376 }
377 }
378 CheckGroup::Unordered(check_dag) => match check_dag.apply(context) {
379 Ok(matches) => {
380 if matches.range().is_none() {
381 Ok(Err(vec![matches]))
382 } else {
383 Ok(Ok(vec![matches]))
384 }
385 }
386 Err(err) => {
387 test_result.failed(CheckFailedError::MatchNoneErrorNote {
388 span: check_dag.span(),
389 match_file: context.match_file(),
390 error: Some(RelatedError::new(err)),
391 });
392 Ok(Err(vec![]))
393 }
394 },
395 CheckGroup::Bounded {
396 left: check_dag,
397 ref right,
398 } => {
399 let initial_result = check_dag.apply(context);
400 match check_group(right, test_result, context) {
401 Ok(Ok(mut right_matches)) => {
402 let right_range = right_matches[0]
403 .range()
404 .expect("expected at least one match");
405 match initial_result {
406 Ok(mut left_matches) => {
407 if let Some(left_range) = left_matches.range() {
408 if left_range.start >= right_range.start
409 || left_range.end >= right_range.start
410 {
411 // At least one matching CHECK-DAG overlaps following CHECK,
412 // so visit each match result and rewrite overlapping matches
413 // to better guide users
414 let right_pattern_span = right.first_pattern_span();
415 for mr in left_matches.iter_mut() {
416 match mr {
417 MatchResult {
418 info: Some(ref mut info),
419 ty,
420 } if ty.is_ok() => {
421 let left_range = info.span.range();
422 if left_range.start >= right_range.start
423 || left_range.end >= right_range.start
424 {
425 let span = info.span;
426 let pattern_span = info.pattern_span;
427 *mr = MatchResult::failed(CheckFailedError::MatchFoundButDiscarded {
428 span,
429 input_file: context.input_file(),
430 labels: vec![
431 RelatedLabel::error(Label::new(pattern_span, "matched by this pattern"), context.match_file()),
432 RelatedLabel::warn(Label::new(right_pattern_span, "because it cannot be reordered past this pattern"), context.match_file()),
433 RelatedLabel::note(Label::point(right_range.start, "which begins here"), context.input_file()),
434 ],
435 note: None,
436 });
437 }
438 }
439 MatchResult {
440 info: Some(ref mut info),
441 ty: MatchType::Failed(ref err),
442 } => {
443 let span = info.span;
444 let pattern_span = info.pattern_span;
445 match err {
446 CheckFailedError::MatchError { .. }
447 | CheckFailedError::MatchFoundErrorNote { .. }
448 | CheckFailedError::MatchFoundConstraintFailed { .. }
449 | CheckFailedError::MatchFoundButDiscarded { .. } => {
450 if span.start() >= right_range.start || span.end() >= right_range.start {
451 *mr = MatchResult::failed(CheckFailedError::MatchFoundButDiscarded {
452 span,
453 input_file: context.input_file(),
454 labels: vec![
455 RelatedLabel::error(Label::new(pattern_span, "matched by this pattern"), context.match_file()),
456 RelatedLabel::warn(Label::new(right_pattern_span, "because it cannot be reordered past this pattern"), context.match_file()),
457 RelatedLabel::note(Label::point(right_range.start, "which begins here"), context.input_file()),
458 ],
459 note: None,
460 });
461 }
462 }
463 _ => (),
464 }
465 }
466 _ => continue,
467 }
468 }
469 }
470 }
471 let mut matched = Vec::with_capacity(1 + right_matches.len());
472 matched.push(left_matches);
473 matched.append(&mut right_matches);
474 Ok(Ok(matched))
475 }
476 Err(err) => {
477 test_result.failed(CheckFailedError::MatchNoneErrorNote {
478 span: check_dag.span(),
479 match_file: context.match_file(),
480 error: Some(RelatedError::new(err)),
481 });
482 Ok(Ok(right_matches))
483 }
484 }
485 }
486 Ok(Err(mut right_matches)) => match initial_result {
487 Ok(left_matches) => {
488 let mut matched = Vec::with_capacity(1 + right_matches.len());
489 let is_ok = left_matches.range().is_none();
490 matched.push(left_matches);
491 matched.append(&mut right_matches);
492 if is_ok {
493 Ok(Err(matched))
494 } else {
495 Ok(Ok(matched))
496 }
497 }
498 Err(err) => {
499 test_result.failed(CheckFailedError::MatchNoneErrorNote {
500 span: check_dag.span(),
501 match_file: context.match_file(),
502 error: Some(RelatedError::new(err)),
503 });
504 Ok(Err(right_matches))
505 }
506 },
507 Err(right_err) => {
508 let result = match initial_result {
509 Ok(left_matches) => {
510 if left_matches.range().is_none() {
511 Err(vec![left_matches])
512 } else {
513 Ok(vec![left_matches])
514 }
515 }
516 Err(left_err) => {
517 test_result.failed(CheckFailedError::MatchNoneErrorNote {
518 span: check_dag.span(),
519 match_file: context.match_file(),
520 error: Some(RelatedError::new(left_err)),
521 });
522 Err(vec![])
523 }
524 };
525 test_result.failed(CheckFailedError::MatchNoneErrorNote {
526 span: right.span(),
527 match_file: context.match_file(),
528 error: Some(RelatedError::new(Report::new(right_err))),
529 });
530 Ok(result)
531 }
532 }
533 }
534 CheckGroup::Tree(ref tree) => check_tree(tree, test_result, context),
535 }
536}
537
538fn check_tree<'input, 'a: 'input>(
539 tree: &CheckTree<'a>,
540 test_result: &mut TestResult,
541 context: &mut MatchContext<'input, 'a>,
542) -> Result<Result<Vec<Matches<'input>>, Vec<Matches<'input>>>, CheckFailedError> {
543 match tree {
544 CheckTree::Leaf(ref group) => check_group(group, test_result, context),
545 CheckTree::Both {
546 ref root,
547 ref left,
548 ref right,
549 } => {
550 let mut matched = vec![];
551 match check_tree(left, test_result, context).expect("unexpected check-not error") {
552 // There may be some errors, but at least one pattern matched
553 Ok(mut left_matched) => {
554 assert!(
555 !left_matched.is_empty(),
556 "expected at least one match result"
557 );
558 // Set aside the start of the exclusion region between `left` and `right`
559 let left_end = left_matched
560 .iter()
561 .filter_map(|matches| matches.range().map(|r| r.end))
562 .max()
563 .unwrap();
564 // Add the left matches to the pending test results
565 matched.append(&mut left_matched);
566 match check_tree(right, test_result, context)
567 .expect("unexpected check-not error")
568 {
569 Ok(mut right_matched) => {
570 assert!(
571 !right_matched.is_empty(),
572 "expected at least one match result"
573 );
574 // Find the end of the exclusion region
575 let right_start = right_matched
576 .iter()
577 .filter_map(|matches| matches.range().map(|r| r.start))
578 .min()
579 .unwrap();
580 // Ensure none of the CHECK-NOT patterns match in the exclusion region
581 let exclusion = context.search_range(left_end..right_start);
582 match check_not(root, exclusion, context) {
583 Ok(Ok(num_passed)) => {
584 (0..num_passed).for_each(|_| test_result.passed());
585 }
586 Ok(Err(info)) => {
587 let right_pattern_span = match right.leftmost() {
588 Left(group) => group.first_pattern_span(),
589 Right(patterns) => patterns.first_pattern_span(),
590 };
591 matched.push(Matches::from_iter([MatchResult::failed(CheckFailedError::MatchFoundButExcluded {
592 span: info.span,
593 input_file: context.input_file(),
594 labels: vec![
595 RelatedLabel::error(Label::new(info.pattern_span, "excluded by this pattern"), context.match_file()),
596 RelatedLabel::note(Label::new(right_pattern_span, "exclusion is bounded by this pattern"), context.match_file()),
597 RelatedLabel::note(Label::point(right_start, "which corresponds to this location in the input"), context.input_file()),
598 ],
599 })]));
600 }
601 Err(err) => {
602 // Something went wrong with this pattern
603 matched.push(Matches::from_iter([MatchResult::failed(
604 CheckFailedError::MatchNoneErrorNote {
605 span: root.span(),
606 match_file: context.match_file(),
607 error: Some(RelatedError::new(err)),
608 },
609 )]));
610 }
611 }
612 // Add the right matches to the test results
613 matched.append(&mut right_matched);
614 }
615 Err(mut right_matched) => {
616 // TODO: Check to see if the right-hand side would have matched BEFORE left, and present a better error
617
618 // Since none of the right-hand patterns matched, we will treat the CHECK-NOT patterns
619 // as having passed, since the error of interest is the failed match
620 (0..root.pattern_len()).for_each(|_| test_result.passed());
621 // Add the right matches to the test results
622 matched.append(&mut right_matched);
623 }
624 }
625 Ok(Ok(matched))
626 }
627 // None of the left-hand patterns matched
628 Err(mut left_matched) => {
629 // Add the failed matches to the test results
630 matched.append(&mut left_matched);
631 // Proceed with the right-hand patterns
632 let left_end = context.cursor().start();
633 match check_tree(right, test_result, context)
634 .expect("unexpected check-not error")
635 {
636 Ok(mut right_matched) => {
637 assert!(
638 !right_matched.is_empty(),
639 "expected at least one match result"
640 );
641 // Find the end of the exclusion region
642 let right_start = right_matched
643 .iter()
644 .filter_map(|matches| matches.range().map(|r| r.start))
645 .min()
646 .unwrap();
647 // Ensure none of the CHECK-NOT patterns match in the exclusion region
648 let exclusion = context.search_range(left_end..right_start);
649 match check_not(root, exclusion, context) {
650 Ok(Ok(num_passed)) => {
651 (0..num_passed).for_each(|_| test_result.passed());
652 }
653 Ok(Err(info)) => {
654 matched.push(Matches::from_iter([MatchResult::failed(
655 CheckFailedError::MatchFoundButExcluded {
656 span: info.span,
657 input_file: context.input_file(),
658 labels: vec![RelatedLabel::error(
659 Label::new(info.pattern_span, "by this pattern"),
660 context.match_file(),
661 )],
662 },
663 )]));
664 }
665 Err(err) => {
666 // Something went wrong with this pattern
667 matched.push(Matches::from_iter([MatchResult::failed(
668 CheckFailedError::MatchNoneErrorNote {
669 span: root.span(),
670 match_file: context.match_file(),
671 error: Some(RelatedError::new(err)),
672 },
673 )]));
674 }
675 }
676 // Add the right matches to the test results
677 matched.append(&mut right_matched);
678 Ok(Ok(matched))
679 }
680 Err(mut right_matched) => {
681 // Since none of the right-hand patterns matched, we will treat the CHECK-NOT patterns
682 // as having passed, since the error of interest is the failed match
683 (0..root.pattern_len()).for_each(|_| test_result.passed());
684 // Add the right matches to the test results
685 matched.append(&mut right_matched);
686 Ok(Err(matched))
687 }
688 }
689 }
690 }
691 }
692 CheckTree::Left { root, ref left } => {
693 let left_end = context.cursor().start();
694 match check_tree(left, test_result, context).expect("unexpected check-not error") {
695 Ok(mut left_matched) => {
696 assert!(
697 !left_matched.is_empty(),
698 "expected at least one match result"
699 );
700
701 let exclusion = context.search_block();
702 match check_not(root, exclusion, context) {
703 Ok(Ok(num_passed)) => {
704 (0..num_passed).for_each(|_| test_result.passed());
705 }
706 Ok(Err(info)) => {
707 left_matched.push(Matches::from_iter([MatchResult::failed(
708 CheckFailedError::MatchFoundButExcluded {
709 span: info.span,
710 input_file: context.input_file(),
711 labels: vec![RelatedLabel::error(
712 Label::new(info.pattern_span, "by this pattern"),
713 context.match_file(),
714 )],
715 },
716 )]));
717 }
718 Err(err) => {
719 // Something went wrong with this pattern
720 left_matched.push(Matches::from_iter([MatchResult::failed(
721 CheckFailedError::MatchNoneErrorNote {
722 span: root.span(),
723 match_file: context.match_file(),
724 error: Some(RelatedError::new(err)),
725 },
726 )]));
727 }
728 }
729 Ok(Ok(left_matched))
730 }
731 Err(mut left_matched) => {
732 // None of the left-hand patterns matched, but we can proceed with the CHECK-NOT anyway
733 let right_start = context.cursor().end();
734 let exclusion = context.search_range(left_end..right_start);
735 match check_not(root, exclusion, context) {
736 Ok(Ok(num_passed)) => {
737 (0..num_passed).for_each(|_| test_result.passed());
738 }
739 Ok(Err(info)) => {
740 left_matched.push(Matches::from_iter([MatchResult::failed(
741 CheckFailedError::MatchFoundButExcluded {
742 span: info.span,
743 input_file: context.input_file(),
744 labels: vec![RelatedLabel::error(
745 Label::new(info.pattern_span, "by this pattern"),
746 context.match_file(),
747 )],
748 },
749 )]));
750 }
751 Err(err) => {
752 // Something went wrong with this pattern
753 left_matched.push(Matches::from_iter([MatchResult::failed(
754 CheckFailedError::MatchNoneErrorNote {
755 span: root.span(),
756 match_file: context.match_file(),
757 error: Some(RelatedError::new(err)),
758 },
759 )]));
760 }
761 }
762 Ok(Err(left_matched))
763 }
764 }
765 }
766 CheckTree::Right { root, ref right } => {
767 let left_end = context.cursor().start();
768 match check_tree(right, test_result, context).expect("unexpected check-not error") {
769 Ok(mut right_matched) => {
770 assert!(
771 !right_matched.is_empty(),
772 "expected at least one match result"
773 );
774 let right_start = right_matched
775 .iter()
776 .filter_map(|matches| matches.range().map(|r| r.start))
777 .min()
778 .unwrap();
779
780 let exclusion = context.search_range(left_end..right_start);
781 match check_not(root, exclusion, context) {
782 Ok(Ok(num_passed)) => {
783 (0..num_passed).for_each(|_| test_result.passed());
784 }
785 Ok(Err(info)) => {
786 right_matched.push(Matches::from_iter([MatchResult::failed(
787 CheckFailedError::MatchFoundButExcluded {
788 span: info.span,
789 input_file: context.input_file(),
790 labels: vec![RelatedLabel::error(
791 Label::new(info.pattern_span, "by this pattern"),
792 context.match_file(),
793 )],
794 },
795 )]));
796 }
797 Err(err) => {
798 // Something went wrong with this pattern
799 right_matched.push(Matches::from_iter([MatchResult::failed(
800 CheckFailedError::MatchNoneErrorNote {
801 span: root.span(),
802 match_file: context.match_file(),
803 error: Some(RelatedError::new(err)),
804 },
805 )]));
806 }
807 }
808 Ok(Ok(right_matched))
809 }
810 Err(right_matched) => {
811 // Since none of the right-hand patterns matched, we will treat the CHECK-NOT patterns
812 // as having passed, since the error of interest is the failed match
813 (0..root.pattern_len()).for_each(|_| test_result.passed());
814 Ok(Err(right_matched))
815 }
816 }
817 }
818 }
819}
820
821fn check_not<'input, 'a: 'input>(
822 patterns: &MatchAny<'a>,
823 input: Input<'input>,
824 context: &mut MatchContext<'input, 'a>,
825) -> DiagResult<Result<usize, MatchInfo<'input>>> {
826 match patterns.try_match_mut(input, context)? {
827 MatchResult {
828 info: Some(info),
829 ty,
830 } if ty.is_ok() => Ok(Err(info)),
831 result => {
832 assert!(!result.is_ok());
833 Ok(Ok(patterns.pattern_len()))
834 }
835 }
836}
837
838#[derive(Debug)]
839pub struct BlockInfo {
840 /// The span of the CHECK-LABEL span which started this block, if applicable
841 #[allow(unused)]
842 pub label_info: Option<MatchInfo<'static>>,
843 /// The section index range in the check program which covers all of the sections
844 /// included in this block
845 pub sections: Option<Range<usize>>,
846 /// The starting byte index of the block, if known.
847 ///
848 /// The index begins at the start of the next line following the CHECK-LABEL line
849 ///
850 /// If None, the CHECK-LABEL pattern was never found; `end` must also be None
851 /// in that case
852 pub start: Option<usize>,
853 /// The ending byte index of the block, if known.
854 ///
855 /// The index ends at the last byte of the line preceding a subsequent CHECK-LABEL.
856 ///
857 /// If subsequent blocks with start indices are recorded, this must be Some with
858 /// an end index relative to the next start index in ascending order.
859 pub end: Option<usize>,
860 /// The end-of-file index
861 pub eof: usize,
862}
863impl BlockInfo {
864 pub fn range(&self) -> Range<usize> {
865 Range::new(self.start.unwrap_or(0), self.end.unwrap_or(self.eof))
866 }
867}