this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

internal/core/compile: add matchN builtin validator

The matchN validator can be used to implement a host
of useful validators, including the JSON schema
equavalent of "not", "oneOf", and "anyOf".

Issue #3176
Issue #3165

Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: I904bfe38a09a8a83d08a09d46866a69f87c8cc7c
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1198922
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>

+957 -3
+832
cue/testdata/builtins/matchn.txtar
··· 1 + -- in.cue -- 2 + import "math" 3 + 4 + #Foo: { 5 + a: int 6 + } 7 + 8 + match: { 9 + [=~"^single"]: matchN(1, [#Foo]) 10 + singleOK: a: 2 11 + singleErr: a: "foo" 12 + 13 + [=~"^incomplete"]: matchN(1, [#Foo]) 14 + incompleteOK: a: int 15 + incompleteErr: a: string 16 + 17 + #A: { 18 + a: int 19 + b: _ 20 + ... 21 + } 22 + 23 + defaults: { 24 + // Because validators distribute over disjunctions, this validator may erase 25 + // a default value. Nonethenless, it will be guaranteed that the value 26 + // resulting from evaluation does not violate the validator. 27 + // TODO(defaults): take this use case into consideration for the defaults 28 + // rethink, as it seems less than ideal. Note that this typically not an 29 + // issue if the schema matched against is not concrete. 30 + [=~"^pickTop"]: matchN(1, [2]) 31 + pickTopOK1: *2 | int 32 + pickTopOK2: int 33 + pickTopErr: *3 | int // Final values taken. 34 + 35 + // Nested default values will be evaluated and may not be overridden by 36 + // values in the validator. 37 + [=~"^pickNested1"]: matchN(1, [{a: 2}]) 38 + pickNested1OK1: a: *2 | int 39 + pickNested1OK2: a: int 40 + pickNested1Err: a: *3 | int 41 + 42 + [=~"^pickNested2"]: matchN(1, [{a: <=2}]) 43 + pickNested2OK1: a: *2 | int 44 + pickNested2OK2: a: int 45 + pickNested2Err: a: *3 | int 46 + } 47 + 48 + // Stress test potential exponential behavior. 49 + nestedOK: { 50 + matchN(4, [#A, #A, #A, #A]) 51 + 52 + a: 2 53 + b: { 54 + matchN(4, [#A, #A, #A, #A]) 55 + 56 + a: 3 57 + b: matchN(4, [#A, #A, #A, #A]) 58 + b: a: 4 59 + c: matchN(4, [#A, #A, #A, #A]) 60 + c: a: 5 61 + } 62 + c: { 63 + matchN(4, [#A, #A, #A, #A]) 64 + 65 + a: 3 66 + b: matchN(4, [#A, #A, #A, #A]) 67 + b: a: 4 68 + c: matchN(4, [#A, #A, #A, #A]) 69 + c: a: 5 70 + } 71 + } 72 + } 73 + 74 + not: { 75 + [=~"^single"]: matchN(0, [#Foo]) 76 + singleOK: a: "foo" 77 + singleErr: a: 2 78 + 79 + [=~"^double"]: matchN(0, [matchN(0, [#Foo])]) 80 + doubleOK: a: 2 81 + doubleErr: a: "foo" 82 + } 83 + 84 + oneOf: { 85 + [=~"^multiple1"]: matchN(1, [math.MultipleOf(3), math.MultipleOf(5)]) 86 + 87 + multiple1Err1: 1 88 + 89 + multiple1OK1: 3 90 + multiple1OK2: 5 91 + 92 + multiple1Err2: 15 93 + } 94 + 95 + anyOf: { 96 + [=~"^multiple1"]: matchN(>0, [math.MultipleOf(3), math.MultipleOf(5)]) 97 + 98 + multiple1Err1: 1 99 + 100 + multiple1OK1: 3 101 + multiple1OK2: 5 102 + multiple1OK3: 15 103 + } 104 + 105 + 106 + allOf: { 107 + [=~"^multiple1"]: matchN(2, [math.MultipleOf(3), math.MultipleOf(5)]) 108 + 109 + multiple1Err1: 1 110 + multiple1Err2: 3 111 + multiple1Err3: 5 112 + 113 + multiple1OK1: 15 114 + } 115 + -- out/eval/stats -- 116 + Leaks: 3 117 + Freed: 505 118 + Reused: 497 119 + Allocs: 11 120 + Retain: 40 121 + 122 + Unifications: 496 123 + Conjuncts: 859 124 + Disjuncts: 545 125 + -- out/eval -- 126 + Errors: 127 + match.singleErr: invalid value {a:"foo"} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 128 + ./in.cue:8:17 129 + ./in.cue:8:24 130 + ./in.cue:10:13 131 + match.incompleteErr: invalid value {a:string} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 132 + ./in.cue:12:21 133 + ./in.cue:12:28 134 + ./in.cue:14:17 135 + match.defaults.pickNested1Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:2}])): 0 matched, expected 1: 136 + ./in.cue:36:23 137 + ./in.cue:36:30 138 + ./in.cue:39:19 139 + match.defaults.pickNested2Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:<=2}])): 0 matched, expected 1: 140 + ./in.cue:41:23 141 + ./in.cue:41:30 142 + ./in.cue:44:19 143 + not.singleErr: invalid value {a:2} (does not satisfy matchN(0, [{a:int}])): 1 matched, expected 0: 144 + ./in.cue:74:17 145 + ./in.cue:74:24 146 + ./in.cue:76:13 147 + not.doubleErr: invalid value {a:"foo"} (does not satisfy matchN(0, [matchN(0, [#Foo])])): 1 matched, expected 0: 148 + ./in.cue:78:17 149 + ./in.cue:78:24 150 + ./in.cue:80:13 151 + oneOf.multiple1Err1: invalid value 1 (does not satisfy matchN(1, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected 1: 152 + ./in.cue:84:20 153 + ./in.cue:84:27 154 + ./in.cue:86:17 155 + oneOf.multiple1Err2: invalid value 15 (does not satisfy matchN(1, [math.MultipleOf(3),math.MultipleOf(5)])): 2 matched, expected 1: 156 + ./in.cue:84:20 157 + ./in.cue:84:27 158 + ./in.cue:91:17 159 + anyOf.multiple1Err1: invalid value 1 (does not satisfy matchN(>0, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected >0: 160 + ./in.cue:95:20 161 + ./in.cue:95:27 162 + ./in.cue:97:17 163 + allOf.multiple1Err1: invalid value 1 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected 2: 164 + ./in.cue:106:20 165 + ./in.cue:106:27 166 + ./in.cue:108:17 167 + allOf.multiple1Err2: invalid value 3 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 1 matched, expected 2: 168 + ./in.cue:106:20 169 + ./in.cue:106:27 170 + ./in.cue:109:17 171 + allOf.multiple1Err3: invalid value 5 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 1 matched, expected 2: 172 + ./in.cue:106:20 173 + ./in.cue:106:27 174 + ./in.cue:110:17 175 + 176 + Result: 177 + (_|_){ 178 + // [eval] 179 + #Foo: (#struct){ 180 + a: (int){ int } 181 + } 182 + match: (_|_){ 183 + // [eval] 184 + singleOK: (struct){ 185 + a: (int){ 2 } 186 + } 187 + singleErr: (_|_){ 188 + // [eval] match.singleErr: invalid value {a:"foo"} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 189 + // ./in.cue:8:17 190 + // ./in.cue:8:24 191 + // ./in.cue:10:13 192 + a: (string){ "foo" } 193 + } 194 + incompleteOK: (struct){ 195 + a: (int){ int } 196 + } 197 + incompleteErr: (_|_){ 198 + // [eval] match.incompleteErr: invalid value {a:string} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 199 + // ./in.cue:12:21 200 + // ./in.cue:12:28 201 + // ./in.cue:14:17 202 + a: (string){ string } 203 + } 204 + #A: (#struct){ 205 + a: (int){ int } 206 + b: (_){ _ } 207 + } 208 + defaults: (_|_){ 209 + // [eval] 210 + pickTopOK1: (int){ |(*(int){ 2 }, (int){ &(matchN(1, (#list){ 211 + 0: (int){ 2 } 212 + }), int) }) } 213 + pickTopOK2: (int){ &(matchN(1, (#list){ 214 + 0: (_|_){// 2 215 + } 216 + }), int) } 217 + pickTopErr: (int){ &(matchN(1, (#list){ 218 + 0: (int){ 2 } 219 + }), int) } 220 + pickNested1OK1: (struct){ 221 + a: (int){ |(*(int){ 2 }, (int){ int }) } 222 + } 223 + pickNested1OK2: (struct){ 224 + a: (int){ int } 225 + } 226 + pickNested1Err: (_|_){ 227 + // [eval] match.defaults.pickNested1Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:2}])): 0 matched, expected 1: 228 + // ./in.cue:36:23 229 + // ./in.cue:36:30 230 + // ./in.cue:39:19 231 + a: (int){ |(*(int){ 3 }, (int){ int }) } 232 + } 233 + pickNested2OK1: (struct){ 234 + a: (int){ |(*(int){ 2 }, (int){ int }) } 235 + } 236 + pickNested2OK2: (struct){ 237 + a: (int){ int } 238 + } 239 + pickNested2Err: (_|_){ 240 + // [eval] match.defaults.pickNested2Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:<=2}])): 0 matched, expected 1: 241 + // ./in.cue:41:23 242 + // ./in.cue:41:30 243 + // ./in.cue:44:19 244 + a: (int){ |(*(int){ 3 }, (int){ int }) } 245 + } 246 + } 247 + nestedOK: (struct){ 248 + a: (int){ 2 } 249 + b: (struct){ 250 + a: (int){ 3 } 251 + b: (struct){ 252 + a: (int){ 4 } 253 + } 254 + c: (struct){ 255 + a: (int){ 5 } 256 + } 257 + } 258 + c: (struct){ 259 + a: (int){ 3 } 260 + b: (struct){ 261 + a: (int){ 4 } 262 + } 263 + c: (struct){ 264 + a: (int){ 5 } 265 + } 266 + } 267 + } 268 + } 269 + not: (_|_){ 270 + // [eval] 271 + singleOK: (struct){ 272 + a: (string){ "foo" } 273 + } 274 + singleErr: (_|_){ 275 + // [eval] not.singleErr: invalid value {a:2} (does not satisfy matchN(0, [{a:int}])): 1 matched, expected 0: 276 + // ./in.cue:74:17 277 + // ./in.cue:74:24 278 + // ./in.cue:76:13 279 + a: (int){ 2 } 280 + } 281 + doubleOK: (struct){ 282 + a: (int){ 2 } 283 + } 284 + doubleErr: (_|_){ 285 + // [eval] not.doubleErr: invalid value {a:"foo"} (does not satisfy matchN(0, [matchN(0, [#Foo])])): 1 matched, expected 0: 286 + // ./in.cue:78:17 287 + // ./in.cue:78:24 288 + // ./in.cue:80:13 289 + a: (string){ "foo" } 290 + } 291 + } 292 + oneOf: (_|_){ 293 + // [eval] 294 + multiple1Err1: (_|_){ 295 + // [eval] oneOf.multiple1Err1: invalid value 1 (does not satisfy matchN(1, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected 1: 296 + // ./in.cue:84:20 297 + // ./in.cue:84:27 298 + // ./in.cue:86:17 299 + } 300 + multiple1OK1: (int){ 3 } 301 + multiple1OK2: (int){ 5 } 302 + multiple1Err2: (_|_){ 303 + // [eval] oneOf.multiple1Err2: invalid value 15 (does not satisfy matchN(1, [math.MultipleOf(3),math.MultipleOf(5)])): 2 matched, expected 1: 304 + // ./in.cue:84:20 305 + // ./in.cue:84:27 306 + // ./in.cue:91:17 307 + } 308 + } 309 + anyOf: (_|_){ 310 + // [eval] 311 + multiple1Err1: (_|_){ 312 + // [eval] anyOf.multiple1Err1: invalid value 1 (does not satisfy matchN(>0, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected >0: 313 + // ./in.cue:95:20 314 + // ./in.cue:95:27 315 + // ./in.cue:97:17 316 + } 317 + multiple1OK1: (int){ 3 } 318 + multiple1OK2: (int){ 5 } 319 + multiple1OK3: (int){ 15 } 320 + } 321 + allOf: (_|_){ 322 + // [eval] 323 + multiple1Err1: (_|_){ 324 + // [eval] allOf.multiple1Err1: invalid value 1 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected 2: 325 + // ./in.cue:106:20 326 + // ./in.cue:106:27 327 + // ./in.cue:108:17 328 + } 329 + multiple1Err2: (_|_){ 330 + // [eval] allOf.multiple1Err2: invalid value 3 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 1 matched, expected 2: 331 + // ./in.cue:106:20 332 + // ./in.cue:106:27 333 + // ./in.cue:109:17 334 + } 335 + multiple1Err3: (_|_){ 336 + // [eval] allOf.multiple1Err3: invalid value 5 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 1 matched, expected 2: 337 + // ./in.cue:106:20 338 + // ./in.cue:106:27 339 + // ./in.cue:110:17 340 + } 341 + multiple1OK1: (int){ 15 } 342 + } 343 + } 344 + -- out/evalalpha -- 345 + Errors: 346 + match.singleErr: invalid value {a:"foo"} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 347 + ./in.cue:8:17 348 + ./in.cue:8:24 349 + match.incompleteErr: invalid value {a:string} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 350 + ./in.cue:12:21 351 + ./in.cue:12:28 352 + match.defaults.pickNested1Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:2}])): 0 matched, expected 1: 353 + ./in.cue:36:23 354 + ./in.cue:36:30 355 + match.defaults.pickNested2Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:<=2}])): 0 matched, expected 1: 356 + ./in.cue:41:23 357 + ./in.cue:41:30 358 + not.singleErr: invalid value {a:2} (does not satisfy matchN(0, [{a:int}])): 1 matched, expected 0: 359 + ./in.cue:74:17 360 + ./in.cue:74:24 361 + not.doubleErr: invalid value {a:"foo"} (does not satisfy matchN(0, [matchN(0, [{a:int}])])): 1 matched, expected 0: 362 + ./in.cue:78:17 363 + ./in.cue:78:24 364 + oneOf.multiple1Err1: invalid value 1 (does not satisfy matchN(1, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected 1: 365 + ./in.cue:84:20 366 + ./in.cue:84:27 367 + ./in.cue:86:17 368 + oneOf.multiple1Err2: invalid value 15 (does not satisfy matchN(1, [math.MultipleOf(3),math.MultipleOf(5)])): 2 matched, expected 1: 369 + ./in.cue:84:20 370 + ./in.cue:84:27 371 + ./in.cue:91:17 372 + anyOf.multiple1Err1: invalid value 1 (does not satisfy matchN(>0, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected >0: 373 + ./in.cue:95:20 374 + ./in.cue:95:27 375 + ./in.cue:97:17 376 + allOf.multiple1Err1: invalid value 1 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected 2: 377 + ./in.cue:106:20 378 + ./in.cue:106:27 379 + ./in.cue:108:17 380 + allOf.multiple1Err2: invalid value 3 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 1 matched, expected 2: 381 + ./in.cue:106:20 382 + ./in.cue:106:27 383 + ./in.cue:109:17 384 + allOf.multiple1Err3: invalid value 5 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 1 matched, expected 2: 385 + ./in.cue:106:20 386 + ./in.cue:106:27 387 + ./in.cue:110:17 388 + 389 + Result: 390 + (_|_){ 391 + // [eval] 392 + #Foo: (#struct){ 393 + a: (int){ int } 394 + } 395 + match: (_|_){ 396 + // [eval] 397 + singleOK: (struct){ 398 + a: (int){ 2 } 399 + } 400 + singleErr: (_|_){ 401 + // [eval] match.singleErr: invalid value {a:"foo"} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 402 + // ./in.cue:8:17 403 + // ./in.cue:8:24 404 + a: (string){ "foo" } 405 + } 406 + incompleteOK: (struct){ 407 + a: (int){ int } 408 + } 409 + incompleteErr: (_|_){ 410 + // [eval] match.incompleteErr: invalid value {a:string} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 411 + // ./in.cue:12:21 412 + // ./in.cue:12:28 413 + a: (string){ string } 414 + } 415 + #A: (#struct){ 416 + a: (int){ int } 417 + b: (_){ _ } 418 + } 419 + defaults: (_|_){ 420 + // [eval] 421 + pickTopOK1: (int){ |(*(int){ 2 }, (int){ &(matchN(1, (#list){ 422 + 0: (int){ 2 } 423 + }), int) }) } 424 + pickTopOK2: (int){ &(matchN(1, (#list){ 425 + 0: (int){ 2 } 426 + }), int) } 427 + pickTopErr: (int){ &(matchN(1, (#list){ 428 + 0: (int){ 2 } 429 + }), int) } 430 + pickNested1OK1: (struct){ 431 + a: (int){ |(*(int){ 2 }, (int){ int }) } 432 + } 433 + pickNested1OK2: (struct){ 434 + a: (int){ int } 435 + } 436 + pickNested1Err: (_|_){ 437 + // [eval] match.defaults.pickNested1Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:2}])): 0 matched, expected 1: 438 + // ./in.cue:36:23 439 + // ./in.cue:36:30 440 + a: (int){ |(*(int){ 3 }, (int){ int }) } 441 + } 442 + pickNested2OK1: (struct){ 443 + a: (int){ |(*(int){ 2 }, (int){ int }) } 444 + } 445 + pickNested2OK2: (struct){ 446 + a: (int){ int } 447 + } 448 + pickNested2Err: (_|_){ 449 + // [eval] match.defaults.pickNested2Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:<=2}])): 0 matched, expected 1: 450 + // ./in.cue:41:23 451 + // ./in.cue:41:30 452 + a: (int){ |(*(int){ 3 }, (int){ int }) } 453 + } 454 + } 455 + nestedOK: (struct){ 456 + a: (int){ 2 } 457 + b: (struct){ 458 + a: (int){ 3 } 459 + b: (struct){ 460 + a: (int){ 4 } 461 + } 462 + c: (struct){ 463 + a: (int){ 5 } 464 + } 465 + } 466 + c: (struct){ 467 + a: (int){ 3 } 468 + b: (struct){ 469 + a: (int){ 4 } 470 + } 471 + c: (struct){ 472 + a: (int){ 5 } 473 + } 474 + } 475 + } 476 + } 477 + not: (_|_){ 478 + // [eval] 479 + singleOK: (struct){ 480 + a: (string){ "foo" } 481 + } 482 + singleErr: (_|_){ 483 + // [eval] not.singleErr: invalid value {a:2} (does not satisfy matchN(0, [{a:int}])): 1 matched, expected 0: 484 + // ./in.cue:74:17 485 + // ./in.cue:74:24 486 + a: (int){ 2 } 487 + } 488 + doubleOK: (struct){ 489 + a: (int){ 2 } 490 + } 491 + doubleErr: (_|_){ 492 + // [eval] not.doubleErr: invalid value {a:"foo"} (does not satisfy matchN(0, [matchN(0, [{a:int}])])): 1 matched, expected 0: 493 + // ./in.cue:78:17 494 + // ./in.cue:78:24 495 + a: (string){ "foo" } 496 + } 497 + } 498 + oneOf: (_|_){ 499 + // [eval] 500 + multiple1Err1: (_|_){ 501 + // [eval] oneOf.multiple1Err1: invalid value 1 (does not satisfy matchN(1, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected 1: 502 + // ./in.cue:84:20 503 + // ./in.cue:84:27 504 + // ./in.cue:86:17 505 + } 506 + multiple1OK1: (int){ 3 } 507 + multiple1OK2: (int){ 5 } 508 + multiple1Err2: (_|_){ 509 + // [eval] oneOf.multiple1Err2: invalid value 15 (does not satisfy matchN(1, [math.MultipleOf(3),math.MultipleOf(5)])): 2 matched, expected 1: 510 + // ./in.cue:84:20 511 + // ./in.cue:84:27 512 + // ./in.cue:91:17 513 + } 514 + } 515 + anyOf: (_|_){ 516 + // [eval] 517 + multiple1Err1: (_|_){ 518 + // [eval] anyOf.multiple1Err1: invalid value 1 (does not satisfy matchN(>0, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected >0: 519 + // ./in.cue:95:20 520 + // ./in.cue:95:27 521 + // ./in.cue:97:17 522 + } 523 + multiple1OK1: (int){ 3 } 524 + multiple1OK2: (int){ 5 } 525 + multiple1OK3: (int){ 15 } 526 + } 527 + allOf: (_|_){ 528 + // [eval] 529 + multiple1Err1: (_|_){ 530 + // [eval] allOf.multiple1Err1: invalid value 1 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected 2: 531 + // ./in.cue:106:20 532 + // ./in.cue:106:27 533 + // ./in.cue:108:17 534 + } 535 + multiple1Err2: (_|_){ 536 + // [eval] allOf.multiple1Err2: invalid value 3 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 1 matched, expected 2: 537 + // ./in.cue:106:20 538 + // ./in.cue:106:27 539 + // ./in.cue:109:17 540 + } 541 + multiple1Err3: (_|_){ 542 + // [eval] allOf.multiple1Err3: invalid value 5 (does not satisfy matchN(2, [math.MultipleOf(3),math.MultipleOf(5)])): 1 matched, expected 2: 543 + // ./in.cue:106:20 544 + // ./in.cue:106:27 545 + // ./in.cue:110:17 546 + } 547 + multiple1OK1: (int){ 15 } 548 + } 549 + } 550 + -- diff/-out/evalalpha<==>+out/eval -- 551 + diff old new 552 + --- old 553 + +++ new 554 + @@ -2,27 +2,21 @@ 555 + match.singleErr: invalid value {a:"foo"} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 556 + ./in.cue:8:17 557 + ./in.cue:8:24 558 + - ./in.cue:10:13 559 + match.incompleteErr: invalid value {a:string} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 560 + ./in.cue:12:21 561 + ./in.cue:12:28 562 + - ./in.cue:14:17 563 + match.defaults.pickNested1Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:2}])): 0 matched, expected 1: 564 + ./in.cue:36:23 565 + ./in.cue:36:30 566 + - ./in.cue:39:19 567 + match.defaults.pickNested2Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:<=2}])): 0 matched, expected 1: 568 + ./in.cue:41:23 569 + ./in.cue:41:30 570 + - ./in.cue:44:19 571 + not.singleErr: invalid value {a:2} (does not satisfy matchN(0, [{a:int}])): 1 matched, expected 0: 572 + ./in.cue:74:17 573 + ./in.cue:74:24 574 + - ./in.cue:76:13 575 + -not.doubleErr: invalid value {a:"foo"} (does not satisfy matchN(0, [matchN(0, [#Foo])])): 1 matched, expected 0: 576 + +not.doubleErr: invalid value {a:"foo"} (does not satisfy matchN(0, [matchN(0, [{a:int}])])): 1 matched, expected 0: 577 + ./in.cue:78:17 578 + ./in.cue:78:24 579 + - ./in.cue:80:13 580 + oneOf.multiple1Err1: invalid value 1 (does not satisfy matchN(1, [math.MultipleOf(3),math.MultipleOf(5)])): 0 matched, expected 1: 581 + ./in.cue:84:20 582 + ./in.cue:84:27 583 + @@ -63,7 +57,6 @@ 584 + // [eval] match.singleErr: invalid value {a:"foo"} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 585 + // ./in.cue:8:17 586 + // ./in.cue:8:24 587 + - // ./in.cue:10:13 588 + a: (string){ "foo" } 589 + } 590 + incompleteOK: (struct){ 591 + @@ -73,7 +66,6 @@ 592 + // [eval] match.incompleteErr: invalid value {a:string} (does not satisfy matchN(1, [{a:int}])): 0 matched, expected 1: 593 + // ./in.cue:12:21 594 + // ./in.cue:12:28 595 + - // ./in.cue:14:17 596 + a: (string){ string } 597 + } 598 + #A: (#struct){ 599 + @@ -86,8 +78,7 @@ 600 + 0: (int){ 2 } 601 + }), int) }) } 602 + pickTopOK2: (int){ &(matchN(1, (#list){ 603 + - 0: (_|_){// 2 604 + - } 605 + + 0: (int){ 2 } 606 + }), int) } 607 + pickTopErr: (int){ &(matchN(1, (#list){ 608 + 0: (int){ 2 } 609 + @@ -102,7 +93,6 @@ 610 + // [eval] match.defaults.pickNested1Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:2}])): 0 matched, expected 1: 611 + // ./in.cue:36:23 612 + // ./in.cue:36:30 613 + - // ./in.cue:39:19 614 + a: (int){ |(*(int){ 3 }, (int){ int }) } 615 + } 616 + pickNested2OK1: (struct){ 617 + @@ -115,7 +105,6 @@ 618 + // [eval] match.defaults.pickNested2Err: invalid value {a:*3 | int} (does not satisfy matchN(1, [{a:<=2}])): 0 matched, expected 1: 619 + // ./in.cue:41:23 620 + // ./in.cue:41:30 621 + - // ./in.cue:44:19 622 + a: (int){ |(*(int){ 3 }, (int){ int }) } 623 + } 624 + } 625 + @@ -150,7 +139,6 @@ 626 + // [eval] not.singleErr: invalid value {a:2} (does not satisfy matchN(0, [{a:int}])): 1 matched, expected 0: 627 + // ./in.cue:74:17 628 + // ./in.cue:74:24 629 + - // ./in.cue:76:13 630 + a: (int){ 2 } 631 + } 632 + doubleOK: (struct){ 633 + @@ -157,10 +145,9 @@ 634 + a: (int){ 2 } 635 + } 636 + doubleErr: (_|_){ 637 + - // [eval] not.doubleErr: invalid value {a:"foo"} (does not satisfy matchN(0, [matchN(0, [#Foo])])): 1 matched, expected 0: 638 + + // [eval] not.doubleErr: invalid value {a:"foo"} (does not satisfy matchN(0, [matchN(0, [{a:int}])])): 1 matched, expected 0: 639 + // ./in.cue:78:17 640 + // ./in.cue:78:24 641 + - // ./in.cue:80:13 642 + a: (string){ "foo" } 643 + } 644 + } 645 + -- diff/explanation -- 646 + The old evaluator does not correctly handle ToDataAll if a node is 647 + mid-evaluation. The new evaluator does. 648 + -- diff/todo/p3 -- 649 + Missing error positions. 650 + -- out/compile -- 651 + --- in.cue 652 + { 653 + #Foo: { 654 + a: int 655 + } 656 + match: { 657 + [=~"^single"]: matchN(1, [ 658 + 〈2;#Foo〉, 659 + ]) 660 + singleOK: { 661 + a: 2 662 + } 663 + singleErr: { 664 + a: "foo" 665 + } 666 + [=~"^incomplete"]: matchN(1, [ 667 + 〈2;#Foo〉, 668 + ]) 669 + incompleteOK: { 670 + a: int 671 + } 672 + incompleteErr: { 673 + a: string 674 + } 675 + #A: { 676 + a: int 677 + b: _ 678 + ... 679 + } 680 + defaults: { 681 + [=~"^pickTop"]: matchN(1, [ 682 + 2, 683 + ]) 684 + pickTopOK1: (*2|int) 685 + pickTopOK2: int 686 + pickTopErr: (*3|int) 687 + [=~"^pickNested1"]: matchN(1, [ 688 + { 689 + a: 2 690 + }, 691 + ]) 692 + pickNested1OK1: { 693 + a: (*2|int) 694 + } 695 + pickNested1OK2: { 696 + a: int 697 + } 698 + pickNested1Err: { 699 + a: (*3|int) 700 + } 701 + [=~"^pickNested2"]: matchN(1, [ 702 + { 703 + a: <=2 704 + }, 705 + ]) 706 + pickNested2OK1: { 707 + a: (*2|int) 708 + } 709 + pickNested2OK2: { 710 + a: int 711 + } 712 + pickNested2Err: { 713 + a: (*3|int) 714 + } 715 + } 716 + nestedOK: { 717 + matchN(4, [ 718 + 〈2;#A〉, 719 + 〈2;#A〉, 720 + 〈2;#A〉, 721 + 〈2;#A〉, 722 + ]) 723 + a: 2 724 + b: { 725 + matchN(4, [ 726 + 〈3;#A〉, 727 + 〈3;#A〉, 728 + 〈3;#A〉, 729 + 〈3;#A〉, 730 + ]) 731 + a: 3 732 + b: matchN(4, [ 733 + 〈3;#A〉, 734 + 〈3;#A〉, 735 + 〈3;#A〉, 736 + 〈3;#A〉, 737 + ]) 738 + b: { 739 + a: 4 740 + } 741 + c: matchN(4, [ 742 + 〈3;#A〉, 743 + 〈3;#A〉, 744 + 〈3;#A〉, 745 + 〈3;#A〉, 746 + ]) 747 + c: { 748 + a: 5 749 + } 750 + } 751 + c: { 752 + matchN(4, [ 753 + 〈3;#A〉, 754 + 〈3;#A〉, 755 + 〈3;#A〉, 756 + 〈3;#A〉, 757 + ]) 758 + a: 3 759 + b: matchN(4, [ 760 + 〈3;#A〉, 761 + 〈3;#A〉, 762 + 〈3;#A〉, 763 + 〈3;#A〉, 764 + ]) 765 + b: { 766 + a: 4 767 + } 768 + c: matchN(4, [ 769 + 〈3;#A〉, 770 + 〈3;#A〉, 771 + 〈3;#A〉, 772 + 〈3;#A〉, 773 + ]) 774 + c: { 775 + a: 5 776 + } 777 + } 778 + } 779 + } 780 + not: { 781 + [=~"^single"]: matchN(0, [ 782 + 〈2;#Foo〉, 783 + ]) 784 + singleOK: { 785 + a: "foo" 786 + } 787 + singleErr: { 788 + a: 2 789 + } 790 + [=~"^double"]: matchN(0, [ 791 + matchN(0, [ 792 + 〈3;#Foo〉, 793 + ]), 794 + ]) 795 + doubleOK: { 796 + a: 2 797 + } 798 + doubleErr: { 799 + a: "foo" 800 + } 801 + } 802 + oneOf: { 803 + [=~"^multiple1"]: matchN(1, [ 804 + 〈import;math〉.MultipleOf(3), 805 + 〈import;math〉.MultipleOf(5), 806 + ]) 807 + multiple1Err1: 1 808 + multiple1OK1: 3 809 + multiple1OK2: 5 810 + multiple1Err2: 15 811 + } 812 + anyOf: { 813 + [=~"^multiple1"]: matchN(>0, [ 814 + 〈import;math〉.MultipleOf(3), 815 + 〈import;math〉.MultipleOf(5), 816 + ]) 817 + multiple1Err1: 1 818 + multiple1OK1: 3 819 + multiple1OK2: 5 820 + multiple1OK3: 15 821 + } 822 + allOf: { 823 + [=~"^multiple1"]: matchN(2, [ 824 + 〈import;math〉.MultipleOf(3), 825 + 〈import;math〉.MultipleOf(5), 826 + ]) 827 + multiple1Err1: 1 828 + multiple1Err2: 3 829 + multiple1Err3: 5 830 + multiple1OK1: 15 831 + } 832 + }
+13 -2
internal/core/adt/composite.go
··· 718 718 // ToDataAll returns a new v where v and all its descendents contain only 719 719 // the regular fields. 720 720 func (v *Vertex) ToDataAll(ctx *OpContext) *Vertex { 721 + v.Finalize(ctx) 722 + 723 + // TODO(mpvl): this is to work around a bug in the old evaluator, where 724 + // finalize does not always work. 725 + if v.status == evaluating && ctx.isDevVersion() { 726 + return v.ToDataSingle() 727 + } 728 + 721 729 arcs := make([]*Vertex, 0, len(v.Arcs)) 722 730 for _, a := range v.Arcs { 723 731 if !a.IsDefined(ctx) { ··· 756 764 case *Vertex: 757 765 return x.ToDataAll(ctx) 758 766 759 - // The following cases are always erroneous, but we handle them anyway 760 - // to avoid issues with the closedness algorithm down the line. 761 767 case *Disjunction: 762 768 d := *x 763 769 values := x.Values ··· 1332 1338 // CloseInfo is a unique number that tracks a group of conjuncts that need 1333 1339 // belong to a single originating definition. 1334 1340 CloseInfo CloseInfo 1341 + } 1342 + 1343 + // MakeConjunct creates a conjunct from current Environment and CloseInfo of c. 1344 + func (c *OpContext) MakeConjunct(x Expr) Conjunct { 1345 + return MakeConjunct(c.e, x, c.ci) 1335 1346 } 1336 1347 1337 1348 // TODO(perf): replace with composite literal if this helps performance.
+5 -1
internal/core/adt/expr.go
··· 1584 1584 } 1585 1585 1586 1586 func (x *Builtin) WriteName(w io.Writer, c *OpContext) { 1587 - _, _ = fmt.Fprintf(w, "%s.%s", x.Package.StringValue(c), x.Name) 1587 + if x.Package != InvalidLabel { 1588 + _, _ = fmt.Fprintf(w, "%s.%s", x.Package.StringValue(c), x.Name) 1589 + } else { 1590 + _, _ = fmt.Fprintf(w, "%s", x.Name) // stdlib 1591 + } 1588 1592 } 1589 1593 1590 1594 // Kind here represents the case where Builtin is used as a Validator.
+1
internal/core/compile/builtin.go
··· 27 27 structParam = adt.Param{Value: &adt.BasicType{K: adt.StructKind}} 28 28 listParam = adt.Param{Value: &adt.BasicType{K: adt.ListKind}} 29 29 intParam = adt.Param{Value: &adt.BasicType{K: adt.IntKind}} 30 + topParam = adt.Param{Value: &adt.BasicType{K: adt.TopKind}} 30 31 ) 31 32 32 33 var lenBuiltin = &adt.Builtin{
+2
internal/core/compile/predeclared.go
··· 44 44 return lenBuiltin 45 45 case "close", "__close": 46 46 return closeBuiltin 47 + case "matchN", "__matchN": 48 + return matchNBuiltin 47 49 case "and", "__and": 48 50 return andBuiltin 49 51 case "or", "__or":
+104
internal/core/compile/validator.go
··· 1 + // Copyright 2024 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // https://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package compile 16 + 17 + // This file contains validator and other non-monotonic builtins. 18 + 19 + import ( 20 + "cuelang.org/go/internal/core/adt" 21 + ) 22 + 23 + // matchN is a validator that checks that the number of schemas in the given 24 + // list that unify with "self" matches the number passed as the first argument 25 + // of the validator. Note that this number may itself be a number constraint 26 + // and does not have to be a concrete number. 27 + var matchNBuiltin = &adt.Builtin{ 28 + Name: "matchN", 29 + Params: []adt.Param{topParam, intParam, listParam}, // varargs 30 + Result: adt.BoolKind, 31 + Func: func(c *adt.OpContext, args []adt.Value) adt.Expr { 32 + if !c.IsValidator { 33 + return c.NewErrf("matchN is a validator and should not be used as a function") 34 + } 35 + 36 + self := finalizeSelf(c, args[0]) 37 + if err := bottom(c, self); err != nil { 38 + return &adt.Bool{B: false} 39 + } 40 + 41 + constraints := c.Elems(args[2]) 42 + 43 + var count int64 44 + for _, check := range constraints { 45 + v := unifyValidator(c, self, check) 46 + if err := bottom(c, v); err == nil { 47 + // TODO: is it always true that the lack of an error signifies 48 + // success? 49 + count++ 50 + } 51 + } 52 + 53 + bound := args[1] 54 + // TODO: consider a mode to require "all" to pass, for instance by 55 + // supporting the value null or "all". 56 + 57 + b := checkNum(c, bound, count) 58 + if b != nil { 59 + return b 60 + } 61 + return &adt.Bool{B: true} 62 + }, 63 + } 64 + 65 + // finalizeSelf ensures a value is fully evaluated and then strips it of any 66 + // of its validators or default values. 67 + func finalizeSelf(c *adt.OpContext, self adt.Value) adt.Value { 68 + if x, ok := self.(*adt.Vertex); ok { 69 + self = x.ToDataAll(c) 70 + } 71 + return self 72 + } 73 + 74 + func unifyValidator(c *adt.OpContext, self, check adt.Value) *adt.Vertex { 75 + v := &adt.Vertex{} 76 + closeInfo := c.CloseInfo() 77 + v.AddConjunct(adt.MakeConjunct(nil, self, closeInfo)) 78 + v.AddConjunct(adt.MakeConjunct(nil, check, closeInfo)) 79 + v.Finalize(c) 80 + return v 81 + } 82 + 83 + func checkNum(ctx *adt.OpContext, bound adt.Expr, count int64) *adt.Bottom { 84 + n := adt.Vertex{} 85 + n.AddConjunct(ctx.MakeConjunct(bound)) 86 + n.AddConjunct(ctx.MakeConjunct(ctx.NewInt64(count))) 87 + n.Finalize(ctx) 88 + 89 + b, _ := n.BaseValue.(*adt.Bottom) 90 + if b != nil { 91 + return ctx.NewErrf("%d matched, expected %v", count, bound) 92 + } 93 + return nil 94 + } 95 + 96 + func bottom(c *adt.OpContext, v adt.Value) *adt.Bottom { 97 + switch x := v.(type) { 98 + case *adt.Vertex: 99 + return x.Err(c) 100 + case *adt.Bottom: 101 + return x 102 + } 103 + return nil 104 + }