@geastack/compiler 1.0.15 → 1.0.17

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 (61) hide show
  1. package/dist/conversion/nodes.d.ts +31 -0
  2. package/dist/conversion/nodes.js +46 -2
  3. package/dist/conversion/record-view.d.ts +24 -0
  4. package/dist/conversion/record-view.js +54 -0
  5. package/dist/ir/certify.js +23 -1
  6. package/dist/ir/integers.js +24 -2
  7. package/dist/ir/lower-operands.d.ts +10 -1
  8. package/dist/ir/lower-operands.js +58 -2
  9. package/dist/ir/lower.js +12 -3
  10. package/dist/ir/reflection-demand.js +5 -0
  11. package/dist/ir/transfer.js +17 -8
  12. package/dist/plugins/apple/host.d.ts +2 -0
  13. package/dist/plugins/apple/host.js +13 -6
  14. package/dist/plugins/gea/host.d.ts +2 -0
  15. package/dist/plugins/gea/host.js +11 -4
  16. package/dist/plugins/host-package.d.ts +37 -0
  17. package/dist/plugins/host-package.js +28 -0
  18. package/dist/plugins/load.js +15 -0
  19. package/dist/plugins/webgl/host.d.ts +9 -3
  20. package/dist/plugins/webgl/host.js +15 -6
  21. package/dist/plugins/webgl/plugin.js +4 -1
  22. package/dist/representation/embedded-carriers.d.ts +22 -0
  23. package/dist/representation/embedded-carriers.js +105 -0
  24. package/dist/semantics/frontend.js +4 -1
  25. package/dist/semantics/model/operands.d.ts +17 -0
  26. package/dist/semantics/model/operations.d.ts +28 -1
  27. package/dist/semantics/normalize/flow/callable-reach.js +1 -3
  28. package/dist/semantics/normalize/global-host-mutations.js +2 -2
  29. package/dist/semantics/normalize/producers/allocations.js +2 -0
  30. package/dist/semantics/normalize/producers/bindings.js +5 -1
  31. package/dist/semantics/normalize/producers/boundary.d.ts +1 -0
  32. package/dist/semantics/normalize/producers/boundary.js +6 -2
  33. package/dist/semantics/normalize/producers/class-lifecycle.js +3 -0
  34. package/dist/semantics/normalize/producers/control.js +2 -2
  35. package/dist/semantics/normalize/producers/erasure.d.ts +7 -0
  36. package/dist/semantics/normalize/producers/erasure.js +19 -0
  37. package/dist/semantics/normalize/producers/exact-arms.d.ts +12 -0
  38. package/dist/semantics/normalize/producers/exact-arms.js +12 -0
  39. package/dist/semantics/normalize/producers/invocations.js +33 -1
  40. package/dist/semantics/normalize/reachability.d.ts +20 -5
  41. package/dist/semantics/normalize/reachability.js +119 -14
  42. package/dist/semantics/normalize/specialization.js +1 -4
  43. package/dist/targets/cpp/class-layout.d.ts +40 -0
  44. package/dist/targets/cpp/class-layout.js +112 -0
  45. package/dist/targets/cpp/class-properties/emit-class-properties.js +4 -4
  46. package/dist/targets/cpp/conversions.js +63 -1
  47. package/dist/targets/cpp/emit-callable.js +7 -6
  48. package/dist/targets/cpp/emit-context.d.ts +38 -1
  49. package/dist/targets/cpp/emit-context.js +28 -2
  50. package/dist/targets/cpp/emit-narrowing.d.ts +1 -1
  51. package/dist/targets/cpp/emit-narrowing.js +54 -1
  52. package/dist/targets/cpp/emit-properties.js +13 -3
  53. package/dist/targets/cpp/emit-record-view.js +31 -0
  54. package/dist/targets/cpp/emit.d.ts +3 -3
  55. package/dist/targets/cpp/emit.js +11 -5
  56. package/dist/targets/cpp/prototype/emit-prototype-regexp.d.ts +1 -1
  57. package/dist/targets/cpp/prototype/emit-prototype-regexp.js +58 -2
  58. package/dist/targets/cpp/translation-unit.js +32 -16
  59. package/package.json +5 -5
  60. package/src/targets/cpp/runtime/gea_dynamic_proxy.h +38 -0
  61. package/src/targets/cpp/runtime/gea_runtime.h +247 -45
@@ -10,7 +10,7 @@ import { alignedValueText, bindsReceiver, callableObjectAbi, movedValueText, unb
10
10
  import { memberAccessOperator, reactiveRevisionText } from './emit-carrier-members.js';
11
11
  import { structuralRecordViewText } from './emit-record-view.js';
12
12
  import { emitDateConstruct, isDateCarrier } from './prototype/emit-prototype-date.js';
13
- import { unwrapPresentValue, bindingReference, cppConstructedThunkName, cppConstructThunkName, cppEnvironmentStructName, cppReceiverName, cppThunkName, createCppEmitBlockedError, declareCell, defineValue, defineValueAlias, isCppEmitBlockedError, isIntegerStorageValue, operandText, paddedArguments, captureFieldText } from './emit-context.js';
13
+ import { unwrapPresentValue, bindingReference, cppConstructedThunkName, cppConstructThunkName, cppEnvironmentStructName, cppReceiverName, cppThunkEntryText, cppThunkName, createCppEmitBlockedError, declareCell, defineValue, defineValueAlias, isCppEmitBlockedError, isIntegerStorageValue, operandText, paddedArguments, captureFieldText } from './emit-context.js';
14
14
  import { classMemberOf, classStaticMemberOf, cppFieldInitializerStatements, lazyArrowFieldPlanOf } from './class-layout.js';
15
15
  import { declaredRecordFieldOf } from './records.js';
16
16
  import { hostArityCallLines, hostResultText } from './host/emit-host-arity.js';
@@ -523,7 +523,8 @@ const emitLazyArrowFieldCall = (ctx, lines, operation) => {
523
523
  // conversion below then adapts the stored result to the read's, or refuses
524
524
  // and declines the fusion.
525
525
  const storedField = ctx.classes.get(site.owner)?.fields.find((field) => field.key === fieldName)?.representation ?? null;
526
- const storedAbi = storedField !== null && (storedField.kind === 'function' || storedField.kind === 'function-family' || storedField.kind === 'function-value-dispatch')
526
+ const storedAbi = storedField !== null &&
527
+ (storedField.kind === 'function' || storedField.kind === 'function-family' || storedField.kind === 'function-value-dispatch')
527
528
  ? storedField.abi
528
529
  : callee.abi;
529
530
  const bodyAbi = ctx.abiOfCallable(plan.body);
@@ -1500,8 +1501,8 @@ export const emitAllocateCallable = (ctx, lines, operation) => {
1500
1501
  // Brace-initializing this carrier with two values is what used to leave every
1501
1502
  // pre-`class` constructor function in three.js's renderer unclaimed.
1502
1503
  const pointers = payloadCarrier.kind === 'function-and-constructor'
1503
- ? `&${cppThunkName(operation.functionId)}, &${cppConstructedThunkName(operation.functionId)}`
1504
- : `&${cppThunkName(operation.functionId)}`;
1504
+ ? `${cppThunkEntryText(ctx, operation.functionId)}, &${cppConstructedThunkName(operation.functionId)}`
1505
+ : cppThunkEntryText(ctx, operation.functionId);
1505
1506
  // Tagged by the SOURCE DECLARATION, not by this copy's thunk. Two
1506
1507
  // instantiations of one generic function are two thunks and one JavaScript
1507
1508
  // function object; the runtime's own `CallableDeclarationTag` comment says
@@ -1550,11 +1551,11 @@ export const emitAllocateCallable = (ctx, lines, operation) => {
1550
1551
  return `gea::Value::boxCallable<${abi.restFrom}>(${callable})`;
1551
1552
  };
1552
1553
  if (admission.kind === 'none') {
1553
- lines.push(`${name} = gea::host::installOrdinaryConstructorPrototype(${boxText(`${callableType}{&${cppThunkName(operation.functionId)}, nullptr}`)});`);
1554
+ lines.push(`${name} = gea::host::installOrdinaryConstructorPrototype(${boxText(`${callableType}{${cppThunkEntryText(ctx, operation.functionId)}, nullptr}`)});`);
1554
1555
  return;
1555
1556
  }
1556
1557
  const dynamicEnvironment = packedEnvironmentText(ctx, lines, operation.functionId, admission);
1557
- lines.push(`${name} = gea::host::installOrdinaryConstructorPrototype(${boxText(`${callableType}{&${cppThunkName(operation.functionId)}, ${dynamicEnvironment}}`)});`);
1558
+ lines.push(`${name} = gea::host::installOrdinaryConstructorPrototype(${boxText(`${callableType}{${cppThunkEntryText(ctx, operation.functionId)}, ${dynamicEnvironment}}`)});`);
1558
1559
  return;
1559
1560
  }
1560
1561
  if (admission.kind === 'none') {
@@ -678,6 +678,13 @@ export interface EmitContext {
678
678
  readonly generatorBody: boolean;
679
679
  /** The conventions of emitted bodies, shared with their signature renderer. */
680
680
  readonly abiOfCallable: (callable: FunctionId) => CallableAbi | null;
681
+ /**
682
+ * The `name`/`length`/source text each function object must be able to
683
+ * answer, keyed by the body -- empty when the program never reads any of
684
+ * them (`translation-unit.ts`'s `preserveFunctionFacts` census). Read by
685
+ * `cppThunkEntryText`, at the sites that mint a function object.
686
+ */
687
+ readonly functionFacts: ReadonlyMap<FunctionId, CallableFactsSpelling>;
681
688
  /** The execution context whose frame this body is, so a cell owned elsewhere is recognisable as one. */
682
689
  readonly owner: FunctionId | RegionId;
683
690
  /** See `emit-narrowing.ts`'s `PrinterDrift`; one program-wide list, shared by every body's context. */
@@ -1622,7 +1629,7 @@ export declare const directCalleesOf: (body: IrBody, abiOfCallable: (callable: F
1622
1629
  readonly directCallees: ReadonlyMap<IrValueId, string>;
1623
1630
  readonly directCalleeAbis: ReadonlyMap<IrValueId, CallableAbi>;
1624
1631
  };
1625
- export declare const createEmitContext: (abi: CallableAbi | null, owner: FunctionId | RegionId, placements: ReadonlyMap<DeclarationId, BindingPlacement>, classes: ReadonlyMap<DeclarationId, ClassLayout>, hosts: HostSpellings, deriver: RepresentationDeriver, bodyFacts: EmitBodyFacts, wellKnownSymbols?: ReadonlyMap<DeclarationId, string>, captures?: CaptureIndex, symbolKeys?: Map<string, string>, templateObjects?: Map<string, TemplateObjectDefinition>, directCallableBindings?: ReadonlyMap<DeclarationId, FunctionId>, virtualDispatch?: ReadonlyMap<string, CallableAbi>, narrowedFormals?: ReadonlySet<number>, repeatedConstructors?: ReadonlyMap<DeclarationId, DeclarationId>, dyingArguments?: ReadonlySet<IrValueId>, instantiation?: InstantiationFacts, abiOfCallable?: (callable: FunctionId) => CallableAbi | null, hostMethodAliases?: ReadonlyMap<DeclarationId, HostMethodAlias>, callableMemberCandidates?: ReadonlyMap<string, FunctionId>, borrowableMemberBodies?: ReadonlySet<FunctionId>, stableBorrowEntries?: ReadonlyMap<string, StableBorrowEntry>, printerDrift?: PrinterDrift[], conversions?: ConversionCensus | null, nativeSelections?: ReadonlyMap<string, NativeSelectionHelper> | undefined, callableIdentityDemand?: CallableIdentityDemand, nativeIntegrityRestricted?: boolean, fixedFieldStateConstant?: boolean) => {
1632
+ export declare const createEmitContext: (abi: CallableAbi | null, owner: FunctionId | RegionId, placements: ReadonlyMap<DeclarationId, BindingPlacement>, classes: ReadonlyMap<DeclarationId, ClassLayout>, hosts: HostSpellings, deriver: RepresentationDeriver, bodyFacts: EmitBodyFacts, wellKnownSymbols?: ReadonlyMap<DeclarationId, string>, captures?: CaptureIndex, symbolKeys?: Map<string, string>, templateObjects?: Map<string, TemplateObjectDefinition>, directCallableBindings?: ReadonlyMap<DeclarationId, FunctionId>, virtualDispatch?: ReadonlyMap<string, CallableAbi>, narrowedFormals?: ReadonlySet<number>, repeatedConstructors?: ReadonlyMap<DeclarationId, DeclarationId>, dyingArguments?: ReadonlySet<IrValueId>, instantiation?: InstantiationFacts, abiOfCallable?: (callable: FunctionId) => CallableAbi | null, functionFacts?: ReadonlyMap<FunctionId, CallableFactsSpelling>, hostMethodAliases?: ReadonlyMap<DeclarationId, HostMethodAlias>, callableMemberCandidates?: ReadonlyMap<string, FunctionId>, borrowableMemberBodies?: ReadonlySet<FunctionId>, stableBorrowEntries?: ReadonlyMap<string, StableBorrowEntry>, printerDrift?: PrinterDrift[], conversions?: ConversionCensus | null, nativeSelections?: ReadonlyMap<string, NativeSelectionHelper> | undefined, callableIdentityDemand?: CallableIdentityDemand, nativeIntegrityRestricted?: boolean, fixedFieldStateConstant?: boolean) => {
1626
1633
  readonly ctx: EmitContext;
1627
1634
  readonly prepass: EmitBodyPrepassFacts;
1628
1635
  };
@@ -1739,6 +1746,36 @@ export declare const cppFormalName: (ordinal: number) => string;
1739
1746
  export declare const cppReceiverName = "gea_this";
1740
1747
  /** The thunk that adapts one function body to the environment-passing invoke pointer a callable carrier holds. */
1741
1748
  export declare const cppThunkName: (functionId: string) => string;
1749
+ /**
1750
+ * What one function object answers for `name`, `length` and
1751
+ * `Function.prototype.toString`, already spelled for the registration call:
1752
+ * `abiType` is `cppAbiType` of the thunk's own convention, which the
1753
+ * registry's `Invoke` template parameter must match exactly.
1754
+ */
1755
+ export interface CallableFactsSpelling {
1756
+ readonly abiType: string;
1757
+ readonly name: string;
1758
+ readonly length: number;
1759
+ readonly source: string;
1760
+ }
1761
+ /**
1762
+ * The invoke pointer a minting site stores in a callable carrier: `&thunk`,
1763
+ * or -- when the program reads function facts -- `&thunk` handed back by
1764
+ * `gea::CallableObject<Abi>::entryWithFacts<&thunk>(name, length, text)`,
1765
+ * which registers the facts the first time any site mints this function.
1766
+ *
1767
+ * Registration lives HERE, at the mint, and not beside the thunk, because a
1768
+ * namespace-scope `static const bool ... = registerSource<&thunk>(...)` is a
1769
+ * static initializer that takes the thunk's address, and a static initializer
1770
+ * with a side effect is a root the linker may not drop. Every emitted
1771
+ * function, and its source text, then survived `--gc-sections` and LTO
1772
+ * whether or not anything reached it: a raw HTTP server whose `EventEmitter`
1773
+ * boxed one listener kept all 384 of its functions. A mint site is reachable
1774
+ * exactly when a function object can exist, and only an existing function
1775
+ * object can be asked for its facts, so registering there preserves every
1776
+ * observable answer and pins nothing else.
1777
+ */
1778
+ export declare const cppThunkEntryText: (ctx: EmitContext, functionId: FunctionId) => string;
1742
1779
  /** The thunk that adapts one class's construct function to the environment-passing construct pointer a constructor carrier holds. */
1743
1780
  export declare const cppConstructThunkName: (declaration: DeclarationId) => string;
1744
1781
  /**
@@ -4,7 +4,7 @@ import { noInstantiationFacts } from '../../ir/instantiation.js';
4
4
  import { observesEveryCallableIdentity } from '../../ir/callable-identity-demand.js';
5
5
  import { hostCallName, hostMemberOf, statedHostIntrinsicLength } from './host/host-members.js';
6
6
  import { representationKey } from '../../representation/model.js';
7
- import { cppBodyName, cppConstructName, cppNarrowedIntegerType, cppStringLiteral, cppTypeOf, cppUndefinedIn } from './types.js';
7
+ import { cppBodyName, cppConstructName, cppNarrowedIntegerType, cppStringLiteral, cppStringViewLiteral, cppTypeOf, cppUndefinedIn } from './types.js';
8
8
  import { hostBuiltinFunctionIdentityText, hostClassValueText, hostFunctionValueText, hostJsonMemberValueText, hostMemberValueText } from './host/emit-host-value.js';
9
9
  import { createConversionNodes } from '../../conversion/nodes.js';
10
10
  import { createCppConversionRegistry } from './conversions.js';
@@ -75,7 +75,7 @@ export const directCalleesOf = (body, abiOfCallable) => {
75
75
  }
76
76
  return { directCallees, directCalleeAbis };
77
77
  };
78
- export const createEmitContext = (abi, owner, placements, classes, hosts, deriver, bodyFacts, wellKnownSymbols = new Map(), captures = emptyCaptureIndex, symbolKeys = new Map(), templateObjects = new Map(), directCallableBindings = new Map(), virtualDispatch = new Map(), narrowedFormals = new Set(), repeatedConstructors = new Map(), dyingArguments = new Set(), instantiation = noInstantiationFacts, abiOfCallable = () => null, hostMethodAliases = new Map(), callableMemberCandidates = new Map(), borrowableMemberBodies = new Set(), stableBorrowEntries = new Map(), printerDrift = [],
78
+ export const createEmitContext = (abi, owner, placements, classes, hosts, deriver, bodyFacts, wellKnownSymbols = new Map(), captures = emptyCaptureIndex, symbolKeys = new Map(), templateObjects = new Map(), directCallableBindings = new Map(), virtualDispatch = new Map(), narrowedFormals = new Set(), repeatedConstructors = new Map(), dyingArguments = new Set(), instantiation = noInstantiationFacts, abiOfCallable = () => null, functionFacts = new Map(), hostMethodAliases = new Map(), callableMemberCandidates = new Map(), borrowableMemberBodies = new Set(), stableBorrowEntries = new Map(), printerDrift = [],
79
79
  // A context built without the program's census (the value-contract
80
80
  // checks) gets one over the same tables with nothing pre-minted: the
81
81
  // registry answers every pair the eager graph would have, one node later.
@@ -126,6 +126,7 @@ conversions = null, nativeSelections = undefined, callableIdentityDemand = obser
126
126
  const ctx = {
127
127
  abi: effectiveAbiOf(abi, admission),
128
128
  abiOfCallable,
129
+ functionFacts,
129
130
  owner,
130
131
  placements,
131
132
  classes,
@@ -537,6 +538,31 @@ export const cppFormalName = (ordinal) => `gea_arg_${ordinal}`;
537
538
  export const cppReceiverName = 'gea_this';
538
539
  /** The thunk that adapts one function body to the environment-passing invoke pointer a callable carrier holds. */
539
540
  export const cppThunkName = (functionId) => `${cppBodyName(functionId)}_thunk`;
541
+ /**
542
+ * The invoke pointer a minting site stores in a callable carrier: `&thunk`,
543
+ * or -- when the program reads function facts -- `&thunk` handed back by
544
+ * `gea::CallableObject<Abi>::entryWithFacts<&thunk>(name, length, text)`,
545
+ * which registers the facts the first time any site mints this function.
546
+ *
547
+ * Registration lives HERE, at the mint, and not beside the thunk, because a
548
+ * namespace-scope `static const bool ... = registerSource<&thunk>(...)` is a
549
+ * static initializer that takes the thunk's address, and a static initializer
550
+ * with a side effect is a root the linker may not drop. Every emitted
551
+ * function, and its source text, then survived `--gc-sections` and LTO
552
+ * whether or not anything reached it: a raw HTTP server whose `EventEmitter`
553
+ * boxed one listener kept all 384 of its functions. A mint site is reachable
554
+ * exactly when a function object can exist, and only an existing function
555
+ * object can be asked for its facts, so registering there preserves every
556
+ * observable answer and pins nothing else.
557
+ */
558
+ export const cppThunkEntryText = (ctx, functionId) => {
559
+ const thunk = `&${cppThunkName(functionId)}`;
560
+ const facts = ctx.functionFacts.get(functionId);
561
+ if (facts === undefined)
562
+ return thunk;
563
+ return (`gea::CallableObject<${facts.abiType}>::entryWithFacts<${thunk}>(` +
564
+ `${cppStringViewLiteral(facts.name)}, ${facts.length}, ${cppStringViewLiteral(facts.source)})`);
565
+ };
540
566
  /** The thunk that adapts one class's construct function to the environment-passing construct pointer a constructor carrier holds. */
541
567
  export const cppConstructThunkName = (declaration) => `${cppConstructName(declaration)}_thunk`;
542
568
  /**
@@ -3,7 +3,7 @@ import type { CallableAbi, Representation, TaggedUnionArm } from '../../represen
3
3
  import type { IrOperand, MergeLiveArmRebuildOperation } from '../../ir/model.js';
4
4
  import type { DeclarationId, FunctionId } from '../../identity/ids.js';
5
5
  import type { ConversionNode } from '../../conversion/algebra.js';
6
- import type { ConversionCensus } from '../../conversion/nodes.js';
6
+ import { type ConversionCensus } from '../../conversion/nodes.js';
7
7
  import type { RecordLayoutPolicy } from '../../representation/policies.js';
8
8
  import type { ClassLayout } from '../../projection/classes.js';
9
9
  import type { CaptureIndex } from './emit-context.js';
@@ -2,7 +2,9 @@ import { classRefTransportKind, constructorUpcastMember } from './class-ref-tran
2
2
  import { abiKey, arrayExtensionKey, containsUnresolved, isBooleanShapedMergeTarget, isCanonicalNumberPropertyKeyText, representationKey, walkRepresentation, carriesUndefined } from '../../representation/model.js';
3
3
  import { transferOf } from '../../ir/transfer.js';
4
4
  import { coercionText } from './emit-coercion.js';
5
- import { structuralRecordViewText } from './emit-record-view.js';
5
+ import { EXACT_ARM_MATERIALIZER, exactArmIndexOf } from '../../conversion/nodes.js';
6
+ import { structuralRecordViewText, viewPlanFor } from './emit-record-view.js';
7
+ import { recordViewDispatchesArms } from '../../conversion/record-view.js';
6
8
  import { createCppEmitBlockedError, cppConstructThunkName, cppThunkName, defineValue, isCppEmitBlockedError, isDeferredValue, operandText } from './emit-context.js';
7
9
  import { booleanTestText } from './emit-presence.js';
8
10
  import { recastedRecordToArrayText } from './emit-arrays.js';
@@ -476,6 +478,16 @@ export const narrowedLoadText = (held, read, text) => {
476
478
  }
477
479
  if (inner.kind !== 'tagged-union')
478
480
  return null;
481
+ // Every arm search below SELECTS: it loads the arm the read names and
482
+ // trusts the narrowing that licensed the load to have killed the rest. A
483
+ // store into a declared slot has no such licence -- `const ctx:
484
+ // AudioContextLike | null = Ctor ? new Ctor() : createNativeAudioContext()`
485
+ // converts a `record | class` into the interface's record, and the class
486
+ // arm is as live as the record's. This chain cannot tell the two apart (a
487
+ // class viewed as a record needs the program's layouts), so the census
488
+ // decides it: a pair some arm reaches only through the structural view is
489
+ // `conversion/record-view.ts`'s `dispatch` plan, installed and rendered
490
+ // ahead of this chain (`conversions.ts`'s `staticRecipe`, `recipeText`).
479
491
  // A join may preserve another join (or an optional) as one physical arm.
480
492
  // Search that nested carrier before trying to compare the outer arm list
481
493
  // with the read's list. Otherwise a read of the preserved inner union is
@@ -1907,6 +1919,14 @@ export const dictionaryCastableToDictionary = (source, target) => {
1907
1919
  throw error;
1908
1920
  }
1909
1921
  };
1922
+ const dictionaryToRecordText = (source, target, text) => {
1923
+ if (source.kind !== 'dictionary' || source.key !== 'string' || source.value.kind !== 'dynamic' || source.ownership !== 'shared-refcount')
1924
+ return null;
1925
+ if (target.kind !== 'record' || target.accessors.length !== 0 || target.ownership === 'borrowed')
1926
+ return null;
1927
+ const boxed = dynamicCarrierBoxText(source, text);
1928
+ return boxed === null ? null : unboxedLoadText(target, boxed);
1929
+ };
1910
1930
  const recastedDictionaryText = (source, target, text) => {
1911
1931
  if (!dictionaryCastableToDictionary(source, target))
1912
1932
  return null;
@@ -3435,6 +3455,18 @@ export const conversionChain = [
3435
3455
  id: 'dictionary-to-dictionary',
3436
3456
  apply: (source, target, text) => source.kind === 'dictionary' && target.kind === 'dictionary' ? claimed(recastedDictionaryText(source, target, text)) : undefined
3437
3457
  },
3458
+ // The mirror of `record-to-dictionary` for the one dictionary whose values
3459
+ // are boxed: `globalThis as unknown as { AudioContext?: Ctor }` reads named
3460
+ // fields out of the open string-keyed surface `references.ts` gives
3461
+ // `globalThis`. The closed field list comes from the TARGET, so this is not
3462
+ // the reconstruction `conversion/derive.ts` refuses (recovering a field list
3463
+ // from an open dictionary); it is the dynamic-object product read
3464
+ // (`unboxedLoadText`'s record branch) over that dictionary boxed, which
3465
+ // `gea::detail::dynamicRecordHasField` already reads as a document.
3466
+ {
3467
+ id: 'dictionary-to-record',
3468
+ apply: (source, target, text) => claimed(dictionaryToRecordText(source, target, text))
3469
+ },
3438
3470
  // An Array of one element carrier copied into an Array of another, each
3439
3471
  // element through its own conversion. A copy, so only for an array that is
3440
3472
  // not written through both names afterwards: a matcher table built once
@@ -3695,6 +3727,11 @@ export const recipeText = (ctx, node, text) => {
3695
3727
  // `conversions.ts`'s read of a structural value as a class nothing instantiates.
3696
3728
  if (node.capability.kind === 'atom' && node.capability.materializer.id === 'gea::host::unreachableValue')
3697
3729
  return `((void)(${text}), gea::host::unreachableValue<${cppTypeOf(node.target)}>())`;
3730
+ // The census's exact-arm projection (`nodes.ts`'s `exactArmFor`): the arm
3731
+ // index is a function of the pair, so it is re-derived here rather than
3732
+ // carried on the node.
3733
+ if (node.capability.kind === 'static' && node.capability.materializer.id === EXACT_ARM_MATERIALIZER)
3734
+ return `gea::host::exactArm<${exactArmIndexOf(node.source, node.target)}>(${text})`;
3698
3735
  // `conversions.ts`'s read of a class instance's structural view as the class: the boxed origin, narrowed.
3699
3736
  if (node.capability.kind === 'atom' && node.capability.materializer.id === 'gea::record::viewOrigin')
3700
3737
  return narrowedLoadText({ kind: 'dynamic', reason: 'declared-any-never-narrowed' }, node.target, node.source.kind === 'optional'
@@ -3706,6 +3743,15 @@ export const recipeText = (ctx, node, text) => {
3706
3743
  ? `${helper.name}(${text})`
3707
3744
  : nativeSelectionText(node.capability.materializer.nativeSelection, node.source, node.target, text);
3708
3745
  }
3746
+ // A view that DISPATCHES on a sum's live arm is rendered ahead of the
3747
+ // chain: the chain would select the exact arm and trust a narrowing that,
3748
+ // at a store, never happened (`narrowedLoadText`'s arm search says why).
3749
+ // `conversions.ts`'s `staticRecipe` installs the node in the same order.
3750
+ if (node.capability.kind === 'static' && node.capability.materializer.id === 'view:structural-record') {
3751
+ const view = viewPlanFor(ctx.layouts, node.source, node.target);
3752
+ if (view !== null && recordViewDispatchesArms(view))
3753
+ return structuralRecordViewText(ctx, node.source, node.target, text);
3754
+ }
3709
3755
  return convertedValueText(node.source, node.target, text) ?? structuralRecordViewText(ctx, node.source, node.target, text);
3710
3756
  };
3711
3757
  /**
@@ -3828,6 +3874,13 @@ const ownsItsStorage = (carrier) => {
3828
3874
  return carrier.ownership === 'shared-refcount';
3829
3875
  if (carrier.kind === 'optional')
3830
3876
  return ownsItsStorage(carrier.payload);
3877
+ // A sum owns whatever its live arm owns. `gea::TaggedUnion` has a real move
3878
+ // constructor (`TaggedUnionOps::moveConstruct`), so a dying union hands its
3879
+ // string or reference over instead of `copyConstruct`ing it and then
3880
+ // destroying the original -- per header value, per request, in node-compat's
3881
+ // `writeHead`, where this was three heap copies of one content-type string.
3882
+ if (carrier.kind === 'tagged-union')
3883
+ return carrier.arms.some((arm) => ownsItsStorage(arm.value));
3831
3884
  return false;
3832
3885
  };
3833
3886
  /**
@@ -592,7 +592,7 @@ export const emitGet = (ctx, lines, operation) => {
592
592
  // struct-member fallthrough at the end would take `re.test` and emit
593
593
  // `re->test`, which is a clang error naming a member instead of a refusal
594
594
  // naming the method.
595
- const regexpMember = regexpMemberText(ctx, operation.receiver, operation.key, operation.result.id, operation.result.representation, (fieldName, storage) => narrowedFieldReadText(ctx, operation, fieldName, storage)) ?? stringObjectMemberText(ctx, operation.receiver, operation.key, operation.result.representation);
595
+ const regexpMember = regexpMemberText(ctx, operation.receiver, operation.key, operation.result.id, operation.result.representation, (fieldName, storage, declared) => narrowedFieldReadText(ctx, operation, fieldName, storage, null, declared)) ?? stringObjectMemberText(ctx, operation.receiver, operation.key, operation.result.representation);
596
596
  if (regexpMember !== null) {
597
597
  // An empty rendering is a deferred RegExp.prototype method read -- see
598
598
  // the identical comment on the array-access branch above.
@@ -911,8 +911,18 @@ const fixedFieldPresenceText = (ctx, receiver, fieldName) => {
911
911
  * the original defect got in. A disagreement it declines to convert is a
912
912
  * defect upstream, not a spelling this may invent, so that refuses by name.
913
913
  */
914
- const narrowedFieldReadText = (ctx, operation, fieldName, storage, presence = null) => {
915
- const declared = declaredFieldRepresentation(ctx, operation.receiver, fieldName);
914
+ const narrowedFieldReadText = (ctx, operation, fieldName, storage, presence = null,
915
+ /**
916
+ * The member's physical carrier, supplied by a caller that HAS it where the
917
+ * lookup below cannot. A compiler-owned native layout
918
+ * (`ExecResult`/`MatchResult`) never gets a sealed record layout, so
919
+ * `declaredFieldRepresentation` answers null for its members and this
920
+ * function would hand back the bare load -- correct until the read is
921
+ * narrowed, and a type error the moment it is. The caller that knows the
922
+ * struct says so instead of this function guessing.
923
+ */
924
+ declaredStorage = null) => {
925
+ const declared = declaredStorage ?? declaredFieldRepresentation(ctx, operation.receiver, fieldName);
916
926
  if (declared === null)
917
927
  return storage;
918
928
  const published = operation.result.representation;
@@ -266,6 +266,37 @@ const recordViewText = (ctx, plan, text) => {
266
266
  }
267
267
  return recastedUnionFromHomes(plan.source, plan.target, text, homes);
268
268
  }
269
+ case 'dispatch': {
270
+ // The same discriminant chain `taggedUnionArmText` spells, with the last
271
+ // arm untested: the plan admitted every arm, so one of them is live.
272
+ // Each home is spelled at the whole target's type so an optional target
273
+ // wraps once per arm and an absent arm is its empty state.
274
+ const targetType = cppTypeOf(plan.target);
275
+ const payload = plan.target.kind === 'optional' ? plan.target.payload : plan.target;
276
+ const homes = [];
277
+ for (const [index, arm] of plan.arms.entries()) {
278
+ const from = plan.source.arms[index];
279
+ if (from === undefined)
280
+ return null;
281
+ const armText = `${text}.get<${index}>()`;
282
+ const rendered = arm.via === 'exact'
283
+ ? armText
284
+ : arm.via === 'absent'
285
+ ? null
286
+ : arm.via === 'convert'
287
+ ? convertedValueText(from.value, payload, armText)
288
+ : recordViewText(ctx, arm.via, armText);
289
+ if (arm.via !== 'absent' && rendered === null)
290
+ return null;
291
+ homes.push(rendered === null ? `${targetType}()` : `${targetType}(${rendered})`);
292
+ }
293
+ let result = homes[homes.length - 1];
294
+ if (result === undefined)
295
+ return null;
296
+ for (let index = homes.length - 2; index >= 0; index--)
297
+ result = `${text}.is<${index}>() ? ${homes[index]} : (${result})`;
298
+ return `(${result})`;
299
+ }
269
300
  case 'fields':
270
301
  return recordFieldsViewText(ctx, plan, text);
271
302
  }
@@ -8,8 +8,8 @@ import type { CallableAbi } from '../../representation/model.js';
8
8
  import type { RepresentationDeriver } from '../../representation/derive.js';
9
9
  import type { CppArtifact } from './document.js';
10
10
  import type { HostSpellings } from './host/host-members.js';
11
- import { type CaptureIndex, type TemplateObjectDefinition } from './emit-context.js';
12
- import type { ConversionCensus } from '../../conversion/nodes.js';
11
+ import { type CaptureIndex, type CallableFactsSpelling, type TemplateObjectDefinition } from './emit-context.js';
12
+ import { type ConversionCensus } from '../../conversion/nodes.js';
13
13
  import { type PrinterDrift } from './emit-narrowing.js';
14
14
  import type { IntegerStorageFacts } from '../../ir/integers.js';
15
15
  import { type InstantiationFacts } from '../../ir/instantiation.js';
@@ -41,5 +41,5 @@ import type { HostMethodAlias } from './host/host-method-aliases.js';
41
41
  * the same namespace or an absent constant -- see the call site for why an
42
42
  * absent arm is the one thing that may accompany it.
43
43
  */
44
- export declare const emitBody: (body: IrBody, placements: ReadonlyMap<DeclarationId, BindingPlacement>, classes: ReadonlyMap<DeclarationId, ClassLayout>, hosts: HostSpellings, deriver: RepresentationDeriver, wellKnownSymbols: ReadonlyMap<DeclarationId, string> | undefined, captures: CaptureIndex | undefined, symbolKeys: Map<string, string>, templateObjects: Map<string, TemplateObjectDefinition>, directCallableBindings?: ReadonlyMap<DeclarationId, FunctionId>, virtualDispatch?: ReadonlyMap<string, CallableAbi>, narrowedStorage?: IntegerStorageFacts, narrowedFormals?: ReadonlySet<number>, repeatedConstructors?: ReadonlyMap<DeclarationId, DeclarationId>, dyingArguments?: ReadonlySet<IrValueId>, instantiation?: InstantiationFacts, abiOfCallable?: (callable: FunctionId) => IrBody["abi"], hostMethodAliases?: ReadonlyMap<DeclarationId, HostMethodAlias>, callableMemberCandidates?: ReadonlyMap<string, FunctionId>, borrowableMemberBodies?: ReadonlySet<FunctionId>, stableBorrowEntries?: ReadonlyMap<string, StableBorrowEntry>, printerDrift?: PrinterDrift[], conversions?: ConversionCensus | null, nativeSelections?: ReadonlyMap<string, NativeSelectionHelper> | undefined, callableIdentityDemand?: CallableIdentityDemand, nativeIntegrityRestricted?: boolean, fixedFieldStateConstant?: boolean) => readonly CppArtifact[];
44
+ export declare const emitBody: (body: IrBody, placements: ReadonlyMap<DeclarationId, BindingPlacement>, classes: ReadonlyMap<DeclarationId, ClassLayout>, hosts: HostSpellings, deriver: RepresentationDeriver, wellKnownSymbols: ReadonlyMap<DeclarationId, string> | undefined, captures: CaptureIndex | undefined, symbolKeys: Map<string, string>, templateObjects: Map<string, TemplateObjectDefinition>, directCallableBindings?: ReadonlyMap<DeclarationId, FunctionId>, virtualDispatch?: ReadonlyMap<string, CallableAbi>, narrowedStorage?: IntegerStorageFacts, narrowedFormals?: ReadonlySet<number>, repeatedConstructors?: ReadonlyMap<DeclarationId, DeclarationId>, dyingArguments?: ReadonlySet<IrValueId>, instantiation?: InstantiationFacts, abiOfCallable?: (callable: FunctionId) => IrBody["abi"], functionFacts?: ReadonlyMap<FunctionId, CallableFactsSpelling>, hostMethodAliases?: ReadonlyMap<DeclarationId, HostMethodAlias>, callableMemberCandidates?: ReadonlyMap<string, FunctionId>, borrowableMemberBodies?: ReadonlySet<FunctionId>, stableBorrowEntries?: ReadonlyMap<string, StableBorrowEntry>, printerDrift?: PrinterDrift[], conversions?: ConversionCensus | null, nativeSelections?: ReadonlyMap<string, NativeSelectionHelper> | undefined, callableIdentityDemand?: CallableIdentityDemand, nativeIntegrityRestricted?: boolean, fixedFieldStateConstant?: boolean) => readonly CppArtifact[];
45
45
  export { cppConstructedThunkName, cppConstructThunkName, cppFormalName, cppReceiverName, cppThunkName, isCppEmitBlockedError } from './emit-context.js';
@@ -7,7 +7,7 @@ import { constructedBaseOf } from '../../projection/classes.js';
7
7
  import { allOperationsOf } from '../../ir/model.js';
8
8
  import { resultOfIrOperation } from '../../ir/queries.js';
9
9
  import { representationKey } from '../../representation/model.js';
10
- import { cppFormalName, cppReceiverName, createCppEmitBlockedError, createEmitContext, defineValue, defineValueAlias, directCalleesOf, emptyCaptureIndex, isIntegerStorageValue, isCppEmitBlockedError, operandText, storageTypeOf, cppThunkName, sealFactFieldsForRender } from './emit-context.js';
10
+ import { cppFormalName, cppReceiverName, createCppEmitBlockedError, createEmitContext, defineValue, defineValueAlias, directCalleesOf, emptyCaptureIndex, isIntegerStorageValue, isCppEmitBlockedError, operandText, storageTypeOf, cppThunkEntryText, sealFactFieldsForRender } from './emit-context.js';
11
11
  import { hostMemberReadsOf } from './host/emit-host-properties.js';
12
12
  import { hostNamespaceReadsOf } from './host-namespace-reads.js';
13
13
  import { functionSourceReadsOf } from './function-source-reads.js';
@@ -18,6 +18,7 @@ import { reactiveOriginsOf } from './reactive-origins.js';
18
18
  import { renderTryRegion } from './emit-exceptions.js';
19
19
  import { emitReturn } from './emit-return.js';
20
20
  import { collectDirectBindingSinks, classObjectReadsOf, hostClassReadsOf, collectFormalCells, earlyCapturedCellPrologue, emitBindingRead, emitBindingWrite } from './emit-bindings.js';
21
+ import { EXACT_ARM_MATERIALIZER } from '../../conversion/nodes.js';
21
22
  import { alignedValueText, emitMergeLiveArmRebuild, namedConversionText, widenedStoreText } from './emit-narrowing.js';
22
23
  import { admitDenseWindows, collectCapacityHints, emitAllocateArrayObject, emitDenseSetup, emitFillLoop } from './emit-arrays.js';
23
24
  import { noInstantiationFacts } from '../../ir/instantiation.js';
@@ -888,7 +889,12 @@ const emitConvert = (ctx, lines, operation) => {
888
889
  // (`alignedValueText` asks the census for the same pair and renders the
889
890
  // same node; a pair the census refused is its drift row, and the chain's).
890
891
  const named = ctx.conversions.nodeById(operation.conversionUse);
891
- const text = (named?.capability.kind === 'coercion'
892
+ // A coercion and an exact-arm projection each share their (source, target)
893
+ // pair with the store node `alignedValueText` would look up, so for those
894
+ // the instruction's own node is the only thing that says which one runs.
895
+ const namedOverPair = named?.capability.kind === 'coercion' ||
896
+ (named?.capability.kind === 'static' && named.capability.materializer.id === EXACT_ARM_MATERIALIZER);
897
+ const text = (namedOverPair
892
898
  ? namedConversionText(ctx, 'emit.ts:1026', named, sourceText)
893
899
  : alignedValueText(ctx, 'emit.ts:1026', operation.source.representation, operation.result.representation, sourceText)) ??
894
900
  // A method value escaping into a receiver-less slot: the receiver is
@@ -1456,7 +1462,7 @@ const settleDeadCalleeSideEffects = (ctx, operation) => {
1456
1462
  // updating.
1457
1463
  const carrier = operation.result.representation;
1458
1464
  if (carrier.kind === 'function-value-dispatch' && carrier.abi.parameters.length === 0 && carrier.abi.receiver === null) {
1459
- ctx.deferredTexts.set(operation.result.id, `${cppTypeOf(carrier)}{&${cppThunkName(operation.functionId)}, nullptr}`);
1465
+ ctx.deferredTexts.set(operation.result.id, `${cppTypeOf(carrier)}{${cppThunkEntryText(ctx, operation.functionId)}, nullptr}`);
1460
1466
  }
1461
1467
  }
1462
1468
  return;
@@ -1618,7 +1624,7 @@ export const emitBody = (body, placements, classes, hosts, deriver, wellKnownSym
1618
1624
  symbolKeys, templateObjects, directCallableBindings = new Map(),
1619
1625
  // `virtualDispatch` is every `class key` with a dispatch member (`virtual-methods.ts`);
1620
1626
  // `narrowedStorage`/`narrowedFormals` are what the integer census settled here.
1621
- virtualDispatch = new Map(), narrowedStorage = { reads: new Map(), integral: new Set(), magnitudes: new Map() }, narrowedFormals = new Set(), repeatedConstructors = new Map(), dyingArguments = new Set(), instantiation = noInstantiationFacts, abiOfCallable = () => null, hostMethodAliases = new Map(), callableMemberCandidates = new Map(), borrowableMemberBodies = new Set(), stableBorrowEntries = new Map(), printerDrift = [], conversions = null, nativeSelections = undefined, callableIdentityDemand = observesEveryCallableIdentity, nativeIntegrityRestricted = true, fixedFieldStateConstant = false) => {
1627
+ virtualDispatch = new Map(), narrowedStorage = { reads: new Map(), integral: new Set(), magnitudes: new Map() }, narrowedFormals = new Set(), repeatedConstructors = new Map(), dyingArguments = new Set(), instantiation = noInstantiationFacts, abiOfCallable = () => null, functionFacts = new Map(), hostMethodAliases = new Map(), callableMemberCandidates = new Map(), borrowableMemberBodies = new Set(), stableBorrowEntries = new Map(), printerDrift = [], conversions = null, nativeSelections = undefined, callableIdentityDemand = observesEveryCallableIdentity, nativeIntegrityRestricted = true, fixedFieldStateConstant = false) => {
1622
1628
  // Every fact this body settles before a single line renders, computed here
1623
1629
  // -- from `body` and the plain, already-available inputs above -- and
1624
1630
  // handed to `createEmitContext` as one argument instead of mutated onto the
@@ -1707,7 +1713,7 @@ virtualDispatch = new Map(), narrowedStorage = { reads: new Map(), integral: new
1707
1713
  directCallees: directCalleeFacts.directCallees,
1708
1714
  directCalleeAbis: directCalleeFacts.directCalleeAbis
1709
1715
  };
1710
- const { ctx, prepass } = createEmitContext(body.abi, body.sourceOwner, placements, classes, hosts, deriver, bodyFacts, wellKnownSymbols, captures, symbolKeys, templateObjects, directCallableBindings, virtualDispatch, narrowedFormals, repeatedConstructors, dyingArguments, instantiation, abiOfCallable, hostMethodAliases, callableMemberCandidates, borrowableMemberBodies, stableBorrowEntries, printerDrift, conversions, nativeSelections, callableIdentityDemand, nativeIntegrityRestricted, fixedFieldStateConstant);
1716
+ const { ctx, prepass } = createEmitContext(body.abi, body.sourceOwner, placements, classes, hosts, deriver, bodyFacts, wellKnownSymbols, captures, symbolKeys, templateObjects, directCallableBindings, virtualDispatch, narrowedFormals, repeatedConstructors, dyingArguments, instantiation, abiOfCallable, functionFacts, hostMethodAliases, callableMemberCandidates, borrowableMemberBodies, stableBorrowEntries, printerDrift, conversions, nativeSelections, callableIdentityDemand, nativeIntegrityRestricted, fixedFieldStateConstant);
1711
1717
  // `ownedValues` stays a genuine render-time OUTPUT buffer (`EmitContext`'s
1712
1718
  // own doc: `defineValue` grows it as each operation's result is named) --
1713
1719
  // unlike `ownedDyingValues` above, a formal argument's membership in it is
@@ -72,7 +72,7 @@ export declare const regExpConstructionFromObject: (ctx: EmitContext, argument:
72
72
  * Stated once; `regexpMemberText` and the prototype-read walk both ask it.
73
73
  */
74
74
  export declare const deferredRegexpMethodClaim: (staticKeyTexts: ReadonlyMap<IrValueId, string>, receiver: IrOperand, key: IrOperand) => PrototypeMethodRead | null;
75
- export declare const regexpMemberText: (ctx: EmitContext, receiver: IrOperand, key: IrOperand, result: IrValueId | null, resultRepresentation: Representation | undefined, narrow: (fieldName: string, storage: string) => string) => string | null;
75
+ export declare const regexpMemberText: (ctx: EmitContext, receiver: IrOperand, key: IrOperand, result: IrValueId | null, resultRepresentation: Representation | undefined, narrow: (fieldName: string, storage: string, declared: Representation | null) => string) => string | null;
76
76
  /**
77
77
  * `regexpStoreRefusal`'s whole decision, spelled as three outcomes rather than
78
78
  * collapsed into "throws or doesn't":
@@ -216,6 +216,57 @@ const patternMethods = new Set(['test', 'exec', 'toString']);
216
216
  * report group positions for non-participating groups at all.
217
217
  */
218
218
  const resultDataMembers = new Set(['index', 'input', 'length', 'groups']);
219
+ /**
220
+ * What each of those members PHYSICALLY holds, per role.
221
+ *
222
+ * Stated here because nothing else can state it. These two structs are
223
+ * compiler-owned native layouts: `representation/derive.ts` deliberately does
224
+ * NOT seal a record layout for `RegExpExecArray`/`RegExpMatchArray` -- see its
225
+ * own comment on why sealing "a plausible-looking struct" for them would emit
226
+ * a program that compiles and is not a regular expression -- so
227
+ * `recordFieldsOfShape` answers nothing for their shape id and the generic
228
+ * `narrowedFieldReadText` has no declared carrier to reconcile against. It
229
+ * therefore returned the bare member load for every read, which is right only
230
+ * while the read is NOT narrowed.
231
+ *
232
+ * It is wrong the moment it is. `if (m.groups !== undefined) m.groups['id']`
233
+ * publishes the payload while the field stores the optional, and the bare load
234
+ * emitted `Optional<Ref<Dictionary<std::string>>>` where a
235
+ * `Ref<Dictionary<std::string>>` was wanted -- `->has` then resolved on
236
+ * `gea::Ref`, which has no such member, and every program that read a named
237
+ * capture group by key failed to compile (node-compat's `apps/http-parity`
238
+ * router was the first). The narrowing obligation the `narrow` parameter
239
+ * documents was being honoured at the call and dropped inside it, for want of
240
+ * this table.
241
+ *
242
+ * The three optionals are the two interfaces' own `?`, and they differ by
243
+ * role on purpose: `RegExpExecArray` declares `index`/`input` REQUIRED and
244
+ * `RegExpMatchArray` declares them optional, because a global pattern's match
245
+ * really does answer an array with neither. `length` is `double` on an exec
246
+ * result and the inherited `ArrayObject::length()` on a match result; neither
247
+ * is optional. Keep this in step with `ExecResult`/`MatchResult` in
248
+ * `runtime/gea_runtime.h` -- they are the same two facts, and clang checks
249
+ * only one of them.
250
+ */
251
+ const namedGroupsStorage = {
252
+ kind: 'optional',
253
+ payload: { kind: 'dictionary', key: 'string', value: { kind: 'string' }, ownership: 'shared-refcount' },
254
+ absence: 'undefined'
255
+ };
256
+ const numberStorage = { kind: 'scalar', domain: 'number' };
257
+ const stringStorage = { kind: 'string' };
258
+ const optionalOf = (payload) => ({ kind: 'optional', payload, absence: 'undefined' });
259
+ const resultDataMemberStorage = (role, key) => {
260
+ if (key === 'groups')
261
+ return namedGroupsStorage;
262
+ if (key === 'length')
263
+ return numberStorage;
264
+ if (key === 'index')
265
+ return role === 'match-result' ? optionalOf(numberStorage) : numberStorage;
266
+ if (key === 'input')
267
+ return role === 'match-result' ? optionalOf(stringStorage) : stringStorage;
268
+ return null;
269
+ };
219
270
  /** Whether `text` is a canonical array index -- a run of digits with no sign, point, or leading zero. */
220
271
  const canonicalCaptureSlot = (text) => {
221
272
  if (text.length === 0)
@@ -318,6 +369,11 @@ export const regexpMemberText = (ctx, receiver, key, result, resultRepresentatio
318
369
  // fails to assign. The capture reads below do NOT go through it: they choose
319
370
  // between `capture` and `capturedOrAbsent` on the published carrier already,
320
371
  // and are not fields at all.
372
+ //
373
+ // `declared` is what the member physically holds. The caller cannot look it
374
+ // up for these two receivers -- their layout is never sealed -- so this
375
+ // supplies it from `resultDataMemberStorage`, and passing `null` (a RegExp
376
+ // pattern member) leaves the caller on its own lookup exactly as before.
321
377
  narrow) => {
322
378
  const role = regexpRoleOf(receiver.representation);
323
379
  if (role === null)
@@ -349,7 +405,7 @@ narrow) => {
349
405
  return unboxedReadText(resultRepresentation ?? { kind: 'dynamic', reason: 'declared-any-never-narrowed' }, `gea::runtime::regex::dynamicGet(${receiverText}, ${propertyKeyText(ctx, key, site)})`, site);
350
406
  }
351
407
  if (patternDataMembers.has(staticKey))
352
- return narrow(staticKey, `${receiverText}->${staticKey}`);
408
+ return narrow(staticKey, `${receiverText}->${staticKey}`, null);
353
409
  if (deferredRegexpMethodClaim(ctx.staticKeyTexts, receiver, key) !== null) {
354
410
  if (result === null) {
355
411
  throw createCppEmitBlockedError(`host-invocation:RegExp.prototype.${staticKey}`, `"${staticKey}" is a RegExp.prototype method, and this access publishes no value for its call to consume`);
@@ -369,7 +425,7 @@ narrow) => {
369
425
  // `MatchResult`; one spelling for both compiled the exec result's read as
370
426
  // a call on a `double`.
371
427
  const member = staticKey === 'length' && role === 'match-result' ? 'length()' : staticKey;
372
- return narrow(staticKey, `${receiverText}->${member}`);
428
+ return narrow(staticKey, `${receiverText}->${member}`, resultDataMemberStorage(role, staticKey));
373
429
  }
374
430
  if (canonicalCaptureSlot(staticKey))
375
431
  return captureReadText(role, receiverText, staticKey, resultRepresentation);