@scriptc/compiler 0.0.18 → 0.0.20
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.
- package/dist/frontend/lib-contract.d.ts +1 -0
- package/dist/frontend/lib-contract.js +67 -4
- package/dist/frontend/lib-contract.js.map +1 -1
- package/dist/index.js +92 -20
- package/dist/index.js.map +1 -1
- package/dist/library/int-infer.d.ts +6 -1
- package/dist/library/int-infer.js +118 -31
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +65 -0
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/profile.d.ts +6 -5
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar-validate.js +4 -1
- package/dist/library/sidecar-validate.js.map +1 -1
- package/dist/library/sidecar.d.ts +32 -4
- package/dist/library/sidecar.js +301 -54
- package/dist/library/sidecar.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
package/dist/library/sidecar.js
CHANGED
|
@@ -24,15 +24,17 @@
|
|
|
24
24
|
* pattern) is allowed with a pinned order: depth-first source order of
|
|
25
25
|
* the constituent declarations (A's arms in A's own declaration order,
|
|
26
26
|
* then B's), an arm name appearing in several constituents keeping its
|
|
27
|
-
* FIRST occurrence.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* FIRST occurrence. Type aliases are transparent where the wire has no
|
|
28
|
+
* alias identity: a reference alias tables the target declaration, while
|
|
29
|
+
* a scalar alias projects as its underlying bool/number/string/bytes
|
|
30
|
+
* TypeRef. Neither alias adds a table entry or reorders the target.
|
|
30
31
|
*
|
|
31
32
|
* Integer slots (ask 4): the profile's `sidecar.integer_slots` declares
|
|
32
33
|
* specific number slots i64/u64 by slot path; the projection spells each
|
|
33
|
-
* declared slot's TypeRef/descriptor `i64
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* declared slot's TypeRef/descriptor `i64`, nested under `optional` when
|
|
35
|
+
* the TypeScript slot is optional (the frozen format-1 vocabulary — u64 is
|
|
36
|
+
* the stricter compile-time obligation over the same wire spelling), and
|
|
37
|
+
* refuses paths that resolve to no bare or optional number slot,
|
|
36
38
|
* and emits `integer_slots` as the resolved-decision list, in profile
|
|
37
39
|
* declaration order, each entry recording the DECLARED class ({i64, u64}
|
|
38
40
|
* — the flattening is TypeRef-only). The list is an ATTESTATION (schema
|
|
@@ -137,6 +139,10 @@ function classify(decl, index) {
|
|
|
137
139
|
if (s.k === "object") {
|
|
138
140
|
return { c: "struct", storage: decl.form === "interface" ? "node" : "value", fields: s.fields, decl, index };
|
|
139
141
|
}
|
|
142
|
+
if (decl.form === "alias" &&
|
|
143
|
+
(s.k === "bool" || s.k === "number" || s.k === "text" || s.k === "bytes")) {
|
|
144
|
+
return { c: "scalar", shape: s, decl, index };
|
|
145
|
+
}
|
|
140
146
|
if (decl.form === "alias" && s.k === "ref")
|
|
141
147
|
return { c: "alias", target: s.name, decl, index };
|
|
142
148
|
if (decl.form === "alias" && s.k === "stringLit")
|
|
@@ -191,7 +197,10 @@ class Projector {
|
|
|
191
197
|
table = new Map();
|
|
192
198
|
inProgress = new Set();
|
|
193
199
|
flatArms = new Map();
|
|
200
|
+
allFlatArms = new Map();
|
|
194
201
|
flattening = new Set();
|
|
202
|
+
allFlattening = new Set();
|
|
203
|
+
irPatterning = new Set();
|
|
195
204
|
synthCounter = 0;
|
|
196
205
|
/** The profile's declared integer slots (ask 4), by slot path; entries
|
|
197
206
|
* move to `intConsumed` as the projection spells them — a declared path
|
|
@@ -200,9 +209,11 @@ class Projector {
|
|
|
200
209
|
intDeclared = new Map();
|
|
201
210
|
intConsumed = new Map();
|
|
202
211
|
/** The record-field slots' resolution facts for the inference: the
|
|
203
|
-
* containing record's
|
|
204
|
-
*
|
|
205
|
-
|
|
212
|
+
* containing record's complete projected IR shape plus the target field
|
|
213
|
+
* (IR shapes intern structurally by field names and field types). Pattern
|
|
214
|
+
* construction is deferred until the whole contract graph has projected,
|
|
215
|
+
* so an invalid later sibling still takes its ordinary SidecarError path. */
|
|
216
|
+
pendingIntRecordFacts = [];
|
|
206
217
|
constructor(facts, config, entryLoc) {
|
|
207
218
|
this.facts = facts;
|
|
208
219
|
this.config = config;
|
|
@@ -216,20 +227,28 @@ class Projector {
|
|
|
216
227
|
this.intDeclared.set(e.slot, e.cls);
|
|
217
228
|
}
|
|
218
229
|
/** Spell a projected slot i64 when the profile declared it (ask 4).
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
230
|
+
* A NUMBER slot may be bare or optional: optional<number> composes the
|
|
231
|
+
* schema's two existing constructors as optional<i64>, and the proof
|
|
232
|
+
* applies only to its present numeric arm. Slices and named types still
|
|
233
|
+
* refuse — the declaration must match the wire shape the schema freezes.
|
|
234
|
+
* The document spells i64 for both classes (the frozen format-1
|
|
235
|
+
* vocabulary has no u64; u64 is the stricter compile-time obligation
|
|
236
|
+
* over the same wire spelling). */
|
|
224
237
|
intify(ref, slotPath, loc) {
|
|
225
238
|
const cls = this.intDeclared.get(slotPath);
|
|
226
239
|
if (cls === undefined)
|
|
227
240
|
return ref;
|
|
228
|
-
if (ref.kind
|
|
229
|
-
|
|
241
|
+
if (ref.kind === "f64") {
|
|
242
|
+
this.intConsumed.set(slotPath, cls);
|
|
243
|
+
return { kind: "i64" };
|
|
244
|
+
}
|
|
245
|
+
if (ref.kind === "optional" && ref.inner.kind === "f64") {
|
|
246
|
+
this.intConsumed.set(slotPath, cls);
|
|
247
|
+
return { kind: "optional", inner: { kind: "i64" } };
|
|
248
|
+
}
|
|
249
|
+
{
|
|
250
|
+
throw new SidecarError(`the profile declares integer slot '${slotPath}' (${cls}), but that slot is not a number or optional number slot (it projects as '${ref.kind}')`, loc);
|
|
230
251
|
}
|
|
231
|
-
this.intConsumed.set(slotPath, cls);
|
|
232
|
-
return { kind: "i64" };
|
|
233
252
|
}
|
|
234
253
|
/** intify for a struct field (declared or synthesized), recording the
|
|
235
254
|
* record-resolution fact the inference maps onto interned IR shapes. */
|
|
@@ -238,15 +257,123 @@ class Projector {
|
|
|
238
257
|
const before = this.intConsumed.has(slotPath);
|
|
239
258
|
const out = this.intify(ref, slotPath, loc);
|
|
240
259
|
if (!before && this.intConsumed.has(slotPath)) {
|
|
241
|
-
this.
|
|
242
|
-
|
|
260
|
+
this.pendingIntRecordFacts.push({
|
|
261
|
+
fields: allFields,
|
|
262
|
+
tagged: false,
|
|
243
263
|
targetField: field,
|
|
244
264
|
cls: this.intConsumed.get(slotPath),
|
|
245
265
|
path: slotPath,
|
|
266
|
+
loc,
|
|
246
267
|
});
|
|
247
268
|
}
|
|
248
269
|
return out;
|
|
249
270
|
}
|
|
271
|
+
/** Canonical union constructor mirroring the frontend: nested unions
|
|
272
|
+
* flatten, structurally repeated arms collapse, and one surviving arm is
|
|
273
|
+
* just that arm. */
|
|
274
|
+
irUnionPattern(arms) {
|
|
275
|
+
const flat = [];
|
|
276
|
+
for (const arm of arms) {
|
|
277
|
+
if (arm.kind === "union")
|
|
278
|
+
flat.push(...arm.arms);
|
|
279
|
+
else
|
|
280
|
+
flat.push(arm);
|
|
281
|
+
}
|
|
282
|
+
const unique = new Map();
|
|
283
|
+
for (const arm of flat)
|
|
284
|
+
unique.set(JSON.stringify(arm), arm);
|
|
285
|
+
const canonical = [...unique.entries()]
|
|
286
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
287
|
+
.map(([, arm]) => arm);
|
|
288
|
+
if (canonical.length === 0)
|
|
289
|
+
throw new Error("sidecar pattern bug: empty union");
|
|
290
|
+
return canonical.length === 1 ? canonical[0] : { kind: "union", arms: canonical };
|
|
291
|
+
}
|
|
292
|
+
irFieldPattern(field) {
|
|
293
|
+
const inner = this.irTypePattern(field.shape, field.loc);
|
|
294
|
+
return field.optional
|
|
295
|
+
? this.irUnionPattern([inner, { kind: "undefinedT" }])
|
|
296
|
+
: inner;
|
|
297
|
+
}
|
|
298
|
+
irRecordPattern(fields, tagged = false) {
|
|
299
|
+
const projected = fields.map((field) => ({
|
|
300
|
+
name: field.name,
|
|
301
|
+
type: this.irFieldPattern(field),
|
|
302
|
+
}));
|
|
303
|
+
if (tagged)
|
|
304
|
+
projected.push({ name: "kind", type: { kind: "string" } });
|
|
305
|
+
projected.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
306
|
+
return {
|
|
307
|
+
kind: "record",
|
|
308
|
+
fields: projected,
|
|
309
|
+
...(tagged ? { kindMayBeOmitted: true } : {}),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/** Flatten a tagged union for IR identity, preserving every source
|
|
313
|
+
* constituent. This is deliberately separate from unionArms(): the
|
|
314
|
+
* sidecar's wire table is first-discriminant-name-wins, while the
|
|
315
|
+
* frontend's structural union interner retains later same-named arms
|
|
316
|
+
* whenever their payload shapes differ. irUnionPattern performs the
|
|
317
|
+
* frontend's structural deduplication after this walk. */
|
|
318
|
+
irTaggedArmPatterns(unionName, loc) {
|
|
319
|
+
return this.allUnionArms(unionName, loc).map((arm) => this.irRecordPattern(arm.fields, true));
|
|
320
|
+
}
|
|
321
|
+
/** Convert a sidecar-supported syntactic type into the exact structural
|
|
322
|
+
* IR pattern the frontend maps it to. Projection has already refused
|
|
323
|
+
* unsupported shapes before an integer fact is recorded, so the default
|
|
324
|
+
* cases are internal consistency checks. */
|
|
325
|
+
irTypePattern(shape, loc) {
|
|
326
|
+
switch (shape.k) {
|
|
327
|
+
case "number":
|
|
328
|
+
return { kind: "f64" };
|
|
329
|
+
case "text":
|
|
330
|
+
case "stringLit":
|
|
331
|
+
return { kind: "string" };
|
|
332
|
+
case "bool":
|
|
333
|
+
return { kind: "bool" };
|
|
334
|
+
case "bytes":
|
|
335
|
+
return { kind: "bytes", elem: "u8" };
|
|
336
|
+
case "absent":
|
|
337
|
+
return { kind: shape.unit === "null" ? "nullT" : "undefinedT" };
|
|
338
|
+
case "array":
|
|
339
|
+
return { kind: "array", elem: this.irTypePattern(shape.elem, loc) };
|
|
340
|
+
case "union":
|
|
341
|
+
return this.irUnionPattern(shape.parts.map((part) => this.irTypePattern(part, loc)));
|
|
342
|
+
case "object":
|
|
343
|
+
// A declared `{}` annotation is TypeScript's top non-nullish type,
|
|
344
|
+
// not the inferred shape of an empty object literal. The frontend
|
|
345
|
+
// therefore lowers it to dyn (types.ts's declared-empty rule).
|
|
346
|
+
return shape.fields.length === 0
|
|
347
|
+
? { kind: "dyn" }
|
|
348
|
+
: this.irRecordPattern(shape.fields);
|
|
349
|
+
case "ref": {
|
|
350
|
+
const resolved = this.resolve(shape.name, loc);
|
|
351
|
+
if (resolved.c.c === "scalar")
|
|
352
|
+
return this.irTypePattern(resolved.c.shape, loc);
|
|
353
|
+
if (resolved.c.c === "enum")
|
|
354
|
+
return { kind: "string" };
|
|
355
|
+
if (resolved.c.c === "struct" && resolved.c.fields.length === 0) {
|
|
356
|
+
return { kind: "dyn" };
|
|
357
|
+
}
|
|
358
|
+
if (this.irPatterning.has(resolved.name)) {
|
|
359
|
+
throw new SidecarError(`the contract type graph is cyclic through '${resolved.name}' — recursive contract types cannot encode`, loc);
|
|
360
|
+
}
|
|
361
|
+
this.irPatterning.add(resolved.name);
|
|
362
|
+
try {
|
|
363
|
+
return resolved.c.c === "struct"
|
|
364
|
+
? this.irRecordPattern(resolved.c.fields)
|
|
365
|
+
: this.irUnionPattern(this.irTaggedArmPatterns(resolved.name, loc));
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
this.irPatterning.delete(resolved.name);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
case "void":
|
|
372
|
+
case "tuple":
|
|
373
|
+
case "unsupported":
|
|
374
|
+
throw new SidecarError(`an integer-slot record contains a shape outside the sidecar's structural vocabulary (${shape.k})`, loc);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
250
377
|
/** A declared slot path that the whole projection never spelled: the
|
|
251
378
|
* path names nothing (or names a slot outside the schema's integer
|
|
252
379
|
* grammar), and silently ignoring it would drop an ABI obligation. */
|
|
@@ -294,6 +421,17 @@ class Projector {
|
|
|
294
421
|
cur = c.target;
|
|
295
422
|
}
|
|
296
423
|
}
|
|
424
|
+
/** Resolve only aliases whose names have no wire identity. Named
|
|
425
|
+
* records/enums/unions return null and retain their table projection. */
|
|
426
|
+
scalarShape(shape, loc) {
|
|
427
|
+
if (shape.k === "bool" || shape.k === "number" || shape.k === "text" || shape.k === "bytes") {
|
|
428
|
+
return shape;
|
|
429
|
+
}
|
|
430
|
+
if (shape.k !== "ref")
|
|
431
|
+
return null;
|
|
432
|
+
const resolved = this.resolve(shape.name, loc);
|
|
433
|
+
return resolved.c.c === "scalar" ? resolved.c.shape : null;
|
|
434
|
+
}
|
|
297
435
|
/** A kind-tagged union's arms, its reference constituents flattened
|
|
298
436
|
* DEPTH-FIRST IN SOURCE ORDER (the ask-3 composition rule, pinned by
|
|
299
437
|
* conformance): an inline arm lands where it is spelled; a constituent
|
|
@@ -346,6 +484,113 @@ class Projector {
|
|
|
346
484
|
this.flattening.delete(unionName);
|
|
347
485
|
}
|
|
348
486
|
}
|
|
487
|
+
/** Every source constituent of a composed tagged union, including later
|
|
488
|
+
* occurrences of an already-seen discriminant name. The wire table keeps
|
|
489
|
+
* first occurrence, but structural matching and integer obligations must
|
|
490
|
+
* retain all shapes the frontend can lower under that discriminant. */
|
|
491
|
+
allUnionArms(unionName, loc) {
|
|
492
|
+
const memo = this.allFlatArms.get(unionName);
|
|
493
|
+
if (memo !== undefined)
|
|
494
|
+
return memo;
|
|
495
|
+
if (this.allFlattening.has(unionName)) {
|
|
496
|
+
throw new SidecarError(`union composition is cyclic through '${unionName}' — a union cannot spread itself`, loc);
|
|
497
|
+
}
|
|
498
|
+
const c = this.lookup(unionName, loc);
|
|
499
|
+
if (c.c !== "tagged") {
|
|
500
|
+
throw new SidecarError(`'${unionName}' is not a kind-tagged union of object literals`, c.decl.loc);
|
|
501
|
+
}
|
|
502
|
+
this.allFlattening.add(unionName);
|
|
503
|
+
try {
|
|
504
|
+
const out = [];
|
|
505
|
+
for (const part of c.parts) {
|
|
506
|
+
if (part.p === "arm") {
|
|
507
|
+
out.push({ name: part.name, fields: part.fields, loc: part.loc });
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const r = this.resolve(part.name, part.loc);
|
|
511
|
+
if (r.c.c !== "tagged") {
|
|
512
|
+
throw new SidecarError(`constituent '${part.name}' of union '${unionName}' is not a kind-tagged union — only kind-tagged unions compose by reference`, part.loc);
|
|
513
|
+
}
|
|
514
|
+
out.push(...this.allUnionArms(r.name, part.loc));
|
|
515
|
+
}
|
|
516
|
+
this.allFlatArms.set(unionName, out);
|
|
517
|
+
return out;
|
|
518
|
+
}
|
|
519
|
+
finally {
|
|
520
|
+
this.allFlattening.delete(unionName);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/** Record one integer obligation for every lowered structural arm carrying
|
|
524
|
+
* the wire-selected discriminant. A composed union can repeat an arm name
|
|
525
|
+
* with a different payload field name; the sidecar's scalar descriptor
|
|
526
|
+
* omits that source name, so each compatible record shape must prove the
|
|
527
|
+
* same boundary slot. Incompatible later payloads refuse rather than let a
|
|
528
|
+
* non-integer value ride an integer-attested wire arm. */
|
|
529
|
+
recordIntegerUnionArmFacts(unionName, armName, intifiedRef, cls, path, loc) {
|
|
530
|
+
const selectedOptional = intifiedRef.kind === "optional" && intifiedRef.inner.kind === "i64";
|
|
531
|
+
if (intifiedRef.kind !== "i64" && !selectedOptional) {
|
|
532
|
+
throw new Error(`sidecar pattern bug: integer arm '${path}' has ref '${intifiedRef.kind}'`);
|
|
533
|
+
}
|
|
534
|
+
for (const arm of this.allUnionArms(unionName, loc)) {
|
|
535
|
+
if (arm.name !== armName)
|
|
536
|
+
continue;
|
|
537
|
+
if (arm.fields.length !== 1) {
|
|
538
|
+
throw new SidecarError(`integer slot '${path}' (${cls}) selects a scalar ${selectedOptional ? "optional " : ""}number payload, but another composed '${armName}' arm has ${arm.fields.length === 0 ? "no payload" : `${arm.fields.length} payload fields`}`, arm.loc);
|
|
539
|
+
}
|
|
540
|
+
const field = arm.fields[0];
|
|
541
|
+
const ref = this.fieldRef(field, unionName);
|
|
542
|
+
const candidateOptional = ref.kind === "optional" && ref.inner.kind === "f64";
|
|
543
|
+
if (ref.kind !== "f64" && !candidateOptional) {
|
|
544
|
+
throw new SidecarError(`integer slot '${path}' (${cls}) selects a scalar ${selectedOptional ? "optional " : ""}number payload, but another composed '${armName}' arm projects as '${ref.kind}'`, arm.loc);
|
|
545
|
+
}
|
|
546
|
+
if (!selectedOptional && candidateOptional) {
|
|
547
|
+
throw new SidecarError(`integer slot '${path}' (${cls}) selects a required number payload, but another composed '${armName}' arm makes that payload optional`, arm.loc);
|
|
548
|
+
}
|
|
549
|
+
this.pendingIntRecordFacts.push({
|
|
550
|
+
fields: arm.fields,
|
|
551
|
+
tagged: true,
|
|
552
|
+
targetField: field.name,
|
|
553
|
+
cls,
|
|
554
|
+
path,
|
|
555
|
+
loc: arm.loc,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/** Record a number_bytes obligation for every composed occurrence of the
|
|
560
|
+
* wire-selected discriminant. Unlike a scalar descriptor, number_bytes
|
|
561
|
+
* exposes both source field names, so a later same-named arm must retain
|
|
562
|
+
* those names and the required number-then-bytes family. Text and bytes
|
|
563
|
+
* are the same sidecar bytes payload but distinct IR shapes; compatible
|
|
564
|
+
* occurrences therefore each need their own structural inference fact. */
|
|
565
|
+
recordIntegerNumberBytesArmFacts(unionName, armName, numberField, bytesField, cls, path, loc) {
|
|
566
|
+
for (const arm of this.allUnionArms(unionName, loc)) {
|
|
567
|
+
if (arm.name !== armName)
|
|
568
|
+
continue;
|
|
569
|
+
const [number, bytes] = arm.fields;
|
|
570
|
+
const numberShape = number === undefined ? null : this.scalarShape(number.shape, number.loc);
|
|
571
|
+
const bytesShape = bytes === undefined ? null : this.scalarShape(bytes.shape, bytes.loc);
|
|
572
|
+
const compatible = arm.fields.length === 2 &&
|
|
573
|
+
number !== undefined &&
|
|
574
|
+
bytes !== undefined &&
|
|
575
|
+
!number.optional &&
|
|
576
|
+
!bytes.optional &&
|
|
577
|
+
number.name === numberField &&
|
|
578
|
+
bytes.name === bytesField &&
|
|
579
|
+
numberShape?.k === "number" &&
|
|
580
|
+
(bytesShape?.k === "text" || bytesShape?.k === "bytes");
|
|
581
|
+
if (!compatible) {
|
|
582
|
+
throw new SidecarError(`integer slot '${path}' (${cls}) selects number_bytes payload fields '${numberField}' and '${bytesField}', but another composed '${armName}' arm does not have the same required number-then-bytes fields`, arm.loc);
|
|
583
|
+
}
|
|
584
|
+
this.pendingIntRecordFacts.push({
|
|
585
|
+
fields: arm.fields,
|
|
586
|
+
tagged: true,
|
|
587
|
+
targetField: numberField,
|
|
588
|
+
cls,
|
|
589
|
+
path,
|
|
590
|
+
loc: arm.loc,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
349
594
|
/** Project a syntactic field to a TypeRef, tabling every named type it
|
|
350
595
|
* references. `container`/`member` seed synthesized names. */
|
|
351
596
|
fieldRef(field, container) {
|
|
@@ -380,9 +625,12 @@ class Projector {
|
|
|
380
625
|
throw new SidecarError(`'${container}.${member}' is an inline union — declare it as a named kind-tagged union (or a string-literal-union enum) and reference it by name`, loc);
|
|
381
626
|
}
|
|
382
627
|
case "ref": {
|
|
383
|
-
//
|
|
384
|
-
//
|
|
628
|
+
// Scalar aliases dissolve to their underlying TypeRef. Named-type
|
|
629
|
+
// aliases remain transparent to the aliased table declaration.
|
|
385
630
|
const r = this.resolve(shape.name, loc);
|
|
631
|
+
if (r.c.c === "scalar") {
|
|
632
|
+
return this.shapeRef(r.c.shape, container, member, loc);
|
|
633
|
+
}
|
|
386
634
|
if (r.c.c === "struct") {
|
|
387
635
|
this.tableNamed(r.name, loc);
|
|
388
636
|
return { kind: r.c.storage, name: r.name };
|
|
@@ -430,6 +678,11 @@ class Projector {
|
|
|
430
678
|
this.tableNamed(this.resolve(name, loc).name, loc);
|
|
431
679
|
return;
|
|
432
680
|
}
|
|
681
|
+
if (c.c === "scalar") {
|
|
682
|
+
// Scalar aliases dissolve at their reference site and never have a
|
|
683
|
+
// type-table entry of their own.
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
433
686
|
this.inProgress.add(name);
|
|
434
687
|
try {
|
|
435
688
|
if (c.c === "enum") {
|
|
@@ -453,7 +706,7 @@ class Projector {
|
|
|
453
706
|
seen.add(f.name);
|
|
454
707
|
entry.fields.push({
|
|
455
708
|
name: f.name,
|
|
456
|
-
type: this.intifyStructField(this.fieldRef(f, name), name, f.name, c.fields
|
|
709
|
+
type: this.intifyStructField(this.fieldRef(f, name), name, f.name, c.fields, f.loc),
|
|
457
710
|
});
|
|
458
711
|
}
|
|
459
712
|
this.table.set(name, { kind: "struct", entry, anchor: c.index, sub: -1 });
|
|
@@ -469,14 +722,7 @@ class Projector {
|
|
|
469
722
|
const before = this.intConsumed.has(slotPath);
|
|
470
723
|
payload = this.intify(payload, slotPath, arm.loc);
|
|
471
724
|
if (!before && this.intConsumed.has(slotPath)) {
|
|
472
|
-
|
|
473
|
-
// field; its IR record carries the 'kind' discriminant too.
|
|
474
|
-
this.intRecordFacts.push({
|
|
475
|
-
fieldNames: [...arm.fields.map((f) => f.name), "kind"],
|
|
476
|
-
targetField: arm.fields[0].name,
|
|
477
|
-
cls: this.intConsumed.get(slotPath),
|
|
478
|
-
path: slotPath,
|
|
479
|
-
});
|
|
725
|
+
this.recordIntegerUnionArmFacts(name, arm.name, payload, this.intConsumed.get(slotPath), slotPath, arm.loc);
|
|
480
726
|
}
|
|
481
727
|
entry.arms.push({ name: arm.name, payload });
|
|
482
728
|
}
|
|
@@ -518,7 +764,7 @@ class Projector {
|
|
|
518
764
|
seen.add(f.name);
|
|
519
765
|
entry.fields.push({
|
|
520
766
|
name: f.name,
|
|
521
|
-
type: this.intifyStructField(this.fieldRef(f, name), name, f.name, fields
|
|
767
|
+
type: this.intifyStructField(this.fieldRef(f, name), name, f.name, fields, f.loc),
|
|
522
768
|
});
|
|
523
769
|
}
|
|
524
770
|
return name;
|
|
@@ -530,43 +776,35 @@ class Projector {
|
|
|
530
776
|
return { kind: "void" };
|
|
531
777
|
if (fields.length === 2) {
|
|
532
778
|
const [first, second] = [fields[0], fields[1]];
|
|
779
|
+
const firstShape = this.scalarShape(first.shape, first.loc);
|
|
780
|
+
const secondShape = this.scalarShape(second.shape, second.loc);
|
|
533
781
|
// Family 4 covers exactly the number-first two-field record; a
|
|
534
782
|
// bytes-first spelling takes the record family instead
|
|
535
783
|
// (declaration order is semantic everywhere — the clarified rule).
|
|
536
784
|
if (!first.optional &&
|
|
537
785
|
!second.optional &&
|
|
538
|
-
|
|
539
|
-
(
|
|
786
|
+
firstShape?.k === "number" &&
|
|
787
|
+
(secondShape?.k === "text" || secondShape?.k === "bytes")) {
|
|
540
788
|
// The number half is an ask-4 declarable slot:
|
|
541
789
|
// `<msg>.<arm>.<numberField>` (the schema's number_bytes path).
|
|
542
790
|
const slotPath = `${msgName}.${arm.name}.${first.name}`;
|
|
543
791
|
const numRef = this.intify({ kind: "f64" }, slotPath, first.loc);
|
|
544
792
|
if (numRef.kind === "i64") {
|
|
545
|
-
this.
|
|
546
|
-
fieldNames: [first.name, second.name, "kind"],
|
|
547
|
-
targetField: first.name,
|
|
548
|
-
cls: this.intConsumed.get(slotPath),
|
|
549
|
-
path: slotPath,
|
|
550
|
-
});
|
|
793
|
+
this.recordIntegerNumberBytesArmFacts(msgName, arm.name, first.name, second.name, this.intConsumed.get(slotPath), slotPath, first.loc);
|
|
551
794
|
}
|
|
552
795
|
return { kind: "number_bytes", number_field: first.name, number_class: numRef.kind, bytes_field: second.name };
|
|
553
796
|
}
|
|
554
797
|
}
|
|
555
798
|
if (fields.length === 1 && !fields[0].optional) {
|
|
556
799
|
let ref = this.fieldRef(fields[0], msgName);
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
targetField: fields[0].name,
|
|
566
|
-
cls: this.intConsumed.get(slotPath),
|
|
567
|
-
path: slotPath,
|
|
568
|
-
});
|
|
569
|
-
}
|
|
800
|
+
// A bare or optional number payload is an ask-4 declarable slot:
|
|
801
|
+
// `<msg>.<arm>`. Calling intify for every one-field family also makes
|
|
802
|
+
// a declaration targeting a non-number refuse at the precise arm.
|
|
803
|
+
const slotPath = `${msgName}.${arm.name}`;
|
|
804
|
+
const before = this.intConsumed.has(slotPath);
|
|
805
|
+
ref = this.intify(ref, slotPath, arm.loc);
|
|
806
|
+
if (!before && this.intConsumed.has(slotPath)) {
|
|
807
|
+
this.recordIntegerUnionArmFacts(msgName, arm.name, ref, this.intConsumed.get(slotPath), slotPath, arm.loc);
|
|
570
808
|
}
|
|
571
809
|
switch (ref.kind) {
|
|
572
810
|
case "bytes":
|
|
@@ -591,6 +829,14 @@ class Projector {
|
|
|
591
829
|
// payload fields — tables a synthesized record (family 5).
|
|
592
830
|
return { kind: "record", name: this.tableSynthesized(msgName, arm.name, fields, arm.loc) };
|
|
593
831
|
}
|
|
832
|
+
/** Materialize structural join patterns only after normal sidecar
|
|
833
|
+
* projection has validated every field reachable from these facts. */
|
|
834
|
+
finishedIntRecordFacts() {
|
|
835
|
+
return this.pendingIntRecordFacts.map(({ fields, tagged, ...fact }) => ({
|
|
836
|
+
shape: this.irRecordPattern(fields, tagged),
|
|
837
|
+
...fact,
|
|
838
|
+
}));
|
|
839
|
+
}
|
|
594
840
|
/** The finished type table, each array in declaration order (synthesized
|
|
595
841
|
* entries anchor at their containing declaration). */
|
|
596
842
|
finishedTable() {
|
|
@@ -760,6 +1006,7 @@ export function buildSidecar(input) {
|
|
|
760
1006
|
// Every declared integer slot must have been spelled by now — the
|
|
761
1007
|
// whole contract (model, msg, helpers, channels) is projected.
|
|
762
1008
|
projector.checkIntConsumed();
|
|
1009
|
+
const intRecordFacts = projector.finishedIntRecordFacts();
|
|
763
1010
|
const doc = {
|
|
764
1011
|
format: SIDECAR_FORMAT,
|
|
765
1012
|
wire_version: config.wireVersion,
|
|
@@ -818,7 +1065,7 @@ export function buildSidecar(input) {
|
|
|
818
1065
|
ok: true,
|
|
819
1066
|
doc,
|
|
820
1067
|
json: JSON.stringify(doc, null, 2) + "\n",
|
|
821
|
-
integerSlotFacts: { helpers: helperIntFacts, records:
|
|
1068
|
+
integerSlotFacts: { helpers: helperIntFacts, records: intRecordFacts },
|
|
822
1069
|
};
|
|
823
1070
|
}
|
|
824
1071
|
catch (e) {
|