|
| 1 | +use std::borrow::Cow; |
| 2 | + |
| 3 | +use serde_json::Value; |
| 4 | + |
| 5 | +use crate::cache::Cache; |
| 6 | + |
| 7 | +#[derive(Debug)] |
| 8 | +pub struct Directive { |
| 9 | + pub kind: DirectiveKind, |
| 10 | + pub path: String, |
| 11 | + pub lineno: usize, |
| 12 | +} |
| 13 | + |
| 14 | +#[derive(Debug)] |
| 15 | +pub enum DirectiveKind { |
| 16 | + /// `//@ has <path>` |
| 17 | + /// |
| 18 | + /// Checks the path exists. |
| 19 | + HasPath, |
| 20 | + |
| 21 | + /// `//@ has <path> <value>` |
| 22 | + /// |
| 23 | + /// Check one thing at the path is equal to the value. |
| 24 | + HasValue { value: String }, |
| 25 | + |
| 26 | + /// `//@ !has <path>` |
| 27 | + /// |
| 28 | + /// Checks the path doesn't exist. |
| 29 | + HasNotPath, |
| 30 | + |
| 31 | + /// `//@ !has <path> <value>` |
| 32 | + /// |
| 33 | + /// Checks the path exists, but doesn't have the given value. |
| 34 | + HasNotValue { value: String }, |
| 35 | + |
| 36 | + /// `//@ is <path> <value>` |
| 37 | + /// |
| 38 | + /// Check the path is the given value. |
| 39 | + Is { value: String }, |
| 40 | + |
| 41 | + /// `//@ is <path> <value> <value>...` |
| 42 | + /// |
| 43 | + /// Check that the path matches to exactly every given value. |
| 44 | + IsMany { values: Vec<String> }, |
| 45 | + |
| 46 | + /// `//@ !is <path> <value>` |
| 47 | + /// |
| 48 | + /// Check the path isn't the given value. |
| 49 | + IsNot { value: String }, |
| 50 | + |
| 51 | + /// `//@ count <path> <value>` |
| 52 | + /// |
| 53 | + /// Check the path has the expected number of matches. |
| 54 | + CountIs { expected: usize }, |
| 55 | + |
| 56 | + /// `//@ set <name> = <path>` |
| 57 | + Set { variable: String }, |
| 58 | +} |
| 59 | + |
| 60 | +impl DirectiveKind { |
| 61 | + /// Returns both the kind and the path. |
| 62 | + /// |
| 63 | + /// Returns `None` if the directive isn't from jsondocck (e.g. from compiletest). |
| 64 | + pub fn parse<'a>( |
| 65 | + directive_name: &str, |
| 66 | + negated: bool, |
| 67 | + args: &'a [String], |
| 68 | + ) -> Option<(Self, &'a str)> { |
| 69 | + let kind = match (directive_name, negated) { |
| 70 | + ("count", false) => { |
| 71 | + assert_eq!(args.len(), 2); |
| 72 | + let expected = args[1].parse().expect("invalid number for `count`"); |
| 73 | + Self::CountIs { expected } |
| 74 | + } |
| 75 | + |
| 76 | + ("ismany", false) => { |
| 77 | + // FIXME: Make this >= 3, and migrate len(values)==1 cases to @is |
| 78 | + assert!(args.len() >= 2, "Not enough args to `ismany`"); |
| 79 | + let values = args[1..].to_owned(); |
| 80 | + Self::IsMany { values } |
| 81 | + } |
| 82 | + |
| 83 | + ("is", false) => { |
| 84 | + assert_eq!(args.len(), 2); |
| 85 | + Self::Is { value: args[1].clone() } |
| 86 | + } |
| 87 | + ("is", true) => { |
| 88 | + assert_eq!(args.len(), 2); |
| 89 | + Self::IsNot { value: args[1].clone() } |
| 90 | + } |
| 91 | + |
| 92 | + ("set", false) => { |
| 93 | + assert_eq!(args.len(), 3); |
| 94 | + assert_eq!(args[1], "="); |
| 95 | + return Some((Self::Set { variable: args[0].clone() }, &args[2])); |
| 96 | + } |
| 97 | + |
| 98 | + ("has", false) => match args { |
| 99 | + [_path] => Self::HasPath, |
| 100 | + [_path, value] => Self::HasValue { value: value.clone() }, |
| 101 | + _ => panic!("`//@ has` must have 2 or 3 arguments, but got {args:?}"), |
| 102 | + }, |
| 103 | + ("has", true) => match args { |
| 104 | + [_path] => Self::HasNotPath, |
| 105 | + [_path, value] => Self::HasNotValue { value: value.clone() }, |
| 106 | + _ => panic!("`//@ !has` must have 2 or 3 arguments, but got {args:?}"), |
| 107 | + }, |
| 108 | + // Ignore compiletest directives, like //@ edition |
| 109 | + (_, false) if KNOWN_DIRECTIVE_NAMES.contains(&directive_name) => { |
| 110 | + return None; |
| 111 | + } |
| 112 | + _ => { |
| 113 | + panic!("Invalid directive `//@ {}{directive_name}`", if negated { "!" } else { "" }) |
| 114 | + } |
| 115 | + }; |
| 116 | + |
| 117 | + Some((kind, &args[0])) |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +impl Directive { |
| 122 | + /// Performs the actual work of ensuring a directive passes. |
| 123 | + pub fn check(&self, cache: &mut Cache) -> Result<(), String> { |
| 124 | + let matches = cache.select(&self.path); |
| 125 | + match &self.kind { |
| 126 | + DirectiveKind::HasPath => { |
| 127 | + if matches.is_empty() { |
| 128 | + return Err("matched to no values".to_owned()); |
| 129 | + } |
| 130 | + } |
| 131 | + DirectiveKind::HasNotPath => { |
| 132 | + if !matches.is_empty() { |
| 133 | + return Err(format!("matched to {matches:?}, but wanted no matches")); |
| 134 | + } |
| 135 | + } |
| 136 | + DirectiveKind::HasValue { value } => { |
| 137 | + let want_value = string_to_value(value, cache); |
| 138 | + if !matches.contains(&want_value.as_ref()) { |
| 139 | + return Err(format!( |
| 140 | + "matched to {matches:?}, which didn't contain {want_value:?}" |
| 141 | + )); |
| 142 | + } |
| 143 | + } |
| 144 | + DirectiveKind::HasNotValue { value } => { |
| 145 | + let wantnt_value = string_to_value(value, cache); |
| 146 | + if matches.contains(&wantnt_value.as_ref()) { |
| 147 | + return Err(format!( |
| 148 | + "matched to {matches:?}, which contains unwanted {wantnt_value:?}" |
| 149 | + )); |
| 150 | + } else if matches.is_empty() { |
| 151 | + return Err(format!( |
| 152 | + "got no matches, but expected some matched (not containing {wantnt_value:?}" |
| 153 | + )); |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + DirectiveKind::Is { value } => { |
| 158 | + let want_value = string_to_value(value, cache); |
| 159 | + let matched = get_one(&matches)?; |
| 160 | + if matched != want_value.as_ref() { |
| 161 | + return Err(format!("matched to {matched:?} but want {want_value:?}")); |
| 162 | + } |
| 163 | + } |
| 164 | + DirectiveKind::IsNot { value } => { |
| 165 | + let wantnt_value = string_to_value(value, cache); |
| 166 | + let matched = get_one(&matches)?; |
| 167 | + if matched == wantnt_value.as_ref() { |
| 168 | + return Err(format!("got value {wantnt_value:?}, but want anything else")); |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + DirectiveKind::IsMany { values } => { |
| 173 | + // Serde json doesn't implement Ord or Hash for Value, so we must |
| 174 | + // use a Vec here. While in theory that makes setwize equality |
| 175 | + // O(n^2), in practice n will never be large enough to matter. |
| 176 | + let expected_values = |
| 177 | + values.iter().map(|v| string_to_value(v, cache)).collect::<Vec<_>>(); |
| 178 | + if expected_values.len() != matches.len() { |
| 179 | + return Err(format!( |
| 180 | + "Expected {} values, but matched to {} values ({:?})", |
| 181 | + expected_values.len(), |
| 182 | + matches.len(), |
| 183 | + matches |
| 184 | + )); |
| 185 | + }; |
| 186 | + for got_value in matches { |
| 187 | + if !expected_values.iter().any(|exp| &**exp == got_value) { |
| 188 | + return Err(format!("has match {got_value:?}, which was not expected",)); |
| 189 | + } |
| 190 | + } |
| 191 | + } |
| 192 | + DirectiveKind::CountIs { expected } => { |
| 193 | + if *expected != matches.len() { |
| 194 | + return Err(format!( |
| 195 | + "matched to `{matches:?}` with length {}, but expected length {expected}", |
| 196 | + matches.len(), |
| 197 | + )); |
| 198 | + } |
| 199 | + } |
| 200 | + DirectiveKind::Set { variable } => { |
| 201 | + let value = get_one(&matches)?; |
| 202 | + let r = cache.variables.insert(variable.to_owned(), value.clone()); |
| 203 | + assert!(r.is_none(), "name collision: {variable:?} is duplicated"); |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + Ok(()) |
| 208 | + } |
| 209 | +} |
| 210 | + |
| 211 | +fn get_one<'a>(matches: &[&'a Value]) -> Result<&'a Value, String> { |
| 212 | + match matches { |
| 213 | + [] => Err("matched to no values".to_owned()), |
| 214 | + [matched] => Ok(matched), |
| 215 | + _ => Err(format!("matched to multiple values {matches:?}, but want exactly 1")), |
| 216 | + } |
| 217 | +} |
| 218 | + |
| 219 | +// FIXME: This setup is temporary until we figure out how to improve this situation. |
| 220 | +// See <https://github.com/rust-lang/rust/issues/125813#issuecomment-2141953780>. |
| 221 | +include!(concat!(env!("CARGO_MANIFEST_DIR"), "/../compiletest/src/directive-list.rs")); |
| 222 | + |
| 223 | +fn string_to_value<'a>(s: &str, cache: &'a Cache) -> Cow<'a, Value> { |
| 224 | + if s.starts_with("$") { |
| 225 | + Cow::Borrowed(&cache.variables.get(&s[1..]).unwrap_or_else(|| { |
| 226 | + // FIXME(adotinthevoid): Show line number |
| 227 | + panic!("No variable: `{}`. Current state: `{:?}`", &s[1..], cache.variables) |
| 228 | + })) |
| 229 | + } else { |
| 230 | + Cow::Owned(serde_json::from_str(s).expect(&format!("Cannot convert `{}` to json", s))) |
| 231 | + } |
| 232 | +} |
0 commit comments