compiletest/directives/line.rs
1use std::fmt;
2
3use camino::Utf8Path;
4
5const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
6
7/// If the given line begins with the appropriate comment prefix for a directive,
8/// returns a struct containing various parts of the directive.
9pub(crate) fn line_directive<'a>(
10 file_path: &'a Utf8Path,
11 line_number: usize,
12 original_line: &'a str,
13) -> Option<DirectiveLine<'a>> {
14 // Ignore lines that don't start with the comment prefix.
15 let after_comment =
16 original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start();
17
18 let revision;
19 let raw_directive;
20
21 if let Some(after_open_bracket) = after_comment.strip_prefix('[') {
22 // A comment like `//@[foo]` only applies to revision `foo`.
23 let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
24 panic!(
25 "malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`"
26 )
27 };
28
29 revision = Some(line_revision);
30 raw_directive = after_close_bracket.trim_start();
31 } else {
32 revision = None;
33 raw_directive = after_comment;
34 };
35
36 // The directive name ends at the first occurrence of colon, space, or end-of-string.
37 let name = raw_directive.split([':', ' ']).next().expect("split is never empty");
38
39 Some(DirectiveLine { file_path, line_number, revision, raw_directive, name })
40}
41
42/// The (partly) broken-down contents of a line containing a test directive,
43/// which `iter_directives` passes to its callback function.
44///
45/// For example:
46///
47/// ```text
48/// //@ compile-flags: -O
49/// ^^^^^^^^^^^^^^^^^ raw_directive
50/// ^^^^^^^^^^^^^ name
51///
52/// //@ [foo] compile-flags: -O
53/// ^^^ revision
54/// ^^^^^^^^^^^^^^^^^ raw_directive
55/// ^^^^^^^^^^^^^ name
56/// ```
57pub(crate) struct DirectiveLine<'a> {
58 /// Path of the file containing this line.
59 ///
60 /// Mostly used for diagnostics, but some directives (e.g. `//@ pp-exact`)
61 /// also use it to compute a value based on the filename.
62 pub(crate) file_path: &'a Utf8Path,
63 pub(crate) line_number: usize,
64
65 /// Some test directives start with a revision name in square brackets
66 /// (e.g. `[foo]`), and only apply to that revision of the test.
67 /// If present, this field contains the revision name (e.g. `foo`).
68 pub(crate) revision: Option<&'a str>,
69
70 /// The main part of the directive, after removing the comment prefix
71 /// and the optional revision specifier.
72 ///
73 /// This is "raw" because the directive's name and colon-separated value
74 /// (if present) have not yet been extracted or checked.
75 raw_directive: &'a str,
76
77 /// Name of the directive.
78 ///
79 /// Invariant: `self.raw_directive.starts_with(self.name)`
80 pub(crate) name: &'a str,
81}
82
83impl<'ln> DirectiveLine<'ln> {
84 pub(crate) fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool {
85 self.revision.is_none() || self.revision == test_revision
86 }
87
88 /// Helper method used by `value_after_colon` and `remark_after_space`.
89 /// Don't call this directly.
90 fn rest_after_separator(&self, separator: u8) -> Option<&'ln str> {
91 let n = self.name.len();
92 if self.raw_directive.as_bytes().get(n) != Some(&separator) {
93 return None;
94 }
95
96 Some(&self.raw_directive[n + 1..])
97 }
98
99 /// If this directive uses `name: value` syntax, returns the part after
100 /// the colon character.
101 pub(crate) fn value_after_colon(&self) -> Option<&'ln str> {
102 self.rest_after_separator(b':')
103 }
104
105 /// If this directive uses `name remark` syntax, returns the part after
106 /// the separating space.
107 pub(crate) fn remark_after_space(&self) -> Option<&'ln str> {
108 self.rest_after_separator(b' ')
109 }
110
111 /// Allows callers to print `raw_directive` if necessary,
112 /// without accessing the field directly.
113 pub(crate) fn display(&self) -> impl fmt::Display {
114 self.raw_directive
115 }
116}