schemurai 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -4
  3. data/ext/schemurai_native/Cargo.lock +55 -1
  4. data/ext/schemurai_native/Cargo.toml +4 -1
  5. data/ext/schemurai_native/extconf.rb +1 -1
  6. data/ext/schemurai_native/glue.c +1679 -62
  7. data/ext/schemurai_native/protocol.def +45 -0
  8. data/ext/schemurai_native/src/budget.rs +49 -0
  9. data/ext/schemurai_native/src/container.rs +460 -0
  10. data/ext/schemurai_native/src/content.rs +74 -27
  11. data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
  12. data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
  13. data/ext/schemurai_native/src/diagnostics.rs +1087 -307
  14. data/ext/schemurai_native/src/enumeration.rs +6 -1
  15. data/ext/schemurai_native/src/evaluation.rs +49 -20
  16. data/ext/schemurai_native/src/format.rs +126 -14
  17. data/ext/schemurai_native/src/graph.rs +37 -0
  18. data/ext/schemurai_native/src/instance_graph.rs +90 -0
  19. data/ext/schemurai_native/src/integer_scan.rs +132 -0
  20. data/ext/schemurai_native/src/lib.rs +917 -238
  21. data/ext/schemurai_native/src/memo.rs +11 -38
  22. data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
  23. data/ext/schemurai_native/src/number.rs +250 -59
  24. data/ext/schemurai_native/src/pattern.rs +147 -109
  25. data/ext/schemurai_native/src/pointer.rs +97 -0
  26. data/ext/schemurai_native/src/runtime/properties.rs +162 -0
  27. data/ext/schemurai_native/src/runtime/reports.rs +266 -0
  28. data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
  29. data/ext/schemurai_native/src/runtime.rs +1020 -0
  30. data/ext/schemurai_native/src/scope.rs +60 -16
  31. data/ext/schemurai_native/src/string.rs +68 -0
  32. data/ext/schemurai_native/src/tracking.rs +244 -174
  33. data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
  34. data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
  35. data/lib/schemurai/container_checks.rb +534 -0
  36. data/lib/schemurai/decimal.rb +11 -0
  37. data/lib/schemurai/dialect.rb +0 -12
  38. data/lib/schemurai/ecma_regexp.rb +46 -0
  39. data/lib/schemurai/evaluation_pool.rb +66 -0
  40. data/lib/schemurai/evaluator.rb +9 -48
  41. data/lib/schemurai/json_pointer.rb +11 -0
  42. data/lib/schemurai/native/diagnostics.rb +4 -3
  43. data/lib/schemurai/native/evaluator.rb +38 -10
  44. data/lib/schemurai/native/null_numeric.rb +23 -0
  45. data/lib/schemurai/number_checks.rb +128 -0
  46. data/lib/schemurai/string_checks.rb +166 -0
  47. data/lib/schemurai/type_checks.rb +59 -0
  48. data/lib/schemurai/value_equality.rb +72 -0
  49. data/lib/schemurai/version.rb +1 -1
  50. data/lib/schemurai/vm/compiler.rb +18 -26
  51. data/lib/schemurai/vm/evaluator.rb +87 -545
  52. data/lib/schemurai/vm/limited_evaluator.rb +7 -22
  53. metadata +25 -2
@@ -38,6 +38,8 @@ SN_CONSTANT(TYPE_INTEGER, 32)
38
38
  SN_CONSTANT(TYPE_STRING, 64)
39
39
  SN_CONSTANT(TYPE_INTEGRAL_NUMBER, 48)
40
40
  SN_CONSTANT(TYPE_UNSUPPORTED, 128)
41
+ /* Native-only scalar kind outside the JSON type masks. */
42
+ SN_CONSTANT(TYPE_IDENTITY_SCALAR, 256)
41
43
  SN_CONSTANT(MAXIMUM, 1)
42
44
  SN_CONSTANT(MINIMUM, 2)
43
45
  SN_CONSTANT(EXCLUSIVE_MAXIMUM, 4)
@@ -50,9 +52,48 @@ SN_CONSTANT(TRACKS_DYNAMIC_SCOPE, 2)
50
52
  SN_CONSTANT(VERDICT_INVALID, 0)
51
53
  SN_CONSTANT(VERDICT_VALID, 1)
52
54
  SN_CONSTANT(VERDICT_UNSUPPORTED, 2)
55
+ SN_CONSTANT(REGEXP_INVALID, 3)
56
+
57
+ /* Runtime requests handled by C only after Rust evaluation returns. */
58
+ SN_CONSTANT(RUNTIME_REGEXP_STRICT, 0)
59
+ SN_CONSTANT(RUNTIME_REGEXP_ASSERTION, 1)
60
+ SN_CONSTANT(RUNTIME_JSON, 2)
61
+ SN_CONSTANT(RUNTIME_POINTER, 3)
62
+ SN_CONSTANT(RUNTIME_IDENTITY_METHODS, 4)
63
+ SN_CONSTANT(RUNTIME_REQUIRED_MISSING, 5)
64
+ SN_CONSTANT(RUNTIME_DIAGNOSTIC_MESSAGE, 6)
65
+ SN_CONSTANT(RUNTIME_SCALAR_CONTAINS, 7)
66
+ /* Null numeric requests add twice the assertion bit and the decimal-mode bit. */
67
+ SN_CONSTANT(RUNTIME_NULL_NUMBER, 8)
68
+ SN_CONSTANT(RUNTIME_OBJECT_REPORT, 55)
69
+ SN_CONSTANT(RUNTIME_PROPERTY_MERGE, 56)
70
+ SN_CONSTANT(RUNTIME_PATH_POP, 57)
71
+ SN_CONSTANT(RUNTIME_ARRAY_REPORT, 58)
72
+ SN_CONSTANT(RUNTIME_NATIVE_REPORT, 59)
73
+ SN_CONSTANT(RUNTIME_NATIVE_CHILD, 60)
74
+ SN_CONSTANT(RUNTIME_OBJECT_CHILDREN, 61)
75
+ SN_CONSTANT(RUNTIME_ARRAY_CHILDREN, 62)
76
+ SN_CONSTANT(RUNTIME_CALLBACK_CHECKPOINT, 63)
77
+ /* Type requests add twice the type mask and the union-opcode bit. */
78
+ SN_CONSTANT(RUNTIME_TYPE, 64)
79
+ /* Numeric frame requests add 64 times the step and the required type mask. */
80
+ SN_CONSTANT(RUNTIME_NUMBER, 1024)
81
+ /* String frames add 256 times the step, required type and option flags. */
82
+ SN_CONSTANT(RUNTIME_STRING, 2048)
83
+ /* Occurrence-based Ruby value primitives, without schema evaluation. */
84
+ SN_CONSTANT(RUNTIME_EQUAL, 8192)
85
+ SN_CONSTANT(RUNTIME_ENUM, 8193)
86
+ SN_CONSTANT(RUNTIME_UNIQUE, 8194)
87
+ /* Container header frames add 64 times the step and required type mask. */
88
+ SN_CONSTANT(RUNTIME_ARRAY_HEADER, 16384)
89
+ SN_CONSTANT(RUNTIME_OBJECT_HEADER, 32768)
53
90
  SN_CONSTANT(ADAPTER_UNSUPPORTED, 3)
54
91
  SN_CONSTANT(STATUS_PANIC, -1)
55
92
  SN_CONSTANT(STATUS_EXCEPTION, -2)
93
+ SN_CONSTANT(STATUS_STACK_ERROR, -3)
94
+ SN_CONSTANT(STATUS_DECIMAL_NAN, -4)
95
+ SN_CONSTANT(STATUS_DECIMAL_POSITIVE_INFINITY, -5)
96
+ SN_CONSTANT(STATUS_DECIMAL_NEGATIVE_INFINITY, -6)
56
97
  SN_CONSTANT(OPCODE_UNKNOWN, -1)
57
98
  SN_CONSTANT(SMALL_HASH_ORDINARY, 16)
58
99
  SN_CONSTANT(SMALL_HASH_COUNT_MASK, 15)
@@ -61,3 +102,7 @@ SN_CONSTANT(ENCODING_OTHER, 0)
61
102
  SN_CONSTANT(ENCODING_UTF8, 1)
62
103
  SN_CONSTANT(ENCODING_ASCII, 2)
63
104
  SN_CONSTANT(ENCODING_SEVEN_BIT, 3)
105
+
106
+ SN_CONSTANT(CALLBACK_RUNTIME, 0)
107
+ SN_CONSTANT(CALLBACK_BOOLEAN, 1)
108
+ SN_CONSTANT(CALLBACK_TYPE, 2)
@@ -19,10 +19,21 @@ pub struct Outcome {
19
19
  pub(super) struct Budget {
20
20
  pub limits: Limits,
21
21
  pub hit: Cell<(u32, usize)>,
22
+ pub failure: Cell<i32>,
22
23
  deadline: Option<Instant>,
23
24
  checks: Cell<usize>,
24
25
  }
25
26
  impl Budget {
27
+ // Defer Ruby exception construction until all Rust frames have unwound.
28
+ pub fn status(&self, verdict: super::Verdict) -> i32 {
29
+ let failure = self.failure.get();
30
+ if failure != 0 {
31
+ failure
32
+ } else {
33
+ verdict as i32
34
+ }
35
+ }
36
+
26
37
  pub fn new(limits: Limits) -> Self {
27
38
  Self {
28
39
  limits,
@@ -36,6 +47,15 @@ impl Budget {
36
47
  ..Self::default()
37
48
  }
38
49
  }
50
+ pub fn resume(limits: Limits, deadline: &Cell<Option<Instant>>) -> Self {
51
+ let mut budget = Self::new(limits);
52
+ if let Some(original) = deadline.get() {
53
+ budget.deadline = Some(original);
54
+ } else {
55
+ deadline.set(budget.deadline);
56
+ }
57
+ budget
58
+ }
39
59
  pub fn timed(&self) -> bool {
40
60
  self.limits.enabled & 2 != 0
41
61
  }
@@ -98,4 +118,33 @@ mod tests {
98
118
  .check_deadline()
99
119
  );
100
120
  }
121
+
122
+ #[test]
123
+ fn runtime_retries_retain_the_original_native_deadline() {
124
+ let original = Instant::now() - Duration::from_secs(1);
125
+ let deadline = Cell::new(Some(original));
126
+ let budget = Budget::resume(
127
+ Limits {
128
+ timeout: 10.0,
129
+ enabled: 2,
130
+ ..Limits::default()
131
+ },
132
+ &deadline,
133
+ );
134
+ assert!(!budget.check_deadline());
135
+ assert_eq!(budget.hit.get(), (2, 0));
136
+ assert_eq!(deadline.get(), Some(original));
137
+ deadline.set(None);
138
+ assert!(
139
+ Budget::resume(
140
+ Limits {
141
+ timeout: 10.0,
142
+ enabled: 2,
143
+ ..Limits::default()
144
+ },
145
+ &deadline
146
+ )
147
+ .check_deadline()
148
+ );
149
+ }
101
150
  }
@@ -0,0 +1,460 @@
1
+ //! Ruby container primitives and native-child requests without storage loans.
2
+ use super::*;
3
+
4
+ #[derive(Debug)]
5
+ pub(super) struct Header {
6
+ source: Value,
7
+ checks: u8,
8
+ object: bool,
9
+ native_bounds: bool,
10
+ native_required: bool,
11
+ required_count: usize,
12
+ native_dependencies: bool,
13
+ dependencies: Vec<Dependency>,
14
+ }
15
+ #[derive(Debug)]
16
+ struct Dependency {
17
+ count: usize,
18
+ keyword: &'static str,
19
+ }
20
+ impl Header {
21
+ pub(super) fn contains(
22
+ &self,
23
+ caches: &runtime::Caches,
24
+ required: i32,
25
+ value: Value,
26
+ child: usize,
27
+ scalar: (i32, i32),
28
+ ) -> Verdict {
29
+ caches.contains(self.source, value, child, required, scalar)
30
+ }
31
+ pub(super) fn children(
32
+ &self,
33
+ caches: &runtime::Caches,
34
+ required: i32,
35
+ value: Value,
36
+ node: usize,
37
+ ) -> Verdict {
38
+ caches.container_children(self.source, value, node, required, self.object)
39
+ }
40
+ pub(super) fn runtime_required(&self, caches: &runtime::Caches, kind: i32) -> bool {
41
+ !self.native_bounds
42
+ || !self.native_required
43
+ || !self.native_dependencies
44
+ || kind == TYPE_UNSUPPORTED
45
+ || caches.dynamic_calls()
46
+ }
47
+ pub(super) fn valid(&self, caches: &runtime::Caches, required: i32, value: Value) -> Verdict {
48
+ caches
49
+ .container(self.source, value, required, self.object)
50
+ .map_or(Unsupported, |frame| frame.step(0))
51
+ }
52
+ pub(super) fn failures(
53
+ &self,
54
+ caches: &runtime::Caches,
55
+ required: i32,
56
+ value: Value,
57
+ silent: bool,
58
+ mut visit: impl FnMut(&'static str, Value) -> Result<(), Verdict>,
59
+ ) -> Result<bool, Verdict> {
60
+ let frame = caches.container(self.source, value, required, self.object)?;
61
+ if !frame.predicate(1)? {
62
+ if required != 0 {
63
+ visit("type", frame.ordinal())?;
64
+ }
65
+ return Ok(false);
66
+ }
67
+ for (bit, step, keyword) in [
68
+ (
69
+ 1,
70
+ 2,
71
+ if self.object {
72
+ "maxProperties"
73
+ } else {
74
+ "maxItems"
75
+ },
76
+ ),
77
+ (
78
+ 2,
79
+ 3,
80
+ if self.object {
81
+ "minProperties"
82
+ } else {
83
+ "minItems"
84
+ },
85
+ ),
86
+ (4, 4, "uniqueItems"),
87
+ ] {
88
+ if self.checks & bit != 0 && !frame.predicate(step)? {
89
+ visit(keyword, frame.ordinal())?;
90
+ if silent {
91
+ return Ok(true);
92
+ }
93
+ }
94
+ }
95
+ if self.checks & 32 != 0 {
96
+ frame.predicate(7)?;
97
+ for index in 0..self.required_count {
98
+ let step = i32::try_from(index)
99
+ .ok()
100
+ .and_then(|index| index.checked_add(8))
101
+ .ok_or(Unsupported)?;
102
+ if !frame.predicate(step)? {
103
+ visit("required", frame.ordinal())?;
104
+ if silent {
105
+ return Ok(true);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ if self.checks & 8 != 0 {
111
+ frame.predicate(5)?;
112
+ }
113
+ if self.checks & 16 != 0 {
114
+ frame.predicate(6)?;
115
+ }
116
+ let mut step = self.required_count.checked_add(8).ok_or(Unsupported)?;
117
+ for dependency in &self.dependencies {
118
+ let trigger = i32::try_from(step).map_err(|_| Unsupported)?;
119
+ if frame.predicate(trigger)? {
120
+ frame.predicate(trigger.checked_add(1).ok_or(Unsupported)?)?;
121
+ for index in 0..dependency.count {
122
+ let key = step
123
+ .checked_add(index)
124
+ .and_then(|step| step.checked_add(2))
125
+ .and_then(|step| i32::try_from(step).ok())
126
+ .ok_or(Unsupported)?;
127
+ if !frame.predicate(key)? {
128
+ visit(dependency.keyword, frame.ordinal())?;
129
+ if silent {
130
+ return Ok(true);
131
+ }
132
+ }
133
+ }
134
+ }
135
+ step = step
136
+ .checked_add(dependency.count)
137
+ .and_then(|step| step.checked_add(2))
138
+ .ok_or(Unsupported)?;
139
+ }
140
+ Ok(true)
141
+ }
142
+ }
143
+ impl Engine {
144
+ pub(super) fn scalar_callbacks_safe(&self, root: usize) -> bool {
145
+ self.callback_roots.get(root) == Some(&true)
146
+ }
147
+ pub(super) fn native_continuations_safe(&self, root: usize) -> bool {
148
+ self.validity_supported.get(root) == Some(&true)
149
+ }
150
+ pub(super) fn report_continuations_safe(&self, root: usize) -> bool {
151
+ self.report_supported.get(root) == Some(&true)
152
+ }
153
+ // Admit callbacks only when every reachable assertion has a runtime path.
154
+ // Otherwise a later unsupported container could replay an already invoked
155
+ // user block through the VM. Native continuations bound physical stack
156
+ // depth, so cycles need coverage propagation rather than a DAG proof.
157
+ pub(super) fn compile_native_validity_support(&mut self) {
158
+ let count = self.nodes.len();
159
+ self.has_references = self
160
+ .nodes
161
+ .iter()
162
+ .flatten()
163
+ .any(|op| matches!(op, Op::Ref(_)));
164
+ // One zero-depth dispatch vertex per name avoids repeating every
165
+ // possible binding edge for each dynamic reference.
166
+ let recursive = count + self.anchor_tables.len();
167
+ let mut edges = vec![vec![]; recursive + 1];
168
+ let mut admitted = vec![true; edges.len()];
169
+ let mut callbacks = vec![false; edges.len()];
170
+ let mut report_unsupported = vec![false; edges.len()];
171
+ for (name, bindings) in self.anchor_tables.iter().enumerate() {
172
+ edges[count + name].extend(bindings.iter().map(|&(_, target)| target));
173
+ }
174
+ edges[recursive].extend(
175
+ self.resources
176
+ .values()
177
+ .copied()
178
+ .filter(|&root| self.metadata[root].recursive_anchor),
179
+ );
180
+ for (node, ops) in self.nodes.iter().enumerate() {
181
+ admitted[node] = true;
182
+ for op in ops {
183
+ // Unresolved references have native failure and report paths;
184
+ // only instructions without an execution path block callbacks.
185
+ report_unsupported[node] |= matches!(op, Op::Unsupported);
186
+ match op {
187
+ Op::UnresolvedRef(_)
188
+ | Op::Boolean(_)
189
+ | Op::Type(..)
190
+ | Op::Number(..)
191
+ | Op::String(..)
192
+ | Op::Const(_)
193
+ | Op::Enum(_) => {}
194
+ Op::Array(_, rules) if rules.header_only() => {}
195
+ Op::Array(_, rules) => {
196
+ callbacks[node] = true;
197
+ edges[node].extend(
198
+ rules
199
+ .callback_children()
200
+ .chain(rules.unevaluated.iter())
201
+ .copied(),
202
+ );
203
+ }
204
+ Op::Object(_, rules) if rules.header_only() => {}
205
+ Op::Object(_, rules) => {
206
+ callbacks[node] = true;
207
+ edges[node].extend(
208
+ rules
209
+ .callback_children()
210
+ .chain(rules.unevaluated.iter())
211
+ .copied(),
212
+ );
213
+ }
214
+ Op::Ref(reference) => {
215
+ if let Some((name, fallback)) = reference.callback_dynamic_target() {
216
+ edges[node].extend([fallback, count + name]);
217
+ } else if let Some(fallback) = reference.recursive_target() {
218
+ edges[node].extend([fallback, recursive]);
219
+ } else {
220
+ edges[node].push(reference.static_target().expect("static reference"));
221
+ }
222
+ }
223
+ Op::All(children) => edges[node].extend(children),
224
+ Op::Any(children) | Op::One(children) => {
225
+ edges[node].extend(children.iter().map(|&(child, _)| child))
226
+ }
227
+ Op::Not(child) => edges[node].push(*child),
228
+ Op::Conditional(condition, yes, no) => {
229
+ edges[node].push(*condition);
230
+ edges[node].extend(yes.iter().chain(no));
231
+ }
232
+ Op::Unsupported => admitted[node] = false,
233
+ }
234
+ }
235
+ }
236
+ let parents = graph::parents(&edges);
237
+ graph::propagate(&mut report_unsupported, &parents);
238
+ // Tracked arrays enter collection even through validity. They need the
239
+ // complete result path, while ordinary validity children may also use
240
+ // the existing object callbacks.
241
+ for node in 0..count {
242
+ if self.tracks[node] {
243
+ admitted[node] &= !report_unsupported[node];
244
+ }
245
+ }
246
+ let mut unsupported: Vec<_> = admitted.into_iter().map(|allowed| !allowed).collect();
247
+ graph::propagate(&mut unsupported, &parents);
248
+ self.report_supported = report_unsupported[..count]
249
+ .iter()
250
+ .map(|&blocked| !blocked)
251
+ .collect();
252
+ graph::propagate(&mut callbacks, &parents);
253
+ self.validity_supported = unsupported[..count]
254
+ .iter()
255
+ .map(|&blocked| !blocked)
256
+ .collect();
257
+ self.callback_roots = callbacks[..count]
258
+ .iter()
259
+ .zip(&self.validity_supported)
260
+ .map(|(&callback, &supported)| callback && supported)
261
+ .collect();
262
+ }
263
+ pub(super) fn scalar_callback_kind(&self, child: usize) -> (i32, i32) {
264
+ if self.metadata[child].enters_scope {
265
+ return (CALLBACK_RUNTIME, 0);
266
+ }
267
+ match self.nodes[child].as_slice() {
268
+ [Op::Boolean(value)] => (CALLBACK_BOOLEAN, i32::from(*value)),
269
+ [Op::Type(mask, union)] => (CALLBACK_TYPE, mask * 2 + i32::from(*union)),
270
+ _ => (CALLBACK_RUNTIME, 0),
271
+ }
272
+ }
273
+ pub(super) fn callback_container_node(&self, node: usize) -> bool {
274
+ self.nodes[node].iter().any(|op| match op {
275
+ Op::Array(_, rules) => !rules.header_only() && self.array_callbacks(rules),
276
+ Op::Object(_, rules) => !rules.header_only() && self.object_callbacks(rules),
277
+ _ => false,
278
+ })
279
+ }
280
+ // Runtime children inherit reference context but export validity only.
281
+ pub(super) fn array_callbacks(&self, rules: &Array) -> bool {
282
+ rules.unevaluated.is_none() && self.callback_children_safe(rules.callback_children())
283
+ }
284
+ pub(super) fn array_report_callbacks(&self, rules: &Array) -> bool {
285
+ rules
286
+ .callback_children()
287
+ .chain(rules.unevaluated.iter())
288
+ .all(|&child| self.report_supported.get(child) == Some(&true))
289
+ }
290
+ pub(super) fn object_report_callbacks(&self, rules: &Object) -> bool {
291
+ rules
292
+ .callback_children()
293
+ .chain(rules.unevaluated.iter())
294
+ .all(|&child| self.report_supported.get(child) == Some(&true))
295
+ }
296
+ pub(super) fn object_callbacks(&self, rules: &Object) -> bool {
297
+ rules.unevaluated.is_none() && self.callback_children_safe(rules.callback_children())
298
+ }
299
+ fn callback_children_safe<'a>(&self, mut children: impl Iterator<Item = &'a usize>) -> bool {
300
+ children.all(|&child| self.validity_supported.get(child) == Some(&true))
301
+ }
302
+ pub(super) fn scalar_contains(&self, rules: &Array) -> bool {
303
+ rules.has_no_item_schemas() && rules.contains.is_some() && self.array_callbacks(rules)
304
+ }
305
+ pub(super) fn container_header(
306
+ &mut self,
307
+ source: Value,
308
+ maximum: &'static [u8],
309
+ minimum: &'static [u8],
310
+ object: bool,
311
+ tail: u8,
312
+ ) -> Result<Header, Value> {
313
+ let maximum = self.field(source, maximum)?;
314
+ let minimum = self.field(source, minimum)?;
315
+ let native_bounds = self.native_count(maximum)? && self.native_count(minimum)?;
316
+ let required = if object {
317
+ self.field(source, b"required\0")?
318
+ } else {
319
+ unsafe { sn_nil() }
320
+ };
321
+ let inspect_required = unsafe { sn_truthy(required) } != 0;
322
+ let required_kind = unsafe { sn_kind(required) };
323
+ let mut dependencies = vec![];
324
+ let mut native_dependencies = true;
325
+ if object {
326
+ for (field, keyword) in [
327
+ (b"dependencies\0".as_slice(), "dependencies"),
328
+ (b"dependent_required\0", "dependentRequired"),
329
+ ] {
330
+ let entries = self.field(source, field)?;
331
+ for pair in self.values(entries) {
332
+ let names = unsafe { sn_entry(pair, 1) };
333
+ let kind = unsafe { sn_kind(names) };
334
+ native_dependencies &= kind == TYPE_ARRAY;
335
+ dependencies.push(Dependency {
336
+ count: if matches!(kind, TYPE_ARRAY | TYPE_OBJECT) {
337
+ unsafe { sn_len(names) }
338
+ } else {
339
+ 0
340
+ },
341
+ keyword,
342
+ });
343
+ }
344
+ }
345
+ }
346
+ Ok(Header {
347
+ source,
348
+ native_bounds,
349
+ native_required: !inspect_required || required_kind == TYPE_ARRAY,
350
+ native_dependencies,
351
+ dependencies,
352
+ required_count: if matches!(required_kind, TYPE_ARRAY | TYPE_OBJECT) {
353
+ unsafe { sn_len(required) }
354
+ } else {
355
+ 0
356
+ },
357
+ checks: unsafe { sn_truthy(maximum) } as u8
358
+ | ((unsafe { sn_truthy(minimum) } as u8) << 1)
359
+ | tail
360
+ | if inspect_required { 32 } else { 0 }
361
+ | if object { 16 } else { 0 },
362
+ object,
363
+ })
364
+ }
365
+ fn native_count(&mut self, value: Value) -> Result<bool, Value> {
366
+ if unsafe { sn_truthy(value) } == 0 {
367
+ return Ok(true);
368
+ }
369
+ if unsafe { sn_fixnum(value) } != 0 {
370
+ return Ok(unsafe { sn_integer(value) } >= 0);
371
+ }
372
+ if unsafe { sn_kind(value) } & TYPE_INTEGER == 0 {
373
+ return Ok(false);
374
+ }
375
+ if unsafe { sn_is_float(value) } != 0 {
376
+ return Ok(unsafe { sn_float(value) } >= 0.0);
377
+ }
378
+ let negative = self.field(value, b"negative?\0")?;
379
+ Ok(unsafe { sn_true(negative) } == 0)
380
+ }
381
+ }
382
+ impl Array {
383
+ fn callback_children(&self) -> impl Iterator<Item = &usize> {
384
+ self.prefix
385
+ .iter()
386
+ .chain(&self.items)
387
+ .chain(self.additional.iter().filter(|_| self.list))
388
+ .chain(self.contains.iter())
389
+ }
390
+ fn has_no_item_schemas(&self) -> bool {
391
+ self.prefix.is_empty()
392
+ && self.items.is_empty()
393
+ && (!self.list || self.additional.is_none())
394
+ && self.unevaluated.is_none()
395
+ }
396
+ pub(super) fn header_only(&self) -> bool {
397
+ self.has_no_item_schemas() && self.contains.is_none()
398
+ }
399
+ }
400
+ impl Object {
401
+ fn callback_children(&self) -> impl Iterator<Item = &usize> {
402
+ self.properties
403
+ .iter()
404
+ .map(|(_, child)| child)
405
+ .chain(self.patterns.iter().map(|(_, child)| child))
406
+ .chain(self.additional.iter())
407
+ .chain(self.names.iter())
408
+ .chain(self.dependent_schemas.iter().map(|(_, child)| child))
409
+ }
410
+ pub(super) fn valid_header(
411
+ &self,
412
+ caches: &runtime::Caches,
413
+ value: Value,
414
+ undefined: Value,
415
+ budget: &budget::Budget,
416
+ ) -> Verdict {
417
+ let status = bounds(unsafe { sn_len(value) }, self.min, self.max);
418
+ if status != Valid {
419
+ return status;
420
+ }
421
+ let status = Self::check_keys(&self.required_order, caches, value, undefined, budget);
422
+ if status != Valid {
423
+ return status;
424
+ }
425
+ self.check_dependent_required(caches, value, undefined, budget)
426
+ }
427
+ pub(super) fn header_only(&self) -> bool {
428
+ self.properties.is_empty()
429
+ && self.patterns.is_empty()
430
+ && self.additional.is_none()
431
+ && self.names.is_none()
432
+ && self.dependent_schemas.is_empty()
433
+ && self.unevaluated.is_none()
434
+ }
435
+ }
436
+
437
+ #[cfg(test)]
438
+ mod tests {
439
+ use super::*;
440
+
441
+ #[test]
442
+ fn admits_unresolved_reports_but_propagates_unsupported_instructions() {
443
+ let mut engine = Engine {
444
+ nodes: vec![
445
+ vec![Op::UnresolvedRef(0)],
446
+ vec![Op::All(vec![0, 1])],
447
+ vec![Op::Unsupported],
448
+ vec![Op::All(vec![1, 2])],
449
+ ],
450
+ tracks: vec![false, true, false, true],
451
+ metadata: vec![scope::Metadata::default(); 4],
452
+ ..Engine::default()
453
+ };
454
+ engine.compile_native_validity_support();
455
+ for root in 0..4 {
456
+ assert_eq!(engine.native_continuations_safe(root), root < 2);
457
+ assert_eq!(engine.report_continuations_safe(root), root < 2);
458
+ }
459
+ }
460
+ }