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}