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