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