litcheck_filecheck/rules/
next.rs

1use crate::common::*;
2
3#[derive(Debug)]
4pub struct CheckNext<M> {
5    pattern: M,
6}
7impl<M> CheckNext<M>
8where
9    M: MatcherMut,
10{
11    pub fn new(pattern: M) -> Self {
12        Self { pattern }
13    }
14}
15impl<M> Spanned for CheckNext<M>
16where
17    M: MatcherMut,
18{
19    fn span(&self) -> SourceSpan {
20        self.pattern.span()
21    }
22}
23impl<M> Rule for CheckNext<M>
24where
25    M: MatcherMut,
26{
27    fn kind(&self) -> Check {
28        Check::Next
29    }
30
31    fn apply<'input, 'context, C>(&self, context: &mut C) -> DiagResult<Matches<'input>>
32    where
33        C: Context<'input, 'context> + ?Sized,
34    {
35        let cursor = context.cursor();
36        let start = cursor.start_of_next_line();
37        let block_end = cursor.end();
38        let next_eol = cursor.next_newline_from(start).unwrap_or(block_end);
39        if start >= block_end {
40            // Cannot match, since the search range would
41            // overflow the current block.
42            return Ok(Matches::from(MatchResult {
43                ty: MatchType::Failed(CheckFailedError::MatchNoneButExpected {
44                    span: self.pattern.span(),
45                    match_file: context.match_file(),
46                    note: if cursor.end_of_file() == block_end {
47                        None
48                    } else {
49                        Some(
50                            "search was stopped at end of the preceding CHECK-LABEL scope"
51                                .to_string(),
52                        )
53                    },
54                }),
55                info: None,
56            }));
57        }
58        let input = context.search_range(start..next_eol);
59        let result = self.pattern.try_match_mut(input, context)?;
60        match &result {
61            MatchResult {
62                ty,
63                info: Some(ref info),
64            } if ty.is_ok() => {
65                context.cursor_mut().set_start(info.span.end());
66            }
67            _ => (),
68        }
69        Ok(result.into())
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::pattern::matcher::*;
77    use crate::rules::CheckPlain;
78
79    #[test]
80    fn check_next_test() -> DiagResult<()> {
81        let mut context = TestContext::new();
82        context
83            .with_checks(
84                "
85CHECK: @inc4
86CHECK-NEXT: entry:
87",
88            )
89            .with_input(
90                "
91define void @inc4(i64* %p) {
92entry:
93        %0 = tail call i64 @llvm.atomic.load.add.i64.p0i64(i64* %p, i64 1)
94        ret void
95}
96",
97            );
98        let mut mctx = context.match_context();
99        let pattern = SubstringMatcher::new(
100            Span::new(SourceSpan::from(8..12), Cow::Borrowed("@inc4")),
101            &mctx.config,
102        )
103        .expect("expected pattern to be valid");
104        let rule = CheckPlain::new(pattern);
105        let matches = rule
106            .apply(&mut mctx)
107            .expect("expected non-fatal application of rule");
108        TestResult::from_matches(matches, &mctx).into_result()?;
109
110        let pattern = SubstringMatcher::new(
111            Span::new(SourceSpan::from(22..30), Cow::Borrowed("entry:")),
112            &mctx.config,
113        )
114        .expect("expected pattern to be valid");
115        let rule = CheckNext::new(pattern);
116        let matches = rule
117            .apply(&mut mctx)
118            .expect("expected non-fatal application of rule");
119        let matched = TestResult::from_matches(matches, &mctx).into_result()?;
120        assert_eq!(matched.len(), 1);
121        assert_eq!(matched[0].span.offset(), 30);
122        assert_eq!(matched[0].span.len(), 6);
123        let input = mctx.search();
124        assert_eq!(input.as_str(matched[0].matched_range()), "entry:");
125        assert_eq!(input.buffer()[mctx.cursor().start()], b'\n');
126
127        Ok(())
128    }
129
130    #[test]
131    fn check_next_multiple_times_test() -> DiagResult<()> {
132        let mut context = TestContext::new();
133        context
134            .with_checks(
135                "
136CHECK: @inc4
137CHECK-NEXT: entry:
138CHECK-NEXT:         %0 = tail call
139CHECK-NEXT:         ret void
140CHECK-NEXT: }
141",
142            )
143            .with_input(
144                "
145define void @inc4(i64* %p) {
146entry:
147        %0 = tail call i64 @llvm.atomic.load.add.i64.p0i64(i64* %p, i64 1)
148        ret void
149}
150",
151            );
152
153        context.check()?;
154
155        Ok(())
156    }
157
158    #[test]
159    fn check_next_real() -> DiagResult<()> {
160        let mut context = TestContext::new();
161        context
162            .with_checks(
163                "
164;; RUN: midenc compile --stdout --emit=hir $s | filecheck %s
165(module
166    (func $main (local i32)
167        i32.const 1
168        local.set 0
169        local.get 0
170        drop
171    )
172)
173
174;; CHECK: (module #locals
175;; CHECK-NEXT:     ;; Functions
176;; CHECK-NEXT:     (func (export #main)
177;; CHECK-NEXT:         (block 0
178;; CHECK-NEXT:             (let (v0 i32) (const.i32 0))
179;; CHECK-NEXT:             (let (v1 i32) (const.i32 1))
180;; CHECK-NEXT:             (br (block 1)))
181;; CHECK-EMPTY:
182;; CHECK-NEXT:         (block 1
183;; CHECK-NEXT:             (ret))
184;; CHECK-NEXT:     )
185;; CHECK-NEXT: )
186",
187            )
188            .with_input(
189                "
190(module #locals
191    ;; Functions
192    (func (export #main)
193        (block 0
194            (let (v0 i32) (const.i32 0))
195            (let (v1 i32) (const.i32 1))
196            (br (block 1)))
197
198        (block 1
199            (ret))
200    )
201)
202",
203            );
204
205        context.check()?;
206
207        Ok(())
208    }
209}