@scriptc/runtime 0.0.21 → 0.0.23

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/src/scr_json.c CHANGED
@@ -333,6 +333,8 @@ static ScrDyn *scr_dyn_alloc(ScrDynKind kind) {
333
333
  d->v.arr.len = 0; /* cap/items preserved from the node's last life */
334
334
  } else if (kind == SCR_DYN_OBJ) {
335
335
  d->v.obj.len = 0; /* cap/entries preserved */
336
+ d->v.obj.source_identity = NULL;
337
+ d->v.obj.source_access = NULL;
336
338
  } else {
337
339
  memset(&d->v, 0, sizeof d->v);
338
340
  }
@@ -369,6 +371,9 @@ void scr_dyn_release(ScrDyn *d) {
369
371
  free(d->v.obj.entries[i].key);
370
372
  scr_dyn_release(d->v.obj.entries[i].value);
371
373
  }
374
+ if (d->v.obj.source_identity) {
375
+ d->v.obj.source_access(d->v.obj.source_identity, false);
376
+ }
372
377
  break;
373
378
  case SCR_DYN_FUNC:
374
379
  scr_closure_release(d->v.fn.clo); /* sig/name are static literals */
@@ -387,6 +392,16 @@ void scr_dyn_release(ScrDyn *d) {
387
392
  * only producer) — same story as the promise arm. */
388
393
  scr_dyn_jsval_ops()->release(d->v.jsval.cell);
389
394
  break;
395
+ case SCR_DYN_TYPED_REF:
396
+ scr_dyn_release(d->v.typed_ref.materialized);
397
+ while (d->v.typed_ref.casts) {
398
+ ScrDynTypedCast *cast = d->v.typed_ref.casts;
399
+ d->v.typed_ref.casts = cast->next;
400
+ cast->release(cast->ptr);
401
+ free(cast);
402
+ }
403
+ d->v.typed_ref.release(d->v.typed_ref.ptr);
404
+ break;
390
405
  default:
391
406
  break; /* null/bool/num have no children */
392
407
  }
@@ -440,6 +455,12 @@ void scr_dyn_arr_push(ScrDyn *arr, ScrDyn *item) {
440
455
  * the generic "Spread syntax requires ...iterable[Symbol.iterator] to be
441
456
  * a function". Borrows src. */
442
457
  void scr_dyn_arr_push_spread(ScrDyn *arr, const ScrDyn *src, const char *what) {
458
+ if (src->kind == SCR_DYN_TYPED_REF) {
459
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(src);
460
+ scr_dyn_arr_push_spread(arr, materialized, what);
461
+ scr_dyn_release(materialized);
462
+ return;
463
+ }
443
464
  if (src->kind == SCR_DYN_ARR) {
444
465
  for (size_t i = 0; i < src->v.arr.len; i++) {
445
466
  scr_dyn_arr_push(arr, scr_dyn_retain(src->v.arr.items[i]));
@@ -502,6 +523,12 @@ void scr_dyn_arr_push_spread(ScrDyn *arr, const ScrDyn *src, const char *what) {
502
523
  * undefined no kind prefix, null V8's "object null"). Borrows both; +1 or
503
524
  * NULL with the TypeError pending. */
504
525
  ScrDyn *scr_dyn_iter_pack(const ScrDyn *src, const ScrStr *msg) {
526
+ if (src->kind == SCR_DYN_TYPED_REF) {
527
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(src);
528
+ ScrDyn *out = scr_dyn_iter_pack(materialized, msg);
529
+ scr_dyn_release(materialized);
530
+ return out;
531
+ }
505
532
  if (src->kind == SCR_DYN_ARR || src->kind == SCR_DYN_BYTES || src->kind == SCR_DYN_STR) {
506
533
  ScrDyn *out = scr_dyn_new_arr();
507
534
  scr_dyn_arr_push_spread(out, src, ""); /* iterable kinds never consult `what` */
@@ -611,6 +638,21 @@ ScrDyn *scr_dyn_new_str(ScrStr *s) {
611
638
 
612
639
  ScrDyn *scr_dyn_new_arr(void) { return scr_dyn_alloc(SCR_DYN_ARR); }
613
640
  ScrDyn *scr_dyn_new_obj(void) { return scr_dyn_alloc(SCR_DYN_OBJ); }
641
+ ScrDyn *scr_dyn_new_obj_with_identity(
642
+ void *source, void *(*source_retain)(void *),
643
+ ScrDyn *(*source_access)(void *, bool materialize)) {
644
+ ScrDyn *d = scr_dyn_alloc(SCR_DYN_OBJ);
645
+ d->v.obj.source_identity = source_retain(source);
646
+ d->v.obj.source_access = source_access;
647
+ return d;
648
+ }
649
+
650
+ bool scr_dyn_obj_same_source(const ScrDyn *a, const ScrDyn *b) {
651
+ return a && b && a->kind == SCR_DYN_OBJ && b->kind == SCR_DYN_OBJ &&
652
+ a->v.obj.source_identity && b->v.obj.source_identity &&
653
+ a->v.obj.source_identity == b->v.obj.source_identity;
654
+ }
655
+
614
656
  ScrDyn *scr_dyn_new_obj_null_proto(void) {
615
657
  ScrDyn *d = scr_dyn_alloc(SCR_DYN_OBJ);
616
658
  d->null_proto = true;
@@ -629,6 +671,136 @@ ScrDyn *scr_dyn_new_buffer_copy(const ScrBytes *b) {
629
671
  return d;
630
672
  }
631
673
 
674
+ ScrDyn *scr_dyn_new_typed_ref(
675
+ void *ptr, void *(*retain)(void *), void (*release)(void *),
676
+ const char *type_key, size_t type_key_len,
677
+ ScrDyn *(*materialize)(void *),
678
+ void (*commit)(void *, const ScrDyn *)) {
679
+ ScrDyn *d = scr_dyn_alloc(SCR_DYN_TYPED_REF);
680
+ d->v.typed_ref.ptr = retain(ptr);
681
+ d->v.typed_ref.retain = retain;
682
+ d->v.typed_ref.release = release;
683
+ d->v.typed_ref.type_key = type_key;
684
+ d->v.typed_ref.type_key_len = type_key_len;
685
+ d->v.typed_ref.materialize = materialize;
686
+ d->v.typed_ref.commit = commit;
687
+ d->v.typed_ref.materialized = NULL;
688
+ d->v.typed_ref.casts = NULL;
689
+ return d;
690
+ }
691
+
692
+ bool scr_dyn_typed_ref_is(
693
+ const ScrDyn *d, const char *type_key, size_t type_key_len) {
694
+ return d && d->kind == SCR_DYN_TYPED_REF &&
695
+ d->v.typed_ref.type_key_len == type_key_len &&
696
+ memcmp(d->v.typed_ref.type_key, type_key, type_key_len) == 0;
697
+ }
698
+
699
+ void *scr_dyn_typed_ref_unbox(const ScrDyn *d) {
700
+ return d->v.typed_ref.retain(d->v.typed_ref.ptr);
701
+ }
702
+
703
+ static void scr_dyn_typed_ref_preserve_child(
704
+ ScrDyn **fresh_slot, ScrDyn *cached_child) {
705
+ ScrDyn *fresh_child = *fresh_slot;
706
+ if (!cached_child || !fresh_child ||
707
+ cached_child->kind != SCR_DYN_TYPED_REF ||
708
+ fresh_child->kind != SCR_DYN_TYPED_REF ||
709
+ !scr_dyn_strict_eq(cached_child, fresh_child)) {
710
+ return;
711
+ }
712
+ *fresh_slot = scr_dyn_retain(cached_child);
713
+ scr_dyn_release(fresh_child);
714
+ }
715
+
716
+ /* A live record/array materializer creates typed capsules for mutable
717
+ * children. Preserve an existing child capsule when the fresh view points
718
+ * at the same static source, so retaining `value.child` across parent
719
+ * refreshes keeps JavaScript reference identity as well as liveness. */
720
+ static void scr_dyn_typed_ref_preserve_children(
721
+ ScrDyn *cached, ScrDyn *fresh) {
722
+ if (cached->kind != fresh->kind) return;
723
+ if (cached->kind == SCR_DYN_ARR) {
724
+ size_t n = cached->v.arr.len < fresh->v.arr.len
725
+ ? cached->v.arr.len
726
+ : fresh->v.arr.len;
727
+ for (size_t i = 0; i < n; i++) {
728
+ scr_dyn_typed_ref_preserve_child(
729
+ &fresh->v.arr.items[i], cached->v.arr.items[i]);
730
+ }
731
+ return;
732
+ }
733
+ if (cached->kind == SCR_DYN_OBJ) {
734
+ for (size_t i = 0; i < fresh->v.obj.len; i++) {
735
+ ScrDynEntry *entry = &fresh->v.obj.entries[i];
736
+ ScrDyn *cached_child = scr_dyn_obj_get(
737
+ cached, entry->key, entry->key_len);
738
+ scr_dyn_typed_ref_preserve_child(&entry->value, cached_child);
739
+ }
740
+ }
741
+ }
742
+
743
+ ScrDyn *scr_dyn_typed_ref_materialize(const ScrDyn *d) {
744
+ ScrDyn *capsule = (ScrDyn *)d;
745
+ if (!capsule->v.typed_ref.materialized) {
746
+ capsule->v.typed_ref.materialized =
747
+ capsule->v.typed_ref.materialize(capsule->v.typed_ref.ptr);
748
+ } else {
749
+ /* Keep the stable dyn object identity while refreshing its contents
750
+ * from the live typed source. The fresh snapshot owns exactly one
751
+ * reference; swapping payloads lets its release dispose the old
752
+ * detached contents without changing the cached node's address. */
753
+ ScrDyn *fresh =
754
+ capsule->v.typed_ref.materialize(capsule->v.typed_ref.ptr);
755
+ scr_dyn_typed_ref_preserve_children(
756
+ capsule->v.typed_ref.materialized, fresh);
757
+ size_t cached_rc = capsule->v.typed_ref.materialized->rc;
758
+ size_t fresh_rc = fresh->rc;
759
+ ScrDyn old = *capsule->v.typed_ref.materialized;
760
+ *capsule->v.typed_ref.materialized = *fresh;
761
+ capsule->v.typed_ref.materialized->rc = cached_rc;
762
+ *fresh = old;
763
+ fresh->rc = fresh_rc;
764
+ scr_dyn_release(fresh);
765
+ }
766
+ return scr_dyn_retain(capsule->v.typed_ref.materialized);
767
+ }
768
+
769
+ void scr_dyn_typed_ref_commit(ScrDyn *d) {
770
+ if (d && d->kind == SCR_DYN_TYPED_REF && d->v.typed_ref.commit &&
771
+ d->v.typed_ref.materialized) {
772
+ d->v.typed_ref.commit(d->v.typed_ref.ptr,
773
+ d->v.typed_ref.materialized);
774
+ }
775
+ }
776
+
777
+ void *scr_dyn_typed_ref_cached_cast(
778
+ const ScrDyn *d, const char *type_key, size_t type_key_len) {
779
+ for (ScrDynTypedCast *cast = d->v.typed_ref.casts; cast;
780
+ cast = cast->next) {
781
+ if (cast->type_key_len == type_key_len &&
782
+ memcmp(cast->type_key, type_key, type_key_len) == 0) {
783
+ return cast->retain(cast->ptr);
784
+ }
785
+ }
786
+ return NULL;
787
+ }
788
+
789
+ void scr_dyn_typed_ref_cache_cast(
790
+ ScrDyn *d, const char *type_key, size_t type_key_len, void *ptr,
791
+ void *(*retain)(void *), void (*release)(void *)) {
792
+ if (!ptr) return;
793
+ ScrDynTypedCast *cast = malloc(sizeof *cast);
794
+ if (!cast) scr_trap("scriptc: out of memory\n");
795
+ cast->type_key = type_key;
796
+ cast->type_key_len = type_key_len;
797
+ cast->ptr = retain(ptr);
798
+ cast->retain = retain;
799
+ cast->release = release;
800
+ cast->next = d->v.typed_ref.casts;
801
+ d->v.typed_ref.casts = cast;
802
+ }
803
+
632
804
  ScrBytes *scr_dyn_bytes_copy_out(const ScrDyn *d) {
633
805
  return scr_bytes_copy(d->v.bytes); /* extraction copies too (+1) */
634
806
  }
@@ -760,6 +932,14 @@ const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
760
932
  * normalization — pick by the engine's typeof. */
761
933
  d = scr_dyn_isl_typeof_is(cb, "function") ? "function" : "an instance of Object";
762
934
  break;
935
+ case SCR_DYN_TYPED_REF: {
936
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(cb);
937
+ const char *specific = scr_dyn_specific_type(materialized, detail, cap);
938
+ if (specific != detail) snprintf(detail, cap, "%s", specific);
939
+ scr_dyn_release(materialized);
940
+ d = detail;
941
+ break;
942
+ }
763
943
  case SCR_DYN_BOOL:
764
944
  snprintf(detail, cap, "type boolean (%s)", cb->v.b ? "true" : "false");
765
945
  break;
@@ -931,6 +1111,24 @@ const ScrDynJsvalOps *scr_dyn_jsval_ops(void) {
931
1111
  }
932
1112
 
933
1113
  bool scr_dyn_isl_typeof_is(const ScrDyn *d, const char *name) {
1114
+ if (d->kind == SCR_DYN_TYPED_REF) {
1115
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1116
+ bool out = scr_dyn_isl_typeof_is(materialized, name);
1117
+ if (materialized->kind != SCR_DYN_JSVAL) {
1118
+ bool object =
1119
+ materialized->kind == SCR_DYN_NULL ||
1120
+ materialized->kind == SCR_DYN_OBJ ||
1121
+ materialized->kind == SCR_DYN_ARR ||
1122
+ materialized->kind == SCR_DYN_BYTES ||
1123
+ materialized->kind == SCR_DYN_HANDLE ||
1124
+ materialized->kind == SCR_DYN_PROMISE;
1125
+ out = (strcmp(name, "object") == 0 && object) ||
1126
+ (strcmp(name, "function") == 0 &&
1127
+ materialized->kind == SCR_DYN_FUNC);
1128
+ }
1129
+ scr_dyn_release(materialized);
1130
+ return out;
1131
+ }
934
1132
  if (d->kind != SCR_DYN_JSVAL) return false;
935
1133
  ScrStr *t = scr_dyn_jsval_ops()->type_of(d->v.jsval.cell);
936
1134
  size_t n = strlen(name);
@@ -940,10 +1138,26 @@ bool scr_dyn_isl_typeof_is(const ScrDyn *d, const char *name) {
940
1138
  }
941
1139
 
942
1140
  bool scr_dyn_isl_is_array(const ScrDyn *d) {
1141
+ if (d->kind == SCR_DYN_TYPED_REF) {
1142
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1143
+ bool out = materialized->kind == SCR_DYN_ARR ||
1144
+ scr_dyn_isl_is_array(materialized);
1145
+ scr_dyn_release(materialized);
1146
+ return out;
1147
+ }
943
1148
  return d->kind == SCR_DYN_JSVAL && scr_dyn_jsval_ops()->is_array(d->v.jsval.cell);
944
1149
  }
945
1150
 
946
1151
  bool scr_dyn_isl_is_error(const ScrDyn *d) {
1152
+ if (d->kind == SCR_DYN_TYPED_REF) {
1153
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1154
+ bool out =
1155
+ (materialized->kind == SCR_DYN_OBJ &&
1156
+ scr_dyn_obj_get(materialized, "%error", 6) != NULL) ||
1157
+ scr_dyn_isl_is_error(materialized);
1158
+ scr_dyn_release(materialized);
1159
+ return out;
1160
+ }
947
1161
  return d->kind == SCR_DYN_JSVAL && scr_dyn_jsval_ops()->is_error(d->v.jsval.cell);
948
1162
  }
949
1163
 
@@ -1103,7 +1317,8 @@ bool scr_dyn_truthy(const ScrDyn *d) {
1103
1317
  case SCR_DYN_BYTES:
1104
1318
  case SCR_DYN_FUNC:
1105
1319
  case SCR_DYN_HANDLE:
1106
- case SCR_DYN_PROMISE: return true;
1320
+ case SCR_DYN_PROMISE:
1321
+ case SCR_DYN_TYPED_REF: return true;
1107
1322
  case SCR_DYN_JSVAL:
1108
1323
  /* Route to the engine's ToBoolean: objects/arrays/functions are
1109
1324
  * true, but the symbol/bigint edge (0n is falsy) needs the engine. */
@@ -1116,6 +1331,12 @@ bool scr_dyn_truthy(const ScrDyn *d) {
1116
1331
  * null answers "object" — JS's oldest wart, preserved. */
1117
1332
  ScrStr *scr_dyn_typeof(const ScrDyn *d) {
1118
1333
  const char *s;
1334
+ if (d->kind == SCR_DYN_TYPED_REF) {
1335
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1336
+ ScrStr *out = scr_dyn_typeof(materialized);
1337
+ scr_dyn_release(materialized);
1338
+ return out;
1339
+ }
1119
1340
  /* An island value answers the ENGINE's typeof — "object" for the
1120
1341
  * wrapped objects/arrays, "function" for engine functions (row 1 of
1121
1342
  * the jsval→dyn op table; scalars normalized away at wrap time). */
@@ -1127,7 +1348,8 @@ ScrStr *scr_dyn_typeof(const ScrDyn *d) {
1127
1348
  case SCR_DYN_ARR:
1128
1349
  case SCR_DYN_BYTES:
1129
1350
  case SCR_DYN_HANDLE:
1130
- case SCR_DYN_PROMISE: s = "object"; break;
1351
+ case SCR_DYN_PROMISE:
1352
+ case SCR_DYN_TYPED_REF: s = "object"; break; /* handled above */
1131
1353
  case SCR_DYN_BOOL: s = "boolean"; break;
1132
1354
  case SCR_DYN_NUM: s = "number"; break;
1133
1355
  case SCR_DYN_STR: s = "string"; break;
@@ -1222,6 +1444,12 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) {
1222
1444
  scr_str_release(j);
1223
1445
  return true;
1224
1446
  }
1447
+ case SCR_DYN_TYPED_REF: {
1448
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1449
+ bool present = scr_dyn_json_write(b, materialized);
1450
+ scr_dyn_release(materialized);
1451
+ return present;
1452
+ }
1225
1453
  case SCR_DYN_HANDLE:
1226
1454
  default: {
1227
1455
  const char *msg = "JSON.stringify of a runtime handle is not supported yet";
@@ -1409,8 +1637,17 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
1409
1637
  case SCR_DYN_OBJ:
1410
1638
  return scr_str_new("[object Object]", 15);
1411
1639
  case SCR_DYN_HANDLE:
1412
- /* IncomingMessage/ServerResponse/Socket inherit
1413
- * Object.prototype.toString Node's String() answer exactly. */
1640
+ if (d->v.handle.tag >= SCR_DYNH_ABORT_SIGNAL &&
1641
+ d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) {
1642
+ ScrJsonBuf b;
1643
+ scr_jb_init(&b);
1644
+ scr_jb_puts(&b, "[object ");
1645
+ scr_jb_puts(&b, scr_dyn_handle_cls(d));
1646
+ scr_jb_putc(&b, ']');
1647
+ return scr_jb_finish(&b);
1648
+ }
1649
+ /* IncomingMessage/ServerResponse/Socket and the other Node handles
1650
+ * inherit Object.prototype.toString without a @@toStringTag. */
1414
1651
  return scr_str_new("[object Object]", 15);
1415
1652
  case SCR_DYN_PROMISE:
1416
1653
  /* Object.prototype.toString with the Promise @@toStringTag. */
@@ -1447,6 +1684,12 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
1447
1684
  ScrStr *s = scr_dyn_jsval_ops()->to_str(d->v.jsval.cell);
1448
1685
  return s ? s : scr_str_new("", 0);
1449
1686
  }
1687
+ case SCR_DYN_TYPED_REF: {
1688
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1689
+ ScrStr *s = scr_dyn_to_string(materialized, enc);
1690
+ scr_dyn_release(materialized);
1691
+ return s;
1692
+ }
1450
1693
  case SCR_DYN_UNDEF:
1451
1694
  case SCR_DYN_NULL:
1452
1695
  default: {
@@ -1487,25 +1730,60 @@ ScrStr *scr_dyn_string_coerce(const ScrDyn *d) {
1487
1730
  return scr_dyn_to_string(d, NULL);
1488
1731
  }
1489
1732
 
1733
+ static bool scr_dyn_to_primitive_result_is_object(const ScrDyn *d) {
1734
+ switch (d->kind) {
1735
+ case SCR_DYN_OBJ:
1736
+ case SCR_DYN_ARR:
1737
+ case SCR_DYN_BYTES:
1738
+ case SCR_DYN_FUNC:
1739
+ case SCR_DYN_HANDLE:
1740
+ case SCR_DYN_PROMISE:
1741
+ case SCR_DYN_TYPED_REF:
1742
+ return true;
1743
+ case SCR_DYN_JSVAL:
1744
+ /* Engine scalars normally normalize into their native dyn kinds on
1745
+ * ingress, but preserve the correct answer for any producer that keeps
1746
+ * a wrapped object/function (or a defensively wrapped null). */
1747
+ return !scr_dyn_is_nullish(d) &&
1748
+ (scr_dyn_isl_typeof_is(d, "object") ||
1749
+ scr_dyn_isl_typeof_is(d, "function"));
1750
+ default:
1751
+ return false;
1752
+ }
1753
+ }
1754
+
1490
1755
  /* JS ToString over a dyn value WITH the object protocol (the WHATWG
1491
1756
  * USVString conversions — URLSearchParams names/values): an OBJ whose
1492
1757
  * own 'toString' member is callable is invoked with zero arguments (its
1493
- * throw propagates, catchably); a non-primitive answer falls through to
1494
- * 'valueOf' (ToPrimitive's string hint); exhaustion is the spec's
1758
+ * throw propagates, catchably); without an own member, an ordinary object
1759
+ * inherits Object.prototype.toString and answers "[object Object]". A
1760
+ * non-primitive answer falls through to 'valueOf' (ToPrimitive's string
1761
+ * hint); exhaustion is the spec's
1495
1762
  * "Cannot convert object to primitive value" TypeError. Every other
1496
1763
  * kind matches scr_dyn_string_coerce (units RENDER — ToString(null) is
1497
1764
  * "null"). Borrows; +1, or NULL with the exception pending. */
1498
1765
  ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
1766
+ if (d->kind == SCR_DYN_TYPED_REF) {
1767
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1768
+ ScrStr *out = scr_dyn_string_coerce_js(materialized);
1769
+ scr_dyn_release(materialized);
1770
+ return out;
1771
+ }
1499
1772
  if (d->kind == SCR_DYN_OBJ) {
1500
1773
  static const char *const hint[2] = { "toString", "valueOf" };
1501
1774
  for (int i = 0; i < 2; i++) {
1502
1775
  ScrDyn *m = scr_dyn_obj_get(d, hint[i], strlen(hint[i])); /* borrowed */
1776
+ if (!m && i == 0 && !d->null_proto) {
1777
+ return scr_str_new("[object Object]", 15);
1778
+ }
1503
1779
  if (!m || m->kind != SCR_DYN_FUNC) continue;
1780
+ /* OrdinaryToPrimitive performs a method call, not a bare function
1781
+ * call: an own coercion hook observes the source object as `this`. */
1782
+ scr_dyn_this_push_dyn(d);
1504
1783
  ScrDyn *r = scr_dyn_call(m, NULL, 0, hint[i]);
1784
+ scr_dyn_this_pop();
1505
1785
  if (!r) return NULL; /* the method threw — pending */
1506
- if (r->kind == SCR_DYN_OBJ || r->kind == SCR_DYN_ARR ||
1507
- r->kind == SCR_DYN_FUNC || r->kind == SCR_DYN_HANDLE ||
1508
- r->kind == SCR_DYN_PROMISE) {
1786
+ if (scr_dyn_to_primitive_result_is_object(r)) {
1509
1787
  scr_dyn_release(r); /* non-primitive answer: try the next method */
1510
1788
  continue;
1511
1789
  }
@@ -1520,6 +1798,81 @@ ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
1520
1798
  return scr_dyn_string_coerce(d);
1521
1799
  }
1522
1800
 
1801
+ /* JS ToNumber over a checked-dynamic value, including OrdinaryToPrimitive's
1802
+ * NUMBER hint for object snapshots. This is the numeric twin of
1803
+ * scr_dyn_string_coerce_js above: valueOf precedes toString, inherited
1804
+ * Object.prototype.valueOf returns the receiver (so conversion continues),
1805
+ * and the inherited toString fallback supplies "[object Object]". Borrows d;
1806
+ * false means an object hook threw or no primitive value could be produced. */
1807
+ bool scr_dyn_number_coerce_js(const ScrDyn *d, double *out) {
1808
+ if (d->kind == SCR_DYN_TYPED_REF) {
1809
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1810
+ bool ok = scr_dyn_number_coerce_js(materialized, out);
1811
+ scr_dyn_release(materialized);
1812
+ return ok;
1813
+ }
1814
+ switch (d->kind) {
1815
+ case SCR_DYN_NULL:
1816
+ *out = 0.0;
1817
+ return true;
1818
+ case SCR_DYN_BOOL:
1819
+ *out = d->v.b ? 1.0 : 0.0;
1820
+ return true;
1821
+ case SCR_DYN_NUM:
1822
+ *out = d->v.num;
1823
+ return true;
1824
+ case SCR_DYN_STR:
1825
+ *out = scr_string_to_number(d->v.str);
1826
+ return true;
1827
+ case SCR_DYN_UNDEF:
1828
+ *out = NAN;
1829
+ return true;
1830
+ case SCR_DYN_OBJ: {
1831
+ static const char *const hint[2] = { "valueOf", "toString" };
1832
+ for (int i = 0; i < 2; i++) {
1833
+ ScrDyn *m = scr_dyn_obj_get(d, hint[i], strlen(hint[i])); /* borrowed */
1834
+ if (!m) {
1835
+ if (!d->null_proto && i == 0) {
1836
+ /* Inherited Object.prototype.valueOf returns the object, so the
1837
+ * number-hint protocol advances to toString. */
1838
+ continue;
1839
+ }
1840
+ if (!d->null_proto && i == 1) {
1841
+ *out = NAN; /* Number("[object Object]") */
1842
+ return true;
1843
+ }
1844
+ continue;
1845
+ }
1846
+ if (m->kind != SCR_DYN_FUNC) continue;
1847
+ scr_dyn_this_push_dyn(d);
1848
+ ScrDyn *r = scr_dyn_call(m, NULL, 0, hint[i]);
1849
+ scr_dyn_this_pop();
1850
+ if (!r) return false;
1851
+ if (scr_dyn_to_primitive_result_is_object(r)) {
1852
+ scr_dyn_release(r);
1853
+ continue;
1854
+ }
1855
+ bool ok = scr_dyn_number_coerce_js(r, out);
1856
+ scr_dyn_release(r);
1857
+ return ok;
1858
+ }
1859
+ static const char msg[] = "Cannot convert object to primitive value";
1860
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
1861
+ return false;
1862
+ }
1863
+ default: {
1864
+ /* Arrays, byte views, functions, promises, and native handles inherit a
1865
+ * valueOf that returns the receiver, then use their existing JS-exact
1866
+ * string rendering for the numeric conversion. */
1867
+ ScrStr *text = scr_dyn_string_coerce(d);
1868
+ if (!text) return false;
1869
+ *out = scr_string_to_number(text);
1870
+ scr_str_release(text);
1871
+ return true;
1872
+ }
1873
+ }
1874
+ }
1875
+
1523
1876
  /* The checked-dynamic keyed WRITE (`h.k = v` on a dyn receiver): OBJ sets
1524
1877
  * the member (later writes win, insertion order — JS); undefined/null
1525
1878
  * throws Node's "Cannot set properties of ..."; every other kind throws
@@ -1532,6 +1885,12 @@ static const char *scr_dyn_kind_name(const ScrDyn *d);
1532
1885
  * valid dense index, every other kind false (tsc admits `in` only on
1533
1886
  * object-typed operands). Borrows both; never throws. */
1534
1887
  bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) {
1888
+ if (v->kind == SCR_DYN_TYPED_REF) {
1889
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(v);
1890
+ bool out = scr_dyn_has_key(materialized, key);
1891
+ scr_dyn_release(materialized);
1892
+ return out;
1893
+ }
1535
1894
  if (v->kind == SCR_DYN_OBJ) return scr_dyn_obj_get(v, key->data, key->len) != NULL;
1536
1895
  if (v->kind == SCR_DYN_ARR) {
1537
1896
  if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return true;
@@ -1549,6 +1908,13 @@ bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) {
1549
1908
  }
1550
1909
 
1551
1910
  void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
1911
+ if (recv->kind == SCR_DYN_TYPED_REF) {
1912
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(recv);
1913
+ scr_dyn_key_set(materialized, key, value);
1914
+ if (!scr_exc_pending()) scr_dyn_typed_ref_commit(recv);
1915
+ scr_dyn_release(materialized);
1916
+ return;
1917
+ }
1552
1918
  if (recv->kind == SCR_DYN_OBJ) {
1553
1919
  scr_dyn_obj_set(recv, key->data, key->len, scr_dyn_retain(value));
1554
1920
  return;
@@ -1689,6 +2055,12 @@ void scr_jb_put_dyn(ScrJsonBuf *b, const ScrDyn *d) {
1689
2055
  scr_str_release(j);
1690
2056
  return;
1691
2057
  }
2058
+ case SCR_DYN_TYPED_REF: {
2059
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
2060
+ scr_jb_put_dyn(b, materialized);
2061
+ scr_dyn_release(materialized);
2062
+ return;
2063
+ }
1692
2064
  case SCR_DYN_OBJ: {
1693
2065
  scr_jb_putc(b, '{');
1694
2066
  bool first = true;
@@ -1747,6 +2119,7 @@ static const char *scr_dyn_kind_name(const ScrDyn *d) {
1747
2119
  case SCR_DYN_JSVAL: return "an island value"; /* "got an island value" — validated
1748
2120
  * extraction of engine-held values has no armed route yet (lane
1749
2121
  * dom-jsval-long-tail); the failure names the world honestly. */
2122
+ case SCR_DYN_TYPED_REF: return "object";
1750
2123
  }
1751
2124
  return "unknown";
1752
2125
  }
@@ -2307,6 +2680,11 @@ bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b) {
2307
2680
  * strict equality answers). Mixed kinds already answered false above
2308
2681
  * — a dyn copy is a different object, which is Node's answer too. */
2309
2682
  return a == b || scr_dyn_jsval_ops()->strict_eq(a->v.jsval.cell, b->v.jsval.cell);
2683
+ case SCR_DYN_TYPED_REF:
2684
+ return a->v.typed_ref.ptr == b->v.typed_ref.ptr &&
2685
+ a->v.typed_ref.type_key_len == b->v.typed_ref.type_key_len &&
2686
+ memcmp(a->v.typed_ref.type_key, b->v.typed_ref.type_key,
2687
+ a->v.typed_ref.type_key_len) == 0;
2310
2688
  default: return a == b;
2311
2689
  }
2312
2690
  }
@@ -2433,6 +2811,12 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up) {
2433
2811
  * silent wrong answer. Loud fence (lane dom-jsval-long-tail). */
2434
2812
  scr_dyn_isl_fence(v, "structuredClone");
2435
2813
  return NULL;
2814
+ case SCR_DYN_TYPED_REF: {
2815
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(v);
2816
+ ScrDyn *out = scr_sc_clone(materialized, up);
2817
+ scr_dyn_release(materialized);
2818
+ return out;
2819
+ }
2436
2820
  case SCR_DYN_FUNC:
2437
2821
  case SCR_DYN_HANDLE:
2438
2822
  default: {
@@ -2544,6 +2928,12 @@ static void scr_dyn_objwalk_push(ScrDyn *out, ScrObjWalk mode, const char *key,
2544
2928
  }
2545
2929
 
2546
2930
  static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
2931
+ if (v->kind == SCR_DYN_TYPED_REF) {
2932
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(v);
2933
+ ScrDyn *out = scr_dyn_objwalk(materialized, mode);
2934
+ scr_dyn_release(materialized);
2935
+ return out;
2936
+ }
2547
2937
  if (v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL) {
2548
2938
  static const char msg[] = "Cannot convert undefined or null to object";
2549
2939
  scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
@@ -2667,6 +3057,12 @@ ScrDyn *scr_dyn_obj_keys(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWAL
2667
3057
  * existing two-arg stance — a dyn array target has no property table). */
2668
3058
  static void scr_dyn_assign_from(ScrDyn *target, const ScrDyn *src) {
2669
3059
  if (target->kind != SCR_DYN_OBJ) return;
3060
+ if (src->kind == SCR_DYN_TYPED_REF) {
3061
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(src);
3062
+ scr_dyn_assign_from(target, materialized);
3063
+ scr_dyn_release(materialized);
3064
+ return;
3065
+ }
2670
3066
  if (src->kind == SCR_DYN_UNDEF || src->kind == SCR_DYN_NULL) return;
2671
3067
  if (src->kind == SCR_DYN_OBJ) {
2672
3068
  for (size_t i = 0; i < src->v.obj.len; i++) {
@@ -2714,6 +3110,14 @@ ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src) {
2714
3110
  if (!scr_dyn_jsval_ops()->assign(target->v.jsval.cell, src)) return NULL;
2715
3111
  return scr_dyn_retain(target);
2716
3112
  }
3113
+ if (target->kind == SCR_DYN_TYPED_REF) {
3114
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(target);
3115
+ ScrDyn *assigned = scr_dyn_assign(materialized, src);
3116
+ if (!scr_exc_pending()) scr_dyn_typed_ref_commit(target);
3117
+ scr_dyn_release(assigned);
3118
+ scr_dyn_release(materialized);
3119
+ return scr_exc_pending() ? NULL : scr_dyn_retain(target);
3120
+ }
2717
3121
  if (src->kind == SCR_DYN_JSVAL) {
2718
3122
  /* A wrapped SOURCE onto a dyn target: the engine lists its own
2719
3123
  * [key, value] pairs (getters running) and each lands as a dyn