lib/preserves/src/validation/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 const smoke = struct {
4 const preserves = @import("preserves");
5
6 test "smoke: every atom and compound kind round-trips" {
7 const allocator = std.testing.allocator;
8 var arena = std.heap.ArenaAllocator.init(allocator);
9 defer arena.deinit();
10 const a = arena.allocator();
11
12 const V = preserves.Value(preserves.NoEmbedded);
13
14 const atoms = [_]V{
15 V.initBoolean(true),
16 V.initDouble(3.14),
17 V.initI128(-42),
18 V.initU128(@as(u128, @intCast(std.math.maxInt(i128))) + 7),
19 try V.initString(a, "hello"),
20 try V.initByteString(a, &[_]u8{ 0xde, 0xad, 0xbe, 0xef }),
21 try V.initSymbol(a, "greet"),
22 };
23 const expected_atom_classes = [_]preserves.AtomClass{
24 .boolean,
25 .double,
26 .signed_integer,
27 .signed_integer,
28 .string,
29 .byte_string,
30 .symbol,
31 };
32 inline for (expected_atom_classes, 0..) |want, i| {
33 try std.testing.expectEqual(want, atoms[i].atomClass().?);
34 }
35
36 const fields = try a.alloc(V, 2);
37 fields[0] = V.initI128(1);
38 fields[1] = V.initI128(2);
39 const label = try V.initSymbol(a, "pair");
40 const rec = try V.initRecord(a, label, fields);
41 try std.testing.expectEqual(preserves.CompoundClass.record, rec.compoundClass().?);
42
43 const seq_items = try a.alloc(V, 3);
44 seq_items[0] = V.initBoolean(false);
45 seq_items[1] = V.initI128(10);
46 seq_items[2] = try V.initString(a, "x");
47 const seq = V.initSequence(seq_items);
48 try std.testing.expectEqual(preserves.CompoundClass.sequence, seq.compoundClass().?);
49
50 const set_items = try a.alloc(V, 2);
51 set_items[0] = V.initI128(100);
52 set_items[1] = V.initI128(200);
53 const set_val = V.initSet(set_items);
54 try std.testing.expectEqual(preserves.CompoundClass.set, set_val.compoundClass().?);
55
56 const dict_entries = try a.alloc(V.DictionaryEntry, 1);
57 dict_entries[0] = .{
58 .key = try V.initSymbol(a, "k"),
59 .value = V.initI128(99),
60 };
61 const dict = V.initDictionary(dict_entries);
62 try std.testing.expectEqual(preserves.CompoundClass.dictionary, dict.compoundClass().?);
63
64 try std.testing.expect(rec.eql(rec));
65 try std.testing.expect(seq.eql(seq));
66 try std.testing.expect(set_val.eql(set_val));
67 try std.testing.expect(dict.eql(dict));
68 try std.testing.expect(!rec.eql(seq));
69 }
70 };
71
72 const roundtrip = struct {
73 const preserves = @import("preserves");
74
75 const Value = preserves.Value;
76 const NoEmbedded = preserves.NoEmbedded;
77 const SignedInteger = preserves.SignedInteger;
78 const Tier = preserves.integer_mod.Tier;
79
80 const V = Value(NoEmbedded);
81 const packed_limits: preserves.packed_reader.Limits = .{
82 .max_depth = 64,
83 .max_nodes = 65_536,
84 .max_collection_items = 4096,
85 .max_retained_bytes = 16 * 1024 * 1024,
86 };
87
88 fn parseHexBytes(allocator: std.mem.Allocator, text: []const u8) ![]u8 {
89 var bytes: std.ArrayListUnmanaged(u8) = .empty;
90 defer bytes.deinit(allocator);
91
92 var tokens = std.mem.tokenizeScalar(u8, text, ' ');
93 while (tokens.next()) |token| {
94 try bytes.append(allocator, try std.fmt.parseInt(u8, token, 16));
95 }
96
97 return bytes.toOwnedSlice(allocator);
98 }
99
100 fn packedRoundtrip(allocator: std.mem.Allocator, v: V) !V {
101 const bytes = try preserves.encodePacked(NoEmbedded, allocator, v);
102 defer allocator.free(bytes);
103 return try preserves.decodePacked(NoEmbedded, allocator, bytes, packed_limits);
104 }
105
106 fn textRoundtrip(allocator: std.mem.Allocator, v: V) !V {
107 const text = try preserves.encodeText(NoEmbedded, allocator, v);
108 defer allocator.free(text);
109 return try preserves.decodeText(NoEmbedded, allocator, text);
110 }
111
112 fn expectPackedRoundtrip(allocator: std.mem.Allocator, v: V) !void {
113 var rt = try packedRoundtrip(allocator, v);
114 defer rt.deinit(allocator);
115 try std.testing.expect(v.eql(rt));
116 }
117
118 fn expectTextRoundtrip(allocator: std.mem.Allocator, v: V) !void {
119 var rt = try textRoundtrip(allocator, v);
120 defer rt.deinit(allocator);
121 try std.testing.expect(v.eql(rt));
122 }
123
124 test "packed round-trip: every atom kind" {
125 const allocator = std.testing.allocator;
126 var arena = std.heap.ArenaAllocator.init(allocator);
127 defer arena.deinit();
128 const a = arena.allocator();
129
130 const atoms = [_]V{
131 V.initBoolean(true),
132 V.initBoolean(false),
133 V.initDouble(3.14),
134 V.initDouble(-0.0),
135 V.initI128(0),
136 V.initI128(1),
137 V.initI128(-1),
138 V.initI128(std.math.maxInt(i128)),
139 V.initI128(std.math.minInt(i128)),
140 try V.initString(a, ""),
141 try V.initString(a, "hello world"),
142 try V.initByteString(a, &[_]u8{}),
143 try V.initByteString(a, &[_]u8{ 0xde, 0xad, 0xbe, 0xef }),
144 try V.initSymbol(a, "foo"),
145 try V.initSymbol(a, "name-with.dots"),
146 };
147
148 for (atoms) |at| try expectPackedRoundtrip(allocator, at);
149 }
150
151 test "packed round-trip: every compound kind" {
152 const allocator = std.testing.allocator;
153 var arena = std.heap.ArenaAllocator.init(allocator);
154 defer arena.deinit();
155 const a = arena.allocator();
156
157 const label = try V.initSymbol(a, "pair");
158 const fields = try a.alloc(V, 2);
159 fields[0] = V.initI128(1);
160 fields[1] = try V.initString(a, "two");
161 const record = try V.initRecord(a, label, fields);
162 try expectPackedRoundtrip(allocator, record);
163
164 const seq_items = try a.alloc(V, 3);
165 seq_items[0] = V.initBoolean(true);
166 seq_items[1] = V.initI128(2);
167 seq_items[2] = try V.initSymbol(a, "three");
168 try expectPackedRoundtrip(allocator, V.initSequence(seq_items));
169
170 const set_items = try a.alloc(V, 3);
171 set_items[0] = V.initI128(10);
172 set_items[1] = V.initI128(20);
173 set_items[2] = V.initI128(30);
174 try expectPackedRoundtrip(allocator, V.initSet(set_items));
175
176 const dict_entries = try a.alloc(V.DictionaryEntry, 2);
177 dict_entries[0] = .{ .key = try V.initSymbol(a, "k1"), .value = V.initI128(1) };
178 dict_entries[1] = .{ .key = try V.initSymbol(a, "k2"), .value = V.initI128(2) };
179 try expectPackedRoundtrip(allocator, V.initDictionary(dict_entries));
180 }
181
182 test "packed round-trip: nested composite" {
183 const allocator = std.testing.allocator;
184 var arena = std.heap.ArenaAllocator.init(allocator);
185 defer arena.deinit();
186 const a = arena.allocator();
187
188 const inner_seq_items = try a.alloc(V, 2);
189 inner_seq_items[0] = V.initBoolean(true);
190 inner_seq_items[1] = V.initI128(-7);
191 const inner_seq = V.initSequence(inner_seq_items);
192
193 const inner_set_items = try a.alloc(V, 2);
194 inner_set_items[0] = V.initI128(100);
195 inner_set_items[1] = V.initI128(-5);
196 const inner_set = V.initSet(inner_set_items);
197
198 const outer_dict_entries = try a.alloc(V.DictionaryEntry, 2);
199 outer_dict_entries[0] = .{
200 .key = try V.initSymbol(a, "seq"),
201 .value = inner_seq,
202 };
203 outer_dict_entries[1] = .{
204 .key = try V.initSymbol(a, "set"),
205 .value = inner_set,
206 };
207 const dict = V.initDictionary(outer_dict_entries);
208
209 const rec_fields = try a.alloc(V, 1);
210 rec_fields[0] = dict;
211 const rec_label = try V.initSymbol(a, "wrap");
212 const root = try V.initRecord(a, rec_label, rec_fields);
213
214 try expectPackedRoundtrip(allocator, root);
215 }
216
217 test "packed round-trip: integer tier preservation (i128)" {
218 const allocator = std.testing.allocator;
219 const v = V.initI128(std.math.maxInt(i128));
220 try std.testing.expectEqual(Tier.i128, @as(Tier, v.signed_integer.repr));
221 var rt = try packedRoundtrip(allocator, v);
222 defer rt.deinit(allocator);
223 try std.testing.expectEqual(Tier.i128, @as(Tier, rt.signed_integer.repr));
224 try std.testing.expect(v.eql(rt));
225 }
226
227 test "packed round-trip: integer tier preservation (u128)" {
228 const allocator = std.testing.allocator;
229 const v = V.initU128(std.math.maxInt(u128));
230 try std.testing.expectEqual(Tier.u128, @as(Tier, v.signed_integer.repr));
231 var rt = try packedRoundtrip(allocator, v);
232 defer rt.deinit(allocator);
233 try std.testing.expectEqual(Tier.u128, @as(Tier, rt.signed_integer.repr));
234 try std.testing.expect(v.eql(rt));
235 }
236
237 test "packed round-trip: integer tier preservation (300-bit big)" {
238 const allocator = std.testing.allocator;
239 var raw: [38]u8 = undefined;
240 raw[0] = 0x01;
241 @memset(raw[1..], 0x00);
242 var si = try SignedInteger.fromCanonicalBytes(allocator, &raw);
243 defer si.deinit(allocator);
244 try std.testing.expectEqual(Tier.big, @as(Tier, si.repr));
245
246 const v: V = .{ .signed_integer = try si.clone(allocator) };
247 var owned = v;
248 defer owned.deinit(allocator);
249
250 var rt = try packedRoundtrip(allocator, owned);
251 defer rt.deinit(allocator);
252 try std.testing.expectEqual(Tier.big, @as(Tier, rt.signed_integer.repr));
253 try std.testing.expect(owned.eql(rt));
254 }
255
256 test "packed encoding canonicalizes set element order" {
257 const allocator = std.testing.allocator;
258 var arena = std.heap.ArenaAllocator.init(allocator);
259 defer arena.deinit();
260 const a = arena.allocator();
261
262 const unsorted = try a.alloc(V, 3);
263 unsorted[0] = V.initI128(30);
264 unsorted[1] = V.initI128(10);
265 unsorted[2] = V.initI128(20);
266 const unsorted_set = V.initSet(unsorted);
267
268 const sorted = try a.alloc(V, 3);
269 sorted[0] = V.initI128(10);
270 sorted[1] = V.initI128(20);
271 sorted[2] = V.initI128(30);
272 const sorted_set = V.initSet(sorted);
273
274 const a_bytes = try preserves.encodePacked(NoEmbedded, allocator, unsorted_set);
275 defer allocator.free(a_bytes);
276 const b_bytes = try preserves.encodePacked(NoEmbedded, allocator, sorted_set);
277 defer allocator.free(b_bytes);
278 try std.testing.expectEqualSlices(u8, a_bytes, b_bytes);
279 }
280
281 test "packed set order follows encoded representation rather than semantic order" {
282 const allocator = std.testing.allocator;
283 var items = [_]V{ V.initI128(-1), V.initI128(0) };
284 try std.testing.expectEqual(std.math.Order.lt, items[0].compare(items[1]));
285
286 const encoded = try preserves.encodePacked(
287 NoEmbedded,
288 allocator,
289 V.initSet(&items),
290 );
291 defer allocator.free(encoded);
292 try std.testing.expectEqualSlices(
293 u8,
294 &.{ 0xb6, 0xb0, 0x00, 0xb0, 0x01, 0xff, 0x84 },
295 encoded,
296 );
297
298 var decoded = try preserves.decodePacked(NoEmbedded, allocator, encoded, packed_limits);
299 defer decoded.deinit(allocator);
300 try std.testing.expectEqual(@as(i128, 0), try decoded.set[0].signed_integer.toI128());
301 try std.testing.expectEqual(@as(i128, -1), try decoded.set[1].signed_integer.toI128());
302 }
303
304 test "packed encoding canonicalizes dictionary key order" {
305 const allocator = std.testing.allocator;
306 var arena = std.heap.ArenaAllocator.init(allocator);
307 defer arena.deinit();
308 const a = arena.allocator();
309
310 const unsorted = try a.alloc(V.DictionaryEntry, 2);
311 unsorted[0] = .{ .key = try V.initSymbol(a, "z"), .value = V.initI128(1) };
312 unsorted[1] = .{ .key = try V.initSymbol(a, "a"), .value = V.initI128(2) };
313
314 const sorted = try a.alloc(V.DictionaryEntry, 2);
315 sorted[0] = .{ .key = try V.initSymbol(a, "a"), .value = V.initI128(2) };
316 sorted[1] = .{ .key = try V.initSymbol(a, "z"), .value = V.initI128(1) };
317
318 const a_bytes = try preserves.encodePacked(NoEmbedded, allocator, V.initDictionary(unsorted));
319 defer allocator.free(a_bytes);
320 const b_bytes = try preserves.encodePacked(NoEmbedded, allocator, V.initDictionary(sorted));
321 defer allocator.free(b_bytes);
322 try std.testing.expectEqualSlices(u8, a_bytes, b_bytes);
323 }
324
325 test "packed encoding rejects duplicate set members" {
326 const allocator = std.testing.allocator;
327 var arena = std.heap.ArenaAllocator.init(allocator);
328 defer arena.deinit();
329 const a = arena.allocator();
330
331 const duplicate_items = try a.alloc(V, 2);
332 duplicate_items[0] = V.initI128(1);
333 duplicate_items[1] = V.initI128(1);
334
335 try std.testing.expectError(
336 error.DuplicateSetElement,
337 preserves.encodePacked(NoEmbedded, allocator, V.initSet(duplicate_items)),
338 );
339 }
340
341 test "packed decoding rejects duplicate set members" {
342 const allocator = std.testing.allocator;
343 const duplicate = try parseHexBytes(allocator, "B6 B0 01 01 B0 01 01 84");
344 defer allocator.free(duplicate);
345
346 try std.testing.expectError(
347 error.DuplicateSetElement,
348 preserves.decodePacked(NoEmbedded, allocator, duplicate, packed_limits),
349 );
350 }
351
352 test "packed encoding rejects duplicate dictionary keys" {
353 const allocator = std.testing.allocator;
354 var arena = std.heap.ArenaAllocator.init(allocator);
355 defer arena.deinit();
356 const a = arena.allocator();
357
358 const duplicate_entries = try a.alloc(V.DictionaryEntry, 2);
359 duplicate_entries[0] = .{
360 .key = try V.initSymbol(a, "dup"),
361 .value = V.initI128(1),
362 };
363 duplicate_entries[1] = .{
364 .key = try V.initSymbol(a, "dup"),
365 .value = V.initI128(2),
366 };
367
368 try std.testing.expectError(
369 error.DuplicateDictionaryKey,
370 preserves.encodePacked(NoEmbedded, allocator, V.initDictionary(duplicate_entries)),
371 );
372 }
373
374 test "packed decoding rejects duplicate dictionary keys" {
375 const allocator = std.testing.allocator;
376 const duplicate = try parseHexBytes(allocator, "B7 B1 01 61 81 B1 01 61 80 84");
377 defer allocator.free(duplicate);
378
379 try std.testing.expectError(
380 error.DuplicateDictionaryKey,
381 preserves.decodePacked(NoEmbedded, allocator, duplicate, packed_limits),
382 );
383 }
384
385 test "text round-trip: atoms and compounds" {
386 const allocator = std.testing.allocator;
387 var arena = std.heap.ArenaAllocator.init(allocator);
388 defer arena.deinit();
389 const a = arena.allocator();
390
391 try expectTextRoundtrip(allocator, V.initBoolean(true));
392 try expectTextRoundtrip(allocator, V.initBoolean(false));
393 try expectTextRoundtrip(allocator, V.initI128(0));
394 try expectTextRoundtrip(allocator, V.initI128(-12345));
395 try expectTextRoundtrip(allocator, V.initI128(std.math.maxInt(i128)));
396 try expectTextRoundtrip(allocator, V.initU128(std.math.maxInt(u128)));
397 try expectTextRoundtrip(allocator, try V.initString(a, "hello, world"));
398 try expectTextRoundtrip(allocator, try V.initByteString(a, &[_]u8{ 0x00, 0x01, 0xff }));
399 try expectTextRoundtrip(allocator, try V.initSymbol(a, "plain"));
400 try expectTextRoundtrip(allocator, try V.initSymbol(a, "has space"));
401
402 const rec_fields = try a.alloc(V, 2);
403 rec_fields[0] = V.initI128(1);
404 rec_fields[1] = try V.initString(a, "two");
405 const rec = try V.initRecord(a, try V.initSymbol(a, "pair"), rec_fields);
406 try expectTextRoundtrip(allocator, rec);
407
408 const seq_items = try a.alloc(V, 3);
409 seq_items[0] = V.initBoolean(true);
410 seq_items[1] = V.initI128(2);
411 seq_items[2] = try V.initString(a, "x");
412 try expectTextRoundtrip(allocator, V.initSequence(seq_items));
413
414 const dict_entries = try a.alloc(V.DictionaryEntry, 2);
415 dict_entries[0] = .{ .key = try V.initSymbol(a, "name"), .value = try V.initString(a, "Alice") };
416 dict_entries[1] = .{ .key = try V.initSymbol(a, "age"), .value = V.initI128(30) };
417 try expectTextRoundtrip(allocator, V.initDictionary(dict_entries));
418 }
419
420 test "text round-trip: double uses decimal form" {
421 const allocator = std.testing.allocator;
422 try expectTextRoundtrip(allocator, V.initDouble(3.14));
423 try expectTextRoundtrip(allocator, V.initDouble(-0.5));
424 try expectTextRoundtrip(allocator, V.initDouble(1.0));
425 try expectTextRoundtrip(allocator, V.initDouble(1e10));
426 }
427 };
428
429 const @"packed" = @import("packed/root.zig");
430
431 const edges = struct {
432 const preserves = @import("preserves");
433
434 const NoEmbedded = preserves.NoEmbedded;
435 const V = preserves.Value(NoEmbedded);
436 const packed_limits: preserves.packed_reader.Limits = .{
437 .max_depth = 64,
438 .max_nodes = 65_536,
439 .max_collection_items = 4096,
440 .max_retained_bytes = 16 * 1024 * 1024,
441 };
442
443 fn parseHexBytes(allocator: std.mem.Allocator, text: []const u8) ![]u8 {
444 var bytes: std.ArrayListUnmanaged(u8) = .empty;
445 defer bytes.deinit(allocator);
446
447 var tokens = std.mem.tokenizeScalar(u8, text, ' ');
448 while (tokens.next()) |token| {
449 try bytes.append(allocator, try std.fmt.parseInt(u8, token, 16));
450 }
451
452 return bytes.toOwnedSlice(allocator);
453 }
454
455 fn encodeAlloc(allocator: std.mem.Allocator, item: V) ![]u8 {
456 return preserves.encodePacked(NoEmbedded, allocator, item);
457 }
458
459 fn decodeAlloc(allocator: std.mem.Allocator, bytes: []const u8) !V {
460 return preserves.decodePacked(NoEmbedded, allocator, bytes, packed_limits);
461 }
462
463 fn expectPackedHex(
464 allocator: std.mem.Allocator,
465 item: V,
466 hex: []const u8,
467 ) !void {
468 const expected = try parseHexBytes(allocator, hex);
469 defer allocator.free(expected);
470
471 const encoded = try encodeAlloc(allocator, item);
472 defer allocator.free(encoded);
473 try std.testing.expectEqualSlices(u8, expected, encoded);
474
475 var decoded = try decodeAlloc(allocator, encoded);
476 defer decoded.deinit(allocator);
477 try std.testing.expect(item.eql(decoded));
478 }
479
480 test "packed direct vectors replay byte strings symbols doubles records and sets" {
481 const allocator = std.testing.allocator;
482
483 var byte_value = try V.initByteString(allocator, &.{ 0x00, 0xff, 0x41 });
484 defer byte_value.deinit(allocator);
485 try expectPackedHex(allocator, byte_value, "B2 03 00 FF 41");
486
487 var symbol_value = try V.initSymbol(allocator, "demo");
488 defer symbol_value.deinit(allocator);
489 try expectPackedHex(allocator, symbol_value, "B3 04 64 65 6D 6F");
490
491 const double = V.initDouble(1.0);
492 try expectPackedHex(allocator, double, "87 08 3F F0 00 00 00 00 00 00");
493
494 const record_fields = try allocator.alloc(V, 2);
495 record_fields[0] = V.initI128(101);
496 record_fields[1] = try V.initString(allocator, "Blackwell");
497 var record_value = try V.initRecord(
498 allocator,
499 try V.initSymbol(allocator, "person"),
500 record_fields,
501 );
502 defer record_value.deinit(allocator);
503 try expectPackedHex(
504 allocator,
505 record_value,
506 "B4 B3 06 70 65 72 73 6F 6E B0 01 65 B1 09 42 6C 61 63 6B 77 65 6C 6C 84",
507 );
508
509 const set_items = try allocator.alloc(V, 3);
510 set_items[0] = V.initI128(3);
511 set_items[1] = V.initBoolean(false);
512 set_items[2] = try V.initString(allocator, "x");
513 var set_value = V.initSet(set_items);
514 defer set_value.deinit(allocator);
515 try expectPackedHex(allocator, set_value, "B6 80 B0 01 03 B1 01 78 84");
516 }
517
518 test "packed scanner rejects annotation tag explicitly" {
519 const annotation = [_]u8{ 0x85, 0xB1, 0x01, 'a', 0x81 };
520 try std.testing.expectError(
521 error.AnnotationNotSupported,
522 decodeAlloc(std.testing.allocator, &annotation),
523 );
524 }
525
526 test "packed decoder reports typed malformed byte stream errors" {
527 const allocator = std.testing.allocator;
528
529 const cases = [_]struct {
530 hex: []const u8,
531 err: anyerror,
532 }{
533 .{ .hex = "85 B1 01 61 81", .err = error.AnnotationNotSupported },
534 .{ .hex = "B0 01 00", .err = error.NonCanonicalInteger },
535 .{ .hex = "B5 B1 01 61", .err = error.UnexpectedEof },
536 .{ .hex = "81 80", .err = error.UnexpectedTrailingBytes },
537 };
538
539 for (cases) |case| {
540 const bytes = try parseHexBytes(allocator, case.hex);
541 defer allocator.free(bytes);
542 try std.testing.expectError(case.err, decodeAlloc(allocator, bytes));
543 }
544 }
545
546 fn isTypedDecodeError(err: anyerror) bool {
547 return switch (err) {
548 error.OutOfMemory,
549 error.UnexpectedEof,
550 error.UnexpectedTrailingBytes,
551 error.UnexpectedEndMarker,
552 error.UnknownTag,
553 error.OverlongVarint,
554 error.VarintTooWide,
555 error.NonCanonicalInteger,
556 error.InvalidDoubleLength,
557 error.DuplicateSetElement,
558 error.DuplicateDictionaryKey,
559 error.InvalidUtf8,
560 error.EmbeddedNotSupported,
561 => true,
562 else => false,
563 };
564 }
565
566 fn runArbitraryBytesCase(allocator: std.mem.Allocator, bytes: []const u8) !void {
567 var decoded = decodeAlloc(allocator, bytes) catch |err| {
568 try std.testing.expect(isTypedDecodeError(err));
569 return;
570 };
571 defer decoded.deinit(allocator);
572
573 const reencoded = try encodeAlloc(allocator, decoded);
574 defer allocator.free(reencoded);
575
576 var reparsed = try decodeAlloc(allocator, reencoded);
577 defer reparsed.deinit(allocator);
578 try std.testing.expect(decoded.eql(reparsed));
579 }
580 };
581
582 test {
583 _ = @import("packed/test.zig");
584 }
585
586 test "preserves validation package namespace" {
587 std.testing.refAllDecls(smoke);
588 std.testing.refAllDecls(roundtrip);
589 std.testing.refAllDecls(@"packed");
590 std.testing.refAllDecls(edges);
591 }