@geastack/compiler 1.0.15
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/LICENSE +202 -0
- package/README.md +149 -0
- package/dist/cli-analyze.d.ts +1 -0
- package/dist/cli-analyze.js +61 -0
- package/dist/cli-coverage.d.ts +1 -0
- package/dist/cli-coverage.js +329 -0
- package/dist/cli-emit.d.ts +10 -0
- package/dist/cli-emit.js +398 -0
- package/dist/cli-module-graph.d.ts +18 -0
- package/dist/cli-module-graph.js +123 -0
- package/dist/cli-package-source.d.ts +5 -0
- package/dist/cli-package-source.js +116 -0
- package/dist/cli-project.d.ts +2 -0
- package/dist/cli-project.js +60 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +199 -0
- package/dist/compiler.d.ts +228 -0
- package/dist/compiler.js +1029 -0
- package/dist/conversion/algebra.d.ts +218 -0
- package/dist/conversion/algebra.js +155 -0
- package/dist/conversion/build.d.ts +67 -0
- package/dist/conversion/build.js +1378 -0
- package/dist/conversion/derive.d.ts +37 -0
- package/dist/conversion/derive.js +260 -0
- package/dist/conversion/graph-validation.d.ts +8 -0
- package/dist/conversion/graph-validation.js +97 -0
- package/dist/conversion/native-callable-adapter.d.ts +13 -0
- package/dist/conversion/native-callable-adapter.js +53 -0
- package/dist/conversion/native-class-reference.d.ts +12 -0
- package/dist/conversion/native-class-reference.js +39 -0
- package/dist/conversion/native-payload-transport.d.ts +25 -0
- package/dist/conversion/native-payload-transport.js +33 -0
- package/dist/conversion/native-selection.d.ts +49 -0
- package/dist/conversion/native-selection.js +135 -0
- package/dist/conversion/native-sum.d.ts +42 -0
- package/dist/conversion/native-sum.js +115 -0
- package/dist/conversion/nodes.d.ts +55 -0
- package/dist/conversion/nodes.js +91 -0
- package/dist/conversion/operand-view.d.ts +14 -0
- package/dist/conversion/operand-view.js +20 -0
- package/dist/conversion/record-view.d.ts +157 -0
- package/dist/conversion/record-view.js +273 -0
- package/dist/conversion/registry.d.ts +217 -0
- package/dist/conversion/registry.js +1 -0
- package/dist/diagnostics/model.d.ts +76 -0
- package/dist/diagnostics/model.js +60 -0
- package/dist/diagnostics/sweep.d.ts +31 -0
- package/dist/diagnostics/sweep.js +112 -0
- package/dist/identity/ids.d.ts +179 -0
- package/dist/identity/ids.js +212 -0
- package/dist/ir/absent-class-arm.d.ts +48 -0
- package/dist/ir/absent-class-arm.js +157 -0
- package/dist/ir/borrow-effects.d.ts +23 -0
- package/dist/ir/borrow-effects.js +151 -0
- package/dist/ir/borrowed-call-arguments.d.ts +40 -0
- package/dist/ir/borrowed-call-arguments.js +117 -0
- package/dist/ir/build.d.ts +168 -0
- package/dist/ir/build.js +459 -0
- package/dist/ir/call-dispatch.d.ts +38 -0
- package/dist/ir/call-dispatch.js +502 -0
- package/dist/ir/call-entry.d.ts +142 -0
- package/dist/ir/call-entry.js +386 -0
- package/dist/ir/callable-class-flow.d.ts +27 -0
- package/dist/ir/callable-class-flow.js +1386 -0
- package/dist/ir/callable-field-slots.d.ts +22 -0
- package/dist/ir/callable-field-slots.js +166 -0
- package/dist/ir/callable-identity-demand.d.ts +52 -0
- package/dist/ir/callable-identity-demand.js +460 -0
- package/dist/ir/callable-member-candidates.d.ts +9 -0
- package/dist/ir/callable-member-candidates.js +39 -0
- package/dist/ir/callable-records.d.ts +21 -0
- package/dist/ir/callable-records.js +411 -0
- package/dist/ir/captures.d.ts +26 -0
- package/dist/ir/captures.js +335 -0
- package/dist/ir/certificate.d.ts +45 -0
- package/dist/ir/certificate.js +47 -0
- package/dist/ir/certify/carrier-keys.d.ts +73 -0
- package/dist/ir/certify/carrier-keys.js +221 -0
- package/dist/ir/certify/function-source.d.ts +4 -0
- package/dist/ir/certify/function-source.js +28 -0
- package/dist/ir/certify/has-property-key.d.ts +88 -0
- package/dist/ir/certify/has-property-key.js +116 -0
- package/dist/ir/certify/instanceof-key.d.ts +24 -0
- package/dist/ir/certify/instanceof-key.js +35 -0
- package/dist/ir/certify/property-access-keys.d.ts +172 -0
- package/dist/ir/certify/property-access-keys.js +291 -0
- package/dist/ir/certify/property-access.d.ts +3 -0
- package/dist/ir/certify/property-access.js +597 -0
- package/dist/ir/certify/runtime-helper.d.ts +3 -0
- package/dist/ir/certify/runtime-helper.js +326 -0
- package/dist/ir/certify/typeof-operand.d.ts +14 -0
- package/dist/ir/certify/typeof-operand.js +16 -0
- package/dist/ir/certify.d.ts +123 -0
- package/dist/ir/certify.js +481 -0
- package/dist/ir/class-evaluation.d.ts +26 -0
- package/dist/ir/class-evaluation.js +50 -0
- package/dist/ir/class-static-fields.d.ts +60 -0
- package/dist/ir/class-static-fields.js +108 -0
- package/dist/ir/construct-entry.d.ts +26 -0
- package/dist/ir/construct-entry.js +117 -0
- package/dist/ir/dead-values.d.ts +90 -0
- package/dist/ir/dead-values.js +184 -0
- package/dist/ir/deferral.d.ts +99 -0
- package/dist/ir/deferral.js +409 -0
- package/dist/ir/dense-loops.d.ts +230 -0
- package/dist/ir/dense-loops.js +697 -0
- package/dist/ir/dominance.d.ts +82 -0
- package/dist/ir/dominance.js +268 -0
- package/dist/ir/emission-representations.d.ts +71 -0
- package/dist/ir/emission-representations.js +350 -0
- package/dist/ir/facts.d.ts +218 -0
- package/dist/ir/facts.js +181 -0
- package/dist/ir/fixed-data-definition.d.ts +19 -0
- package/dist/ir/fixed-data-definition.js +49 -0
- package/dist/ir/generator-split.d.ts +38 -0
- package/dist/ir/generator-split.js +329 -0
- package/dist/ir/hoist.d.ts +55 -0
- package/dist/ir/hoist.js +375 -0
- package/dist/ir/instantiation.d.ts +61 -0
- package/dist/ir/instantiation.js +137 -0
- package/dist/ir/integer-storage.d.ts +170 -0
- package/dist/ir/integer-storage.js +822 -0
- package/dist/ir/integers.d.ts +161 -0
- package/dist/ir/integers.js +539 -0
- package/dist/ir/integrity-restrictions.d.ts +50 -0
- package/dist/ir/integrity-restrictions.js +118 -0
- package/dist/ir/local-iterators.d.ts +4 -0
- package/dist/ir/local-iterators.js +27 -0
- package/dist/ir/lower-allocation.d.ts +4 -0
- package/dist/ir/lower-allocation.js +325 -0
- package/dist/ir/lower-completion.d.ts +15 -0
- package/dist/ir/lower-completion.js +25 -0
- package/dist/ir/lower-destructuring.d.ts +5 -0
- package/dist/ir/lower-destructuring.js +693 -0
- package/dist/ir/lower-element.d.ts +29 -0
- package/dist/ir/lower-element.js +111 -0
- package/dist/ir/lower-exceptions.d.ts +47 -0
- package/dist/ir/lower-exceptions.js +92 -0
- package/dist/ir/lower-flow.d.ts +137 -0
- package/dist/ir/lower-flow.js +600 -0
- package/dist/ir/lower-graph.d.ts +218 -0
- package/dist/ir/lower-graph.js +687 -0
- package/dist/ir/lower-invocation.d.ts +4 -0
- package/dist/ir/lower-invocation.js +514 -0
- package/dist/ir/lower-narrow.d.ts +53 -0
- package/dist/ir/lower-narrow.js +320 -0
- package/dist/ir/lower-operands.d.ts +272 -0
- package/dist/ir/lower-operands.js +461 -0
- package/dist/ir/lower-property.d.ts +21 -0
- package/dist/ir/lower-property.js +132 -0
- package/dist/ir/lower-protocol.d.ts +11 -0
- package/dist/ir/lower-protocol.js +118 -0
- package/dist/ir/lower-short-circuit.d.ts +41 -0
- package/dist/ir/lower-short-circuit.js +63 -0
- package/dist/ir/lower.d.ts +82 -0
- package/dist/ir/lower.js +1346 -0
- package/dist/ir/model.d.ts +1474 -0
- package/dist/ir/model.js +6 -0
- package/dist/ir/native-absent-property.d.ts +3 -0
- package/dist/ir/native-absent-property.js +13 -0
- package/dist/ir/native-array-transport.d.ts +7 -0
- package/dist/ir/native-array-transport.js +46 -0
- package/dist/ir/native-callable-bind.d.ts +9 -0
- package/dist/ir/native-callable-bind.js +30 -0
- package/dist/ir/native-carrier-predicate.d.ts +34 -0
- package/dist/ir/native-carrier-predicate.js +49 -0
- package/dist/ir/native-class-accessor.d.ts +18 -0
- package/dist/ir/native-class-accessor.js +52 -0
- package/dist/ir/native-class-construction.d.ts +16 -0
- package/dist/ir/native-class-construction.js +71 -0
- package/dist/ir/native-class-initialization.d.ts +15 -0
- package/dist/ir/native-class-initialization.js +56 -0
- package/dist/ir/native-class-origins.d.ts +13 -0
- package/dist/ir/native-class-origins.js +237 -0
- package/dist/ir/native-dictionary-transport.d.ts +6 -0
- package/dist/ir/native-dictionary-transport.js +37 -0
- package/dist/ir/native-enumeration.d.ts +14 -0
- package/dist/ir/native-enumeration.js +26 -0
- package/dist/ir/native-equality.d.ts +10 -0
- package/dist/ir/native-equality.js +18 -0
- package/dist/ir/native-field-owner.d.ts +37 -0
- package/dist/ir/native-field-owner.js +183 -0
- package/dist/ir/native-fixed-layout.d.ts +41 -0
- package/dist/ir/native-fixed-layout.js +232 -0
- package/dist/ir/native-host-construction.d.ts +53 -0
- package/dist/ir/native-host-construction.js +62 -0
- package/dist/ir/native-key-query.d.ts +4 -0
- package/dist/ir/native-key-query.js +38 -0
- package/dist/ir/native-merge-transport.d.ts +25 -0
- package/dist/ir/native-merge-transport.js +71 -0
- package/dist/ir/native-record-index-transport.d.ts +11 -0
- package/dist/ir/native-record-index-transport.js +34 -0
- package/dist/ir/native-record-index.d.ts +12 -0
- package/dist/ir/native-record-index.js +27 -0
- package/dist/ir/native-reflect-field.d.ts +5 -0
- package/dist/ir/native-reflect-field.js +33 -0
- package/dist/ir/native-sequence-transport.d.ts +7 -0
- package/dist/ir/native-sequence-transport.js +53 -0
- package/dist/ir/numeric-intrinsics.d.ts +13 -0
- package/dist/ir/numeric-intrinsics.js +57 -0
- package/dist/ir/object-value-conversions.d.ts +20 -0
- package/dist/ir/object-value-conversions.js +50 -0
- package/dist/ir/physical-class-reflection.d.ts +9 -0
- package/dist/ir/physical-class-reflection.js +57 -0
- package/dist/ir/presence-proof.d.ts +42 -0
- package/dist/ir/presence-proof.js +490 -0
- package/dist/ir/program-facts.d.ts +106 -0
- package/dist/ir/program-facts.js +319 -0
- package/dist/ir/proven-branches.d.ts +16 -0
- package/dist/ir/proven-branches.js +219 -0
- package/dist/ir/queries.d.ts +41 -0
- package/dist/ir/queries.js +213 -0
- package/dist/ir/reflection-demand.d.ts +64 -0
- package/dist/ir/reflection-demand.js +1394 -0
- package/dist/ir/refusal.d.ts +43 -0
- package/dist/ir/refusal.js +28 -0
- package/dist/ir/shake.d.ts +37 -0
- package/dist/ir/shake.js +1078 -0
- package/dist/ir/static-callables.d.ts +11 -0
- package/dist/ir/static-callables.js +163 -0
- package/dist/ir/string-layout-reuse.d.ts +13 -0
- package/dist/ir/string-layout-reuse.js +39 -0
- package/dist/ir/string-length-reuse.d.ts +8 -0
- package/dist/ir/string-length-reuse.js +47 -0
- package/dist/ir/transfer.d.ts +78 -0
- package/dist/ir/transfer.js +270 -0
- package/dist/ir/type-query-results.d.ts +25 -0
- package/dist/ir/type-query-results.js +96 -0
- package/dist/ir/typed-property-access.d.ts +100 -0
- package/dist/ir/typed-property-access.js +161 -0
- package/dist/ir/verify.d.ts +19 -0
- package/dist/ir/verify.js +355 -0
- package/dist/plugins/apple/constructors.d.ts +112 -0
- package/dist/plugins/apple/constructors.js +217 -0
- package/dist/plugins/apple/host.d.ts +181 -0
- package/dist/plugins/apple/host.js +262 -0
- package/dist/plugins/apple/jsx.d.ts +2 -0
- package/dist/plugins/apple/jsx.js +293 -0
- package/dist/plugins/apple/members.d.ts +16 -0
- package/dist/plugins/apple/members.js +190 -0
- package/dist/plugins/apple/plugin.d.ts +2 -0
- package/dist/plugins/apple/plugin.js +156 -0
- package/dist/plugins/gea/component-classes.d.ts +25 -0
- package/dist/plugins/gea/component-classes.js +162 -0
- package/dist/plugins/gea/component-inline.d.ts +13 -0
- package/dist/plugins/gea/component-inline.js +532 -0
- package/dist/plugins/gea/constructors.d.ts +23 -0
- package/dist/plugins/gea/constructors.js +100 -0
- package/dist/plugins/gea/contract.d.ts +150 -0
- package/dist/plugins/gea/contract.js +195 -0
- package/dist/plugins/gea/element-ref.d.ts +31 -0
- package/dist/plugins/gea/element-ref.js +45 -0
- package/dist/plugins/gea/host.d.ts +520 -0
- package/dist/plugins/gea/host.js +762 -0
- package/dist/plugins/gea/lower.d.ts +6 -0
- package/dist/plugins/gea/lower.js +195 -0
- package/dist/plugins/gea/members.d.ts +22 -0
- package/dist/plugins/gea/members.js +221 -0
- package/dist/plugins/gea/plugin.d.ts +22 -0
- package/dist/plugins/gea/plugin.js +307 -0
- package/dist/plugins/gea/prelude.d.ts +78 -0
- package/dist/plugins/gea/prelude.js +153 -0
- package/dist/plugins/gea/producer.d.ts +5 -0
- package/dist/plugins/gea/producer.js +52 -0
- package/dist/plugins/gea/reactive-slots.d.ts +49 -0
- package/dist/plugins/gea/reactive-slots.js +578 -0
- package/dist/plugins/gea/render-bridge.d.ts +6 -0
- package/dist/plugins/gea/render-bridge.js +281 -0
- package/dist/plugins/gea/slots.d.ts +15 -0
- package/dist/plugins/gea/slots.js +36 -0
- package/dist/plugins/gea/template-analysis.d.ts +151 -0
- package/dist/plugins/gea/template-analysis.js +346 -0
- package/dist/plugins/gea/user-agent-styles.d.ts +9 -0
- package/dist/plugins/gea/user-agent-styles.js +94 -0
- package/dist/plugins/installed.d.ts +19 -0
- package/dist/plugins/installed.js +21 -0
- package/dist/plugins/load.d.ts +7 -0
- package/dist/plugins/load.js +207 -0
- package/dist/plugins/model.d.ts +735 -0
- package/dist/plugins/model.js +47 -0
- package/dist/plugins/webgl/host.d.ts +77 -0
- package/dist/plugins/webgl/host.js +162 -0
- package/dist/plugins/webgl/plugin.d.ts +40 -0
- package/dist/plugins/webgl/plugin.js +99 -0
- package/dist/preflight/obligations.d.ts +206 -0
- package/dist/preflight/obligations.js +36 -0
- package/dist/preflight/report.d.ts +20 -0
- package/dist/preflight/report.js +61 -0
- package/dist/preflight/run.d.ts +39 -0
- package/dist/preflight/run.js +403 -0
- package/dist/project-preparation.d.ts +33 -0
- package/dist/project-preparation.js +314 -0
- package/dist/projection/abi.d.ts +82 -0
- package/dist/projection/abi.js +349 -0
- package/dist/projection/apply-arguments.d.ts +12 -0
- package/dist/projection/apply-arguments.js +20 -0
- package/dist/projection/bindings.d.ts +250 -0
- package/dist/projection/bindings.js +340 -0
- package/dist/projection/callee.d.ts +196 -0
- package/dist/projection/callee.js +444 -0
- package/dist/projection/class-property-presence.d.ts +28 -0
- package/dist/projection/class-property-presence.js +58 -0
- package/dist/projection/class-prototype.d.ts +5 -0
- package/dist/projection/class-prototype.js +23 -0
- package/dist/projection/class-storage.d.ts +26 -0
- package/dist/projection/class-storage.js +236 -0
- package/dist/projection/classes.d.ts +283 -0
- package/dist/projection/classes.js +944 -0
- package/dist/projection/coercions.d.ts +20 -0
- package/dist/projection/coercions.js +33 -0
- package/dist/projection/dispatch.d.ts +167 -0
- package/dist/projection/dispatch.js +379 -0
- package/dist/projection/fields.d.ts +123 -0
- package/dist/projection/fields.js +383 -0
- package/dist/projection/instance-test.d.ts +78 -0
- package/dist/projection/instance-test.js +156 -0
- package/dist/projection/slots.d.ts +146 -0
- package/dist/projection/slots.js +663 -0
- package/dist/projection/synthesized-abi.d.ts +61 -0
- package/dist/projection/synthesized-abi.js +94 -0
- package/dist/representation/array-index.d.ts +14 -0
- package/dist/representation/array-index.js +17 -0
- package/dist/representation/callable-object.d.ts +3 -0
- package/dist/representation/callable-object.js +6 -0
- package/dist/representation/collections.d.ts +84 -0
- package/dist/representation/collections.js +139 -0
- package/dist/representation/derive.d.ts +159 -0
- package/dist/representation/derive.js +2546 -0
- package/dist/representation/difference.d.ts +30 -0
- package/dist/representation/difference.js +240 -0
- package/dist/representation/field-descriptor-policy.d.ts +19 -0
- package/dist/representation/field-descriptor-policy.js +39 -0
- package/dist/representation/function-constructor.d.ts +13 -0
- package/dist/representation/function-constructor.js +9 -0
- package/dist/representation/host-abi.d.ts +32 -0
- package/dist/representation/host-abi.js +97 -0
- package/dist/representation/host-templates.d.ts +67 -0
- package/dist/representation/host-templates.js +94 -0
- package/dist/representation/intersection.d.ts +84 -0
- package/dist/representation/intersection.js +149 -0
- package/dist/representation/literal-destination.d.ts +43 -0
- package/dist/representation/literal-destination.js +133 -0
- package/dist/representation/merge.d.ts +18 -0
- package/dist/representation/merge.js +83 -0
- package/dist/representation/model.d.ts +1160 -0
- package/dist/representation/model.js +1019 -0
- package/dist/representation/numeric-index.d.ts +13 -0
- package/dist/representation/numeric-index.js +29 -0
- package/dist/representation/object-shape.d.ts +109 -0
- package/dist/representation/object-shape.js +123 -0
- package/dist/representation/optional.d.ts +12 -0
- package/dist/representation/optional.js +87 -0
- package/dist/representation/plan.d.ts +60 -0
- package/dist/representation/plan.js +78 -0
- package/dist/representation/policies.d.ts +589 -0
- package/dist/representation/policies.js +107 -0
- package/dist/representation/primitive-domain.d.ts +1 -0
- package/dist/representation/primitive-domain.js +1 -0
- package/dist/representation/primitives.d.ts +44 -0
- package/dist/representation/primitives.js +78 -0
- package/dist/representation/publish.d.ts +49 -0
- package/dist/representation/publish.js +648 -0
- package/dist/representation/record-fields.d.ts +96 -0
- package/dist/representation/record-fields.js +116 -0
- package/dist/representation/template-object.d.ts +11 -0
- package/dist/representation/template-object.js +26 -0
- package/dist/representation/union.d.ts +30 -0
- package/dist/representation/union.js +324 -0
- package/dist/representation/value-records.d.ts +3 -0
- package/dist/representation/value-records.js +307 -0
- package/dist/representation/verify.d.ts +17 -0
- package/dist/representation/verify.js +221 -0
- package/dist/semantics/alias-this-field-declaration-transform.d.ts +4 -0
- package/dist/semantics/alias-this-field-declaration-transform.js +207 -0
- package/dist/semantics/ambient-type-realization-transform.d.ts +62 -0
- package/dist/semantics/ambient-type-realization-transform.js +168 -0
- package/dist/semantics/ambient.d.ts +24 -0
- package/dist/semantics/ambient.js +119 -0
- package/dist/semantics/borrowed-builtin-call-bind-source-transform.d.ts +4 -0
- package/dist/semantics/borrowed-builtin-call-bind-source-transform.js +341 -0
- package/dist/semantics/callable-origins.d.ts +46 -0
- package/dist/semantics/callable-origins.js +354 -0
- package/dist/semantics/class-alias.d.ts +7 -0
- package/dist/semantics/class-alias.js +37 -0
- package/dist/semantics/class-heritage.d.ts +34 -0
- package/dist/semantics/class-heritage.js +104 -0
- package/dist/semantics/commonjs-wrapper.d.ts +69 -0
- package/dist/semantics/commonjs-wrapper.js +231 -0
- package/dist/semantics/constructor-slot-subclasses.d.ts +28 -0
- package/dist/semantics/constructor-slot-subclasses.js +112 -0
- package/dist/semantics/declaration-overlay-transform.d.ts +35 -0
- package/dist/semantics/declaration-overlay-transform.js +2157 -0
- package/dist/semantics/define-property-source-transform.d.ts +4 -0
- package/dist/semantics/define-property-source-transform.js +437 -0
- package/dist/semantics/diagnostic-source-preparation.d.ts +24 -0
- package/dist/semantics/diagnostic-source-preparation.js +82 -0
- package/dist/semantics/documentation-ranges.d.ts +22 -0
- package/dist/semantics/documentation-ranges.js +36 -0
- package/dist/semantics/dynamic-fallback.d.ts +86 -0
- package/dist/semantics/dynamic-fallback.js +249 -0
- package/dist/semantics/frontend-timing.d.ts +6 -0
- package/dist/semantics/frontend-timing.js +35 -0
- package/dist/semantics/frontend.d.ts +504 -0
- package/dist/semantics/frontend.js +1157 -0
- package/dist/semantics/host-declaration-provenance.d.ts +11 -0
- package/dist/semantics/host-declaration-provenance.js +18 -0
- package/dist/semantics/host-methods.d.ts +24 -0
- package/dist/semantics/host-methods.js +74 -0
- package/dist/semantics/host-protocols.d.ts +429 -0
- package/dist/semantics/host-protocols.js +1501 -0
- package/dist/semantics/inherited-accessor.d.ts +7 -0
- package/dist/semantics/inherited-accessor.js +48 -0
- package/dist/semantics/interface-families.d.ts +70 -0
- package/dist/semantics/interface-families.js +180 -0
- package/dist/semantics/interface-implementors.d.ts +53 -0
- package/dist/semantics/interface-implementors.js +87 -0
- package/dist/semantics/jsdoc-namepath-transform.d.ts +4 -0
- package/dist/semantics/jsdoc-namepath-transform.js +85 -0
- package/dist/semantics/model/coverage.d.ts +68 -0
- package/dist/semantics/model/coverage.js +73 -0
- package/dist/semantics/model/edges.d.ts +103 -0
- package/dist/semantics/model/edges.js +20 -0
- package/dist/semantics/model/graph.d.ts +55 -0
- package/dist/semantics/model/graph.js +129 -0
- package/dist/semantics/model/operands.d.ts +159 -0
- package/dist/semantics/model/operands.js +26 -0
- package/dist/semantics/model/operations.d.ts +496 -0
- package/dist/semantics/model/operations.js +2 -0
- package/dist/semantics/model/primitive-domain.d.ts +26 -0
- package/dist/semantics/model/primitive-domain.js +38 -0
- package/dist/semantics/model/selected-signature.d.ts +261 -0
- package/dist/semantics/model/selected-signature.js +55 -0
- package/dist/semantics/model/structural-type-table.d.ts +93 -0
- package/dist/semantics/model/structural-type-table.js +237 -0
- package/dist/semantics/model/structural-types.d.ts +367 -0
- package/dist/semantics/model/structural-types.js +200 -0
- package/dist/semantics/module-resolution.d.ts +23 -0
- package/dist/semantics/module-resolution.js +227 -0
- package/dist/semantics/new-callee-class-tag-source-transform.d.ts +4 -0
- package/dist/semantics/new-callee-class-tag-source-transform.js +64 -0
- package/dist/semantics/node-project.d.ts +13 -0
- package/dist/semantics/node-project.js +107 -0
- package/dist/semantics/node-source-hooks.d.ts +1 -0
- package/dist/semantics/node-source-hooks.js +63 -0
- package/dist/semantics/normalize/absent-globals.d.ts +143 -0
- package/dist/semantics/normalize/absent-globals.js +191 -0
- package/dist/semantics/normalize/arguments-objects.d.ts +43 -0
- package/dist/semantics/normalize/arguments-objects.js +60 -0
- package/dist/semantics/normalize/asserted-arm-absence.d.ts +28 -0
- package/dist/semantics/normalize/asserted-arm-absence.js +61 -0
- package/dist/semantics/normalize/assignment-patterns.d.ts +70 -0
- package/dist/semantics/normalize/assignment-patterns.js +147 -0
- package/dist/semantics/normalize/bag-absence.d.ts +8 -0
- package/dist/semantics/normalize/bag-absence.js +42 -0
- package/dist/semantics/normalize/binding-fixpoint.d.ts +36 -0
- package/dist/semantics/normalize/binding-fixpoint.js +140 -0
- package/dist/semantics/normalize/callback-parameter-contracts.d.ts +16 -0
- package/dist/semantics/normalize/callback-parameter-contracts.js +91 -0
- package/dist/semantics/normalize/cells/agreement.d.ts +46 -0
- package/dist/semantics/normalize/cells/agreement.js +34 -0
- package/dist/semantics/normalize/cells/candidates.d.ts +17 -0
- package/dist/semantics/normalize/cells/candidates.js +35 -0
- package/dist/semantics/normalize/cells/facts.d.ts +19 -0
- package/dist/semantics/normalize/cells/facts.js +90 -0
- package/dist/semantics/normalize/cells/index.d.ts +10 -0
- package/dist/semantics/normalize/cells/index.js +6 -0
- package/dist/semantics/normalize/cells/model.d.ts +98 -0
- package/dist/semantics/normalize/cells/model.js +2 -0
- package/dist/semantics/normalize/cells/policies/bag.d.ts +44 -0
- package/dist/semantics/normalize/cells/policies/bag.js +114 -0
- package/dist/semantics/normalize/cells/policies/collection.d.ts +42 -0
- package/dist/semantics/normalize/cells/policies/collection.js +218 -0
- package/dist/semantics/normalize/cells/policies/field.d.ts +28 -0
- package/dist/semantics/normalize/cells/policies/field.js +40 -0
- package/dist/semantics/normalize/cells/policies/index.d.ts +49 -0
- package/dist/semantics/normalize/cells/policies/index.js +58 -0
- package/dist/semantics/normalize/cells/policies/local.d.ts +18 -0
- package/dist/semantics/normalize/cells/policies/local.js +37 -0
- package/dist/semantics/normalize/cells/policies/parameter.d.ts +28 -0
- package/dist/semantics/normalize/cells/policies/parameter.js +39 -0
- package/dist/semantics/normalize/cells/policies/return.d.ts +26 -0
- package/dist/semantics/normalize/cells/policies/return.js +45 -0
- package/dist/semantics/normalize/cells/policies/shared.d.ts +33 -0
- package/dist/semantics/normalize/cells/policies/shared.js +45 -0
- package/dist/semantics/normalize/cells/policy.d.ts +72 -0
- package/dist/semantics/normalize/cells/policy.js +18 -0
- package/dist/semantics/normalize/cells/registry.d.ts +14 -0
- package/dist/semantics/normalize/cells/registry.js +17 -0
- package/dist/semantics/normalize/census-refusal.d.ts +62 -0
- package/dist/semantics/normalize/census-refusal.js +27 -0
- package/dist/semantics/normalize/census.d.ts +109 -0
- package/dist/semantics/normalize/census.js +667 -0
- package/dist/semantics/normalize/collection-bindings.d.ts +278 -0
- package/dist/semantics/normalize/collection-bindings.js +677 -0
- package/dist/semantics/normalize/commonjs-module-record.d.ts +21 -0
- package/dist/semantics/normalize/commonjs-module-record.js +289 -0
- package/dist/semantics/normalize/commonjs-require.d.ts +13 -0
- package/dist/semantics/normalize/commonjs-require.js +1527 -0
- package/dist/semantics/normalize/contribution.d.ts +63 -0
- package/dist/semantics/normalize/contribution.js +172 -0
- package/dist/semantics/normalize/dead-typeof-guards.d.ts +64 -0
- package/dist/semantics/normalize/dead-typeof-guards.js +152 -0
- package/dist/semantics/normalize/deferred-intrinsic-protocols.d.ts +57 -0
- package/dist/semantics/normalize/deferred-intrinsic-protocols.js +109 -0
- package/dist/semantics/normalize/derived-expression-type.d.ts +1070 -0
- package/dist/semantics/normalize/derived-expression-type.js +3463 -0
- package/dist/semantics/normalize/evaluation-order.d.ts +16 -0
- package/dist/semantics/normalize/evaluation-order.js +37 -0
- package/dist/semantics/normalize/field-bindings.d.ts +167 -0
- package/dist/semantics/normalize/field-bindings.js +1125 -0
- package/dist/semantics/normalize/flow/array-element-continuation.d.ts +27 -0
- package/dist/semantics/normalize/flow/array-element-continuation.js +1452 -0
- package/dist/semantics/normalize/flow/callable-array-origins.d.ts +42 -0
- package/dist/semantics/normalize/flow/callable-array-origins.js +318 -0
- package/dist/semantics/normalize/flow/callable-completions.d.ts +45 -0
- package/dist/semantics/normalize/flow/callable-completions.js +82 -0
- package/dist/semantics/normalize/flow/callable-reach.d.ts +237 -0
- package/dist/semantics/normalize/flow/callable-reach.js +7174 -0
- package/dist/semantics/normalize/flow/class-family-member-read.d.ts +16 -0
- package/dist/semantics/normalize/flow/class-family-member-read.js +992 -0
- package/dist/semantics/normalize/flow/collection-value-continuation.d.ts +21 -0
- package/dist/semantics/normalize/flow/collection-value-continuation.js +192 -0
- package/dist/semantics/normalize/flow/component-solver.d.ts +199 -0
- package/dist/semantics/normalize/flow/component-solver.js +587 -0
- package/dist/semantics/normalize/flow/computed-key-set.d.ts +84 -0
- package/dist/semantics/normalize/flow/computed-key-set.js +660 -0
- package/dist/semantics/normalize/flow/export-importers.d.ts +24 -0
- package/dist/semantics/normalize/flow/export-importers.js +269 -0
- package/dist/semantics/normalize/flow/host-text-sink.d.ts +25 -0
- package/dist/semantics/normalize/flow/host-text-sink.js +87 -0
- package/dist/semantics/normalize/flow/intrinsic-array-callback-frames.d.ts +84 -0
- package/dist/semantics/normalize/flow/intrinsic-array-callback-frames.js +180 -0
- package/dist/semantics/normalize/flow/intrinsic-data-definition.d.ts +35 -0
- package/dist/semantics/normalize/flow/intrinsic-data-definition.js +140 -0
- package/dist/semantics/normalize/flow/invocation-facts.d.ts +98 -0
- package/dist/semantics/normalize/flow/invocation-facts.js +257 -0
- package/dist/semantics/normalize/flow/member-call-forwarding.d.ts +122 -0
- package/dist/semantics/normalize/flow/member-call-forwarding.js +835 -0
- package/dist/semantics/normalize/flow/model.d.ts +357 -0
- package/dist/semantics/normalize/flow/model.js +177 -0
- package/dist/semantics/normalize/flow/native-collection-protocol.d.ts +24 -0
- package/dist/semantics/normalize/flow/native-collection-protocol.js +252 -0
- package/dist/semantics/normalize/flow/node-path-token.d.ts +3 -0
- package/dist/semantics/normalize/flow/node-path-token.js +53 -0
- package/dist/semantics/normalize/flow/object-binding-continuation.d.ts +11 -0
- package/dist/semantics/normalize/flow/object-binding-continuation.js +42 -0
- package/dist/semantics/normalize/flow/origin-authority.d.ts +56 -0
- package/dist/semantics/normalize/flow/origin-authority.js +2 -0
- package/dist/semantics/normalize/flow/owned-class-receivers.d.ts +35 -0
- package/dist/semantics/normalize/flow/owned-class-receivers.js +191 -0
- package/dist/semantics/normalize/flow/parameter-values.d.ts +27 -0
- package/dist/semantics/normalize/flow/parameter-values.js +251 -0
- package/dist/semantics/normalize/flow/proof-hypotheses.d.ts +66 -0
- package/dist/semantics/normalize/flow/proof-hypotheses.js +162 -0
- package/dist/semantics/normalize/flow/record-alias-closure.d.ts +27 -0
- package/dist/semantics/normalize/flow/record-alias-closure.js +298 -0
- package/dist/semantics/normalize/flow/seeded-origins.d.ts +43 -0
- package/dist/semantics/normalize/flow/seeded-origins.js +54 -0
- package/dist/semantics/normalize/flow/source-class-data.d.ts +132 -0
- package/dist/semantics/normalize/flow/source-class-data.js +838 -0
- package/dist/semantics/normalize/flow/source-class-instanceof.d.ts +13 -0
- package/dist/semantics/normalize/flow/source-class-instanceof.js +37 -0
- package/dist/semantics/normalize/flow/source-class-static-data.d.ts +16 -0
- package/dist/semantics/normalize/flow/source-class-static-data.js +47 -0
- package/dist/semantics/normalize/flow/source-construction-frames.d.ts +12 -0
- package/dist/semantics/normalize/flow/source-construction-frames.js +118 -0
- package/dist/semantics/normalize/flow/source-invocation-frame-layout.d.ts +34 -0
- package/dist/semantics/normalize/flow/source-invocation-frame-layout.js +78 -0
- package/dist/semantics/normalize/flow/source-invocation-receiver.d.ts +37 -0
- package/dist/semantics/normalize/flow/source-invocation-receiver.js +95 -0
- package/dist/semantics/normalize/flow/source-prototype-method-identity.d.ts +23 -0
- package/dist/semantics/normalize/flow/source-prototype-method-identity.js +81 -0
- package/dist/semantics/normalize/flow/source-record-data.d.ts +29 -0
- package/dist/semantics/normalize/flow/source-record-data.js +626 -0
- package/dist/semantics/normalize/flow/source-value-session.d.ts +77 -0
- package/dist/semantics/normalize/flow/source-value-session.js +3089 -0
- package/dist/semantics/normalize/flow/targets.d.ts +118 -0
- package/dist/semantics/normalize/flow/targets.js +405 -0
- package/dist/semantics/normalize/flow/value-flow.d.ts +36 -0
- package/dist/semantics/normalize/flow/value-flow.js +823 -0
- package/dist/semantics/normalize/flow/value-provenance.d.ts +31 -0
- package/dist/semantics/normalize/flow/value-provenance.js +112 -0
- package/dist/semantics/normalize/gating.d.ts +51 -0
- package/dist/semantics/normalize/gating.js +560 -0
- package/dist/semantics/normalize/generic-function-choice.d.ts +31 -0
- package/dist/semantics/normalize/generic-function-choice.js +115 -0
- package/dist/semantics/normalize/global-host-mutations.d.ts +51 -0
- package/dist/semantics/normalize/global-host-mutations.js +5989 -0
- package/dist/semantics/normalize/host-effect-contracts.d.ts +77 -0
- package/dist/semantics/normalize/host-effect-contracts.js +143 -0
- package/dist/semantics/normalize/host-mutation-computed-keys.d.ts +38 -0
- package/dist/semantics/normalize/host-mutation-computed-keys.js +26 -0
- package/dist/semantics/normalize/host-mutation-key-reader.d.ts +66 -0
- package/dist/semantics/normalize/host-mutation-key-reader.js +192 -0
- package/dist/semantics/normalize/host-mutation-keys.d.ts +114 -0
- package/dist/semantics/normalize/host-mutation-keys.js +183 -0
- package/dist/semantics/normalize/identities.d.ts +172 -0
- package/dist/semantics/normalize/identities.js +426 -0
- package/dist/semantics/normalize/implicit-arguments-tuple.d.ts +97 -0
- package/dist/semantics/normalize/implicit-arguments-tuple.js +197 -0
- package/dist/semantics/normalize/implicit-arguments.d.ts +11 -0
- package/dist/semantics/normalize/implicit-arguments.js +39 -0
- package/dist/semantics/normalize/instantiation.d.ts +73 -0
- package/dist/semantics/normalize/instantiation.js +399 -0
- package/dist/semantics/normalize/intrinsic-property-call.d.ts +18 -0
- package/dist/semantics/normalize/intrinsic-property-call.js +69 -0
- package/dist/semantics/normalize/intrinsic-prototype.d.ts +41 -0
- package/dist/semantics/normalize/intrinsic-prototype.js +87 -0
- package/dist/semantics/normalize/intrinsic-static-member.d.ts +14 -0
- package/dist/semantics/normalize/intrinsic-static-member.js +23 -0
- package/dist/semantics/normalize/jsdoc-type-names.d.ts +32 -0
- package/dist/semantics/normalize/jsdoc-type-names.js +512 -0
- package/dist/semantics/normalize/keyof.d.ts +32 -0
- package/dist/semantics/normalize/keyof.js +110 -0
- package/dist/semantics/normalize/local-bindings.d.ts +176 -0
- package/dist/semantics/normalize/local-bindings.js +1826 -0
- package/dist/semantics/normalize/logical-result-type.d.ts +16 -0
- package/dist/semantics/normalize/logical-result-type.js +102 -0
- package/dist/semantics/normalize/namespace-paths.d.ts +57 -0
- package/dist/semantics/normalize/namespace-paths.js +108 -0
- package/dist/semantics/normalize/normalize.d.ts +57 -0
- package/dist/semantics/normalize/normalize.js +68 -0
- package/dist/semantics/normalize/object-bag-bindings.d.ts +305 -0
- package/dist/semantics/normalize/object-bag-bindings.js +1290 -0
- package/dist/semantics/normalize/omitted-stated-parameter.d.ts +53 -0
- package/dist/semantics/normalize/omitted-stated-parameter.js +59 -0
- package/dist/semantics/normalize/open-type-form.d.ts +10 -0
- package/dist/semantics/normalize/open-type-form.js +32 -0
- package/dist/semantics/normalize/parameter-bindings.d.ts +361 -0
- package/dist/semantics/normalize/parameter-bindings.js +3339 -0
- package/dist/semantics/normalize/parameter-slot.d.ts +264 -0
- package/dist/semantics/normalize/parameter-slot.js +491 -0
- package/dist/semantics/normalize/physical-overload-result.d.ts +77 -0
- package/dist/semantics/normalize/physical-overload-result.js +99 -0
- package/dist/semantics/normalize/primitive-property-absence.d.ts +12 -0
- package/dist/semantics/normalize/primitive-property-absence.js +60 -0
- package/dist/semantics/normalize/producer-context.d.ts +376 -0
- package/dist/semantics/normalize/producer-context.js +1 -0
- package/dist/semantics/normalize/producers/allocations.d.ts +3 -0
- package/dist/semantics/normalize/producers/allocations.js +863 -0
- package/dist/semantics/normalize/producers/binding-kind.d.ts +37 -0
- package/dist/semantics/normalize/producers/binding-kind.js +36 -0
- package/dist/semantics/normalize/producers/bindings.d.ts +85 -0
- package/dist/semantics/normalize/producers/bindings.js +710 -0
- package/dist/semantics/normalize/producers/boundary.d.ts +34 -0
- package/dist/semantics/normalize/producers/boundary.js +123 -0
- package/dist/semantics/normalize/producers/class-lifecycle.d.ts +3 -0
- package/dist/semantics/normalize/producers/class-lifecycle.js +902 -0
- package/dist/semantics/normalize/producers/computations.d.ts +3 -0
- package/dist/semantics/normalize/producers/computations.js +613 -0
- package/dist/semantics/normalize/producers/control-loops.d.ts +30 -0
- package/dist/semantics/normalize/producers/control-loops.js +91 -0
- package/dist/semantics/normalize/producers/control.d.ts +30 -0
- package/dist/semantics/normalize/producers/control.js +923 -0
- package/dist/semantics/normalize/producers/declaration-lifecycle.d.ts +3 -0
- package/dist/semantics/normalize/producers/declaration-lifecycle.js +390 -0
- package/dist/semantics/normalize/producers/destructuring.d.ts +45 -0
- package/dist/semantics/normalize/producers/destructuring.js +1180 -0
- package/dist/semantics/normalize/producers/dynamic-language.d.ts +3 -0
- package/dist/semantics/normalize/producers/dynamic-language.js +70 -0
- package/dist/semantics/normalize/producers/erasure.d.ts +51 -0
- package/dist/semantics/normalize/producers/erasure.js +90 -0
- package/dist/semantics/normalize/producers/installed.d.ts +3 -0
- package/dist/semantics/normalize/producers/installed.js +55 -0
- package/dist/semantics/normalize/producers/invocations.d.ts +35 -0
- package/dist/semantics/normalize/producers/invocations.js +1796 -0
- package/dist/semantics/normalize/producers/iteration-yield.d.ts +59 -0
- package/dist/semantics/normalize/producers/iteration-yield.js +235 -0
- package/dist/semantics/normalize/producers/jsx.d.ts +3 -0
- package/dist/semantics/normalize/producers/jsx.js +360 -0
- package/dist/semantics/normalize/producers/mint.d.ts +39 -0
- package/dist/semantics/normalize/producers/mint.js +44 -0
- package/dist/semantics/normalize/producers/nullish.d.ts +16 -0
- package/dist/semantics/normalize/producers/nullish.js +27 -0
- package/dist/semantics/normalize/producers/object-assignment.d.ts +8 -0
- package/dist/semantics/normalize/producers/object-assignment.js +204 -0
- package/dist/semantics/normalize/producers/object-tag.d.ts +9 -0
- package/dist/semantics/normalize/producers/object-tag.js +119 -0
- package/dist/semantics/normalize/producers/optional-chain.d.ts +44 -0
- package/dist/semantics/normalize/producers/optional-chain.js +75 -0
- package/dist/semantics/normalize/producers/properties.d.ts +21 -0
- package/dist/semantics/normalize/producers/properties.js +1053 -0
- package/dist/semantics/normalize/producers/protocol.d.ts +67 -0
- package/dist/semantics/normalize/producers/protocol.js +695 -0
- package/dist/semantics/normalize/producers/references.d.ts +53 -0
- package/dist/semantics/normalize/producers/references.js +1027 -0
- package/dist/semantics/normalize/producers/shared.d.ts +561 -0
- package/dist/semantics/normalize/producers/shared.js +1683 -0
- package/dist/semantics/normalize/producers/spread-arguments.d.ts +72 -0
- package/dist/semantics/normalize/producers/spread-arguments.js +375 -0
- package/dist/semantics/normalize/producers/tagged-template.d.ts +5 -0
- package/dist/semantics/normalize/producers/tagged-template.js +307 -0
- package/dist/semantics/normalize/producers/tuple-spread.d.ts +189 -0
- package/dist/semantics/normalize/producers/tuple-spread.js +279 -0
- package/dist/semantics/normalize/property-key-domain.d.ts +36 -0
- package/dist/semantics/normalize/property-key-domain.js +150 -0
- package/dist/semantics/normalize/reachability.d.ts +189 -0
- package/dist/semantics/normalize/reachability.js +1057 -0
- package/dist/semantics/normalize/reassigned-bindings.d.ts +13 -0
- package/dist/semantics/normalize/reassigned-bindings.js +41 -0
- package/dist/semantics/normalize/record-storage-families.d.ts +11 -0
- package/dist/semantics/normalize/record-storage-families.js +109 -0
- package/dist/semantics/normalize/return-bindings.d.ts +260 -0
- package/dist/semantics/normalize/return-bindings.js +1311 -0
- package/dist/semantics/normalize/return-paths.d.ts +2 -0
- package/dist/semantics/normalize/return-paths.js +28 -0
- package/dist/semantics/normalize/script-global-redefinition.d.ts +44 -0
- package/dist/semantics/normalize/script-global-redefinition.js +75 -0
- package/dist/semantics/normalize/specialization.d.ts +201 -0
- package/dist/semantics/normalize/specialization.js +1824 -0
- package/dist/semantics/normalize/structural-array-element.d.ts +176 -0
- package/dist/semantics/normalize/structural-array-element.js +700 -0
- package/dist/semantics/normalize/structural-array-read.d.ts +13 -0
- package/dist/semantics/normalize/structural-array-read.js +124 -0
- package/dist/semantics/normalize/structural-callable.d.ts +12 -0
- package/dist/semantics/normalize/structural-callable.js +82 -0
- package/dist/semantics/normalize/structural-creation-order.d.ts +32 -0
- package/dist/semantics/normalize/structural-creation-order.js +111 -0
- package/dist/semantics/normalize/structural-declarations.d.ts +395 -0
- package/dist/semantics/normalize/structural-declarations.js +968 -0
- package/dist/semantics/normalize/structural-declared-body.d.ts +25 -0
- package/dist/semantics/normalize/structural-declared-body.js +147 -0
- package/dist/semantics/normalize/structural-generics.d.ts +56 -0
- package/dist/semantics/normalize/structural-generics.js +120 -0
- package/dist/semantics/normalize/structural-indexed-access.d.ts +11 -0
- package/dist/semantics/normalize/structural-indexed-access.js +126 -0
- package/dist/semantics/normalize/structural-instantiated-member.d.ts +64 -0
- package/dist/semantics/normalize/structural-instantiated-member.js +407 -0
- package/dist/semantics/normalize/structural-joins.d.ts +44 -0
- package/dist/semantics/normalize/structural-joins.js +143 -0
- package/dist/semantics/normalize/structural-layout-relevance.d.ts +29 -0
- package/dist/semantics/normalize/structural-layout-relevance.js +391 -0
- package/dist/semantics/normalize/structural-layout-type.d.ts +11 -0
- package/dist/semantics/normalize/structural-layout-type.js +1468 -0
- package/dist/semantics/normalize/structural-leaves.d.ts +49 -0
- package/dist/semantics/normalize/structural-leaves.js +125 -0
- package/dist/semantics/normalize/structural-local-union.d.ts +13 -0
- package/dist/semantics/normalize/structural-local-union.js +133 -0
- package/dist/semantics/normalize/structural-members.d.ts +26 -0
- package/dist/semantics/normalize/structural-members.js +84 -0
- package/dist/semantics/normalize/structural-mutable-method.d.ts +15 -0
- package/dist/semantics/normalize/structural-mutable-method.js +109 -0
- package/dist/semantics/normalize/structural-parts.d.ts +131 -0
- package/dist/semantics/normalize/structural-parts.js +737 -0
- package/dist/semantics/normalize/structural-receiver.d.ts +66 -0
- package/dist/semantics/normalize/structural-receiver.js +551 -0
- package/dist/semantics/normalize/structural-rules.d.ts +112 -0
- package/dist/semantics/normalize/structural-rules.js +93 -0
- package/dist/semantics/normalize/structural-self-reference.d.ts +150 -0
- package/dist/semantics/normalize/structural-self-reference.js +494 -0
- package/dist/semantics/normalize/structural.d.ts +202 -0
- package/dist/semantics/normalize/structural.js +4218 -0
- package/dist/semantics/normalize/unreduced-type-form.d.ts +41 -0
- package/dist/semantics/normalize/unreduced-type-form.js +53 -0
- package/dist/semantics/normalize/unresolvable-names.d.ts +56 -0
- package/dist/semantics/normalize/unresolvable-names.js +140 -0
- package/dist/semantics/package-sources.d.ts +16 -0
- package/dist/semantics/package-sources.js +365 -0
- package/dist/semantics/program.d.ts +151 -0
- package/dist/semantics/program.js +667 -0
- package/dist/semantics/prototype-install-source-transform.d.ts +4 -0
- package/dist/semantics/prototype-install-source-transform.js +341 -0
- package/dist/semantics/prototype-object-class-source-transform.d.ts +4 -0
- package/dist/semantics/prototype-object-class-source-transform.js +525 -0
- package/dist/semantics/prototype-reparenting.d.ts +50 -0
- package/dist/semantics/prototype-reparenting.js +317 -0
- package/dist/semantics/stable-checker.d.ts +10 -0
- package/dist/semantics/stable-checker.js +148 -0
- package/dist/semantics/subclass-member-overlay-transform.d.ts +35 -0
- package/dist/semantics/subclass-member-overlay-transform.js +1418 -0
- package/dist/semantics/symbol-keyed-expando-source-transform.d.ts +4 -0
- package/dist/semantics/symbol-keyed-expando-source-transform.js +66 -0
- package/dist/semantics/this-constructor-source-transform.d.ts +4 -0
- package/dist/semantics/this-constructor-source-transform.js +122 -0
- package/dist/semantics/uninstantiable-classes.d.ts +22 -0
- package/dist/semantics/uninstantiable-classes.js +75 -0
- package/dist/targets/cpp/balanced-units.d.ts +3 -0
- package/dist/targets/cpp/balanced-units.js +58 -0
- package/dist/targets/cpp/borrowed-call-entry.d.ts +30 -0
- package/dist/targets/cpp/borrowed-call-entry.js +68 -0
- package/dist/targets/cpp/captures.d.ts +68 -0
- package/dist/targets/cpp/captures.js +269 -0
- package/dist/targets/cpp/class-layout.d.ts +313 -0
- package/dist/targets/cpp/class-layout.js +483 -0
- package/dist/targets/cpp/class-properties/computed-method-value.d.ts +25 -0
- package/dist/targets/cpp/class-properties/computed-method-value.js +22 -0
- package/dist/targets/cpp/class-properties/emit-class-properties.d.ts +216 -0
- package/dist/targets/cpp/class-properties/emit-class-properties.js +917 -0
- package/dist/targets/cpp/class-properties/native-prototype.d.ts +11 -0
- package/dist/targets/cpp/class-properties/native-prototype.js +101 -0
- package/dist/targets/cpp/class-ref-transport.d.ts +12 -0
- package/dist/targets/cpp/class-ref-transport.js +32 -0
- package/dist/targets/cpp/conversions.d.ts +3 -0
- package/dist/targets/cpp/conversions.js +2027 -0
- package/dist/targets/cpp/deferral-safety.d.ts +82 -0
- package/dist/targets/cpp/deferral-safety.js +86 -0
- package/dist/targets/cpp/direct-call-receivers.d.ts +37 -0
- package/dist/targets/cpp/direct-call-receivers.js +79 -0
- package/dist/targets/cpp/document.d.ts +74 -0
- package/dist/targets/cpp/document.js +106 -0
- package/dist/targets/cpp/emit-allocation.d.ts +60 -0
- package/dist/targets/cpp/emit-allocation.js +573 -0
- package/dist/targets/cpp/emit-arrays.d.ts +119 -0
- package/dist/targets/cpp/emit-arrays.js +1047 -0
- package/dist/targets/cpp/emit-binding-reference.d.ts +17 -0
- package/dist/targets/cpp/emit-binding-reference.js +120 -0
- package/dist/targets/cpp/emit-bindings.d.ts +158 -0
- package/dist/targets/cpp/emit-bindings.js +769 -0
- package/dist/targets/cpp/emit-buffers.d.ts +156 -0
- package/dist/targets/cpp/emit-buffers.js +573 -0
- package/dist/targets/cpp/emit-callable.d.ts +230 -0
- package/dist/targets/cpp/emit-callable.js +2154 -0
- package/dist/targets/cpp/emit-carrier-members.d.ts +406 -0
- package/dist/targets/cpp/emit-carrier-members.js +1224 -0
- package/dist/targets/cpp/emit-coercion.d.ts +26 -0
- package/dist/targets/cpp/emit-coercion.js +39 -0
- package/dist/targets/cpp/emit-context.d.ts +1882 -0
- package/dist/targets/cpp/emit-context.js +904 -0
- package/dist/targets/cpp/emit-dynamic-properties.d.ts +174 -0
- package/dist/targets/cpp/emit-dynamic-properties.js +1249 -0
- package/dist/targets/cpp/emit-equality.d.ts +39 -0
- package/dist/targets/cpp/emit-equality.js +472 -0
- package/dist/targets/cpp/emit-exceptions.d.ts +104 -0
- package/dist/targets/cpp/emit-exceptions.js +513 -0
- package/dist/targets/cpp/emit-field-owner.d.ts +4 -0
- package/dist/targets/cpp/emit-field-owner.js +27 -0
- package/dist/targets/cpp/emit-in.d.ts +31 -0
- package/dist/targets/cpp/emit-in.js +426 -0
- package/dist/targets/cpp/emit-instanceof.d.ts +19 -0
- package/dist/targets/cpp/emit-instanceof.js +601 -0
- package/dist/targets/cpp/emit-integers.d.ts +68 -0
- package/dist/targets/cpp/emit-integers.js +184 -0
- package/dist/targets/cpp/emit-iterator.d.ts +100 -0
- package/dist/targets/cpp/emit-iterator.js +1137 -0
- package/dist/targets/cpp/emit-json.d.ts +73 -0
- package/dist/targets/cpp/emit-json.js +823 -0
- package/dist/targets/cpp/emit-jsx.d.ts +74 -0
- package/dist/targets/cpp/emit-jsx.js +806 -0
- package/dist/targets/cpp/emit-mixed-binary.d.ts +51 -0
- package/dist/targets/cpp/emit-mixed-binary.js +60 -0
- package/dist/targets/cpp/emit-namespaces.d.ts +101 -0
- package/dist/targets/cpp/emit-namespaces.js +133 -0
- package/dist/targets/cpp/emit-narrowing.d.ts +738 -0
- package/dist/targets/cpp/emit-narrowing.js +3967 -0
- package/dist/targets/cpp/emit-native-equality.d.ts +3 -0
- package/dist/targets/cpp/emit-native-equality.js +14 -0
- package/dist/targets/cpp/emit-native-selection.d.ts +5 -0
- package/dist/targets/cpp/emit-native-selection.js +37 -0
- package/dist/targets/cpp/emit-object-tag.d.ts +4 -0
- package/dist/targets/cpp/emit-object-tag.js +108 -0
- package/dist/targets/cpp/emit-owned-record.d.ts +2 -0
- package/dist/targets/cpp/emit-owned-record.js +15 -0
- package/dist/targets/cpp/emit-presence.d.ts +78 -0
- package/dist/targets/cpp/emit-presence.js +376 -0
- package/dist/targets/cpp/emit-properties.d.ts +105 -0
- package/dist/targets/cpp/emit-properties.js +1640 -0
- package/dist/targets/cpp/emit-record-view.d.ts +20 -0
- package/dist/targets/cpp/emit-record-view.js +354 -0
- package/dist/targets/cpp/emit-return.d.ts +5 -0
- package/dist/targets/cpp/emit-return.js +237 -0
- package/dist/targets/cpp/emit-sum-widening.d.ts +10 -0
- package/dist/targets/cpp/emit-sum-widening.js +69 -0
- package/dist/targets/cpp/emit-tonumber.d.ts +38 -0
- package/dist/targets/cpp/emit-tonumber.js +177 -0
- package/dist/targets/cpp/emit-tostring.d.ts +92 -0
- package/dist/targets/cpp/emit-tostring.js +556 -0
- package/dist/targets/cpp/emit-typeof.d.ts +49 -0
- package/dist/targets/cpp/emit-typeof.js +317 -0
- package/dist/targets/cpp/emit-union-properties.d.ts +181 -0
- package/dist/targets/cpp/emit-union-properties.js +1281 -0
- package/dist/targets/cpp/emit.d.ts +45 -0
- package/dist/targets/cpp/emit.js +2225 -0
- package/dist/targets/cpp/error-types.d.ts +4 -0
- package/dist/targets/cpp/error-types.js +11 -0
- package/dist/targets/cpp/function-source-reads.d.ts +72 -0
- package/dist/targets/cpp/function-source-reads.js +148 -0
- package/dist/targets/cpp/host/atomics.d.ts +25 -0
- package/dist/targets/cpp/host/atomics.js +63 -0
- package/dist/targets/cpp/host/core-globals.d.ts +108 -0
- package/dist/targets/cpp/host/core-globals.js +158 -0
- package/dist/targets/cpp/host/dynamic-argument-members.d.ts +42 -0
- package/dist/targets/cpp/host/dynamic-argument-members.js +61 -0
- package/dist/targets/cpp/host/emit-host-arity.d.ts +139 -0
- package/dist/targets/cpp/host/emit-host-arity.js +232 -0
- package/dist/targets/cpp/host/emit-host-invoke.d.ts +91 -0
- package/dist/targets/cpp/host/emit-host-invoke.js +1105 -0
- package/dist/targets/cpp/host/emit-host-object.d.ts +55 -0
- package/dist/targets/cpp/host/emit-host-object.js +2206 -0
- package/dist/targets/cpp/host/emit-host-properties.d.ts +69 -0
- package/dist/targets/cpp/host/emit-host-properties.js +533 -0
- package/dist/targets/cpp/host/emit-host-reflect.d.ts +4 -0
- package/dist/targets/cpp/host/emit-host-reflect.js +180 -0
- package/dist/targets/cpp/host/emit-host-value.d.ts +120 -0
- package/dist/targets/cpp/host/emit-host-value.js +392 -0
- package/dist/targets/cpp/host/host-members.d.ts +505 -0
- package/dist/targets/cpp/host/host-members.js +819 -0
- package/dist/targets/cpp/host/host-method-aliases.d.ts +30 -0
- package/dist/targets/cpp/host/host-method-aliases.js +76 -0
- package/dist/targets/cpp/host/native-protocols.d.ts +47 -0
- package/dist/targets/cpp/host/native-protocols.js +183 -0
- package/dist/targets/cpp/host/object-protocol.d.ts +351 -0
- package/dist/targets/cpp/host/object-protocol.js +671 -0
- package/dist/targets/cpp/host-namespace-reads.d.ts +83 -0
- package/dist/targets/cpp/host-namespace-reads.js +113 -0
- package/dist/targets/cpp/manifest/capabilities.d.ts +39 -0
- package/dist/targets/cpp/manifest/capabilities.js +1484 -0
- package/dist/targets/cpp/manifest.d.ts +23 -0
- package/dist/targets/cpp/manifest.js +113 -0
- package/dist/targets/cpp/native-error-base.d.ts +38 -0
- package/dist/targets/cpp/native-error-base.js +74 -0
- package/dist/targets/cpp/native-narrowing-transport.d.ts +12 -0
- package/dist/targets/cpp/native-narrowing-transport.js +151 -0
- package/dist/targets/cpp/native-selection-helpers.d.ts +11 -0
- package/dist/targets/cpp/native-selection-helpers.js +46 -0
- package/dist/targets/cpp/native-symbol-keys.d.ts +8 -0
- package/dist/targets/cpp/native-symbol-keys.js +34 -0
- package/dist/targets/cpp/ownership.d.ts +43 -0
- package/dist/targets/cpp/ownership.js +38 -0
- package/dist/targets/cpp/owning-conversion-input.d.ts +14 -0
- package/dist/targets/cpp/owning-conversion-input.js +12 -0
- package/dist/targets/cpp/property-read-claims.d.ts +62 -0
- package/dist/targets/cpp/property-read-claims.js +23 -0
- package/dist/targets/cpp/property-store-claims.d.ts +62 -0
- package/dist/targets/cpp/property-store-claims.js +26 -0
- package/dist/targets/cpp/prototype/emit-prototype-array.d.ts +111 -0
- package/dist/targets/cpp/prototype/emit-prototype-array.js +602 -0
- package/dist/targets/cpp/prototype/emit-prototype-date.d.ts +133 -0
- package/dist/targets/cpp/prototype/emit-prototype-date.js +515 -0
- package/dist/targets/cpp/prototype/emit-prototype-error.d.ts +13 -0
- package/dist/targets/cpp/prototype/emit-prototype-error.js +54 -0
- package/dist/targets/cpp/prototype/emit-prototype-invoke.d.ts +34 -0
- package/dist/targets/cpp/prototype/emit-prototype-invoke.js +1003 -0
- package/dist/targets/cpp/prototype/emit-prototype-iterator.d.ts +47 -0
- package/dist/targets/cpp/prototype/emit-prototype-iterator.js +250 -0
- package/dist/targets/cpp/prototype/emit-prototype-promise.d.ts +46 -0
- package/dist/targets/cpp/prototype/emit-prototype-promise.js +143 -0
- package/dist/targets/cpp/prototype/emit-prototype-regexp.d.ts +335 -0
- package/dist/targets/cpp/prototype/emit-prototype-regexp.js +853 -0
- package/dist/targets/cpp/prototype/emit-prototype-string.d.ts +42 -0
- package/dist/targets/cpp/prototype/emit-prototype-string.js +252 -0
- package/dist/targets/cpp/prototype/prototype-method-reads.d.ts +25 -0
- package/dist/targets/cpp/prototype/prototype-method-reads.js +182 -0
- package/dist/targets/cpp/reactive-dependencies.d.ts +140 -0
- package/dist/targets/cpp/reactive-dependencies.js +432 -0
- package/dist/targets/cpp/reactive-origins.d.ts +44 -0
- package/dist/targets/cpp/reactive-origins.js +82 -0
- package/dist/targets/cpp/records.d.ts +277 -0
- package/dist/targets/cpp/records.js +2427 -0
- package/dist/targets/cpp/recursive-containers.d.ts +43 -0
- package/dist/targets/cpp/recursive-containers.js +144 -0
- package/dist/targets/cpp/regexp-types.d.ts +56 -0
- package/dist/targets/cpp/regexp-types.js +59 -0
- package/dist/targets/cpp/substrate.d.ts +125 -0
- package/dist/targets/cpp/substrate.js +12 -0
- package/dist/targets/cpp/translation-unit.d.ts +219 -0
- package/dist/targets/cpp/translation-unit.js +2063 -0
- package/dist/targets/cpp/types.d.ts +280 -0
- package/dist/targets/cpp/types.js +1135 -0
- package/dist/targets/cpp/virtual-methods.d.ts +95 -0
- package/dist/targets/cpp/virtual-methods.js +264 -0
- package/package.json +95 -0
- package/src/targets/cpp/runtime/gea_dynamic_proxy.h +824 -0
- package/src/targets/cpp/runtime/gea_eval.h +1376 -0
- package/src/targets/cpp/runtime/gea_native_class_prototype.h +44 -0
- package/src/targets/cpp/runtime/gea_runtime.h +29222 -0
- package/src/targets/cpp/runtime/gea_runtime_builtins.cpp +55 -0
|
@@ -0,0 +1,3967 @@
|
|
|
1
|
+
import { classRefTransportKind, constructorUpcastMember } from './class-ref-transport.js';
|
|
2
|
+
import { abiKey, arrayExtensionKey, containsUnresolved, isBooleanShapedMergeTarget, isCanonicalNumberPropertyKeyText, representationKey, walkRepresentation, carriesUndefined } from '../../representation/model.js';
|
|
3
|
+
import { transferOf } from '../../ir/transfer.js';
|
|
4
|
+
import { coercionText } from './emit-coercion.js';
|
|
5
|
+
import { structuralRecordViewText } from './emit-record-view.js';
|
|
6
|
+
import { createCppEmitBlockedError, cppConstructThunkName, cppThunkName, defineValue, isCppEmitBlockedError, isDeferredValue, operandText } from './emit-context.js';
|
|
7
|
+
import { booleanTestText } from './emit-presence.js';
|
|
8
|
+
import { recastedRecordToArrayText } from './emit-arrays.js';
|
|
9
|
+
import { cppAbiParameterType, cppAbiType, cppClassName, cppConstantLiteral, cppRecordFieldName, cppRecordFieldPresenceName, cppRecordStructName, cppResultTypeOf, cppStringLiteral, cppTypeOf, cppUndefinedIn, cppUndefinedValue } from './types.js';
|
|
10
|
+
import { cppRegExpNativeTypes, cppStringObjectNativeType } from './regexp-types.js';
|
|
11
|
+
import { widenedNativeSumText } from './emit-sum-widening.js';
|
|
12
|
+
import { nativeSelectionText } from './emit-native-selection.js';
|
|
13
|
+
import { narrowingReachesTarget } from '../../conversion/build.js';
|
|
14
|
+
/**
|
|
15
|
+
* `native-record-ref` (the String WRAPPER OBJECT, `new String(x)`) -> `string`,
|
|
16
|
+
* the one direction `convertedValueText` needs that neither `narrowedLoadText`
|
|
17
|
+
* nor `widenedStoreText` otherwise reaches: both read their `held`/`written`
|
|
18
|
+
* argument as a search through a union's arms or a dynamic box's tag, and a
|
|
19
|
+
* String object is neither.
|
|
20
|
+
*
|
|
21
|
+
* A short, deliberately duplicated twin of `emit-prototype-regexp.ts`'s
|
|
22
|
+
* `stringObjectToStringText` -- not imported, to avoid a cycle: that file
|
|
23
|
+
* already imports `convertedValueText` FROM here (for a property store's own
|
|
24
|
+
* threaded result, mirroring `emitNativeSidecarSet`), so this file cannot
|
|
25
|
+
* import back from it. Both read the identical one fact,
|
|
26
|
+
* ECMA-262 22.1.3.34/22.1.5.4's `[[StringData]]`, off the identical struct --
|
|
27
|
+
* see `gea::runtime::StringObject`'s doc comment in `runtime/gea_runtime.h`.
|
|
28
|
+
*/
|
|
29
|
+
const stringObjectStringifyText = (source, text) => source.kind === 'native-record-ref' && source.native === cppStringObjectNativeType ? `${text}->value` : null;
|
|
30
|
+
/**
|
|
31
|
+
* Whether the C++ backend has a concrete reconciliation recipe for a promise
|
|
32
|
+
* payload. Promise-to-promise conversion is state adoption plus this payload
|
|
33
|
+
* conversion, so preflight and emission must ask the same authority rather
|
|
34
|
+
* than maintaining a second, inevitably drifting list of payload pairs.
|
|
35
|
+
*/
|
|
36
|
+
export const promisePayloadConvertible = (source, target) => {
|
|
37
|
+
// A void TARGET payload has nowhere to put the source's value, and a void
|
|
38
|
+
// SOURCE payload has no `value()` to read -- except when the source payload
|
|
39
|
+
// is `never`, where the absence is the proof rather than the obstacle: a
|
|
40
|
+
// `Promise<never>` cannot fulfil, so the target's fulfilment channel is
|
|
41
|
+
// never written and only the rejection has to be carried across. This is
|
|
42
|
+
// the one authority both the registry's `recasting` entry and the
|
|
43
|
+
// `promise-payload` chain step ask, so the pair the census admits is the
|
|
44
|
+
// pair the printer renders.
|
|
45
|
+
//
|
|
46
|
+
// The second exception is `void` against `undefined`, which is one promise
|
|
47
|
+
// wearing two TypeScript types rather than two promises: ECMA-262 27.2.1.4
|
|
48
|
+
// fulfils a promise resolved with nothing with `undefined`, and this runtime
|
|
49
|
+
// elides that `undefined` entirely for `void` (`cppResultTypeOf`). So the
|
|
50
|
+
// unit pair reconciles by supplying or dropping the value, in either
|
|
51
|
+
// direction, while `void` against any richer payload stays a hole.
|
|
52
|
+
// `@hono/node-server`'s `listener.ts` needs exactly this: `responseViaCache`
|
|
53
|
+
// is declared `Promise<undefined | void>` and both callers `return` its
|
|
54
|
+
// promise from an async function whose own result is `Promise<void>`.
|
|
55
|
+
if (target.kind === 'void')
|
|
56
|
+
return isUnitPromisePayload(source);
|
|
57
|
+
if (source.kind === 'void')
|
|
58
|
+
return source.bottom === true || isUnitPromisePayload(target);
|
|
59
|
+
try {
|
|
60
|
+
return convertedValueText(source, target, 'gea_promise_payload') !== null;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (isCppEmitBlockedError(error))
|
|
64
|
+
return false;
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
/** A promise payload that carries no information: `void`, and the `undefined` it is spelled as wherever a value is required. */
|
|
69
|
+
const isUnitPromisePayload = (payload) => payload.kind === 'void' || payload.kind === 'undefined';
|
|
70
|
+
/** `RegExpMatchArray` read through the `Array<string>` base it inherits. */
|
|
71
|
+
const regexpMatchArrayBaseText = (source, target, text) => source.kind === 'native-record-ref' &&
|
|
72
|
+
source.native === cppRegExpNativeTypes['match-result'] &&
|
|
73
|
+
source.ownership === 'shared-refcount' &&
|
|
74
|
+
target.kind === 'array-object' &&
|
|
75
|
+
target.ownership === 'shared-refcount' &&
|
|
76
|
+
target.element.kind === 'string'
|
|
77
|
+
? `${cppTypeOf(target)}(${text})`
|
|
78
|
+
: null;
|
|
79
|
+
/** A failed candidate is not a failed search when another union arm can carry the value. */
|
|
80
|
+
const tryCandidateText = (build) => {
|
|
81
|
+
try {
|
|
82
|
+
return build();
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (isCppEmitBlockedError(error))
|
|
86
|
+
return null;
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/** Whether `candidate` is `base` or descends from it in the emitted class layout. */
|
|
91
|
+
const isOrDescendsFrom = (ctx, candidate, base) => {
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
for (let current = candidate; current !== null && !seen.has(current);) {
|
|
94
|
+
if (current === base)
|
|
95
|
+
return true;
|
|
96
|
+
seen.add(current);
|
|
97
|
+
current = ctx.classes.get(current)?.base ?? null;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* A base-class carrier narrowed to a union of descendant classes.
|
|
103
|
+
*
|
|
104
|
+
* The physical carrier intentionally remains one `Ref<Base>`: allocation
|
|
105
|
+
* identity supplies the target union's discriminant, and checked class-family
|
|
106
|
+
* downcasts preserve the same native object. This is shared by cell and field
|
|
107
|
+
* reads because both are projections out of wider storage. An optional target
|
|
108
|
+
* preserves the source's absence around the projected descendant union.
|
|
109
|
+
*/
|
|
110
|
+
export const classFamilyLoadText = (ctx, held, read, text) => {
|
|
111
|
+
const targetUnion = read.kind === 'optional' ? read.payload : read;
|
|
112
|
+
if (targetUnion.kind !== 'tagged-union' || targetUnion.arms.length === 0)
|
|
113
|
+
return null;
|
|
114
|
+
const heldClass = (() => {
|
|
115
|
+
if (held.kind === 'optional' && held.payload.kind === 'class-ref') {
|
|
116
|
+
return { source: held.payload, value: `(*${text})` };
|
|
117
|
+
}
|
|
118
|
+
if (held.kind === 'class-ref')
|
|
119
|
+
return { source: held, value: text };
|
|
120
|
+
if (read.kind === 'optional' || held.kind !== 'tagged-union')
|
|
121
|
+
return null;
|
|
122
|
+
const classes = held.arms.flatMap((arm, index) => (arm.value.kind === 'class-ref' ? [{ source: arm.value, index }] : []));
|
|
123
|
+
if (classes.length !== 1)
|
|
124
|
+
return null;
|
|
125
|
+
if (!held.arms.every((arm) => arm.value.kind === 'class-ref' || arm.value.kind === 'undefined' || arm.value.kind === 'null'))
|
|
126
|
+
return null;
|
|
127
|
+
const sole = classes[0];
|
|
128
|
+
return { source: sole.source, value: `${text}.get<${sole.index}>()` };
|
|
129
|
+
})();
|
|
130
|
+
if (heldClass === null)
|
|
131
|
+
return null;
|
|
132
|
+
const { source, value } = heldClass;
|
|
133
|
+
const arms = targetUnion.arms.map((arm) => arm.value);
|
|
134
|
+
if (!arms.every((arm) => arm.kind === 'class-ref' && arm.ownership === source.ownership && arm.ancestors.includes(source.declaration)))
|
|
135
|
+
return null;
|
|
136
|
+
const classes = arms;
|
|
137
|
+
// A target may retain both an ancestor and one of its descendants (Three's
|
|
138
|
+
// WebGLRenderTarget | WebGLCubeRenderTarget flow does). JS unions do not
|
|
139
|
+
// preserve which syntactic arm produced an object, so allocation identity
|
|
140
|
+
// supplies the canonical answer: test the most-specific family first and
|
|
141
|
+
// retain each arm's original target-union index when rebuilding it.
|
|
142
|
+
const ordered = classes
|
|
143
|
+
.map((arm, index) => ({ arm, index }))
|
|
144
|
+
.sort((left, right) => {
|
|
145
|
+
const leftBelowRight = isOrDescendsFrom(ctx, left.arm.declaration, right.arm.declaration);
|
|
146
|
+
const rightBelowLeft = isOrDescendsFrom(ctx, right.arm.declaration, left.arm.declaration);
|
|
147
|
+
if (leftBelowRight !== rightBelowLeft)
|
|
148
|
+
return leftBelowRight ? -1 : 1;
|
|
149
|
+
return left.index - right.index;
|
|
150
|
+
});
|
|
151
|
+
const unionType = cppTypeOf(targetUnion);
|
|
152
|
+
const armText = (arm, index) => `${unionType}::ofArm<${index}>(gea::host::downcastClassRef<${cppClassName(arm.declaration)}>(${value}))`;
|
|
153
|
+
const fallback = ordered[ordered.length - 1];
|
|
154
|
+
let projected = armText(fallback.arm, fallback.index);
|
|
155
|
+
for (let index = ordered.length - 2; index >= 0; index -= 1) {
|
|
156
|
+
const candidate = ordered[index];
|
|
157
|
+
const arm = candidate.arm;
|
|
158
|
+
const descendants = [...ctx.classes.keys()].filter((candidate) => isOrDescendsFrom(ctx, candidate, arm.declaration) && isOrDescendsFrom(ctx, candidate, source.declaration));
|
|
159
|
+
if (descendants.length === 0)
|
|
160
|
+
return null;
|
|
161
|
+
const test = `gea::host::hasNativeClassLayoutRef<${descendants.map(cppClassName).join(', ')}>(${value})`;
|
|
162
|
+
projected = `${test} ? ${armText(arm, candidate.index)} : (${projected})`;
|
|
163
|
+
}
|
|
164
|
+
if (classes.length > 1)
|
|
165
|
+
projected = `(${projected})`;
|
|
166
|
+
if (read.kind !== 'optional')
|
|
167
|
+
return projected;
|
|
168
|
+
const targetType = cppTypeOf(read);
|
|
169
|
+
if (held.kind === 'optional') {
|
|
170
|
+
if (held.absence !== read.absence)
|
|
171
|
+
return null;
|
|
172
|
+
return `(${text}.has_value() ? ${targetType}(${projected}) : ${targetType}())`;
|
|
173
|
+
}
|
|
174
|
+
if (held.kind !== 'class-ref')
|
|
175
|
+
return null;
|
|
176
|
+
if (read.absence === 'null')
|
|
177
|
+
return `(${text} ? ${targetType}(${projected}) : ${targetType}())`;
|
|
178
|
+
return `${targetType}(${projected})`;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* The live arm of a tagged union, as an expression.
|
|
182
|
+
*
|
|
183
|
+
* More than one arm can carry the target's C++ type -- `A | B | C` where all
|
|
184
|
+
* three lower to `std::string` -- so the load dispatches on the discriminant
|
|
185
|
+
* rather than assuming a position. With one candidate it is a plain `get`, and
|
|
186
|
+
* the chain collapses to nothing; with several it tests each in turn and falls
|
|
187
|
+
* through to the last without testing it, because the narrowing already proved
|
|
188
|
+
* one of them is live and a test with no else has nothing to return.
|
|
189
|
+
*/
|
|
190
|
+
const taggedUnionArmText = (union, target, text) => {
|
|
191
|
+
const targetKey = representationKey(target);
|
|
192
|
+
const exact = union.arms
|
|
193
|
+
.map((arm, index) => ({ arm, index }))
|
|
194
|
+
.filter((entry) => representationKey(entry.arm.value) === targetKey)
|
|
195
|
+
.map((entry) => ({ index: entry.index, text: `${text}.get<${entry.index}>()` }));
|
|
196
|
+
// An arm that CONVERTS to the target answers too. An exact arm is the
|
|
197
|
+
// cheapest answer for its own discriminant, but its existence cannot erase
|
|
198
|
+
// a different live arm which is structurally assignable to the same target.
|
|
199
|
+
// Hono's RegExpRouter exposed this: a truthy value is either `string[]` or
|
|
200
|
+
// `RegExpMatchArray`; both are `Array<string>`, yet preferring the exact arm
|
|
201
|
+
// emitted an unconditional `get<0>()` and crashed on a match-result arm.
|
|
202
|
+
//
|
|
203
|
+
// Hono's `Hono.fetch` is another converting case: the cell is
|
|
204
|
+
// `dynamic | optional(record)` and the read is a
|
|
205
|
+
// `native-record-ref`, so the live arm is the BOX and the load is the
|
|
206
|
+
// unboxing check `unboxedLoadText` already renders. The narrowing that
|
|
207
|
+
// licensed this load proved one arm is live; the discriminant chain below
|
|
208
|
+
// then picks whichever it is, exactly as it does for several exact arms.
|
|
209
|
+
// An arm the target lives INSIDE answers too, on the same terms as a
|
|
210
|
+
// converting arm: `undefined | null | (string|number)` narrowed to `number`
|
|
211
|
+
// reads the `present` arm and then narrows THAT, which is one more level than
|
|
212
|
+
// an arm-key search can see. `narrowedUnionSubsetText` already recurses this
|
|
213
|
+
// way for a sub-union target; this is the same step for a flat one, and it
|
|
214
|
+
// terminates because each call strips one level of nesting.
|
|
215
|
+
const candidates = union.arms.flatMap((arm, index) => {
|
|
216
|
+
const found = exact.find((entry) => entry.index === index);
|
|
217
|
+
if (found)
|
|
218
|
+
return [found];
|
|
219
|
+
const slot = `${text}.get<${index}>()`;
|
|
220
|
+
const converted = tryCandidateText(() => convertedValueText(arm.value, target, slot) ?? narrowedLoadText(arm.value, target, slot));
|
|
221
|
+
return converted === null ? [] : [{ index, text: converted }];
|
|
222
|
+
});
|
|
223
|
+
const last = candidates[candidates.length - 1];
|
|
224
|
+
// A union target that some arm only WIDENS into is a recast, not a
|
|
225
|
+
// narrowing: the narrowing proved nothing about which arm is live, so every
|
|
226
|
+
// arm needs its home or the read would rebuild a live arm as another.
|
|
227
|
+
const partialWidening = target.kind === 'tagged-union' &&
|
|
228
|
+
candidates.some((candidate) => !exact.includes(candidate)) &&
|
|
229
|
+
union.arms.some((arm, index) => arm.value.kind === 'record' && !candidates.some((candidate) => candidate.index === index));
|
|
230
|
+
if (!last || partialWidening) {
|
|
231
|
+
throw createCppEmitBlockedError(`conversion:${representationKey(union)}->${targetKey}`, `narrows tagged union "${representationKey(union)}" to "${targetKey}", which none of its arms carries`);
|
|
232
|
+
}
|
|
233
|
+
let result = last.text;
|
|
234
|
+
for (const entry of candidates.slice(0, -1).reverse()) {
|
|
235
|
+
result = `${text}.is<${entry.index}>() ? ${entry.text} : (${result})`;
|
|
236
|
+
}
|
|
237
|
+
return candidates.length > 1 ? `(${result})` : result;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* A tagged union narrowed to a SUB-union -- fewer arms, not one flat arm.
|
|
241
|
+
*
|
|
242
|
+
* `taggedUnionArmText` above answers "narrowed to one specific representation";
|
|
243
|
+
* this answers the sibling question control flow proves just as often: `h
|
|
244
|
+
* instanceof Headers` inside `const h = init.headers` (a `Headers |
|
|
245
|
+
* Record<string,string> | [string,string][]` cell) leaves the `else` branch
|
|
246
|
+
* holding `Record<string,string> | [string,string][]` -- still a union, just
|
|
247
|
+
* one arm short. Neither `narrowedLoadText`'s optional-of-union case nor
|
|
248
|
+
* `taggedUnionArmText` covers it: the first is about presence, the second
|
|
249
|
+
* searches for arms sharing the target's WHOLE C++ type, which no single arm
|
|
250
|
+
* of a genuinely narrower union has.
|
|
251
|
+
*
|
|
252
|
+
* Every one of the narrower union's arms must have a same-shaped home among
|
|
253
|
+
* the wider union's arms (matched by `representationKey`, not by C++ type
|
|
254
|
+
* alone -- two structurally different arms can still share a C++ type, and
|
|
255
|
+
* conflating them would silently pick the wrong one) -- `null` otherwise,
|
|
256
|
+
* which is a real hole rather than a missing recipe. Each live arm rebuilds at
|
|
257
|
+
* its NEW index with `TaggedUnion<...>::ofArm<Index>`, the identical
|
|
258
|
+
* materializer a value narrowed into a fresh union already uses (`conversions.
|
|
259
|
+
* ts`'s widening/recasting entries) -- this is that same construction, tested
|
|
260
|
+
* over the WIDER union's own discriminant instead of over a single known
|
|
261
|
+
* value.
|
|
262
|
+
*/
|
|
263
|
+
const narrowedUnionSubsetText = (inner, read, text) => {
|
|
264
|
+
const pairs = [];
|
|
265
|
+
for (const [targetIndex, arm] of read.arms.entries()) {
|
|
266
|
+
const heldIndex = inner.arms.findIndex((candidate) => representationKey(candidate.value) === representationKey(arm.value));
|
|
267
|
+
if (heldIndex >= 0) {
|
|
268
|
+
pairs.push({ heldIndex, targetIndex, load: null });
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
// An arm the held arm NARROWS INTO, rather than one it equals. A narrowing
|
|
272
|
+
// does not stop at the top: `typeof x === 'string'` in the else branch of
|
|
273
|
+
// `BodyInit | null | undefined` leaves the same three-arm outer shape with
|
|
274
|
+
// a smaller inner union in its `present` arm, and matching arms by key
|
|
275
|
+
// alone reads that as a union with no such arm at all. Asking this same
|
|
276
|
+
// function one level in is what makes the outer match, and it terminates
|
|
277
|
+
// because each step strips one level of nesting.
|
|
278
|
+
const nested = inner.arms
|
|
279
|
+
.map((candidate, index) => ({
|
|
280
|
+
index,
|
|
281
|
+
load: tryCandidateText(() => narrowedLoadText(candidate.value, arm.value, `${text}.get<${index}>()`))
|
|
282
|
+
}))
|
|
283
|
+
.find((entry) => entry.load !== null);
|
|
284
|
+
if (!nested?.load)
|
|
285
|
+
return null;
|
|
286
|
+
pairs.push({ heldIndex: nested.index, targetIndex, load: nested.load });
|
|
287
|
+
}
|
|
288
|
+
const last = pairs[pairs.length - 1];
|
|
289
|
+
if (!last)
|
|
290
|
+
return null;
|
|
291
|
+
// THE HELD INDEX IS THE DISCRIMINANT, so two target arms may not share one.
|
|
292
|
+
//
|
|
293
|
+
// The chain below tests `text.is<heldIndex>()` to pick a target arm, which is
|
|
294
|
+
// only a discriminant while each target arm comes from a DIFFERENT held arm.
|
|
295
|
+
// Exact homes always do -- two distinct arm keys cannot both equal one arm's
|
|
296
|
+
// -- but the nested search above can land several target arms on the same
|
|
297
|
+
// held arm, and then the first test wins every time: `string | number |
|
|
298
|
+
// boolean | null | undefined` narrowed past its two absences resolved all
|
|
299
|
+
// three of `string`, `number` and `boolean` to held arm 2 (the nested
|
|
300
|
+
// `present` union), and the emitted chain read `is<2>() ? ofArm<0>(...) :
|
|
301
|
+
// (is<2>() ? ofArm<1>(...) : ...)`, which always answered `string`. It
|
|
302
|
+
// compiled, certified, and printed `string:` for the number 7.
|
|
303
|
+
//
|
|
304
|
+
// When they collide, the read does not live across the arms at all -- it
|
|
305
|
+
// lives INSIDE one, and the whole narrowing is that one arm's own, one level
|
|
306
|
+
// down, where its own discriminant is in scope. That recursion is the answer;
|
|
307
|
+
// anything left over refuses rather than emitting a chain whose test cannot
|
|
308
|
+
// decide.
|
|
309
|
+
const collides = new Set(pairs.map((pair) => pair.heldIndex)).size !== pairs.length;
|
|
310
|
+
if (collides) {
|
|
311
|
+
for (const [index, candidate] of inner.arms.entries()) {
|
|
312
|
+
const slot = `${text}.get<${index}>()`;
|
|
313
|
+
// `narrowedLoadText` answers `null` for "nothing to load", which for an
|
|
314
|
+
// arm that already carries exactly the read IS the answer: the arm's own
|
|
315
|
+
// value. Asked as an equality first so that `null` is never mistaken for
|
|
316
|
+
// "this arm cannot".
|
|
317
|
+
if (representationKey(candidate.value) === representationKey(read))
|
|
318
|
+
return slot;
|
|
319
|
+
// Only an arm that is itself a sum can hold the whole read. A plain
|
|
320
|
+
// member "loads" into the read by widening, and taking it would rebuild
|
|
321
|
+
// every live arm as that one: `Promise<{done: true} | {done: false,
|
|
322
|
+
// value: Uint8Array}>` into its `value: unknown` twin read `.get<0>()`
|
|
323
|
+
// with the chunk arm live.
|
|
324
|
+
if (candidate.value.kind !== 'tagged-union' && candidate.value.kind !== 'optional')
|
|
325
|
+
continue;
|
|
326
|
+
const load = narrowedLoadText(candidate.value, read, slot);
|
|
327
|
+
if (load !== null)
|
|
328
|
+
return load;
|
|
329
|
+
}
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
// The same one-time binding `recastedUnionText` makes, for the same reason:
|
|
333
|
+
// this chain is what a NESTED union arm reaches through (`homeOf` there
|
|
334
|
+
// asks `convertedValueText`, which lands here for an inner union narrowed
|
|
335
|
+
// one level down), and it spelled the ~1.3KB target type and the source
|
|
336
|
+
// expression once per arm -- 928 inner spellings on one three.js line after
|
|
337
|
+
// the outer recast alone was bound. A single-pair chain keeps the direct
|
|
338
|
+
// spelling: there is nothing to share.
|
|
339
|
+
const multiPair = pairs.length > 1;
|
|
340
|
+
const boundText = multiPair ? recastUnionSourceName : text;
|
|
341
|
+
const targetType = multiPair ? recastUnionAliasName : cppTypeOf(read);
|
|
342
|
+
const armText = (pair) => `${targetType}::ofArm<${pair.targetIndex}>(${pair.load ?? `${boundText}.get<${pair.heldIndex}>()`})`;
|
|
343
|
+
let result = armText(last);
|
|
344
|
+
for (const pair of pairs.slice(0, -1).reverse()) {
|
|
345
|
+
result = `${boundText}.is<${pair.heldIndex}>() ? ${armText(pair)} : (${result})`;
|
|
346
|
+
}
|
|
347
|
+
if (!multiPair)
|
|
348
|
+
return result;
|
|
349
|
+
// The source arrives as the lambda's PARAMETER, spelled with its own type
|
|
350
|
+
// (an `auto` parameter makes every `.is<k>()` a dependent template name),
|
|
351
|
+
// never as a `const auto&` declared in its body: a nested chain's `text` is the outer chain's own
|
|
352
|
+
// `gea_recast_source.get<2>()`, and a body-local of the same name would be
|
|
353
|
+
// in scope from its own declarator, so the initializer would read the
|
|
354
|
+
// uninitialized inner binding instead of the outer one. An argument is
|
|
355
|
+
// evaluated in the caller's scope, where the outer name is the live one.
|
|
356
|
+
return (`([&](const ${cppTypeOf(inner)}& ${recastUnionSourceName}) -> ${cppTypeOf(read)} { using ${recastUnionAliasName} = ${cppTypeOf(read)}; ` +
|
|
357
|
+
`return ${result}; }(${text}))`);
|
|
358
|
+
};
|
|
359
|
+
/**
|
|
360
|
+
* The load a narrowing licenses, or `null` when the two carriers are the same
|
|
361
|
+
* and there is nothing to load.
|
|
362
|
+
*
|
|
363
|
+
* Returning `null` for "no narrowing needed" and throwing for "no narrowing
|
|
364
|
+
* possible" keeps the two apart: the first is the ordinary case, the second is
|
|
365
|
+
* a program this compiler already knows is wrong.
|
|
366
|
+
*/
|
|
367
|
+
export const narrowedLoadText = (held, read, text) => {
|
|
368
|
+
if (representationKey(held) === representationKey(read))
|
|
369
|
+
return null;
|
|
370
|
+
const remappedSet = genericFunctionSetWideningText(held, read, text);
|
|
371
|
+
if (remappedSet !== null)
|
|
372
|
+
return remappedSet;
|
|
373
|
+
// Two structural views of one host facade still hold the same native value.
|
|
374
|
+
// `Buffer` and `Uint8Array` expose different TypeScript member sets while
|
|
375
|
+
// both map to the host's one BufferFacade C++ type. Equal native identity,
|
|
376
|
+
// ownership, and physical type prove this is an alias-preserving read.
|
|
377
|
+
if (held.kind === 'native-record-ref' &&
|
|
378
|
+
read.kind === 'native-record-ref' &&
|
|
379
|
+
held.native !== null &&
|
|
380
|
+
held.native === read.native &&
|
|
381
|
+
held.ownership === read.ownership &&
|
|
382
|
+
cppTypeOf(held) === cppTypeOf(read))
|
|
383
|
+
return text;
|
|
384
|
+
const unwrapped = held.kind === 'optional' ? `(*${text})` : text;
|
|
385
|
+
const inner = held.kind === 'optional' ? held.payload : held;
|
|
386
|
+
if (representationKey(inner) === representationKey(read))
|
|
387
|
+
return unwrapped;
|
|
388
|
+
// The same array read as the interface that extends it (`elements` of
|
|
389
|
+
// `readonly T[] | undefined`, proven present and then `isNodeArray`): one
|
|
390
|
+
// C++ type either way -- the fields an extension adds live beside the
|
|
391
|
+
// elements -- so the load is the payload itself, exactly as
|
|
392
|
+
// `conversions.ts`'s `gea::ArrayObject::identity` recast states for the
|
|
393
|
+
// bare pair.
|
|
394
|
+
if (sameArrayUpToExtension(inner, read))
|
|
395
|
+
return unwrapped;
|
|
396
|
+
// The guard proved the cell ABSENT: `if ( dstArray === null )` and every read
|
|
397
|
+
// inside that branch. There is nothing in the cell to load -- the read's
|
|
398
|
+
// carrier has one inhabitant -- so this produces the constant, and must be
|
|
399
|
+
// asked BEFORE the arm search below, which answers `null` ("nothing to
|
|
400
|
+
// narrow") for this pair and would let the caller emit the whole
|
|
401
|
+
// `gea::Optional<T>` where a `std::nullptr_t` is declared.
|
|
402
|
+
if (held.kind === 'optional' && read.kind === held.absence)
|
|
403
|
+
return read.kind === 'null' ? 'nullptr' : cppUndefinedValue;
|
|
404
|
+
// Reading a cell as `dynamic` is not a narrowing at all -- it is the box, and
|
|
405
|
+
// the box accepts every carrier. Answered by the widening this file already
|
|
406
|
+
// owns rather than by searching the arms for a `dynamic` one, which no union
|
|
407
|
+
// has: a sum's arms are the declared types, and `any` is not one of them.
|
|
408
|
+
// Without this, a `string | symbol` cell read into an `unknown` parameter
|
|
409
|
+
// refused with "narrows a tagged union to dynamic, which none of its arms
|
|
410
|
+
// carries" -- an accurate sentence about the wrong question.
|
|
411
|
+
//
|
|
412
|
+
// The HELD carrier goes in whole, never `inner`/`unwrapped`: an optional
|
|
413
|
+
// read as `any` still has its presence to state, and `widenedStoreText`'s
|
|
414
|
+
// dynamic branch owns that (`has_value() ? box(payload) : box(absence)`).
|
|
415
|
+
// Handing it the unwrapped payload boxed `(*cell)` unconditionally, so a
|
|
416
|
+
// `number | null` holding `null` reached an `...args: any[]` listener as
|
|
417
|
+
// `Tag::Number` over whatever the dereference found -- node-compat's
|
|
418
|
+
// cluster 'exit' event reported `code=0 signal=` for a worker Node reports
|
|
419
|
+
// as `code=null signal=SIGTERM`. Certified, compiled, wrong.
|
|
420
|
+
if (read.kind === 'dynamic')
|
|
421
|
+
return widenedStoreText(read, held, text);
|
|
422
|
+
// And the mirror: a cell the program declared `any` read at a place the
|
|
423
|
+
// checker narrowed. `if (a instanceof Error) throw a` reads the same cell as
|
|
424
|
+
// an Error record, and that read is the box's tag-and-payload-type check --
|
|
425
|
+
// the narrowing's claim ENFORCED, not assumed. A union's arm search below
|
|
426
|
+
// cannot answer it, because a box has no arms.
|
|
427
|
+
if (inner.kind === 'dynamic')
|
|
428
|
+
return unboxedLoadText(read, unwrapped);
|
|
429
|
+
// Presence narrowing and class widening can happen in the same read. A
|
|
430
|
+
// guarded `Derived | undefined` passed to a `Base` parameter is physically
|
|
431
|
+
// the present `Derived` handle followed by Ref's ordinary upcast. Keeping
|
|
432
|
+
// those as one recipe matters because the exact-payload check above only
|
|
433
|
+
// answers `T | undefined -> T`; it cannot answer a different base carrier.
|
|
434
|
+
//
|
|
435
|
+
// This direction is a store-style widening, so it is proved from the
|
|
436
|
+
// source's ancestry. The downcast immediately below proves the inverse
|
|
437
|
+
// direction from the target's ancestry and remains the checked narrowing.
|
|
438
|
+
if (inner.kind === 'class-ref' &&
|
|
439
|
+
read.kind === 'class-ref' &&
|
|
440
|
+
inner.ownership === read.ownership &&
|
|
441
|
+
inner.ancestors.includes(read.declaration)) {
|
|
442
|
+
return `${cppTypeOf(read)}(${unwrapped})`;
|
|
443
|
+
}
|
|
444
|
+
// A program-class handle read at a DERIVED class -- `part instanceof Mesh`
|
|
445
|
+
// then `part.castShadow = true` on a `traverse` callback's `Object3D`.
|
|
446
|
+
// `gea::Ref<Base>` and `gea::Ref<Derived>` are different C++ types, so the
|
|
447
|
+
// narrowed read needs a cast even though nothing about the object changes:
|
|
448
|
+
// `downcastClassRef` (gea_runtime.h) is `staticCast` plus the one thing this
|
|
449
|
+
// call site can be wrong about on its own, a `static_assert` that `Derived`
|
|
450
|
+
// really descends from `Base`.
|
|
451
|
+
//
|
|
452
|
+
// Only ever the DOWNCAST direction, and it is CHECKED here rather than left
|
|
453
|
+
// to the `static_assert`: this function is also the arm search
|
|
454
|
+
// `narrowedUnionSubsetText` runs over EVERY arm of a union looking for a
|
|
455
|
+
// home, so answering a downcast for two unrelated classes would put a text
|
|
456
|
+
// that cannot compile into a branch the dispatch never takes. The carrier
|
|
457
|
+
// states its own ancestry (`Representation`'s `class-ref`), so the question
|
|
458
|
+
// is answerable without a policy to thread. A read at an ANCESTOR is a
|
|
459
|
+
// widening and `widenedStoreText` answers it with `Ref`'s own converting
|
|
460
|
+
// constructor. Ownership must agree -- a handle and a by-value struct of the
|
|
461
|
+
// same class are different physical things, not a heritage question.
|
|
462
|
+
if (inner.kind === 'class-ref' &&
|
|
463
|
+
read.kind === 'class-ref' &&
|
|
464
|
+
inner.ownership === read.ownership &&
|
|
465
|
+
read.ancestors.includes(inner.declaration)) {
|
|
466
|
+
return `gea::host::downcastClassRef<${cppClassName(read.declaration)}>(${unwrapped})`;
|
|
467
|
+
}
|
|
468
|
+
// A cell that provably holds nothing but absence, read at a place declared
|
|
469
|
+
// wider. hono's `#dispatch` binds `env: E['Bindings']` -- an indexed access
|
|
470
|
+
// through a type parameter's constraint that this program never instantiates
|
|
471
|
+
// -- so its cell is placed `undefined` while every read of it is the declared
|
|
472
|
+
// optional. A read cannot be narrower than the cell here; it is a widening,
|
|
473
|
+
// and the store direction already knows the answer is the empty optional.
|
|
474
|
+
if ((inner.kind === 'undefined' || inner.kind === 'null') && read.kind === 'optional' && read.absence === inner.kind) {
|
|
475
|
+
return widenedStoreText(read, inner, unwrapped);
|
|
476
|
+
}
|
|
477
|
+
if (inner.kind !== 'tagged-union')
|
|
478
|
+
return null;
|
|
479
|
+
// A join may preserve another join (or an optional) as one physical arm.
|
|
480
|
+
// Search that nested carrier before trying to compare the outer arm list
|
|
481
|
+
// with the read's list. Otherwise a read of the preserved inner union is
|
|
482
|
+
// mistaken for a sub-union assembled from several OUTER arms, and a read of
|
|
483
|
+
// an absence carried inside an optional arm is reported as having no arm at
|
|
484
|
+
// all. Control-flow narrowing already proves the selected leaf is live; the
|
|
485
|
+
// recursion below only renders the required sequence of `get`/`*` loads.
|
|
486
|
+
const nested = nestedArmLoadText(inner, read, unwrapped);
|
|
487
|
+
if (nested !== null)
|
|
488
|
+
return nested;
|
|
489
|
+
// A read that is still optional narrowed the arm without proving presence, so
|
|
490
|
+
// the presence test survives into the load: absent stays absent, and present
|
|
491
|
+
// reads the arm the narrowing named. The empty branch is a default-constructed
|
|
492
|
+
// optional rather than a copy of the cell, because the cell's optional holds
|
|
493
|
+
// the wrong payload type by construction -- that is the whole difference this
|
|
494
|
+
// load exists to express.
|
|
495
|
+
if (held.kind === 'optional' && read.kind === 'optional') {
|
|
496
|
+
const narrowed = cppTypeOf(read);
|
|
497
|
+
// The payload itself can still be a sub-union -- `if (x !== undefined &&
|
|
498
|
+
// typeof x !== 'number')` proves both presence and one excluded arm in a
|
|
499
|
+
// single guard, leaving `T | U | undefined` narrowed to `T | U`, still
|
|
500
|
+
// wrapped in the same optional. `taggedUnionArmText` searches for one arm
|
|
501
|
+
// whose WHOLE representation matches the target and has nothing to find
|
|
502
|
+
// when the target is a sub-union rather than a flat arm.
|
|
503
|
+
const armText = read.payload.kind === 'tagged-union'
|
|
504
|
+
? (narrowedUnionSubsetText(inner, read.payload, unwrapped) ?? recastedUnionText(inner, read.payload, unwrapped))
|
|
505
|
+
: taggedUnionArmText(inner, read.payload, unwrapped);
|
|
506
|
+
if (armText === null) {
|
|
507
|
+
throw createCppEmitBlockedError(`conversion:${representationKey(inner)}->${representationKey(read.payload)}`, `narrows an optional tagged union carrying "${representationKey(inner)}" to "${representationKey(read.payload)}", ` +
|
|
508
|
+
'which none of its arms carries');
|
|
509
|
+
}
|
|
510
|
+
return `(${text}.has_value() ? ${narrowed}(${armText}) : ${narrowed}())`;
|
|
511
|
+
}
|
|
512
|
+
// A union read as an OPTIONAL: the read spends absence on one flag where the
|
|
513
|
+
// union spends it on an arm. `f(x: T | null = null)`'s body binds `T | null`
|
|
514
|
+
// while its slot is `T | null | undefined`, so every such body does this
|
|
515
|
+
// once, at the merge that chose between the argument and the default.
|
|
516
|
+
//
|
|
517
|
+
// The arms not named here are proven dead by the narrowing that licensed
|
|
518
|
+
// this load -- the same proof every other branch of this function reads
|
|
519
|
+
// rather than re-derives.
|
|
520
|
+
if (read.kind === 'optional') {
|
|
521
|
+
const absentIndex = inner.arms.findIndex((arm) => arm.value.kind === read.absence);
|
|
522
|
+
if (absentIndex >= 0) {
|
|
523
|
+
const narrowed = cppTypeOf(read);
|
|
524
|
+
return `(${unwrapped}.is<${absentIndex}>() ? ${narrowed}() : ${narrowed}(${taggedUnionArmText(inner, read.payload, unwrapped)}))`;
|
|
525
|
+
}
|
|
526
|
+
// No arm is the bare absent literal, because absence lives INSIDE another
|
|
527
|
+
// arm's own optional. hono's `compose` binds `let handler` to a
|
|
528
|
+
// `Function | (Next | undefined)` cell and reads it back as `Function |
|
|
529
|
+
// undefined`: the union's arms are the record and an
|
|
530
|
+
// `optional(callable)`, and the read's payload is the record alone.
|
|
531
|
+
//
|
|
532
|
+
// So the payload's own arm is the whole test: live means present, and
|
|
533
|
+
// every other arm is absence -- the arms not named here being proven dead
|
|
534
|
+
// by the narrowing that licensed this load, the same proof the branch
|
|
535
|
+
// above reads rather than re-derives.
|
|
536
|
+
const payloadIndex = inner.arms.findIndex((arm) => representationKey(arm.value) === representationKey(read.payload));
|
|
537
|
+
if (payloadIndex >= 0) {
|
|
538
|
+
const narrowed = cppTypeOf(read);
|
|
539
|
+
return `(${unwrapped}.is<${payloadIndex}>() ? ${narrowed}(${unwrapped}.get<${payloadIndex}>()) : ${narrowed}())`;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// The narrower-union case: `read` is still a union, just missing one or more
|
|
543
|
+
// of `inner`'s arms (proven dead by the narrowing that licensed this load),
|
|
544
|
+
// rather than one flat representation `taggedUnionArmText` below can search
|
|
545
|
+
// for. `narrowedUnionSubsetText` covers both the plain case (`inner` held no
|
|
546
|
+
// optional to begin with) and the presence-plus-arm-narrowing case just
|
|
547
|
+
// handled above falling through here when `read` itself is not `optional`
|
|
548
|
+
// (a destructured element proven both present and narrowed in one step).
|
|
549
|
+
if (read.kind === 'tagged-union') {
|
|
550
|
+
const subset = narrowedUnionSubsetText(inner, read, unwrapped);
|
|
551
|
+
if (subset !== null)
|
|
552
|
+
return subset;
|
|
553
|
+
// And the other direction: a read WIDER than the cell. A narrowing does not
|
|
554
|
+
// only ever shrink a union -- the cell's placement is the union of what the
|
|
555
|
+
// program writes to it, and a read's carrier is the declared type at that
|
|
556
|
+
// site, so a cell every writer narrows still gets read at its full declared
|
|
557
|
+
// width. hono's `#newResponse` is the case: the cell holds
|
|
558
|
+
// `Response | ResponseInit` and the read wants
|
|
559
|
+
// `StatusCode | Response | ResponseInit`. Every arm the cell can hold is an
|
|
560
|
+
// arm of the read, which is exactly what `recastedUnionText` proves, and
|
|
561
|
+
// the arms the read has and the cell does not are simply never live.
|
|
562
|
+
const widened = recastedUnionText(inner, read, unwrapped);
|
|
563
|
+
if (widened !== null)
|
|
564
|
+
return widened;
|
|
565
|
+
}
|
|
566
|
+
return taggedUnionArmText(inner, read, unwrapped);
|
|
567
|
+
};
|
|
568
|
+
/**
|
|
569
|
+
* The load for a target that is an arm of an ARM.
|
|
570
|
+
*
|
|
571
|
+
* A join nests: `string | Blob | URLSearchParams | null | undefined` derives as
|
|
572
|
+
* an outer union over the two absence arms and an inner union over the three
|
|
573
|
+
* value arms, so narrowing it to `string` reads through two `get`s and not one.
|
|
574
|
+
* `widenedStoreText` already walks exactly this nesting in the store direction
|
|
575
|
+
* ("A join can nest", below); this is its mirror, and without it the load
|
|
576
|
+
* refused with "narrows a tagged union to string, which none of its arms
|
|
577
|
+
* carries" -- an accurate sentence about the outer union only.
|
|
578
|
+
*
|
|
579
|
+
* `node-compat`'s `new Request(url, init)` is the case: `typeof init.body ===
|
|
580
|
+
* 'string'` narrows `BodyInit | null | undefined` to `string`.
|
|
581
|
+
*
|
|
582
|
+
* The discriminant is tested whenever more than one arm can answer, exactly as
|
|
583
|
+
* `taggedUnionArmText` does for the flat case, and for the same reason: with
|
|
584
|
+
* one candidate the narrowing already proved which arm is live, and a test with
|
|
585
|
+
* no else has nothing to return.
|
|
586
|
+
*/
|
|
587
|
+
const nestedArmLoadText = (inner, read, text) => {
|
|
588
|
+
const readKey = representationKey(read);
|
|
589
|
+
const nestedLoad = (held, at) => {
|
|
590
|
+
if (representationKey(held) === readKey)
|
|
591
|
+
return at;
|
|
592
|
+
// A leaf can satisfy a wider nested read through a real carrier
|
|
593
|
+
// conversion. Hono's RegExpRouter reads an optional `Array<string>` from
|
|
594
|
+
// an optional union whose leaves are `string[]` and `RegExpMatchArray`:
|
|
595
|
+
// neither leaf equals the OPTIONAL target, but both convert to it. Looking
|
|
596
|
+
// only for exact nested keys found the first leaf and emitted its `get`
|
|
597
|
+
// unconditionally, crashing when the second discriminant was live.
|
|
598
|
+
if (held.kind !== 'optional' && held.kind !== 'tagged-union') {
|
|
599
|
+
const converted = tryCandidateText(() => convertedValueText(held, read, at));
|
|
600
|
+
if (converted !== null)
|
|
601
|
+
return converted;
|
|
602
|
+
}
|
|
603
|
+
if (held.kind === 'optional') {
|
|
604
|
+
if (read.kind === held.absence)
|
|
605
|
+
return read.kind === 'null' ? 'nullptr' : cppUndefinedValue;
|
|
606
|
+
if (read.kind === 'optional' && read.absence === held.absence) {
|
|
607
|
+
const payload = nestedLoad(held.payload, `(*${at})`);
|
|
608
|
+
if (payload === null)
|
|
609
|
+
return null;
|
|
610
|
+
const targetType = cppTypeOf(read);
|
|
611
|
+
return `(${at}.has_value() ? ${targetType}(${payload}) : ${targetType}())`;
|
|
612
|
+
}
|
|
613
|
+
return nestedLoad(held.payload, `(*${at})`);
|
|
614
|
+
}
|
|
615
|
+
if (held.kind !== 'tagged-union')
|
|
616
|
+
return null;
|
|
617
|
+
if (read.kind === 'tagged-union') {
|
|
618
|
+
const narrowed = tryCandidateText(() => narrowedUnionSubsetText(held, read, at));
|
|
619
|
+
if (narrowed !== null)
|
|
620
|
+
return narrowed;
|
|
621
|
+
}
|
|
622
|
+
const candidates = held.arms.flatMap((arm, index) => {
|
|
623
|
+
const loaded = nestedLoad(arm.value, `${at}.get<${index}>()`);
|
|
624
|
+
return loaded === null ? [] : [{ index, loaded }];
|
|
625
|
+
});
|
|
626
|
+
const last = candidates[candidates.length - 1];
|
|
627
|
+
if (!last)
|
|
628
|
+
return null;
|
|
629
|
+
let dispatched = last.loaded;
|
|
630
|
+
for (const candidate of candidates.slice(0, -1).reverse()) {
|
|
631
|
+
dispatched = `${at}.is<${candidate.index}>() ? ${candidate.loaded} : (${dispatched})`;
|
|
632
|
+
}
|
|
633
|
+
return candidates.length > 1 ? `(${dispatched})` : dispatched;
|
|
634
|
+
};
|
|
635
|
+
const candidates = [];
|
|
636
|
+
let converted = false;
|
|
637
|
+
for (const [index, arm] of inner.arms.entries()) {
|
|
638
|
+
const armText = `${text}.get<${index}>()`;
|
|
639
|
+
const load = nestedLoad(arm.value, armText);
|
|
640
|
+
if (load === null)
|
|
641
|
+
continue;
|
|
642
|
+
candidates.push({ index, text: load });
|
|
643
|
+
if (arm.value.kind !== 'optional' && arm.value.kind !== 'tagged-union' && representationKey(arm.value) !== readKey)
|
|
644
|
+
converted = true;
|
|
645
|
+
}
|
|
646
|
+
const last = candidates[candidates.length - 1];
|
|
647
|
+
if (!last)
|
|
648
|
+
return null;
|
|
649
|
+
// A flat arm that only CONVERTS into the read proves nothing about which arm
|
|
650
|
+
// is live, so dispatching on it is sound only when every arm has a home: a
|
|
651
|
+
// `{done: true} | {done: false, value: Uint8Array}` cell read as its
|
|
652
|
+
// `value: unknown` twin found a home for the first arm alone and read it
|
|
653
|
+
// unconditionally, with the chunk arm live.
|
|
654
|
+
if (converted && candidates.length !== inner.arms.length)
|
|
655
|
+
return null;
|
|
656
|
+
let result = last.text;
|
|
657
|
+
for (const candidate of candidates.slice(0, -1).reverse()) {
|
|
658
|
+
result = `${text}.is<${candidate.index}>() ? ${candidate.text} : (${result})`;
|
|
659
|
+
}
|
|
660
|
+
return candidates.length > 1 ? `(${result})` : result;
|
|
661
|
+
};
|
|
662
|
+
/**
|
|
663
|
+
* The store a widening needs, or `null` when the value can be assigned as it
|
|
664
|
+
* stands.
|
|
665
|
+
*
|
|
666
|
+
* This is the mirror of `narrowedLoadText` and it exists for the same reason:
|
|
667
|
+
* `const x: string | number = 3` puts a `double` into a `TaggedUnion`, and the
|
|
668
|
+
* language really does widen there -- the union is the declared carrier and the
|
|
669
|
+
* initializer is one arm of it.
|
|
670
|
+
*
|
|
671
|
+
* `gea::Optional<T>` needs nothing: it declares a converting assignment from
|
|
672
|
+
* `T`, so a present value assigns straight in and the presence flag follows.
|
|
673
|
+
* `gea::TaggedUnion` deliberately declares no such thing, because there is no
|
|
674
|
+
* one answer -- two arms can share a C++ type, and an implicit conversion would
|
|
675
|
+
* have to pick one silently. `ofArm<Index>` is where that choice is stated, and
|
|
676
|
+
* the index is the arm's position in the checker's own order, the same order
|
|
677
|
+
* the load reads back.
|
|
678
|
+
*
|
|
679
|
+
* The first arm carrying the written carrier is the one chosen. When several
|
|
680
|
+
* do, they are indistinguishable at runtime by construction -- the load
|
|
681
|
+
* dispatches over exactly that set and reads the same C++ type from any of them
|
|
682
|
+
* -- so the choice is not observable. What would be observable is choosing an
|
|
683
|
+
* arm that does *not* carry it, which is what returning `null` prevents.
|
|
684
|
+
*/
|
|
685
|
+
/**
|
|
686
|
+
* The `gea::Value::Tag` a concrete representation boxes under when it is
|
|
687
|
+
* written into a `dynamic` cell -- the widening direction `conversion/
|
|
688
|
+
* derive.ts`'s narrowing-only algebra has no answer for (see citations.md
|
|
689
|
+
* finding 1). `null` for a carrier this compiler does not yet box (a
|
|
690
|
+
* container, a proxy, an existing `dynamic` itself, which the
|
|
691
|
+
* `representationKey` equality check above already short-circuits): an
|
|
692
|
+
* omission to extend deliberately later, not to guess at here.
|
|
693
|
+
*
|
|
694
|
+
* Exported so `targets/cpp/conversions.ts`'s `widening` registry entry can
|
|
695
|
+
* ask the identical question when the conversion graph -- not just a
|
|
696
|
+
* `convert` this function already renders -- needs to know whether boxing a
|
|
697
|
+
* given carrier is a real, installed capability. One answer, asked from both
|
|
698
|
+
* the preflight authority and the emission authority, rather than a second
|
|
699
|
+
* table naming the same tags that could drift from this one.
|
|
700
|
+
*/
|
|
701
|
+
export const dynamicTagFor = (representation) => {
|
|
702
|
+
switch (representation.kind) {
|
|
703
|
+
case 'null':
|
|
704
|
+
return 'Null';
|
|
705
|
+
case 'undefined':
|
|
706
|
+
return 'Undefined';
|
|
707
|
+
case 'string':
|
|
708
|
+
return 'String';
|
|
709
|
+
// `gea::Symbol` is an id, and `Value::Tag` has a state for it. Missing here
|
|
710
|
+
// rather than deliberately absent: every `symbol`-carrying union refused to
|
|
711
|
+
// box at all, which is what `EventName = string | symbol` -- node's own
|
|
712
|
+
// event-name type -- is made of.
|
|
713
|
+
case 'symbol':
|
|
714
|
+
return 'Symbol';
|
|
715
|
+
case 'scalar':
|
|
716
|
+
return representation.domain === 'boolean' ? 'Boolean' : representation.domain === 'bigint' ? 'BigInt' : 'Number';
|
|
717
|
+
case 'class-ref':
|
|
718
|
+
case 'record':
|
|
719
|
+
case 'record-with-index':
|
|
720
|
+
case 'native-record-ref':
|
|
721
|
+
case 'native-handle':
|
|
722
|
+
case 'array-object':
|
|
723
|
+
case 'dictionary':
|
|
724
|
+
// A source-declared any/unknown boundary retains the collection's native
|
|
725
|
+
// handle, including its key/value types and object identity.
|
|
726
|
+
case 'keyed-collection':
|
|
727
|
+
// ECMA-262 25.1's `ArrayBuffer` is a JS Object exactly like the typed
|
|
728
|
+
// array below it -- `typeof` answers `"object"`, `cppTypeOf` already
|
|
729
|
+
// spells it `gea::Ref<gea::ArrayBuffer>` (`gea::ArrayBuffer` being the
|
|
730
|
+
// one monomorphic `std::vector<std::uint8_t>` alias, so there is only
|
|
731
|
+
// ever one payload address to record, never one per element domain the
|
|
732
|
+
// way `typed-array` needs). `Value::box` records that address as
|
|
733
|
+
// `payloadType()` the identical way it does for every other `Ref<...>`
|
|
734
|
+
// in this list. Missing here meant an `ArrayBuffer` value -- hono's
|
|
735
|
+
// `Context.body`'s own `Data` union carries one -- could never widen
|
|
736
|
+
// into a `dynamic` cell, the same silent gap `typed-array`'s own
|
|
737
|
+
// addition just above closed for typed arrays, blocking both this
|
|
738
|
+
// widen and the reverse read (`unboxedLoadText`, below) for the
|
|
739
|
+
// identical reason.
|
|
740
|
+
case 'array-buffer':
|
|
741
|
+
case 'shared-array-buffer':
|
|
742
|
+
// A `gea::TypedArray<T>` is a JS Object like the rest of this list --
|
|
743
|
+
// ECMA-262 typeof answers `"object"` for it too (`emit-typeof.ts`'s
|
|
744
|
+
// `objectLike`) -- and it boxes the identical way: `cppTypeOf` already
|
|
745
|
+
// spells it `gea::Ref<gea::TypedArray<T>>`, so `Value::box` records that
|
|
746
|
+
// exact C++ type's address as `payloadType()`, one program-wide address
|
|
747
|
+
// per element domain. That per-type address is also what
|
|
748
|
+
// `gea::host::instanceOfTypedArray<T>` (gea_runtime.h) reads back to
|
|
749
|
+
// answer `x instanceof Uint16Array` on a boxed value -- the SAME
|
|
750
|
+
// primitive `instanceOfError` above already uses for the error family,
|
|
751
|
+
// asked of a typed array's own payload type instead of a registration
|
|
752
|
+
// table, because a typed array's C++ type already IS its constructor
|
|
753
|
+
// identity with no subclass to reconcile.
|
|
754
|
+
//
|
|
755
|
+
// Missing here meant a typed-array value could never widen into a
|
|
756
|
+
// `dynamic` cell at all: `emit-instanceof.ts`'s boxed
|
|
757
|
+
// `x instanceof Uint16Array` test has a real box to read only once a
|
|
758
|
+
// typed array can actually get INTO one, and this is the one place that
|
|
759
|
+
// decides that. The same gap silently blocked the reverse read
|
|
760
|
+
// (`unboxedLoadText`, below) and the "typed-array -> dynamic" merge
|
|
761
|
+
// widenings `targets/cpp/conversions.ts`'s `widening` registry asks this
|
|
762
|
+
// exact function about.
|
|
763
|
+
case 'typed-array':
|
|
764
|
+
return 'Object';
|
|
765
|
+
// A `gea::Promise<T>` is a JS Object too -- `typeof new Promise(...) ===
|
|
766
|
+
// 'object'` -- and `Value::box` needs nothing case-specific to hold one:
|
|
767
|
+
// the template already records `payloadTypeTagFor<gea::Promise<T>>()` for
|
|
768
|
+
// whichever `T` this instantiation closes over, one program-wide address
|
|
769
|
+
// per payload type, exactly as it does for every other kind sharing this
|
|
770
|
+
// tag. Missing here meant a value returned from a plain (non-async)
|
|
771
|
+
// function -- `#cachedBody`'s `return (bodyCache[k] as Promise<BodyInit>)
|
|
772
|
+
// .then(...)` in hono's `request.ts`, whose OWN inferred return type is a
|
|
773
|
+
// real `Promise<unknown>` folding to `dynamic` only because its sibling
|
|
774
|
+
// return arm reads a program-declared `any` -- could never widen into a
|
|
775
|
+
// `dynamic` cell at all: `emit-narrowing.ts`'s own generic `held.kind ===
|
|
776
|
+
// 'dynamic'` store (below) and `targets/cpp/conversions.ts`'s `widening`
|
|
777
|
+
// registry both ask this exact function, and both got `null` for a
|
|
778
|
+
// pairing this runtime already has every primitive to satisfy.
|
|
779
|
+
case 'promise':
|
|
780
|
+
return 'Object';
|
|
781
|
+
case 'function':
|
|
782
|
+
case 'function-family':
|
|
783
|
+
case 'function-value-family':
|
|
784
|
+
case 'function-value-dispatch':
|
|
785
|
+
case 'generic-function-set':
|
|
786
|
+
case 'constructor-family':
|
|
787
|
+
case 'constructor-value-dispatch':
|
|
788
|
+
case 'function-and-constructor':
|
|
789
|
+
return 'Function';
|
|
790
|
+
default:
|
|
791
|
+
return null;
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
/**
|
|
795
|
+
* The empty optional, when what is being stored is the absence the cell's own
|
|
796
|
+
* presence flag stands for.
|
|
797
|
+
*
|
|
798
|
+
* This is the one store the payload cannot state, which is why the recursion
|
|
799
|
+
* below has a second half. `gea::Optional<T>` is a presence flag plus a `T`;
|
|
800
|
+
* an absence is neither a `T` nor an arm of one, so asking the payload how to
|
|
801
|
+
* hold it gets no answer -- and the answer that comes back, `null`, is read by
|
|
802
|
+
* every caller as "assign as it stands" and renders `cell = nullptr`. For
|
|
803
|
+
* `Optional<double>` that is a clang error. For `Optional<std::string>` it is
|
|
804
|
+
* worse: `std::string`'s own `const char*` constructor makes
|
|
805
|
+
* `Optional::operator=(T&&)` viable, so the cell comes out *present*, holding
|
|
806
|
+
* a string built from a null pointer, and every later presence test answers the
|
|
807
|
+
* opposite of the truth with nothing to say it did.
|
|
808
|
+
*
|
|
809
|
+
* Which absence the cell holds is checked rather than assumed -- an optional
|
|
810
|
+
* tagged `null` handed an `undefined` would answer a later `=== null` with
|
|
811
|
+
* `true`, and that is a wrong answer, not a spelling gap -- which is the rule
|
|
812
|
+
* `convertedValueText` used to keep as its own private copy, now stated once
|
|
813
|
+
* here, where every store site already asks.
|
|
814
|
+
*/
|
|
815
|
+
const emptyOptionalText = (held, written) => {
|
|
816
|
+
if (written.kind !== 'null' && written.kind !== 'undefined')
|
|
817
|
+
return null;
|
|
818
|
+
return written.kind === held.absence ? `${cppTypeOf(held)}()` : null;
|
|
819
|
+
};
|
|
820
|
+
/**
|
|
821
|
+
* The empty handle, when what is being stored is the absence the handle's own
|
|
822
|
+
* invalid state stands for.
|
|
823
|
+
*
|
|
824
|
+
* The same store `emptyOptionalText` describes, for the carrier that took the
|
|
825
|
+
* `Optional`'s place. `representation/optional.ts` collapses `T | null` onto a
|
|
826
|
+
* host handle because a handle already carries its absence -- `NativeHandle`
|
|
827
|
+
* default-constructs to `id_ = -1` and `valid()` is `id_ >= 0` -- so the cell
|
|
828
|
+
* that used to be an `Optional<Element>` holding nothing is now an `Element`
|
|
829
|
+
* naming nothing. Without this the `null` assigns as it stands and renders
|
|
830
|
+
* `handle = nullptr`, which is the clang error the optional case above exists
|
|
831
|
+
* to prevent, one carrier along.
|
|
832
|
+
*
|
|
833
|
+
* `null` only, matching the collapse exactly: `optionalOf` keeps the presence
|
|
834
|
+
* flag for `undefined`, so a handle carrier is never the destination of one.
|
|
835
|
+
*/
|
|
836
|
+
const emptyHandleText = (held, written) => written.kind === 'null' ? `${cppTypeOf(held)}()` : null;
|
|
837
|
+
/**
|
|
838
|
+
* The parameter index a callable's trailing arguments pack into, or `null` for
|
|
839
|
+
* a fixed-arity convention or a carrier that is not a callable at all.
|
|
840
|
+
*/
|
|
841
|
+
const restFromOf = (representation) => {
|
|
842
|
+
if (representation.kind === 'function' || representation.kind === 'function-value-dispatch')
|
|
843
|
+
return representation.abi.restFrom;
|
|
844
|
+
if (representation.kind === 'function-family' || representation.kind === 'function-value-family')
|
|
845
|
+
return representation.abi.restFrom;
|
|
846
|
+
if (representation.kind === 'function-and-constructor')
|
|
847
|
+
return representation.call.restFrom;
|
|
848
|
+
return null;
|
|
849
|
+
};
|
|
850
|
+
/**
|
|
851
|
+
* One value, boxed.
|
|
852
|
+
*
|
|
853
|
+
* A CALLABLE with a rest parameter takes the ABI-stating form: which formal
|
|
854
|
+
* absorbs the trailing arguments is invisible in the C++ type -- `(a, xs: T[])`
|
|
855
|
+
* and `(a, ...xs: T[])` are the same `CallableObject` -- so a dynamic call
|
|
856
|
+
* through the box would hand the first trailing argument to a slot expecting
|
|
857
|
+
* the packed array (10.2.11 binds a rest parameter to an Array of ALL the
|
|
858
|
+
* remaining ones). The emitter is the only reader of the ABI, so it states the
|
|
859
|
+
* position here and `gea::Value::boxCallable` records the matching thunk.
|
|
860
|
+
* hono's `this[method] = (args1, ...args) => ...` is every `app.get(path,
|
|
861
|
+
* handler)` in the program.
|
|
862
|
+
*/
|
|
863
|
+
export const boxedText = (representation, tag, text) => {
|
|
864
|
+
const restFrom = restFromOf(representation);
|
|
865
|
+
const value = `static_cast<${cppTypeOf(representation)}>(${text})`;
|
|
866
|
+
const abi = representation.kind === 'function-and-constructor' ? representation.call : 'abi' in representation ? representation.abi : null;
|
|
867
|
+
if (abi?.receiver)
|
|
868
|
+
return `gea::Value::boxMethod<${restFrom === null ? -1 : restFrom + 1}>(${value})`;
|
|
869
|
+
if (restFrom === null)
|
|
870
|
+
return `gea::Value::box(gea::Value::Tag::${tag}, ${value})`;
|
|
871
|
+
return `gea::Value::boxCallable<${restFrom}>(${value})`;
|
|
872
|
+
};
|
|
873
|
+
/**
|
|
874
|
+
* How a native carrier boxes into a dynamic cell, rendered over an arbitrary
|
|
875
|
+
* text naming the storage.
|
|
876
|
+
*
|
|
877
|
+
* The dynamic reason is stated HERE rather than at each caller because it is
|
|
878
|
+
* not a choice: `widenedStoreText` records below that no `DynamicReason` is
|
|
879
|
+
* consulted on this edge -- every boundary a reason names admits any concrete
|
|
880
|
+
* value the checker lets reach it -- so a caller asking "box this carrier"
|
|
881
|
+
* asks this one question and must not be free to spell a different reason for
|
|
882
|
+
* it.
|
|
883
|
+
*
|
|
884
|
+
* `storage` is a TEXT, not a member. The recipe is a function of the
|
|
885
|
+
* representation and that text alone: no field key, no struct name, no
|
|
886
|
+
* surrounding statement. So two fields spelling the same carrier spell the
|
|
887
|
+
* same recipe, and the native field table's read hook can render an optional
|
|
888
|
+
* or union carrier's arms ONCE over a slot its key chain selects into
|
|
889
|
+
* (`records.ts`) instead of once per field. `null` is the renderer's own
|
|
890
|
+
* answer that this carrier needs no widening at all.
|
|
891
|
+
*/
|
|
892
|
+
export const dynamicCarrierBoxText = (carrier, storage) => widenedStoreText({ kind: 'dynamic', reason: 'declared-any-never-narrowed' }, carrier, storage);
|
|
893
|
+
/**
|
|
894
|
+
* A `dynamic` value an async body returns, settled into its `Promise<V>`
|
|
895
|
+
* result. The value may itself be a promise, which the return ADOPTS rather
|
|
896
|
+
* than fulfils with (ECMA-262 27.2.1.3.2), so the choice is made at run time
|
|
897
|
+
* by `gea::detail::promiseFromDynamic` -- the store-direction recipe below.
|
|
898
|
+
*/
|
|
899
|
+
export const dynamicPromiseAdoptionText = (result, text) => widenedStoreText(result, { kind: 'dynamic', reason: 'declared-any-never-narrowed' }, text);
|
|
900
|
+
export const widenedStoreText = (held, written, text) => {
|
|
901
|
+
if (representationKey(held) === representationKey(written))
|
|
902
|
+
return null;
|
|
903
|
+
const remappedSet = genericFunctionSetWideningText(written, held, text);
|
|
904
|
+
if (remappedSet !== null)
|
|
905
|
+
return remappedSet;
|
|
906
|
+
// An optional over a union widens twice: the arm becomes the union, and the
|
|
907
|
+
// union assigns into the optional through its converting assignment. Asking
|
|
908
|
+
// recursively is what keeps the two steps from being two separate rules.
|
|
909
|
+
//
|
|
910
|
+
// The payload is asked first and the absence answers only what it leaves
|
|
911
|
+
// unanswered. A payload that really does carry an arm of the written carrier
|
|
912
|
+
// -- an absence arm included -- is that value's own home, and storing the
|
|
913
|
+
// empty optional instead would drop a live value rather than record it.
|
|
914
|
+
if (held.kind === 'optional')
|
|
915
|
+
return widenedStoreText(held.payload, written, text) ?? emptyOptionalText(held, written);
|
|
916
|
+
if (held.kind === 'native-handle')
|
|
917
|
+
return emptyHandleText(held, written);
|
|
918
|
+
// The same store once more, for the program's own refcounted instances:
|
|
919
|
+
// `optional.ts` collapses `TreeNode | null` onto `gea::Ref<TreeNode>`, whose
|
|
920
|
+
// default construction IS that `null`. Without this the literal assigns as it
|
|
921
|
+
// stands and renders `left = nullptr`, which `Ref` does not accept.
|
|
922
|
+
if (held.kind === 'class-ref' && held.ownership === 'shared-refcount' && written.kind === 'null')
|
|
923
|
+
return `${cppTypeOf(held)}()`;
|
|
924
|
+
// A dynamic value returned where `Promise<T>` is declared may itself be a
|
|
925
|
+
// promise -- hono's `formData()` returns its `any`-typed `#cachedBody(...)`
|
|
926
|
+
// -- and an async function's return adopts it (ECMA-262 27.2.1.3.2) rather
|
|
927
|
+
// than fulfilling with the promise object.
|
|
928
|
+
if (held.kind === 'promise' && written.kind === 'dynamic') {
|
|
929
|
+
if (held.value.kind === 'void')
|
|
930
|
+
return `gea::detail::promiseFromDynamic<void>(${text})`;
|
|
931
|
+
const settled = tryCandidateText(() => convertedValueText(written, held.value, 'gea_settled'));
|
|
932
|
+
if (settled === null)
|
|
933
|
+
return null;
|
|
934
|
+
return `gea::detail::promiseFromDynamic<${cppTypeOf(held.value)}>(${text}, [](const gea::Value& gea_settled) { return ${cppTypeOf(held.value)}(${settled}); })`;
|
|
935
|
+
}
|
|
936
|
+
// A held `Promise<T>` is the one case ECMAScript itself widens this way: an
|
|
937
|
+
// `async` function's body returns `T` at a `return` terminator whose ABI
|
|
938
|
+
// result is `Promise<T>` -- the checker accepts this because `T` is
|
|
939
|
+
// assignable to `Promise<T>`, and no ordinary binding or field can ever
|
|
940
|
+
// hold `T` where a `Promise<T>` is declared, so this path is reached only
|
|
941
|
+
// from the return terminator (`emit.ts`). Recursing (rather than a direct
|
|
942
|
+
// `gea::Promise<...>(text)` wrap) lets a payload that itself needs
|
|
943
|
+
// widening -- e.g. a tagged-union arm -- widen first; either way the
|
|
944
|
+
// result relies on `gea::Promise<T>`'s own non-explicit converting
|
|
945
|
+
// constructor, the same mechanism `Optional<T>` already uses above.
|
|
946
|
+
if (held.kind === 'promise')
|
|
947
|
+
return widenedStoreText(held.value, written, text);
|
|
948
|
+
// A `dynamic` cell is the one case this reconciliation widens *into* rather
|
|
949
|
+
// than narrows *out of* -- see citations.md finding 1 for why nothing else
|
|
950
|
+
// in the compiler boxes a concrete value on write. `held.reason` is not
|
|
951
|
+
// consulted: every `DynamicReason` (model.ts) names a boundary that admits
|
|
952
|
+
// any concrete value the checker allows to reach it (declared
|
|
953
|
+
// `any`/`unknown`, an unresolved `JSON.parse`, a thrown carrier,
|
|
954
|
+
// `ToString`'s operand), so the box is unconditional once the cell itself
|
|
955
|
+
// is `dynamic`.
|
|
956
|
+
if (held.kind === 'dynamic') {
|
|
957
|
+
// A value that is ALREADY a box needs no box: a `gea::Value` reaching a
|
|
958
|
+
// `gea::Value` cell is a copy. `dynamicTagFor` answers `null` for a
|
|
959
|
+
// `dynamic` not because it cannot be boxed but because it already is one,
|
|
960
|
+
// so without this the generic tail below read that `null` as a refusal.
|
|
961
|
+
// Two boxes whose carriers state different REASONS are one C++ type, which
|
|
962
|
+
// is why this is a kind test and not a key test -- `dynamic(untyped-
|
|
963
|
+
// callable)` and `dynamic(declared-any-never-narrowed)` are both
|
|
964
|
+
// `gea::Value`, and only the identical pair is caught by this function's
|
|
965
|
+
// own identity gate above.
|
|
966
|
+
if (written.kind === 'dynamic')
|
|
967
|
+
return text;
|
|
968
|
+
// A sum has no single tag: which JavaScript type it is depends on the arm
|
|
969
|
+
// that is live, so the box is built by the same discriminant chain
|
|
970
|
+
// `emit-equality.ts` compares one with. Written out per arm rather than
|
|
971
|
+
// deferred to a runtime helper because `Value::box` needs the payload's
|
|
972
|
+
// STATIC type to record its dispatchers (gea_runtime.h's own note on
|
|
973
|
+
// `box`), and only an arm-indexed `get<I>()` has one.
|
|
974
|
+
//
|
|
975
|
+
// `text` is read once per arm, which is safe for the same reason
|
|
976
|
+
// `absenceComparisonText` reads its operand twice: `operandText` answers an
|
|
977
|
+
// already-materialized SSA name, never an expression with effects.
|
|
978
|
+
//
|
|
979
|
+
// The value is force-cast to the carrier's own declared C++ type
|
|
980
|
+
// (`cppTypeOf`) before it reaches `box`, never left to deduce `box`'s
|
|
981
|
+
// template argument from `text`'s own C++ expression type: `box<T>`
|
|
982
|
+
// records `std::decay_t<T>` as the payload's static type (`gea_runtime.h`'s
|
|
983
|
+
// own comment on `box`), and a checked unboxer downstream (`unboxAs`)
|
|
984
|
+
// compares THAT recorded type against `cppTypeOf` again -- so the two must
|
|
985
|
+
// be the same authority. Deduction gives the wrong one for exactly the
|
|
986
|
+
// case this "already-materialized SSA name" assumption misses: a constant
|
|
987
|
+
// operand's `text` can be the bare literal itself (`emit.ts`'s
|
|
988
|
+
// `emitConstant`/`types.ts`'s `cppConstantLiteral` inline a `dynamic`
|
|
989
|
+
// constant's literal text directly rather than routing it through a
|
|
990
|
+
// variable first), and a bare `41` is a C++ `int`, a bare `"9"` a `const
|
|
991
|
+
// char*` -- neither matches the `double`/`std::string`
|
|
992
|
+
// `Tag::Number`/`Tag::String` promise. `static_cast<double>(41)` converts
|
|
993
|
+
// the literal to the declared type before boxing it -- a plain explicit
|
|
994
|
+
// `box<T>(...)` template argument was tried first and rejected: an
|
|
995
|
+
// ALREADY-typed source read through a `const` accessor (a field read
|
|
996
|
+
// inside a `const` member function, e.g.) is a `const` lvalue, which
|
|
997
|
+
// cannot bind to `box`'s `T&&` when `T` is forced non-const, where
|
|
998
|
+
// `static_cast<T>` copy-constructs a plain prvalue either way and binds
|
|
999
|
+
// cleanly regardless of what qualifiers the source expression carried.
|
|
1000
|
+
// An optional value has no single tag either, for a different reason than
|
|
1001
|
+
// a union does: which JS value it boxes to depends on whether it is
|
|
1002
|
+
// present at all, not on which arm is live. Absent boxes to the exact
|
|
1003
|
+
// value its own absence spells (`null` or `undefined`, `written.absence`
|
|
1004
|
+
// names which) -- reusing `cppConstantLiteral`'s own spelling for that
|
|
1005
|
+
// literal and this SAME function's generic tail below to box it, rather
|
|
1006
|
+
// than hand-writing a second `Tag::Null`/`Tag::Undefined` construction
|
|
1007
|
+
// that could drift from the one the plain bare-`null`/bare-`undefined`
|
|
1008
|
+
// widening already renders. Present recurses one payload down, which
|
|
1009
|
+
// reaches the tagged-union case just above when the payload is one.
|
|
1010
|
+
if (written.kind === 'optional') {
|
|
1011
|
+
const absentRepresentation = { kind: written.absence };
|
|
1012
|
+
const absentText = cppConstantLiteral(written.absence, written.absence, absentRepresentation);
|
|
1013
|
+
const boxedAbsent = widenedStoreText(held, absentRepresentation, absentText);
|
|
1014
|
+
const boxedPresent = widenedStoreText(held, written.payload, `(*${text})`);
|
|
1015
|
+
if (boxedAbsent === null || boxedPresent === null)
|
|
1016
|
+
return null;
|
|
1017
|
+
return `(${text}.has_value() ? ${boxedPresent} : ${boxedAbsent})`;
|
|
1018
|
+
}
|
|
1019
|
+
if (written.kind === 'tagged-union') {
|
|
1020
|
+
const arms = [];
|
|
1021
|
+
for (const [index, arm] of written.arms.entries()) {
|
|
1022
|
+
const armText = `${text}.get<${index}>()`;
|
|
1023
|
+
// An arm that is itself a box is stored as-is, and cannot go through
|
|
1024
|
+
// the recursion below: this function's identity gate answers `null`
|
|
1025
|
+
// for a same-carrier pair -- "no widening needed", which the loop
|
|
1026
|
+
// would read as a refusal. hono's `compose` carries its `handler` as
|
|
1027
|
+
// `tagged-union(dynamic(untyped-callable) | optional(Next))`, and
|
|
1028
|
+
// reading that cell as `dynamic` asks exactly this of arm 0.
|
|
1029
|
+
if (arm.value.kind === 'dynamic') {
|
|
1030
|
+
// A broad Function arm is stored as FunctionValue so the tagged
|
|
1031
|
+
// union retains its executable Function-tag discriminator. Both
|
|
1032
|
+
// FunctionValue -> Value (base conversion) and Value ->
|
|
1033
|
+
// FunctionValue (checked constructor) exist, which makes the two
|
|
1034
|
+
// arms of a C++ conditional expression ambiguous unless the live
|
|
1035
|
+
// FunctionValue is explicitly viewed as its Value base.
|
|
1036
|
+
const dynamicArmText = arm.runtimeDiscriminator.kind === 'callable-tag' ? `static_cast<gea::Value>(${armText})` : armText;
|
|
1037
|
+
arms.push(`${text}.is<${index}>() ? ${dynamicArmText} : `);
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
const armTag = dynamicTagFor(arm.value);
|
|
1041
|
+
// An arm with no tag is not a carrier this box cannot hold -- it is one
|
|
1042
|
+
// whose JavaScript type depends on something a level further in.
|
|
1043
|
+
// `BodyInit | null | undefined` (hono's `createResponseInstance`) has a
|
|
1044
|
+
// `present` arm that is ITSELF a union of seven, every one tagged. So
|
|
1045
|
+
// the arm recurses here -- this case and the optional one above answer
|
|
1046
|
+
// exactly the shapes `dynamicTagFor` returns `null` for -- and an arm
|
|
1047
|
+
// neither reaches still refuses, one level deeper, for the real reason.
|
|
1048
|
+
const boxed = armTag === null ? widenedStoreText(held, arm.value, armText) : boxedText(arm.value, armTag, armText);
|
|
1049
|
+
if (boxed === null)
|
|
1050
|
+
return null;
|
|
1051
|
+
arms.push(`${text}.is<${index}>() ? ${boxed} : `);
|
|
1052
|
+
}
|
|
1053
|
+
return `(${arms.join('')}gea::Value())`;
|
|
1054
|
+
}
|
|
1055
|
+
const tag = dynamicTagFor(written);
|
|
1056
|
+
return tag === null ? null : boxedText(written, tag, text);
|
|
1057
|
+
}
|
|
1058
|
+
// A CALLABLE CELL whose held convention differs from the written one only in
|
|
1059
|
+
// its RESULT -- `const slot: (n: number) => any = concrete`, and hono's
|
|
1060
|
+
// `#addRoute(handler: H)` one union layer up. Asked before the tagged-union
|
|
1061
|
+
// gate below because a bare callable cell never reaches the arm loop, and
|
|
1062
|
+
// `emitBindingWrite` renders a store through this function rather than
|
|
1063
|
+
// through `convertedValueText`, so the pair has no other way in.
|
|
1064
|
+
const adaptedStore = resultAdaptedCallableText(written, held, text);
|
|
1065
|
+
if (adaptedStore !== null)
|
|
1066
|
+
return adaptedStore;
|
|
1067
|
+
if (held.kind !== 'tagged-union')
|
|
1068
|
+
return null;
|
|
1069
|
+
// A bare tagged union has no `has_value()` of its own: it cannot keep an
|
|
1070
|
+
// absence flag the way a `held.kind === 'optional'` above does. So a
|
|
1071
|
+
// `written` that still arrives optional here -- `a && a.b`'s kept side
|
|
1072
|
+
// still wearing its own optional while the merge's own carrier collapsed
|
|
1073
|
+
// to a bare union, or `a || b`/`a ?? b`'s kept-present side one absence
|
|
1074
|
+
// state narrower than `a` itself -- is read unconditionally rather than
|
|
1075
|
+
// refused. That unconditional read is sound, not a risk taken here: the
|
|
1076
|
+
// checker never lets a possibly-absent value reach a non-optional carrier
|
|
1077
|
+
// unless it has already proven presence at this exact point (the same
|
|
1078
|
+
// proof `narrowing`'s own unwrap trusts rather than re-derives,
|
|
1079
|
+
// `targets/cpp/conversions.ts`), one level up, at a store instead of a
|
|
1080
|
+
// load. The arm search below runs over the optional's *payload* -- no arm
|
|
1081
|
+
// an optional target ever selected wraps another optional, so searching
|
|
1082
|
+
// the wrapper itself would never match.
|
|
1083
|
+
if (written.kind === 'optional') {
|
|
1084
|
+
// An arm that IS this optional, whole. The deref below spends the
|
|
1085
|
+
// optional's own absence on the grounds that no arm ever wraps one -- and
|
|
1086
|
+
// hono's `compose` disproves that: `let handler` is a `Function | (Next |
|
|
1087
|
+
// undefined)` cell whose arms are the record and `optional(callable)`, so
|
|
1088
|
+
// writing `next || undefined` into it stores the optional AS the arm.
|
|
1089
|
+
// Dereferencing there would drop the absence the arm exists to hold.
|
|
1090
|
+
const wholeIndex = held.arms.findIndex((arm) => representationKey(arm.value) === representationKey(written));
|
|
1091
|
+
if (wholeIndex >= 0)
|
|
1092
|
+
return `${cppTypeOf(held)}::ofArm<${wholeIndex}>(${text})`;
|
|
1093
|
+
const derefText = `(*${text})`;
|
|
1094
|
+
const payloadKey = representationKey(written.payload);
|
|
1095
|
+
const index = held.arms.findIndex((arm) => representationKey(arm.value) === payloadKey);
|
|
1096
|
+
if (index >= 0)
|
|
1097
|
+
return `${cppTypeOf(held)}::ofArm<${index}>(${derefText})`;
|
|
1098
|
+
// The optional's live payload can reach a union arm by the same nominal
|
|
1099
|
+
// upcast as a bare class value below. This arises when a mutable local is
|
|
1100
|
+
// filled from an optional derived-class property after a presence guard,
|
|
1101
|
+
// while its cell holds a union of base classes. The guard has already
|
|
1102
|
+
// discharged absence at this store; preserve the live object and select
|
|
1103
|
+
// the unique ancestor arm rather than refusing merely because the source
|
|
1104
|
+
// still carries its flow-view Optional wrapper.
|
|
1105
|
+
if (written.payload.kind === 'class-ref') {
|
|
1106
|
+
for (const [armIndex, arm] of held.arms.entries()) {
|
|
1107
|
+
if (arm.value.kind === 'class-ref' &&
|
|
1108
|
+
written.payload.ownership === arm.value.ownership &&
|
|
1109
|
+
written.payload.ancestors.includes(arm.value.declaration)) {
|
|
1110
|
+
return `${cppTypeOf(held)}::ofArm<${armIndex}>(${cppTypeOf(arm.value)}(${derefText}))`;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
for (const [armIndex, arm] of held.arms.entries()) {
|
|
1115
|
+
if (arm.value.kind !== 'tagged-union')
|
|
1116
|
+
continue;
|
|
1117
|
+
const inner = widenedStoreText(arm.value, written.payload, derefText);
|
|
1118
|
+
if (inner !== null)
|
|
1119
|
+
return `${cppTypeOf(held)}::ofArm<${armIndex}>(${inner})`;
|
|
1120
|
+
}
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
const writtenKey = representationKey(written);
|
|
1124
|
+
const index = held.arms.findIndex((arm) => representationKey(arm.value) === writtenKey);
|
|
1125
|
+
if (index >= 0)
|
|
1126
|
+
return `${cppTypeOf(held)}::ofArm<${index}>(${text})`;
|
|
1127
|
+
// A join can nest: `string | number | boolean | null | undefined` derives as
|
|
1128
|
+
// an outer union over the two absence arms and an *inner* union over the
|
|
1129
|
+
// three value arms, so a written `bool` is an arm of an arm and reaches its
|
|
1130
|
+
// home in two wraps rather than one. Only a `tagged-union` arm is searched.
|
|
1131
|
+
// Every other widening this function performs is lossy about which value
|
|
1132
|
+
// arrived -- an `optional` answers the empty optional and a `native-handle`
|
|
1133
|
+
// the empty handle when the payload does not match -- and choosing one of
|
|
1134
|
+
// those from inside a union would silently store an absence where the
|
|
1135
|
+
// program wrote a value.
|
|
1136
|
+
for (const [armIndex, arm] of held.arms.entries()) {
|
|
1137
|
+
if (arm.value.kind === 'tagged-union') {
|
|
1138
|
+
const inner = widenedStoreText(arm.value, written, text);
|
|
1139
|
+
if (inner !== null)
|
|
1140
|
+
return `${cppTypeOf(held)}::ofArm<${armIndex}>(${inner})`;
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
// A CALLABLE reaches an arm the same way it reaches a plain slot: through
|
|
1144
|
+
// one of `gea::CallableObject`'s own implicit converting constructors,
|
|
1145
|
+
// which take the value as it stands and are what `convertedValueText`
|
|
1146
|
+
// already answers with for the identical pair when the target is not a
|
|
1147
|
+
// union. hono's `H = Handler | MiddlewareHandler` is the case -- a
|
|
1148
|
+
// middleware `(c, next) => Promise<void>` is assignable to one arm and the
|
|
1149
|
+
// exact-key search above cannot see it, because assignability here is not
|
|
1150
|
+
// carrier equality.
|
|
1151
|
+
//
|
|
1152
|
+
// Only these four, and only because each one renders as the value itself:
|
|
1153
|
+
// an arm chosen through a LOSSY widening would store something other than
|
|
1154
|
+
// what the program wrote, which is the same line the record recasts below
|
|
1155
|
+
// stay on.
|
|
1156
|
+
if (cppTypeOf(written) === cppTypeOf(arm.value) ||
|
|
1157
|
+
dropsUnboundParameters(written, arm.value) ||
|
|
1158
|
+
dropsAllParametersIntoResultArm(written, arm.value) ||
|
|
1159
|
+
widensResultIntoArm(written, arm.value) ||
|
|
1160
|
+
discardsResultIntoVoid(written, arm.value)) {
|
|
1161
|
+
return `${cppTypeOf(held)}::ofArm<${armIndex}>(${text})`;
|
|
1162
|
+
}
|
|
1163
|
+
if (written.kind === 'class-ref' &&
|
|
1164
|
+
arm.value.kind === 'class-ref' &&
|
|
1165
|
+
written.ownership === arm.value.ownership &&
|
|
1166
|
+
written.ancestors.includes(arm.value.declaration)) {
|
|
1167
|
+
return `${cppTypeOf(held)}::ofArm<${armIndex}>(${cppTypeOf(arm.value)}(${text}))`;
|
|
1168
|
+
}
|
|
1169
|
+
// The fifth pair is not an implicit constructor, so it wraps the text
|
|
1170
|
+
// rather than passing it through -- see `resultAdapterOf`. hono's `H =
|
|
1171
|
+
// Handler | MiddlewareHandler` reaches its arm exactly here: same frame,
|
|
1172
|
+
// a result the arm declares `any`.
|
|
1173
|
+
const adaptedArm = resultAdaptedCallableText(written, arm.value, text);
|
|
1174
|
+
if (adaptedArm !== null)
|
|
1175
|
+
return `${cppTypeOf(held)}::ofArm<${armIndex}>(${adaptedArm})`;
|
|
1176
|
+
// A record recasts into a dictionary/array-object ARM the same way
|
|
1177
|
+
// `convertedValueText`'s own top level recasts it directly -- identity above never matches an object literal against either.
|
|
1178
|
+
// A dictionary reaches a WIDER dictionary arm the same way (`dictionaryCastableToDictionary`);
|
|
1179
|
+
// `conversions.ts` admits only a union with ONE such arm, so the first match is the match.
|
|
1180
|
+
const recast = written.kind === 'record'
|
|
1181
|
+
? arm.value.kind === 'dictionary'
|
|
1182
|
+
? recastedRecordToDictionaryText(written, arm.value, text)
|
|
1183
|
+
: arm.value.kind === 'array-object'
|
|
1184
|
+
? recastedRecordToArrayText(written, arm.value, text)
|
|
1185
|
+
: arm.value.kind === 'record'
|
|
1186
|
+
? recastedRecordText(written, arm.value, text)
|
|
1187
|
+
: null
|
|
1188
|
+
: written.kind === 'dictionary' && arm.value.kind === 'dictionary'
|
|
1189
|
+
? recastedDictionaryText(written, arm.value, text)
|
|
1190
|
+
: null;
|
|
1191
|
+
if (recast !== null)
|
|
1192
|
+
return `${cppTypeOf(held)}::ofArm<${armIndex}>(${recast})`;
|
|
1193
|
+
}
|
|
1194
|
+
return null;
|
|
1195
|
+
};
|
|
1196
|
+
/**
|
|
1197
|
+
* Rebuilding a tagged union at a different arm ordering than the one it was
|
|
1198
|
+
* built with.
|
|
1199
|
+
*
|
|
1200
|
+
* Two declared unions with the same member set do not have to agree on tag
|
|
1201
|
+
* order -- `boolean | number` and `number | boolean` are two unrelated C++
|
|
1202
|
+
* template instantiations (`gea::TaggedUnion<bool, double>` vs
|
|
1203
|
+
* `gea::TaggedUnion<double, bool>`), which is exactly what a control-flow merge
|
|
1204
|
+
* produces when its two branches arrive through independently-declared unions.
|
|
1205
|
+
* Neither `narrowedLoadText` nor `widenedStoreText` covers it: the target is
|
|
1206
|
+
* not one arm of the source, and the source is not one arm of the target. What
|
|
1207
|
+
* it needs is the same discriminant `narrowedLoadText` reads, dispatched into
|
|
1208
|
+
* the same constructor `widenedStoreText` calls -- read the live arm by
|
|
1209
|
+
* whichever tag the source actually holds, and rebuild it at the target's tag
|
|
1210
|
+
* for that same value type. `null` when some source arm has no same-typed home
|
|
1211
|
+
* in the target: that is a real hole in the target's arm set, not a spelling
|
|
1212
|
+
* gap, and inventing a fallback there would silently drop a live value.
|
|
1213
|
+
*/
|
|
1214
|
+
/**
|
|
1215
|
+
* A sum converted ARM BY ARM into one payload, or `null` when an arm has no
|
|
1216
|
+
* way there.
|
|
1217
|
+
*
|
|
1218
|
+
* tsc's `visitEachChild<T>` returns `fn === undefined ? node : fn(node, ...)`
|
|
1219
|
+
* where `fn` is a `VisitEachChildFunction<any>`: the conditional merges `T`
|
|
1220
|
+
* with a declared `any` into `tagged-union(native-record-ref | dynamic)`, and
|
|
1221
|
+
* the function returns `T | undefined`. The typed arm IS the payload; the
|
|
1222
|
+
* dynamic arm is the checked unbox every read of a declared-`any` value into
|
|
1223
|
+
* a typed cell already performs (`unboxedLoadText`, which aborts naming the
|
|
1224
|
+
* carrier on a mismatch). So the load dispatches on the live arm, exactly as
|
|
1225
|
+
* `recastedUnionText` does for a sum whose home is another sum. Only a sum
|
|
1226
|
+
* WITH a dynamic arm is answered: without one, a same-keyed arm beside
|
|
1227
|
+
* others is a narrowing (`narrowedLoadText`), which selects an arm and
|
|
1228
|
+
* proves nothing about the rest, and this must not stand in for it.
|
|
1229
|
+
* `targets/cpp/conversions.ts`'s `recasting` admits the pair on the same
|
|
1230
|
+
* predicate -- through this function -- so the admission and the render
|
|
1231
|
+
* cannot drift.
|
|
1232
|
+
*/
|
|
1233
|
+
export const sumIntoPayloadText = (source, payload, text, wrap) => {
|
|
1234
|
+
if (payload.kind === 'dynamic' || payload.kind === 'optional' || payload.kind === 'tagged-union')
|
|
1235
|
+
return null;
|
|
1236
|
+
if (!source.arms.some((arm) => arm.value.kind === 'dynamic'))
|
|
1237
|
+
return null;
|
|
1238
|
+
const key = representationKey(payload);
|
|
1239
|
+
const homes = [];
|
|
1240
|
+
for (const [index, arm] of source.arms.entries()) {
|
|
1241
|
+
const armText = `${text}.get<${index}>()`;
|
|
1242
|
+
const loaded = representationKey(arm.value) === key ? armText : arm.value.kind === 'dynamic' ? unboxedLoadText(payload, armText) : null;
|
|
1243
|
+
if (loaded === null)
|
|
1244
|
+
return null;
|
|
1245
|
+
homes.push(wrap(loaded));
|
|
1246
|
+
}
|
|
1247
|
+
let result = homes[homes.length - 1] ?? null;
|
|
1248
|
+
if (result === null)
|
|
1249
|
+
return null;
|
|
1250
|
+
for (let index = homes.length - 2; index >= 0; index--)
|
|
1251
|
+
result = `${text}.is<${index}>() ? ${homes[index]} : (${result})`;
|
|
1252
|
+
return `(${result})`;
|
|
1253
|
+
};
|
|
1254
|
+
export const recastedUnionText = (source, target, text,
|
|
1255
|
+
// A third resolver for an arm pair this module cannot answer alone. A
|
|
1256
|
+
// `native-record-ref` names a layout instead of carrying one, so the field
|
|
1257
|
+
// lists a record recast needs are a fact of the EMITTER's context, not of
|
|
1258
|
+
// the representation -- `emit-callable.ts`'s `structuralRecordViewText` is
|
|
1259
|
+
// where both are in hand. `conversions.ts` already ADMITS that pair, so
|
|
1260
|
+
// without this the admission and the render disagree, which is the one
|
|
1261
|
+
// failure mode this compiler exists to remove: hono's `c.json()` passes
|
|
1262
|
+
// `ResponseOrInit<ContentfulStatusCode>` into `ResponseOrInit<StatusCode>`
|
|
1263
|
+
// -- two shape ids over identical field carriers -- and the arm had no home
|
|
1264
|
+
// here even though the conversion graph said it did.
|
|
1265
|
+
viaLayout) => {
|
|
1266
|
+
// A union with N arms otherwise interpolates BOTH the full target spelling
|
|
1267
|
+
// and the source expression at every arm -- and a nested inner-union arm
|
|
1268
|
+
// repeats that again inside every outer arm. The three.js app's `NativeUniformValue
|
|
1269
|
+
// | null | undefined` is a 3-arm outer union over a 32-arm inner one, and
|
|
1270
|
+
// one recast of it -- reached from several sibling call sites glued by the
|
|
1271
|
+
// caller's own `&&`/`||` -- emitted a single line over 1.29MB: 957
|
|
1272
|
+
// `::ofArm<` sites each re-spelling a ~1.3KB template type, because neither
|
|
1273
|
+
// the source nor the target type was ever bound to a short name. Binding
|
|
1274
|
+
// both once, in an IIFE, is `promiseSourceName`'s own reasoning applied
|
|
1275
|
+
// here: `text` is an arbitrary expression that must be evaluated once, and
|
|
1276
|
+
// the target's spelling is exactly as repeatable. Skipped for a single-arm
|
|
1277
|
+
// source -- there is only one spelling of `text` and one of the target type
|
|
1278
|
+
// either way, so the IIFE would add bytes without removing any.
|
|
1279
|
+
const multiArm = source.arms.length > 1;
|
|
1280
|
+
const boundSource = multiArm ? recastUnionSourceName : text;
|
|
1281
|
+
const homeOf = source.arms.map((arm, index) => {
|
|
1282
|
+
const armText = `${boundSource}.get<${index}>()`;
|
|
1283
|
+
const key = representationKey(arm.value);
|
|
1284
|
+
const exact = target.arms.findIndex((candidate) => representationKey(candidate.value) === key);
|
|
1285
|
+
if (exact >= 0)
|
|
1286
|
+
return { index: exact, text: armText };
|
|
1287
|
+
// An arm that is ITSELF a union homes as a whole, never candidate by
|
|
1288
|
+
// candidate. The candidate probe below asks `convertedValueText(arm,
|
|
1289
|
+
// candidate)`, and for a union arm against one of its own members that
|
|
1290
|
+
// question is answered by `narrowedLoadText` -- an UNCHECKED `.get<k>()`
|
|
1291
|
+
// that assumes the member is live. Taken as a home it reads whichever
|
|
1292
|
+
// member sits at that slot regardless of the inner discriminant: the
|
|
1293
|
+
// runtime `Request` constructor's `ArrayBuffer.isView(init.body)`, over
|
|
1294
|
+
// `undefined | null | (string | Uint8Array | ArrayBuffer | ... |
|
|
1295
|
+
// ReadableStream)` narrowed to the four arms the preceding guards left,
|
|
1296
|
+
// recast every inner arm as the `Uint8Array` one, answered `true` for a
|
|
1297
|
+
// `ReadableStream` body, and read `.buffer`/`.byteOffset` out of it.
|
|
1298
|
+
// Certified, compiled, RangeError. The inner union's own discriminant is
|
|
1299
|
+
// what decides the target arm, so the whole inner union narrows (or
|
|
1300
|
+
// recasts) into the whole target, one level down where that discriminant
|
|
1301
|
+
// is in scope -- `narrowedUnionSubsetText`'s collision rule, applied here.
|
|
1302
|
+
if (arm.value.kind === 'tagged-union') {
|
|
1303
|
+
const inner = arm.value;
|
|
1304
|
+
const whole = tryCandidateText(() => narrowedLoadText(inner, target, armText)) ?? recastedUnionText(inner, target, armText, viaLayout);
|
|
1305
|
+
return whole === null ? null : { index: -1, text: whole, whole: true };
|
|
1306
|
+
}
|
|
1307
|
+
const homes = [];
|
|
1308
|
+
for (const [candidateIndex, candidate] of target.arms.entries()) {
|
|
1309
|
+
// `convertedValueText` and not `widenedStoreText` alone: an arm can
|
|
1310
|
+
// reach its home by a recast rather than a widening -- two records that
|
|
1311
|
+
// declare the same fields under two interned shapes, which is what
|
|
1312
|
+
// hono's `Result<T>` is made of -- and only the superset tries all of
|
|
1313
|
+
// them.
|
|
1314
|
+
const stored = tryCandidateText(() => convertedValueText(arm.value, candidate.value, armText));
|
|
1315
|
+
const home = stored ?? viaLayout?.(arm.value, candidate.value, armText) ?? null;
|
|
1316
|
+
if (home !== null)
|
|
1317
|
+
homes.push({ index: candidateIndex, text: home });
|
|
1318
|
+
}
|
|
1319
|
+
return homes.length > 1 ? (undefinedFieldChoice(arm.value, target, homes, armText) ?? homes[0]) : (homes[0] ?? null);
|
|
1320
|
+
});
|
|
1321
|
+
if (homeOf.some((home) => home === null))
|
|
1322
|
+
return null;
|
|
1323
|
+
return recastedUnionFromHomes(source, target, text, homeOf);
|
|
1324
|
+
};
|
|
1325
|
+
/**
|
|
1326
|
+
* Two target arms a record arm converts into, told apart by a dynamic field.
|
|
1327
|
+
*
|
|
1328
|
+
* A record whose field is dynamic converts into every target arm whose field
|
|
1329
|
+
* a checked unbox can reach -- `ReadableStreamReadResult<unknown>`'s
|
|
1330
|
+
* `{ done, value }` into both `{ done: true, value: undefined }` and
|
|
1331
|
+
* `{ done: false, value: Uint8Array }` -- and taking the first one unboxed a
|
|
1332
|
+
* live chunk as `undefined`. Which arm holds is the field's own run-time tag.
|
|
1333
|
+
*/
|
|
1334
|
+
const undefinedFieldChoice = (source, target, homes, armText) => {
|
|
1335
|
+
if (source.kind !== 'record' || homes.length !== 2)
|
|
1336
|
+
return null;
|
|
1337
|
+
const arrow = source.ownership === 'shared-refcount' ? '->' : '.';
|
|
1338
|
+
for (const field of source.fields) {
|
|
1339
|
+
if (field.value.kind !== 'dynamic' || !field.required)
|
|
1340
|
+
continue;
|
|
1341
|
+
const fieldKinds = homes.map((home) => {
|
|
1342
|
+
const arm = target.arms[home.index]?.value;
|
|
1343
|
+
return arm?.kind === 'record' ? arm.fields.find((candidate) => candidate.key === field.key)?.value.kind : undefined;
|
|
1344
|
+
});
|
|
1345
|
+
const absent = fieldKinds.findIndex((kind) => kind === 'undefined');
|
|
1346
|
+
const present = fieldKinds.findIndex((kind) => kind !== undefined && kind !== 'undefined' && kind !== 'dynamic');
|
|
1347
|
+
if (absent < 0 || present < 0)
|
|
1348
|
+
continue;
|
|
1349
|
+
const type = cppTypeOf(target);
|
|
1350
|
+
const wrap = (home) => `${type}::ofArm<${home.index}>(${home.text})`;
|
|
1351
|
+
const test = `${armText}${arrow}${cppRecordFieldName(field.key)}.tag() == gea::Value::Tag::Undefined`;
|
|
1352
|
+
return { index: -1, whole: true, text: `(${test} ? ${wrap(homes[absent])} : ${wrap(homes[present])})` };
|
|
1353
|
+
}
|
|
1354
|
+
return null;
|
|
1355
|
+
};
|
|
1356
|
+
/** The text an arm of a multi-arm source is read through, bound by `recastedUnionFromHomes`; a single-arm source reads the value itself. */
|
|
1357
|
+
export const recastUnionArmText = (source, text, index) => `${source.arms.length > 1 ? recastUnionSourceName : text}.get<${index}>()`;
|
|
1358
|
+
/**
|
|
1359
|
+
* The dispatch over a sum's arms, given each arm's home in the target: the
|
|
1360
|
+
* discriminant read and the `ofArm` rebuild are one answer, whoever decided
|
|
1361
|
+
* the homes -- the chain probe above or a plan the census owns
|
|
1362
|
+
* (`conversion/record-view.ts`).
|
|
1363
|
+
*/
|
|
1364
|
+
export const recastedUnionFromHomes = (source, target, text, homes) => {
|
|
1365
|
+
const multiArm = source.arms.length > 1;
|
|
1366
|
+
const boundSource = multiArm ? recastUnionSourceName : text;
|
|
1367
|
+
const targetType = multiArm ? recastUnionAliasName : cppTypeOf(target);
|
|
1368
|
+
const last = homes.length - 1;
|
|
1369
|
+
const lastHome = homes[last];
|
|
1370
|
+
if (!lastHome)
|
|
1371
|
+
return null;
|
|
1372
|
+
const homeText = (home) => (home.whole ? home.text : `${targetType}::ofArm<${home.index}>(${home.text})`);
|
|
1373
|
+
let result = homeText(lastHome);
|
|
1374
|
+
for (let index = last - 1; index >= 0; index--) {
|
|
1375
|
+
const home = homes[index];
|
|
1376
|
+
if (!home)
|
|
1377
|
+
return null;
|
|
1378
|
+
result = `${boundSource}.is<${index}>() ? ${homeText(home)} : (${result})`;
|
|
1379
|
+
}
|
|
1380
|
+
if (!multiArm)
|
|
1381
|
+
return result;
|
|
1382
|
+
// Parameter, not body-local -- see the nested chain above for why a
|
|
1383
|
+
// same-named `const auto&` in the body reads itself.
|
|
1384
|
+
return (`([&](const ${cppTypeOf(source)}& ${recastUnionSourceName}) -> ${cppTypeOf(target)} { using ${recastUnionAliasName} = ${cppTypeOf(target)}; ` +
|
|
1385
|
+
`return ${result}; }(${text}))`);
|
|
1386
|
+
};
|
|
1387
|
+
/**
|
|
1388
|
+
* One record rebuilt as another whose declared shape it satisfies.
|
|
1389
|
+
*
|
|
1390
|
+
* `type A = Named & { age: number }` and `({ ...base, ...extra })` describe one
|
|
1391
|
+
* shape and intern as two -- an `intersection` and an `object` -- so they derive
|
|
1392
|
+
* two `record` carriers under two shape ids, which this backend spells as two
|
|
1393
|
+
* structs. A value of one is not a value of the other in C++ even though every
|
|
1394
|
+
* member matches, and `return ({ ...base, ...extra })` out of a function
|
|
1395
|
+
* declared to return the intersection is exactly that meeting.
|
|
1396
|
+
*
|
|
1397
|
+
* This is `recastedUnionText`'s structural twin, and it is written for the same
|
|
1398
|
+
* reason: neither a narrowing nor a widening covers a pair where each side is
|
|
1399
|
+
* the whole of the other, just carried under a different identity.
|
|
1400
|
+
*
|
|
1401
|
+
* The relation admitted is TypeScript's own structural one, not shape equality,
|
|
1402
|
+
* because that equality was never the question a return conversion asks.
|
|
1403
|
+
* `return this.options` out of a method declared to return a narrower options
|
|
1404
|
+
* type is legal TypeScript and lowers to exactly this pair, and mongodb's
|
|
1405
|
+
* driver is built out of it: every `CommandOptions` producer hands a record
|
|
1406
|
+
* carrying dozens of fields to a slot declaring an overlapping, differently
|
|
1407
|
+
* ordered, differently optional subset. So:
|
|
1408
|
+
*
|
|
1409
|
+
* - A source field the target does not declare is DROPPED. TypeScript already
|
|
1410
|
+
* erased it at the boundary -- the declared type is what any later read goes
|
|
1411
|
+
* through -- so the rebuild loses nothing the program could still see. (An
|
|
1412
|
+
* `any`-typed re-read is not a counter-example: it reads the target carrier,
|
|
1413
|
+
* which is what the declaration says the value is.)
|
|
1414
|
+
* - A target field the source lacks is filled with the carrier's own absent
|
|
1415
|
+
* value, and only when the carrier HAS one -- `gea::Optional<T>{}` and
|
|
1416
|
+
* `gea::Undefined{}`. A missing field whose carrier is a bare `T` is a real
|
|
1417
|
+
* hole and still refuses.
|
|
1418
|
+
* - Requiredness may WIDEN (source required, target optional) and never
|
|
1419
|
+
* narrow. There is no physical presence bit -- `records.ts` stores exactly
|
|
1420
|
+
* `cppTypeOf(field.value)` -- so `in` answers from `staticKeyPresenceOf`'s
|
|
1421
|
+
* reading of the field list, which proves presence from `required`. Going
|
|
1422
|
+
* the other way would turn a runtime presence flag into a constant `true`:
|
|
1423
|
+
* a silent wrong answer, so it stays refused.
|
|
1424
|
+
*
|
|
1425
|
+
* A field's own carrier is matched by key, by being a bare `T` poured into the
|
|
1426
|
+
* `Optional<T>` the target declares (`gea::Optional`'s converting constructor
|
|
1427
|
+
* performs it, and two optionals over one payload are one C++ type since
|
|
1428
|
+
* `cppTypeOf` never reads `.absence`), or by being the same recast one level
|
|
1429
|
+
* down. The nominal-identity split this exists for does not stop at the top:
|
|
1430
|
+
* hono's pattern router hands `[Pattern, string, [Handler, ParamIndexMap]]` to
|
|
1431
|
+
* a `Route<T>` slot whose innermost tuple interned under a second shape id with
|
|
1432
|
+
* a byte-identical field list, so the OUTER pair matched everywhere but there.
|
|
1433
|
+
* Refusing would refuse the identical question one level in.
|
|
1434
|
+
*
|
|
1435
|
+
* A lambda, not a repeated expression: `text` is evaluated once however many
|
|
1436
|
+
* fields the record has, which a comma-separated aggregate would not guarantee
|
|
1437
|
+
* for a source that is a call. `depth` names the parameter, so a nested rebuild
|
|
1438
|
+
* does not shadow its parent's.
|
|
1439
|
+
*/
|
|
1440
|
+
const absentFieldTexts = new WeakMap();
|
|
1441
|
+
const absentFieldText = (value) => {
|
|
1442
|
+
// Remembered per carrier: an unspellable carrier answers by CATCHING a
|
|
1443
|
+
// thrown refusal, and the recast admission below asks about the same
|
|
1444
|
+
// absent target field once per candidate source -- thousands of throws
|
|
1445
|
+
// for one field on TypeScript's own compiler.
|
|
1446
|
+
const remembered = absentFieldTexts.get(value);
|
|
1447
|
+
if (remembered !== undefined)
|
|
1448
|
+
return remembered;
|
|
1449
|
+
const built = absentFieldTextOf(value);
|
|
1450
|
+
absentFieldTexts.set(value, built);
|
|
1451
|
+
return built;
|
|
1452
|
+
};
|
|
1453
|
+
const absentFieldTextOf = (value) => {
|
|
1454
|
+
// A carrier the one type mapping cannot spell has no default it could be
|
|
1455
|
+
// absent AS, so the field is not recastable -- a refusal, not a throw. This
|
|
1456
|
+
// is asked from the conversion registry's ADMISSION (`conversions.ts`'s
|
|
1457
|
+
// `recasting`), whose own `cppTypeOf(target)` guard spells the record by
|
|
1458
|
+
// shape id and never looks inside it: TypeScript's own compiler put an
|
|
1459
|
+
// `optional(function(...))` whose ABI still carried an unmonomorphized type
|
|
1460
|
+
// parameter in a field here, and the throw took the whole compile down
|
|
1461
|
+
// instead of costing this one pair. Same idiom as `manifest.ts`'s
|
|
1462
|
+
// `isSpellable`: the mapping's throw IS the answer, not a second predicate.
|
|
1463
|
+
const spelled = spelledOrNull(value);
|
|
1464
|
+
if (spelled === null)
|
|
1465
|
+
return null;
|
|
1466
|
+
if (value.kind === 'optional' || value.kind === 'undefined')
|
|
1467
|
+
return `${spelled}{}`;
|
|
1468
|
+
// A property that is not there reads back as `undefined`, and a
|
|
1469
|
+
// default-constructed box is exactly that -- `gea::Value`'s own doc says so
|
|
1470
|
+
// ("a hoisted cell holds *some* value from the moment it exists").
|
|
1471
|
+
if (value.kind === 'dynamic')
|
|
1472
|
+
return `${spelled}{}`;
|
|
1473
|
+
// A union declaring an `undefined` arm can state absence too, but its
|
|
1474
|
+
// default constructor builds ARM 0 whichever arm that is, so the arm is
|
|
1475
|
+
// named rather than assumed.
|
|
1476
|
+
if (value.kind === 'tagged-union') {
|
|
1477
|
+
const index = value.arms.findIndex((arm) => arm.value.kind === 'undefined');
|
|
1478
|
+
const arm = value.arms[index];
|
|
1479
|
+
if (arm !== undefined)
|
|
1480
|
+
return `${spelled}::ofArm<${index}>(${cppTypeOf(arm.value)}{})`;
|
|
1481
|
+
}
|
|
1482
|
+
return null;
|
|
1483
|
+
};
|
|
1484
|
+
const spelledOrNull = (value) => {
|
|
1485
|
+
try {
|
|
1486
|
+
return cppTypeOf(value);
|
|
1487
|
+
}
|
|
1488
|
+
catch {
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
/**
|
|
1493
|
+
* The arm of `union` that carries exactly `from`, or `null` when none or
|
|
1494
|
+
* several do.
|
|
1495
|
+
*
|
|
1496
|
+
* Several is a refusal rather than a pick: `A | B` where both lower to one C++
|
|
1497
|
+
* type has two right answers and the discriminant records which, so choosing
|
|
1498
|
+
* one here would decide a question the source value does not answer.
|
|
1499
|
+
*/
|
|
1500
|
+
const soleArmIndexFor = (union, from) => {
|
|
1501
|
+
const key = representationKey(from);
|
|
1502
|
+
const matches = union.arms.flatMap((arm, index) => (representationKey(arm.value) === key ? [index] : []));
|
|
1503
|
+
return matches.length === 1 ? (matches[0] ?? null) : null;
|
|
1504
|
+
};
|
|
1505
|
+
/** Two array carriers of one element and ownership whose only difference is the extension an interface adds -- one C++ type. */
|
|
1506
|
+
export const sameArrayUpToExtension = (left, right) => left.kind === 'array-object' &&
|
|
1507
|
+
right.kind === 'array-object' &&
|
|
1508
|
+
left.ownership === right.ownership &&
|
|
1509
|
+
representationKey(left.element) === representationKey(right.element) &&
|
|
1510
|
+
arrayExtensionKey(left.extension) !== arrayExtensionKey(right.extension);
|
|
1511
|
+
const recastableFieldValue = (from, to, seen) => {
|
|
1512
|
+
if (representationKey(from) === representationKey(to))
|
|
1513
|
+
return true;
|
|
1514
|
+
if (to.kind === 'optional') {
|
|
1515
|
+
const payload = representationKey(to.payload);
|
|
1516
|
+
if (payload === representationKey(from))
|
|
1517
|
+
return true;
|
|
1518
|
+
if (from.kind === 'optional' && payload === representationKey(from.payload))
|
|
1519
|
+
return true;
|
|
1520
|
+
// An `undefined` source field IS the optional's own absent value: the copy
|
|
1521
|
+
// `EvaluatorResult<undefined>` returned into a slot declared as the
|
|
1522
|
+
// default `EvaluatorResult` writes `value: undefined` into `value?: string
|
|
1523
|
+
// | number`. There is no payload to convert; `recastFieldText` renders the
|
|
1524
|
+
// empty optional.
|
|
1525
|
+
if (from.kind === 'undefined')
|
|
1526
|
+
return true;
|
|
1527
|
+
const fromPayload = from.kind === 'optional' ? from.payload : from;
|
|
1528
|
+
// A payload that is one ARM of the optional's tagged-union payload is the
|
|
1529
|
+
// same admission the bare tagged-union branch below makes, one presence
|
|
1530
|
+
// flag deeper -- and the same render, `recastFieldText`'s optional branch
|
|
1531
|
+
// already descends into its payload before spelling the arm. Without it
|
|
1532
|
+
// tsc's `return evaluatorResult(0)` -- the copy `EvaluatorResult<number>`
|
|
1533
|
+
// with `value: number`, returned where `EvaluatorResult` (default `T`,
|
|
1534
|
+
// `value: string | number | undefined`) is declared -- had the renderer
|
|
1535
|
+
// but not the admission: 19 rows across `checker.ts` and `utilities.ts`.
|
|
1536
|
+
if (to.payload.kind === 'tagged-union' && soleArmIndexFor(to.payload, fromPayload) !== null)
|
|
1537
|
+
return true;
|
|
1538
|
+
// A structural conversion does not stop at a presence flag. A required
|
|
1539
|
+
// source record can fill an optional target record of another interned
|
|
1540
|
+
// shape, and an optional source does the same conversion only on its live
|
|
1541
|
+
// payload. ColorManagement's two concrete color-space records flowing
|
|
1542
|
+
// into its common dictionary value are this exact shape.
|
|
1543
|
+
return recordsRecastableIn(fromPayload, to.payload, seen);
|
|
1544
|
+
}
|
|
1545
|
+
if (to.kind === 'tagged-union' && soleArmIndexFor(to, from) !== null)
|
|
1546
|
+
return true;
|
|
1547
|
+
// A field declared `unknown`/`any` holds any value boxed: the chunk of a
|
|
1548
|
+
// `ReadableStreamReadResult<Uint8Array>` read as `ReadableStreamReadResult<unknown>`.
|
|
1549
|
+
if (to.kind === 'dynamic')
|
|
1550
|
+
return tryCandidateText(() => dynamicCarrierBoxText(from, 'gea_probe')) !== null;
|
|
1551
|
+
// A callable field whose convention differs from the declared one's is
|
|
1552
|
+
// copied through the same adapter a direct store of it takes.
|
|
1553
|
+
if (from.kind === 'function-value-dispatch' && to.kind === 'function-value-dispatch')
|
|
1554
|
+
return resultAdapterOf(from, to) !== null;
|
|
1555
|
+
// Containers copy element by element (`array-copy-recast`,
|
|
1556
|
+
// `dictionary-to-dictionary`), so a field holding one recasts when its
|
|
1557
|
+
// elements convert.
|
|
1558
|
+
// A dynamic field is read out of its box the way any assertion reads one.
|
|
1559
|
+
if ((from.kind === 'array-object' && to.kind === 'array-object') ||
|
|
1560
|
+
(from.kind === 'dictionary' && to.kind === 'dictionary') ||
|
|
1561
|
+
from.kind === 'dynamic')
|
|
1562
|
+
return (tryCandidateText(() => convertedValueText(from, to, 'gea_field')) !== null || emptyContainerRecastText(from, to, 'gea_field') !== null);
|
|
1563
|
+
return recordsRecastableIn(from, to, seen);
|
|
1564
|
+
};
|
|
1565
|
+
/** A record's fields by key, built once per carrier object. */
|
|
1566
|
+
const fieldIndexes = new WeakMap();
|
|
1567
|
+
const fieldIndexOf = (record) => {
|
|
1568
|
+
const remembered = fieldIndexes.get(record);
|
|
1569
|
+
if (remembered !== undefined)
|
|
1570
|
+
return remembered;
|
|
1571
|
+
const built = new Map();
|
|
1572
|
+
for (const field of record.fields)
|
|
1573
|
+
built.set(field.key, field);
|
|
1574
|
+
fieldIndexes.set(record, built);
|
|
1575
|
+
return built;
|
|
1576
|
+
};
|
|
1577
|
+
/** The top-level pairs whose answer is being computed. */
|
|
1578
|
+
const recordRecastPending = new WeakMap();
|
|
1579
|
+
/** Top-level `recordsRecastable` answers, per (source, target) carrier pair. */
|
|
1580
|
+
const recordRecastAnswers = new WeakMap();
|
|
1581
|
+
/**
|
|
1582
|
+
* `seen` is the coinductive cycle guard: `null` at the top level, and the set
|
|
1583
|
+
* of every pair on the current descent once a field's own carrier makes the
|
|
1584
|
+
* walk recurse. A record with a field that cites the record again would recur
|
|
1585
|
+
* forever; the pair is admitted on the assumption under test, which can only
|
|
1586
|
+
* ever admit a cycle, never a pair that fails for a reason the walk would
|
|
1587
|
+
* otherwise have reached.
|
|
1588
|
+
*
|
|
1589
|
+
* The guard's key and set exist only once a descent happens. The conversion
|
|
1590
|
+
* registry asks this about every ordered pair of record carriers a program
|
|
1591
|
+
* has (`conversion/build.ts`), and TypeScript's own compiler has enough of
|
|
1592
|
+
* them, with enough fields each, that a key string, a fresh set and a linear
|
|
1593
|
+
* field scan per target field, per pair, WAS the representations stage (the
|
|
1594
|
+
* main thread sat at 100% for an hour past the previous run's total). Fields
|
|
1595
|
+
* are matched through a per-record index, and a top-level answer is
|
|
1596
|
+
* remembered per pair, which `recastedRecordText` reads back for the pairs
|
|
1597
|
+
* the graph kept.
|
|
1598
|
+
*/
|
|
1599
|
+
const recordsRecastableIn = (source, target, seen) => {
|
|
1600
|
+
if (source.kind !== 'record' || target.kind !== 'record')
|
|
1601
|
+
return false;
|
|
1602
|
+
if (source.accessors.length > 0 || target.accessors.length > 0)
|
|
1603
|
+
return false;
|
|
1604
|
+
if (seen !== null) {
|
|
1605
|
+
const pairKey = `${representationKey(source)}->${representationKey(target)}`;
|
|
1606
|
+
if (seen.has(pairKey))
|
|
1607
|
+
return true;
|
|
1608
|
+
seen.add(pairKey);
|
|
1609
|
+
return recordFieldsRecastable(source, target, seen);
|
|
1610
|
+
}
|
|
1611
|
+
const remembered = recordRecastAnswers.get(source)?.get(target);
|
|
1612
|
+
if (remembered !== undefined)
|
|
1613
|
+
return remembered;
|
|
1614
|
+
// A container field re-enters here through `convertedValueText`, outside
|
|
1615
|
+
// the `seen` descent; the pair already under test is admitted on the same
|
|
1616
|
+
// coinductive assumption.
|
|
1617
|
+
const pending = recordRecastPending.get(source);
|
|
1618
|
+
if (pending?.has(target))
|
|
1619
|
+
return true;
|
|
1620
|
+
if (pending === undefined)
|
|
1621
|
+
recordRecastPending.set(source, new WeakSet([target]));
|
|
1622
|
+
else
|
|
1623
|
+
pending.add(target);
|
|
1624
|
+
let answer;
|
|
1625
|
+
try {
|
|
1626
|
+
answer = recordFieldsRecastable(source, target, null);
|
|
1627
|
+
}
|
|
1628
|
+
finally {
|
|
1629
|
+
recordRecastPending.get(source)?.delete(target);
|
|
1630
|
+
}
|
|
1631
|
+
let byTarget = recordRecastAnswers.get(source);
|
|
1632
|
+
if (byTarget === undefined) {
|
|
1633
|
+
byTarget = new WeakMap();
|
|
1634
|
+
recordRecastAnswers.set(source, byTarget);
|
|
1635
|
+
}
|
|
1636
|
+
byTarget.set(target, answer);
|
|
1637
|
+
return answer;
|
|
1638
|
+
};
|
|
1639
|
+
const recordFieldsRecastable = (source, target, seen) => {
|
|
1640
|
+
const sourceFields = fieldIndexOf(source);
|
|
1641
|
+
let descent = seen;
|
|
1642
|
+
for (const field of target.fields) {
|
|
1643
|
+
const match = sourceFields.get(field.key);
|
|
1644
|
+
if (match === undefined) {
|
|
1645
|
+
if (field.required || absentFieldText(field.value) === null)
|
|
1646
|
+
return false;
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
if (!match.required && field.required)
|
|
1650
|
+
return false;
|
|
1651
|
+
if (match.value === field.value || representationKey(match.value) === representationKey(field.value))
|
|
1652
|
+
continue;
|
|
1653
|
+
// The first descent starts the guard with this pair on it, exactly as the
|
|
1654
|
+
// eager form recorded the pair before walking any field.
|
|
1655
|
+
descent ??= new Set([`${representationKey(source)}->${representationKey(target)}`]);
|
|
1656
|
+
if (!recastableFieldValue(match.value, field.value, descent))
|
|
1657
|
+
return false;
|
|
1658
|
+
}
|
|
1659
|
+
return true;
|
|
1660
|
+
};
|
|
1661
|
+
export const recordsRecastable = (source, target) => recordsRecastableIn(source, target, null);
|
|
1662
|
+
/**
|
|
1663
|
+
* Whether an admitted record recast moves any field across the dynamic
|
|
1664
|
+
* boundary: boxes a native field into a `dynamic` destination (the
|
|
1665
|
+
* `to.kind === 'dynamic'` admission of `recastableFieldValue`) or unboxes a
|
|
1666
|
+
* `dynamic` source field into a native one -- through an optional's payload,
|
|
1667
|
+
* a nested record, or a container's element.
|
|
1668
|
+
*
|
|
1669
|
+
* `targets/cpp/conversions.ts` states `nativeFieldProtocol: 'unused'` for a
|
|
1670
|
+
* record recast, which `ir/reflection-demand.ts` reads as "this copy asks no
|
|
1671
|
+
* dynamic field table, so the fields it copies keep their sealed keys-only
|
|
1672
|
+
* protocol". That is true of a recast that only copies native storage. A
|
|
1673
|
+
* recast that boxes a field hands the boxed payload to every later dynamic
|
|
1674
|
+
* read, and those reads DO go through the payload's field protocol -- so the
|
|
1675
|
+
* claim must not be made for it, or the boxed child is emitted without the
|
|
1676
|
+
* protocol the box needs (`reflection-demand.test.ts`, "a boxed destination
|
|
1677
|
+
* still exposes its payload").
|
|
1678
|
+
*/
|
|
1679
|
+
export const recordRecastCrossesDynamic = (source, target) => recastCrossesDynamicIn(source, target, new Set());
|
|
1680
|
+
const recastCrossesDynamicIn = (source, target, seen) => {
|
|
1681
|
+
if (source.kind !== 'record' || target.kind !== 'record')
|
|
1682
|
+
return false;
|
|
1683
|
+
const pairKey = `${representationKey(source)}->${representationKey(target)}`;
|
|
1684
|
+
if (seen.has(pairKey))
|
|
1685
|
+
return false;
|
|
1686
|
+
seen.add(pairKey);
|
|
1687
|
+
for (const field of target.fields) {
|
|
1688
|
+
const match = source.fields.find((candidate) => candidate.key === field.key);
|
|
1689
|
+
if (match !== undefined && recastFieldCrossesDynamic(match.value, field.value, seen))
|
|
1690
|
+
return true;
|
|
1691
|
+
}
|
|
1692
|
+
return false;
|
|
1693
|
+
};
|
|
1694
|
+
const recastFieldCrossesDynamic = (from, to, seen) => {
|
|
1695
|
+
const source = from.kind === 'optional' ? from.payload : from;
|
|
1696
|
+
const target = to.kind === 'optional' ? to.payload : to;
|
|
1697
|
+
if (representationKey(source) === representationKey(target))
|
|
1698
|
+
return false;
|
|
1699
|
+
if ((source.kind === 'dynamic') !== (target.kind === 'dynamic'))
|
|
1700
|
+
return true;
|
|
1701
|
+
if (source.kind === 'array-object' && target.kind === 'array-object')
|
|
1702
|
+
return recastFieldCrossesDynamic(source.element, target.element, seen);
|
|
1703
|
+
if (source.kind === 'dictionary' && target.kind === 'dictionary')
|
|
1704
|
+
return recastFieldCrossesDynamic(source.value, target.value, seen);
|
|
1705
|
+
return recastCrossesDynamicIn(source, target, seen);
|
|
1706
|
+
};
|
|
1707
|
+
const recastedRecordText = (source, target, text, depth = 0) => {
|
|
1708
|
+
if (!recordsRecastable(source, target))
|
|
1709
|
+
return null;
|
|
1710
|
+
const arrow = source.ownership === 'shared-refcount' ? '->' : '.';
|
|
1711
|
+
const holder = `gea_from${depth === 0 ? '' : `_${depth}`}`;
|
|
1712
|
+
const recastFieldText = (from, to, read, nestedDepth) => {
|
|
1713
|
+
if (representationKey(from) === representationKey(to)) {
|
|
1714
|
+
const arithmetic = to.kind === 'scalar' && to.domain !== 'bigint';
|
|
1715
|
+
return arithmetic ? `static_cast<${cppTypeOf(to)}>(${read})` : read;
|
|
1716
|
+
}
|
|
1717
|
+
if (to.kind === 'optional') {
|
|
1718
|
+
// The absent value, admitted by `recastableFieldValue` for an
|
|
1719
|
+
// `undefined` source; the source field is storage-free and is not read.
|
|
1720
|
+
if (from.kind === 'undefined')
|
|
1721
|
+
return `${cppTypeOf(to)}()`;
|
|
1722
|
+
const fromPayload = from.kind === 'optional' ? from.payload : from;
|
|
1723
|
+
const payloadRead = from.kind === 'optional' ? `(*${read})` : read;
|
|
1724
|
+
const converted = recastFieldText(fromPayload, to.payload, payloadRead, nestedDepth);
|
|
1725
|
+
if (converted === null)
|
|
1726
|
+
return null;
|
|
1727
|
+
const targetType = cppTypeOf(to);
|
|
1728
|
+
return from.kind === 'optional'
|
|
1729
|
+
? `(${read}.has_value() ? ${targetType}(${converted}) : ${targetType}())`
|
|
1730
|
+
: `${targetType}(${converted})`;
|
|
1731
|
+
}
|
|
1732
|
+
if (to.kind === 'tagged-union') {
|
|
1733
|
+
const index = soleArmIndexFor(to, from);
|
|
1734
|
+
return index === null ? null : `${cppTypeOf(to)}::ofArm<${index}>(${read})`;
|
|
1735
|
+
}
|
|
1736
|
+
if (to.kind === 'dynamic')
|
|
1737
|
+
return tryCandidateText(() => dynamicCarrierBoxText(from, read));
|
|
1738
|
+
if (from.kind === 'function-value-dispatch' && to.kind === 'function-value-dispatch')
|
|
1739
|
+
return resultAdaptedCallableText(from, to, read);
|
|
1740
|
+
if ((from.kind === 'array-object' && to.kind === 'array-object') ||
|
|
1741
|
+
(from.kind === 'dictionary' && to.kind === 'dictionary') ||
|
|
1742
|
+
from.kind === 'dynamic')
|
|
1743
|
+
return tryCandidateText(() => convertedValueText(from, to, read)) ?? emptyContainerRecastText(from, to, read);
|
|
1744
|
+
if (from.kind !== 'record' || to.kind !== 'record')
|
|
1745
|
+
return null;
|
|
1746
|
+
return recastedRecordText(from, to, read, nestedDepth + 1);
|
|
1747
|
+
};
|
|
1748
|
+
const reads = target.fields.map((field) => {
|
|
1749
|
+
const read = `${holder}${arrow}${cppRecordFieldName(field.key)}`;
|
|
1750
|
+
const match = source.fields.find((candidate) => candidate.key === field.key);
|
|
1751
|
+
// A field the target declares and the source does not: the carrier's own
|
|
1752
|
+
// absent value, which `recordsRecastable` already proved exists.
|
|
1753
|
+
if (match === undefined)
|
|
1754
|
+
return absentFieldText(field.value);
|
|
1755
|
+
// Inside braces [dcl.init.list]/7 forbids a narrowing every other argument
|
|
1756
|
+
// position allows: a field the integer census holds in a `long long` is a
|
|
1757
|
+
// hard error written into a `double` member, though both carriers say
|
|
1758
|
+
// `scalar(number)`. `static_cast` spells that same conversion explicitly --
|
|
1759
|
+
// `emit-arrays.ts`'s `packElementText` answers the identical rule this way,
|
|
1760
|
+
// `bigint` excluded on its terms too, and it is a no-op where both agree.
|
|
1761
|
+
const converted = recastFieldText(match.value, field.value, read, depth);
|
|
1762
|
+
if (converted === null || field.value.kind !== 'scalar' || field.value.domain === 'bigint')
|
|
1763
|
+
return converted;
|
|
1764
|
+
// `cppTypeOf` answers what the CARRIER is; the struct answers what the
|
|
1765
|
+
// MEMBER is, and the integer-storage census makes those two disagree --
|
|
1766
|
+
// a `scalar(number)` field the census proved integral is declared
|
|
1767
|
+
// `long long`. Inside braces that disagreement is a hard error
|
|
1768
|
+
// ([dcl.init.list]/7), not a silent conversion. Naming the member's own
|
|
1769
|
+
// declared type is the only spelling that cannot drift from it; the
|
|
1770
|
+
// census is not reachable from here and a second opinion about which
|
|
1771
|
+
// fields it narrowed is exactly the drift that keeps producing defects.
|
|
1772
|
+
return `static_cast<decltype(${cppRecordStructName(target.shapeId)}::${cppRecordFieldName(field.key)})>(${converted})`;
|
|
1773
|
+
});
|
|
1774
|
+
if (reads.some((read) => read === null))
|
|
1775
|
+
return null;
|
|
1776
|
+
const presences = target.fields.flatMap((field) => {
|
|
1777
|
+
if (field.required)
|
|
1778
|
+
return [];
|
|
1779
|
+
const match = source.fields.find((candidate) => candidate.key === field.key);
|
|
1780
|
+
if (match === undefined)
|
|
1781
|
+
return ['false'];
|
|
1782
|
+
return [match.required ? 'true' : `${holder}${arrow}${cppRecordFieldPresenceName(field.key)}`];
|
|
1783
|
+
});
|
|
1784
|
+
const structure = `${cppRecordStructName(target.shapeId)}{${[...reads, ...presences].join(', ')}}`;
|
|
1785
|
+
const built = target.ownership === 'shared-refcount' ? `gea::makeRef<${cppRecordStructName(target.shapeId)}>(${structure})` : structure;
|
|
1786
|
+
return `[](const ${cppTypeOf(source)}& ${holder}) { return ${built}; }(${text})`;
|
|
1787
|
+
};
|
|
1788
|
+
/**
|
|
1789
|
+
* A record recast into a dictionary, one level out from `recordsRecastable`
|
|
1790
|
+
* above.
|
|
1791
|
+
*
|
|
1792
|
+
* A `record`'s field set is CLOSED and known at compile time; a `dictionary`'s
|
|
1793
|
+
* is open and string/number-keyed. Going from the closed shape to the open one
|
|
1794
|
+
* loses no information -- every field the record has, the dictionary can hold
|
|
1795
|
+
* -- so the direction is always sound, unlike the reverse (`derive.ts`'s own
|
|
1796
|
+
* `dictionary` case: "Product needs a complete frozen field list", never
|
|
1797
|
+
* derivable from an open one). Concretely this is `return {}` reaching a
|
|
1798
|
+
* function whose declared result is `Record<string, V>`: TypeScript types the
|
|
1799
|
+
* empty literal as the record with zero fields it is, not as the dictionary
|
|
1800
|
+
* its ABI slot wants, and the two carriers stay genuinely different even
|
|
1801
|
+
* though the empty case rebuilds to zero inserts.
|
|
1802
|
+
*
|
|
1803
|
+
* Every field's own value must have an installed conversion into the
|
|
1804
|
+
* dictionary's declared value carrier. This is the same store each field
|
|
1805
|
+
* would perform if the source literal had been contextualized as the index
|
|
1806
|
+
* signature in the first place: a scalar into a declared `any` slot boxes at
|
|
1807
|
+
* that explicit dynamic boundary, and a typed slot keeps its ordinary native
|
|
1808
|
+
* conversion. A field with no such conversion refuses the whole recast before
|
|
1809
|
+
* any output is rendered.
|
|
1810
|
+
*/
|
|
1811
|
+
export const recordCastableToDictionary = (source, target) => {
|
|
1812
|
+
if (source.accessors.length > 0)
|
|
1813
|
+
return false;
|
|
1814
|
+
// A record field names a compile-time key. A symbol-indexed dictionary is
|
|
1815
|
+
// keyed by the runtime Symbol value, and a field's `sym(<declaration>)`
|
|
1816
|
+
// identity cannot manufacture that value at this conversion site. Admitting
|
|
1817
|
+
// the cast would either stringify the symbol (wrong identity) or box it.
|
|
1818
|
+
if (target.key === 'symbol')
|
|
1819
|
+
return source.fields.length === 0;
|
|
1820
|
+
if (target.key === 'number' && source.fields.some((field) => !isCanonicalNumberPropertyKeyText(field.key)))
|
|
1821
|
+
return false;
|
|
1822
|
+
if (containsUnresolved(source) || containsUnresolved(target))
|
|
1823
|
+
return false;
|
|
1824
|
+
return source.fields.every((field) => {
|
|
1825
|
+
try {
|
|
1826
|
+
return convertedValueText(field.value, target.value, 'gea_record_field') !== null;
|
|
1827
|
+
}
|
|
1828
|
+
catch (error) {
|
|
1829
|
+
// This function is queried while the conversion graph is being built.
|
|
1830
|
+
// A nested narrowing recipe can reject its own source/target pair by
|
|
1831
|
+
// throwing the backend's named refusal; in this predicate that means the
|
|
1832
|
+
// field is not convertible, not that graph construction itself failed.
|
|
1833
|
+
// Unexpected errors still escape so internal defects remain visible.
|
|
1834
|
+
if (isCppEmitBlockedError(error))
|
|
1835
|
+
return false;
|
|
1836
|
+
throw error;
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
};
|
|
1840
|
+
const recastedRecordToDictionaryText = (source, target, text) => {
|
|
1841
|
+
if (!recordCastableToDictionary(source, target))
|
|
1842
|
+
return null;
|
|
1843
|
+
const fieldArrow = source.ownership === 'shared-refcount' ? '->' : '.';
|
|
1844
|
+
const dictArrow = target.ownership === 'shared-refcount' ? '->' : '.';
|
|
1845
|
+
const container = target.key === 'number' ? 'gea::NumericDictionary' : target.key === 'symbol' ? 'gea::SymbolDictionary' : 'gea::Dictionary';
|
|
1846
|
+
const storage = `${container}<${cppTypeOf(target.value)}>`;
|
|
1847
|
+
const inserts = source.fields.map((field) => {
|
|
1848
|
+
if (target.key === 'symbol')
|
|
1849
|
+
return null;
|
|
1850
|
+
const key = cppStringLiteral(field.key);
|
|
1851
|
+
const read = `gea_from${fieldArrow}${cppRecordFieldName(field.key)}`;
|
|
1852
|
+
const converted = convertedValueText(field.value, target.value, read);
|
|
1853
|
+
if (converted === null)
|
|
1854
|
+
return null;
|
|
1855
|
+
return `gea_dict${dictArrow}operator[](${key}) = ${converted};`;
|
|
1856
|
+
});
|
|
1857
|
+
if (inserts.some((insert) => insert === null))
|
|
1858
|
+
return null;
|
|
1859
|
+
const statements = inserts.join(' ');
|
|
1860
|
+
const construct = target.ownership === 'shared-refcount' ? `auto gea_dict = gea::makeRef<${storage}>();` : `${storage} gea_dict{};`;
|
|
1861
|
+
return `[](const ${cppTypeOf(source)}& gea_from) { ${construct} ${statements} return gea_dict; }(${text})`;
|
|
1862
|
+
};
|
|
1863
|
+
/**
|
|
1864
|
+
* An open dictionary recast into another open dictionary over the same key
|
|
1865
|
+
* domain whose VALUES it widens into -- `Record<string, string>` reaching a
|
|
1866
|
+
* slot declared `OutgoingHttpHeaders` (`{ [name: string]: string | number |
|
|
1867
|
+
* boolean | readonly string[] | undefined }`). TypeScript's `||` typing
|
|
1868
|
+
* subtype-reduces the two into the wider one, so `@hono/node-server`'s `get
|
|
1869
|
+
* headers()` -- `cache[2] || { 'content-type': defaultContentType }` where
|
|
1870
|
+
* the slot is `Record<string, string> | [string, string][] |
|
|
1871
|
+
* OutgoingHttpHeaders | undefined` -- publishes ONLY the wider dictionary as
|
|
1872
|
+
* the merge's arm, and both the literal and the slot's own string-dictionary
|
|
1873
|
+
* arm have to reach it. Nothing between the two carriers is a view: a
|
|
1874
|
+
* `gea::Dictionary<std::string>` and a `gea::Dictionary<gea::Optional<...>>`
|
|
1875
|
+
* are two template instantiations.
|
|
1876
|
+
*
|
|
1877
|
+
* One level out from `recordCastableToDictionary` above and admitted on the
|
|
1878
|
+
* same terms: every value the source holds converts into the target's value
|
|
1879
|
+
* carrier by the store it would have performed had the entry been written
|
|
1880
|
+
* against the wider index signature in the first place, and the rebuild is a
|
|
1881
|
+
* fresh table -- the same fresh table the record pour builds, with the same
|
|
1882
|
+
* consequence, that a program which keeps writing the SOURCE afterwards and
|
|
1883
|
+
* reads the widened copy does not see the write. The record pour already
|
|
1884
|
+
* stands on that ground for the identical pair one shape narrower; this does
|
|
1885
|
+
* not extend it further.
|
|
1886
|
+
*
|
|
1887
|
+
* String keys only. `gea::NumericDictionary` and `gea::SymbolDictionary` are
|
|
1888
|
+
* different containers with their own iteration, and nothing has asked for
|
|
1889
|
+
* them; a pair over either refuses by name rather than guessing a spelling.
|
|
1890
|
+
*/
|
|
1891
|
+
export const dictionaryCastableToDictionary = (source, target) => {
|
|
1892
|
+
if (source.key !== 'string' || target.key !== 'string')
|
|
1893
|
+
return false;
|
|
1894
|
+
if (representationKey(source.value) === representationKey(target.value))
|
|
1895
|
+
return false;
|
|
1896
|
+
if (containsUnresolved(source) || containsUnresolved(target))
|
|
1897
|
+
return false;
|
|
1898
|
+
try {
|
|
1899
|
+
return convertedValueText(source.value, target.value, 'gea_entry_value') !== null;
|
|
1900
|
+
}
|
|
1901
|
+
catch (error) {
|
|
1902
|
+
// Queried while the conversion graph is being built, exactly as
|
|
1903
|
+
// `recordCastableToDictionary` is: a nested recipe's named refusal means
|
|
1904
|
+
// the value pair does not convert, not that graph construction failed.
|
|
1905
|
+
if (isCppEmitBlockedError(error))
|
|
1906
|
+
return false;
|
|
1907
|
+
throw error;
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
const recastedDictionaryText = (source, target, text) => {
|
|
1911
|
+
if (!dictionaryCastableToDictionary(source, target))
|
|
1912
|
+
return null;
|
|
1913
|
+
const converted = convertedValueText(source.value, target.value, 'gea_entry.second');
|
|
1914
|
+
if (converted === null)
|
|
1915
|
+
return null;
|
|
1916
|
+
const storage = `gea::Dictionary<${cppTypeOf(target.value)}>`;
|
|
1917
|
+
const construct = target.ownership === 'shared-refcount' ? `auto gea_dict = gea::makeRef<${storage}>();` : `${storage} gea_dict{};`;
|
|
1918
|
+
const dictArrow = target.ownership === 'shared-refcount' ? '->' : '.';
|
|
1919
|
+
// `gea::Dictionary`'s own `const_iterator` walks entries in creation order,
|
|
1920
|
+
// which is the order the language enumerates them in and the order the copy
|
|
1921
|
+
// must therefore insert them in.
|
|
1922
|
+
const range = source.ownership === 'shared-refcount' ? '*gea_from' : 'gea_from';
|
|
1923
|
+
return (`[](const ${cppTypeOf(source)}& gea_from) { ${construct} ` +
|
|
1924
|
+
`for (const auto& gea_entry : ${range}) { gea_dict${dictArrow}operator[](gea_entry.first) = ${converted}; } ` +
|
|
1925
|
+
`return gea_dict; }(${text})`);
|
|
1926
|
+
};
|
|
1927
|
+
/**
|
|
1928
|
+
* The text a `convert` renders to, or `null` when nothing installed performs it.
|
|
1929
|
+
*
|
|
1930
|
+
* A convert is a carrier change the graph asked for by name -- a merge that
|
|
1931
|
+
* keeps one side, an optional chain's absent arm -- so unlike a narrowing read
|
|
1932
|
+
* it may widen, and unlike a widening store it may be handed an absence rather
|
|
1933
|
+
* than a payload.
|
|
1934
|
+
*
|
|
1935
|
+
* The absence cases are not spelled here. They used to be, and that private
|
|
1936
|
+
* copy was the defect: `widenedStoreText` -- which every ordinary store site
|
|
1937
|
+
* asks, and which this function's own tail already delegates to -- carried no
|
|
1938
|
+
* absence rule, so a `null` reaching a binding, a field or a return came out
|
|
1939
|
+
* unconverted while the same `null` reaching an explicit `convert` came out
|
|
1940
|
+
* right. One question, two answers, and only the quieter one was wrong. The
|
|
1941
|
+
* rule now lives once, on the store side, and this function reaches it through
|
|
1942
|
+
* the same tail it already used for every other widening.
|
|
1943
|
+
*/
|
|
1944
|
+
/**
|
|
1945
|
+
* The calling convention of a carrier the backend spells
|
|
1946
|
+
* `gea::CallableObject<...>`, or `null` for anything else.
|
|
1947
|
+
*
|
|
1948
|
+
* The constructor carriers are deliberately absent even though they also state
|
|
1949
|
+
* one convention: they spell `gea::ConstructorObject<...>`, which declares no
|
|
1950
|
+
* converting constructor, so the rule below does not hold for them.
|
|
1951
|
+
* `function-and-constructor` is absent for a different reason -- it states two
|
|
1952
|
+
* conventions, and picking one here would answer a question the value does not.
|
|
1953
|
+
*/
|
|
1954
|
+
export const callableObjectAbi = (representation) => {
|
|
1955
|
+
switch (representation.kind) {
|
|
1956
|
+
case 'function':
|
|
1957
|
+
case 'function-family':
|
|
1958
|
+
case 'function-value-family':
|
|
1959
|
+
case 'function-value-dispatch':
|
|
1960
|
+
return representation.abi;
|
|
1961
|
+
default:
|
|
1962
|
+
return null;
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
/**
|
|
1966
|
+
* The callable half of a value that also carries `[[Construct]]`.
|
|
1967
|
+
*
|
|
1968
|
+
* The target's callable ABI is the authority that makes selecting this half
|
|
1969
|
+
* unambiguous: its exact key must equal the source's published `call` ABI.
|
|
1970
|
+
* The new `CallableObject` keeps the same function pointer, environment and
|
|
1971
|
+
* owning reference, so extracting the view preserves both behavior and
|
|
1972
|
+
* lifetime while dropping only the construction entry the target cannot use.
|
|
1973
|
+
*/
|
|
1974
|
+
const callablePartText = (source, target, text) => {
|
|
1975
|
+
if (source.kind !== 'function-and-constructor')
|
|
1976
|
+
return null;
|
|
1977
|
+
const targetAbi = callableObjectAbi(target);
|
|
1978
|
+
if (targetAbi === null || abiKey(source.call) !== abiKey(targetAbi))
|
|
1979
|
+
return null;
|
|
1980
|
+
const sourceType = cppTypeOf(source);
|
|
1981
|
+
const targetType = cppTypeOf(target);
|
|
1982
|
+
return (`[](const ${sourceType}& gea_from) { ${targetType} gea_callable{}; ` +
|
|
1983
|
+
`gea_callable.invoke = gea_from.invoke; gea_callable.environment = gea_from.environment; ` +
|
|
1984
|
+
`gea_callable.environmentOwner = gea_from.environmentOwner; gea_callable.functionObject = gea_from.functionObject; return gea_callable; }(${text})`);
|
|
1985
|
+
};
|
|
1986
|
+
/**
|
|
1987
|
+
* The same positional read, for a rest slot carried as a closed TUPLE rather
|
|
1988
|
+
* than an open Array.
|
|
1989
|
+
*
|
|
1990
|
+
* `Function.prototype.call` is where that shape comes from. Under
|
|
1991
|
+
* `strictBindCallApply` its signature is `call<T, A extends any[], R>(this:
|
|
1992
|
+
* (this: T, ...args: A) => R, thisArg: T, ...args: A): R`, and the checker
|
|
1993
|
+
* infers `A` from the call's OWN arguments -- so
|
|
1994
|
+
* `callback.call(thisArg, value, key, parent)` states the `this` slot as
|
|
1995
|
+
* `(this: unknown, ...args: [string, string, Headers]) => void`, whose rest
|
|
1996
|
+
* parameter derives to a `record` of numeric keys and not to the
|
|
1997
|
+
* `array-object` the open `(...args: any[])` idiom produces. The positions are
|
|
1998
|
+
* the same positions; only the carrier differs.
|
|
1999
|
+
*
|
|
2000
|
+
* A source parameter past the tuple's end is refused rather than defaulted:
|
|
2001
|
+
* the open-array case has an element TYPE to default, while a tuple that does
|
|
2002
|
+
* not declare the position states nothing to stand in for it. An optional
|
|
2003
|
+
* field is refused for the same reason -- its absence is a presence flag the
|
|
2004
|
+
* caller must read, not a value.
|
|
2005
|
+
*
|
|
2006
|
+
* `@hono/node-server`'s `RequestHeaders.forEach` is the measured case: it
|
|
2007
|
+
* forwards to the caller's `(value, key, parent) => void` through
|
|
2008
|
+
* `callback.call(thisArg, value, key, this)`.
|
|
2009
|
+
*/
|
|
2010
|
+
const restTupleFieldActual = (slot, restOrdinal, position, parameter) => {
|
|
2011
|
+
const field = slot.fields[position];
|
|
2012
|
+
if (field === undefined || !field.required)
|
|
2013
|
+
return null;
|
|
2014
|
+
const arrow = slot.ownership === 'shared-refcount' ? '->' : '.';
|
|
2015
|
+
const access = `${adapterFormalName(restOrdinal)}${arrow}${cppRecordFieldName(field.key)}`;
|
|
2016
|
+
if (cppTypeOf(field.value) === cppTypeOf(parameter.value))
|
|
2017
|
+
return access;
|
|
2018
|
+
try {
|
|
2019
|
+
return convertedValueText(field.value, parameter.value, access);
|
|
2020
|
+
}
|
|
2021
|
+
catch (error) {
|
|
2022
|
+
if (isCppEmitBlockedError(error))
|
|
2023
|
+
return null;
|
|
2024
|
+
throw error;
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
/**
|
|
2028
|
+
* One source parameter read out of the slot's own rest Array -- the positional
|
|
2029
|
+
* read ECMA-262 performs when a function is called with an argument list the
|
|
2030
|
+
* caller assembled. A position the array does not reach is `undefined`, which
|
|
2031
|
+
* is exactly what a JavaScript call passes there, so the element's own default
|
|
2032
|
+
* stands in rather than an invented value.
|
|
2033
|
+
*/
|
|
2034
|
+
const restElementActual = (to, restOrdinal, position, parameter) => {
|
|
2035
|
+
const slot = to.parameters[restOrdinal];
|
|
2036
|
+
if (slot === undefined)
|
|
2037
|
+
return null;
|
|
2038
|
+
if (slot.value.kind === 'record')
|
|
2039
|
+
return restTupleFieldActual(slot.value, restOrdinal, position, parameter);
|
|
2040
|
+
if (slot.value.kind !== 'array-object')
|
|
2041
|
+
return null;
|
|
2042
|
+
const element = slot.value.element;
|
|
2043
|
+
const rest = adapterFormalName(restOrdinal);
|
|
2044
|
+
const elementType = cppTypeOf(element);
|
|
2045
|
+
const access = `(${rest}.get() == nullptr || ${rest}->size() <= static_cast<std::size_t>(${position}) ` +
|
|
2046
|
+
`? ${elementType}() : ${rest}->at(${position}))`;
|
|
2047
|
+
if (elementType === cppTypeOf(parameter.value))
|
|
2048
|
+
return access;
|
|
2049
|
+
try {
|
|
2050
|
+
return convertedValueText(element, parameter.value, access);
|
|
2051
|
+
}
|
|
2052
|
+
catch (error) {
|
|
2053
|
+
if (isCppEmitBlockedError(error))
|
|
2054
|
+
return null;
|
|
2055
|
+
throw error;
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
/** `gea::detail::emptyContainerAs` for a shared array or dictionary field whose elements do not convert. */
|
|
2059
|
+
const emptyContainerRecastText = (from, to, read) => {
|
|
2060
|
+
const container = (carrier) => ((carrier.kind === 'array-object' && !carrier.recursive && !carrier.extension) ||
|
|
2061
|
+
(carrier.kind === 'dictionary' && carrier.key === 'string')) &&
|
|
2062
|
+
carrier.ownership === 'shared-refcount';
|
|
2063
|
+
if (from.kind !== to.kind || !container(from) || !container(to))
|
|
2064
|
+
return null;
|
|
2065
|
+
return `gea::detail::emptyContainerAs<${cppTypeOf(to, 'owned')}>(${read}, "a container recast")`;
|
|
2066
|
+
};
|
|
2067
|
+
/**
|
|
2068
|
+
* An object the program asserts (`env as unknown as Bindings`) or narrows
|
|
2069
|
+
* (`body instanceof Uint8Array` over a declared `string | ReadableStream`)
|
|
2070
|
+
* into a union none of whose arms it can become, even as a structural view. The
|
|
2071
|
+
* assertion went through `unknown`, so it is carried as that: boxed, then
|
|
2072
|
+
* read back out with the exact-type check every box read makes, which aborts
|
|
2073
|
+
* by name when the value is not what the program claimed rather than
|
|
2074
|
+
* inventing a layout. The last resort after `structuralRecordViewText`, and
|
|
2075
|
+
* asked only there and in the census's matching fallback.
|
|
2076
|
+
*/
|
|
2077
|
+
export const boxedAssertionText = (source, target, text) => {
|
|
2078
|
+
const objectLike = (carrier) => (carrier.kind === 'record' || carrier.kind === 'native-record-ref' || carrier.kind === 'class-ref') &&
|
|
2079
|
+
carrier.ownership === 'shared-refcount';
|
|
2080
|
+
const union = target.kind === 'optional' ? target.payload : target;
|
|
2081
|
+
if (!objectLike(source) || union.kind !== 'tagged-union')
|
|
2082
|
+
return null;
|
|
2083
|
+
const boxed = dynamicCarrierBoxText(source, text);
|
|
2084
|
+
if (boxed === null)
|
|
2085
|
+
return null;
|
|
2086
|
+
return tryCandidateText(() => convertedValueText({ kind: 'dynamic', reason: 'declared-any-never-narrowed' }, target, boxed));
|
|
2087
|
+
};
|
|
2088
|
+
/** The slot parameters from `first` on, packed into the source's rest Array -- see `restPack` in `resultAdapterOf`. */
|
|
2089
|
+
const restPackedActual = (slots, first, element) => {
|
|
2090
|
+
const elementType = cppTypeOf(element);
|
|
2091
|
+
const elements = [];
|
|
2092
|
+
for (const [offset, slot] of slots.entries()) {
|
|
2093
|
+
const formal = adapterFormalName(first + offset);
|
|
2094
|
+
if (cppTypeOf(slot.value) === elementType) {
|
|
2095
|
+
elements.push(formal);
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
try {
|
|
2099
|
+
const converted = convertedValueText(slot.value, element, formal);
|
|
2100
|
+
if (converted === null)
|
|
2101
|
+
return null;
|
|
2102
|
+
elements.push(converted);
|
|
2103
|
+
}
|
|
2104
|
+
catch (error) {
|
|
2105
|
+
if (isCppEmitBlockedError(error))
|
|
2106
|
+
return null;
|
|
2107
|
+
throw error;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
return `gea::arrayOf<${elementType}>({${elements.join(', ')}})`;
|
|
2111
|
+
};
|
|
2112
|
+
/** The lambda's own names, kept out of every body-local namespace this text can be spliced into. */
|
|
2113
|
+
const adapterEnvironmentName = 'gea_adapt_environment';
|
|
2114
|
+
const adapterReceiverName = 'gea_adapt_receiver';
|
|
2115
|
+
const adapterResultName = 'gea_adapt_result';
|
|
2116
|
+
const adapterFormalName = (ordinal) => `gea_adapt_arg_${ordinal}`;
|
|
2117
|
+
/** The local a promise-to-promise conversion binds its source to, for the same reason the adapter names above are what they are. */
|
|
2118
|
+
const promiseSourceName = 'gea_promise_source';
|
|
2119
|
+
/** `promiseSourceName`'s twins: the promise a payload reconciliation settles, and the rejection it forwards. */
|
|
2120
|
+
const promiseTargetName = 'gea_promise_target';
|
|
2121
|
+
const promiseRejectionName = 'gea_promise_rejection';
|
|
2122
|
+
/** The fulfilment value a pending source hands the payload reconciliation, once it exists. */
|
|
2123
|
+
const promiseValueName = 'gea_promise_value';
|
|
2124
|
+
/**
|
|
2125
|
+
* The local `recastedUnionText` binds its source to, and the alias it binds
|
|
2126
|
+
* the target's spelling to, once per multi-arm recast -- not per arm.
|
|
2127
|
+
*
|
|
2128
|
+
* A fixed name rather than a generated one, same as `promiseSourceName`
|
|
2129
|
+
* above: every use sits inside its own IIFE's block scope (or, for a nested
|
|
2130
|
+
* inner-union arm, inside an IIFE nested textually inside this one), and a
|
|
2131
|
+
* C++ inner scope shadowing an outer one by the same name is ordinary,
|
|
2132
|
+
* unambiguous re-declaration -- not a collision -- because the argument that
|
|
2133
|
+
* feeds the inner IIFE is evaluated in the OUTER scope before the inner
|
|
2134
|
+
* parameter's own binding takes over.
|
|
2135
|
+
*/
|
|
2136
|
+
const recastUnionSourceName = 'gea_recast_source';
|
|
2137
|
+
const recastUnionAliasName = 'GeaRecastArm';
|
|
2138
|
+
/**
|
|
2139
|
+
* A callable whose declared parameters or result convert into its slot's ABI.
|
|
2140
|
+
*
|
|
2141
|
+
* `gea::CallableObject` declares four converting constructors and every one of
|
|
2142
|
+
* them hands the source's result through untouched -- they change the ARITY
|
|
2143
|
+
* (`dropArguments`, `dropTrailingArguments`, `spreadRestOverLeading`) or read
|
|
2144
|
+
* a result the runtime can already reach by arm index (`widenResultIntoArm`,
|
|
2145
|
+
* whose `ResultWidensIntoArm` trait needs a `TaggedUnion` target whose arm IS
|
|
2146
|
+
* the source result). None of them can BOX one, and that is not an omission
|
|
2147
|
+
* to fix in the header: which `Value::Tag` a carrier boxes to is
|
|
2148
|
+
* `dynamicTagFor`'s table, it lives in this emitter, and a second copy of it
|
|
2149
|
+
* in `gea_runtime.h` is precisely the two-authorities defect this compiler
|
|
2150
|
+
* exists to avoid.
|
|
2151
|
+
*
|
|
2152
|
+
* hono's `H = Handler | MiddlewareHandler` is the case that needs it:
|
|
2153
|
+
* `Handler<E, P, I, R = any>` defaults its result parameter to `any`, so both
|
|
2154
|
+
* arms take `(Context, Next)` and return `any`/`Promise<any>` while every
|
|
2155
|
+
* handler a program writes returns something concrete. Same frame, boxed
|
|
2156
|
+
* result.
|
|
2157
|
+
*
|
|
2158
|
+
* So the adapter is rendered HERE, where the tag table is, through
|
|
2159
|
+
* `CallableObject`'s own public `(Invoke, void*)` constructor: a CAPTURELESS
|
|
2160
|
+
* lambda (`+[]` decays it to the `Result (*)(void*, Arguments...)` pointer
|
|
2161
|
+
* that constructor takes) over a heap copy of the source, which is the exact
|
|
2162
|
+
* environment-carrying shape the header's own four constructors use.
|
|
2163
|
+
*
|
|
2164
|
+
* What it refuses, and why each is not a case to widen into later:
|
|
2165
|
+
*
|
|
2166
|
+
* - A source receiver that the target does not supply. A receiver-free
|
|
2167
|
+
* source, however, can fill a method slot: its ABI proves the body consumes
|
|
2168
|
+
* no dynamic receiver, so the adapter ignores the target's receiver. This
|
|
2169
|
+
* is how an arrow assigned to an instance method keeps its lexical `this`.
|
|
2170
|
+
* Two receiver-bearing conventions adapt the
|
|
2171
|
+
* target receiver into the source receiver exactly like an ordinary
|
|
2172
|
+
* parameter; it is the first physical formal in both ABIs.
|
|
2173
|
+
* - A source frame wider than its slot, or a different rest placement. The
|
|
2174
|
+
* adapter may ignore target arguments after the source's declared prefix,
|
|
2175
|
+
* just as an ordinary JavaScript call does; it never invents arguments the
|
|
2176
|
+
* source declared but the target does not supply.
|
|
2177
|
+
* - A void source result flowing anywhere except void or dynamic. Calling a
|
|
2178
|
+
* JavaScript function that returns no value produces `undefined`, so a
|
|
2179
|
+
* genuinely dynamic result receives the runtime's default Undefined box;
|
|
2180
|
+
* no other concrete carrier may be invented from it.
|
|
2181
|
+
* - A pair whose parameters and result already share C++ spellings. It needs
|
|
2182
|
+
* no adapter and must not get one, or an identity store would allocate.
|
|
2183
|
+
*/
|
|
2184
|
+
const resultAdapterOf = (source, target) => {
|
|
2185
|
+
const from = callableObjectAbi(source);
|
|
2186
|
+
const to = callableObjectAbi(target);
|
|
2187
|
+
if (!from || !to)
|
|
2188
|
+
return null;
|
|
2189
|
+
if (from.receiver !== null && to.receiver === null)
|
|
2190
|
+
return null;
|
|
2191
|
+
const ignoresReceiver = from.receiver === null && to.receiver !== null;
|
|
2192
|
+
// A fixed-arity source filling a slot whose trailing frame is ONE rest array:
|
|
2193
|
+
// `(...args: any[]) => void`, JavaScript's "some function, arguments not my
|
|
2194
|
+
// business" idiom. tsc's whole `Debug` family passes itself through it --
|
|
2195
|
+
// `assertIsDefined(value, message, stackCrawlMark || checkDefined)` -- and
|
|
2196
|
+
// every such argument refused to lower, 265 rows in `debug.ts` alone, because
|
|
2197
|
+
// the rest placements did not match. They do not have to: the language
|
|
2198
|
+
// defines the call exactly, the slot binds one Array and the source reads its
|
|
2199
|
+
// own parameters positionally out of it. The rest ELEMENT is what makes this
|
|
2200
|
+
// sound rather than a guess -- a slot declared `any[]` is a genuine dynamic
|
|
2201
|
+
// boundary, so each source parameter is filled through the same conversion an
|
|
2202
|
+
// ordinary dynamic argument already takes, and a slot whose element cannot
|
|
2203
|
+
// convert leaves `null` here and keeps the refusal.
|
|
2204
|
+
// The slot's trailing frame is one rest parameter whichever way the checker
|
|
2205
|
+
// spelled it: an open `any[]` Array, or the closed TUPLE
|
|
2206
|
+
// `Function.prototype.call`'s `A extends any[]` infers from the call's own
|
|
2207
|
+
// arguments -- see `restTupleFieldActual` for why those are the same
|
|
2208
|
+
// positions in two carriers.
|
|
2209
|
+
const restSlotKind = to.restFrom === null ? undefined : to.parameters[to.restFrom]?.value.kind;
|
|
2210
|
+
const restSpread = from.restFrom === null &&
|
|
2211
|
+
to.restFrom !== null &&
|
|
2212
|
+
to.parameters.length === to.restFrom + 1 &&
|
|
2213
|
+
from.parameters.length >= to.restFrom &&
|
|
2214
|
+
(restSlotKind === 'array-object' || restSlotKind === 'record');
|
|
2215
|
+
// A source formal past the end of the slot's frame is OMITTED at every call
|
|
2216
|
+
// the slot admits, and ECMA-262 10.2.1 binds an omitted parameter to
|
|
2217
|
+
// `undefined` -- a value the language supplies, not one the adapter invents.
|
|
2218
|
+
// So the refusal is not "the source frame is wider" but "a source formal
|
|
2219
|
+
// past the slot's frame cannot hold the absence", which `cppUndefinedIn`
|
|
2220
|
+
// (the one authority `paddedArguments` already asks at a direct call that
|
|
2221
|
+
// omits a trailing argument) answers. hono's `utils/url.ts` is the measured
|
|
2222
|
+
// case: `export const getQueryParam: (url, key?) => ... = _getQueryParam as
|
|
2223
|
+
// (...)`, where `_getQueryParam` declares a third `multiple?: boolean`; the
|
|
2224
|
+
// slot is the only contract any caller has, so the third formal is
|
|
2225
|
+
// `Optional<bool>()` at every one of them.
|
|
2226
|
+
//
|
|
2227
|
+
// A rest parameter is not an omitted argument -- what a caller owes one is
|
|
2228
|
+
// an allocated array -- so a source with a rest keeps the arity refusal.
|
|
2229
|
+
const omitted = !restSpread && from.restFrom === null && from.parameters.length > to.parameters.length;
|
|
2230
|
+
// The mirror: a source whose trailing frame is one rest Array filling a
|
|
2231
|
+
// slot of fixed parameters. The slot's parameters from the rest position on
|
|
2232
|
+
// are the arguments the source's Array binds, so the adapter packs them.
|
|
2233
|
+
// An optional slot parameter is packed even when a caller omitted it, so
|
|
2234
|
+
// the Array's length counts every slot position.
|
|
2235
|
+
const restParameter = from.restFrom === null ? undefined : from.parameters[from.restFrom];
|
|
2236
|
+
const restPack = !restSpread &&
|
|
2237
|
+
from.restFrom !== null &&
|
|
2238
|
+
to.restFrom === null &&
|
|
2239
|
+
from.parameters.length === from.restFrom + 1 &&
|
|
2240
|
+
to.parameters.length >= from.restFrom &&
|
|
2241
|
+
restParameter?.value.kind === 'array-object' &&
|
|
2242
|
+
restParameter.value.ownership === 'shared-refcount' &&
|
|
2243
|
+
!restParameter.value.recursive;
|
|
2244
|
+
if (!restSpread && !restPack) {
|
|
2245
|
+
if (from.restFrom !== to.restFrom)
|
|
2246
|
+
return null;
|
|
2247
|
+
if (from.parameters.length > to.parameters.length && !omitted)
|
|
2248
|
+
return null;
|
|
2249
|
+
if (omitted && from.parameters.slice(to.parameters.length).some((parameter) => cppUndefinedIn(parameter.value) === null))
|
|
2250
|
+
return null;
|
|
2251
|
+
}
|
|
2252
|
+
let receiverActual = null;
|
|
2253
|
+
if (from.receiver !== null && to.receiver !== null) {
|
|
2254
|
+
if (cppTypeOf(from.receiver) === cppTypeOf(to.receiver)) {
|
|
2255
|
+
receiverActual = adapterReceiverName;
|
|
2256
|
+
}
|
|
2257
|
+
else {
|
|
2258
|
+
try {
|
|
2259
|
+
receiverActual = convertedValueText(to.receiver, from.receiver, adapterReceiverName);
|
|
2260
|
+
}
|
|
2261
|
+
catch (error) {
|
|
2262
|
+
if (isCppEmitBlockedError(error))
|
|
2263
|
+
return null;
|
|
2264
|
+
throw error;
|
|
2265
|
+
}
|
|
2266
|
+
if (receiverActual === null)
|
|
2267
|
+
return null;
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
const actuals = [];
|
|
2271
|
+
const restOrdinal = to.restFrom;
|
|
2272
|
+
for (const [ordinal, parameter] of from.parameters.entries()) {
|
|
2273
|
+
if (restSpread && restOrdinal !== null && ordinal >= restOrdinal) {
|
|
2274
|
+
const spread = restElementActual(to, restOrdinal, ordinal - restOrdinal, parameter);
|
|
2275
|
+
if (spread === null)
|
|
2276
|
+
return null;
|
|
2277
|
+
actuals.push(spread);
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2280
|
+
if (restPack && ordinal === from.restFrom && parameter.value.kind === 'array-object') {
|
|
2281
|
+
const packed = restPackedActual(to.parameters.slice(ordinal), ordinal, parameter.value.element);
|
|
2282
|
+
if (packed === null)
|
|
2283
|
+
return null;
|
|
2284
|
+
actuals.push(packed);
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
const slot = to.parameters[ordinal];
|
|
2288
|
+
if (slot === undefined) {
|
|
2289
|
+
const absent = omitted ? cppUndefinedIn(parameter.value) : null;
|
|
2290
|
+
if (absent === null)
|
|
2291
|
+
return null;
|
|
2292
|
+
actuals.push(absent);
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2295
|
+
const formal = adapterFormalName(ordinal);
|
|
2296
|
+
if (cppAbiParameterType(parameter) === cppAbiParameterType(slot)) {
|
|
2297
|
+
actuals.push(formal);
|
|
2298
|
+
continue;
|
|
2299
|
+
}
|
|
2300
|
+
try {
|
|
2301
|
+
const converted = convertedValueText(slot.value, parameter.value, formal);
|
|
2302
|
+
if (converted === null)
|
|
2303
|
+
return null;
|
|
2304
|
+
actuals.push(converted);
|
|
2305
|
+
}
|
|
2306
|
+
catch (error) {
|
|
2307
|
+
if (isCppEmitBlockedError(error))
|
|
2308
|
+
return null;
|
|
2309
|
+
throw error;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
let convertedResult;
|
|
2313
|
+
if (to.result.kind === 'void') {
|
|
2314
|
+
convertedResult = null;
|
|
2315
|
+
}
|
|
2316
|
+
else if (from.result.kind === 'void') {
|
|
2317
|
+
if (to.result.kind !== 'dynamic')
|
|
2318
|
+
return null;
|
|
2319
|
+
convertedResult = `${cppTypeOf(to.result)}()`;
|
|
2320
|
+
}
|
|
2321
|
+
else if (cppTypeOf(from.result) === cppTypeOf(to.result)) {
|
|
2322
|
+
convertedResult = adapterResultName;
|
|
2323
|
+
}
|
|
2324
|
+
else {
|
|
2325
|
+
try {
|
|
2326
|
+
convertedResult = convertedValueText(from.result, to.result, adapterResultName);
|
|
2327
|
+
}
|
|
2328
|
+
catch (error) {
|
|
2329
|
+
if (isCppEmitBlockedError(error))
|
|
2330
|
+
return null;
|
|
2331
|
+
throw error;
|
|
2332
|
+
}
|
|
2333
|
+
if (convertedResult === null)
|
|
2334
|
+
return null;
|
|
2335
|
+
}
|
|
2336
|
+
const receiverChanges = receiverActual !== null && receiverActual !== adapterReceiverName;
|
|
2337
|
+
const nativeReceiverTransport = from.receiver !== null && to.receiver !== null && classRefTransportKind(to.receiver, from.receiver) !== null;
|
|
2338
|
+
const ignoresParameters = from.parameters.length < to.parameters.length;
|
|
2339
|
+
const parametersChange = actuals.some((actual, ordinal) => actual !== adapterFormalName(ordinal));
|
|
2340
|
+
const resultChanges = from.result.kind !== to.result.kind || (from.result.kind !== 'void' && cppTypeOf(from.result) !== cppTypeOf(to.result));
|
|
2341
|
+
const nativeFieldProtocolUnused = !restSpread &&
|
|
2342
|
+
!restPack &&
|
|
2343
|
+
(!receiverChanges || nativeReceiverTransport) &&
|
|
2344
|
+
!parametersChange &&
|
|
2345
|
+
(from.result.kind === 'void' || to.result.kind === 'void' || cppTypeOf(from.result) === cppTypeOf(to.result));
|
|
2346
|
+
return ignoresReceiver || ignoresParameters || receiverChanges || parametersChange || resultChanges
|
|
2347
|
+
? { from, to, receiverActual, actuals, convertedResult, nativeFieldProtocolUnused }
|
|
2348
|
+
: null;
|
|
2349
|
+
};
|
|
2350
|
+
/** Whether `source` reaches `target` through a result adapter -- see `resultAdapterOf`. Exported so `conversions.ts` asks the identical question the render below answers. */
|
|
2351
|
+
export const adaptsResultIntoTarget = (source, target) => resultAdapterOf(source, target) !== null;
|
|
2352
|
+
/** The same adapter selection used by the printer publishes its transport facts to the conversion census. */
|
|
2353
|
+
export const resultAdapterTransportOf = (source, target) => {
|
|
2354
|
+
const adapter = resultAdapterOf(source, target);
|
|
2355
|
+
return adapter === null ? null : { nativeConventions: adapter.nativeFieldProtocolUnused ? { from: adapter.from, to: adapter.to } : null };
|
|
2356
|
+
};
|
|
2357
|
+
/** The adapter itself, or `null` for a pair that needs none -- see `resultAdapterOf`. */
|
|
2358
|
+
export const resultAdaptedCallableText = (source, target, text) => {
|
|
2359
|
+
const adapter = resultAdapterOf(source, target);
|
|
2360
|
+
if (adapter === null)
|
|
2361
|
+
return null;
|
|
2362
|
+
const sourceType = cppTypeOf(source);
|
|
2363
|
+
const formals = [
|
|
2364
|
+
`void* ${adapterEnvironmentName}`,
|
|
2365
|
+
...(adapter.to.receiver === null ? [] : [`${cppTypeOf(adapter.to.receiver)} ${adapterReceiverName}`]),
|
|
2366
|
+
...adapter.to.parameters.map((parameter, ordinal) => `${cppAbiParameterType(parameter)} ${adapterFormalName(ordinal)}`)
|
|
2367
|
+
];
|
|
2368
|
+
const sourceActuals = [...(adapter.receiverActual === null ? [] : [adapter.receiverActual]), ...adapter.actuals];
|
|
2369
|
+
const call = `static_cast<${sourceType}*>(${adapterEnvironmentName})->call(${sourceActuals.join(', ')})`;
|
|
2370
|
+
const body = adapter.from.result.kind === 'void'
|
|
2371
|
+
? `${call};${adapter.convertedResult === null ? '' : ` return ${adapter.convertedResult};`}`
|
|
2372
|
+
: adapter.to.result.kind === 'void'
|
|
2373
|
+
? `${call};`
|
|
2374
|
+
: `${cppTypeOf(adapter.from.result)} ${adapterResultName} = ${call}; return ${adapter.convertedResult};`;
|
|
2375
|
+
return `${cppTypeOf(target)}::adaptSource(${sourceType}{${text}}, [](${formals.join(', ')}) -> ${cppResultTypeOf(adapter.to.result)} { ${body} })`;
|
|
2376
|
+
};
|
|
2377
|
+
/**
|
|
2378
|
+
* A callable filling a slot that declares MORE parameters than it does, the
|
|
2379
|
+
* extra ones landing after every one the source itself names./**
|
|
2380
|
+
* A callable filling a slot that declares MORE parameters than it does, the
|
|
2381
|
+
* extra ones landing after every one the source itself names.
|
|
2382
|
+
*
|
|
2383
|
+
* ECMA-262 does not bind the extra arguments, so `() => void` really is a
|
|
2384
|
+
* `(time: number) => void` and TypeScript accepts it everywhere, and the same
|
|
2385
|
+
* is true one prefix over: hono's `Hono.getPath` merges two
|
|
2386
|
+
* `(request: Request) => string` values into a field declared
|
|
2387
|
+
* `(request: Request, options?: {env?}) => string` -- one shared leading
|
|
2388
|
+
* parameter, one trailing parameter neither physical function looks at.
|
|
2389
|
+
* `gea::CallableObject`'s own converting constructor (gea_runtime.h) performs
|
|
2390
|
+
* both shapes, so the value converts by being written where the wider one is
|
|
2391
|
+
* expected and needs no text of its own.
|
|
2392
|
+
*
|
|
2393
|
+
* The source's own parameters must be a TYPE-MATCHING PREFIX of the target's,
|
|
2394
|
+
* not merely a shorter count: converting an arbitrary prefix without proving
|
|
2395
|
+
* each shared position's type matches would let a wrong guess pass a value
|
|
2396
|
+
* into a slot expecting something else, which is what the runtime
|
|
2397
|
+
* constructor's own `IsTypePrefix` proves at compile time rather than
|
|
2398
|
+
* assumes, and this predicate re-asks the identical question so the two
|
|
2399
|
+
* cannot disagree about which pairs the constructor actually accepts. Every
|
|
2400
|
+
* position through the source's own last one has to agree, not only the
|
|
2401
|
+
* first -- `(a: string) => void` is not a sound stand-in for
|
|
2402
|
+
* `(a: string, b: number, c: string) => void` merely because position 0
|
|
2403
|
+
* matches; every position the source declares has to. The result and the
|
|
2404
|
+
* receiver must still agree exactly -- neither is dropped by a call, so a
|
|
2405
|
+
* difference in either is a real mismatch, not a binding the language elides.
|
|
2406
|
+
*/
|
|
2407
|
+
/**
|
|
2408
|
+
* A method value -- its receiver first in its physical convention -- filling
|
|
2409
|
+
* a slot that declares the same convention WITHOUT one: `gea_runtime.h`'s
|
|
2410
|
+
* `CallableObject::bindReceiver`, which packs the receiver the property read
|
|
2411
|
+
* went through in with the value. Everything but the receiver has to agree
|
|
2412
|
+
* exactly, in the C++ spelling the runtime's own signature matching sees
|
|
2413
|
+
* (`cppAbiType`, the one authority on that spelling), so the adapter's
|
|
2414
|
+
* `Result(Receiver, Arguments...)` is the source's own type and nothing is
|
|
2415
|
+
* coerced on the way through.
|
|
2416
|
+
*
|
|
2417
|
+
* Only the SHAPE is decided here; whether the pair can actually be rendered
|
|
2418
|
+
* -- the read's receiver has to be recoverable at the site, and the body must
|
|
2419
|
+
* never read `this` -- is `emit-callable.ts`'s `receiverBoundCallableText`'s
|
|
2420
|
+
* question, a fact of the emitter's context and not of the two carriers.
|
|
2421
|
+
*/
|
|
2422
|
+
/**
|
|
2423
|
+
* A `this`-TYPED function value, stored in a field and read through the object
|
|
2424
|
+
* whose `this` it names: `gea_runtime.h`'s `CallableObject::bindReceiver`
|
|
2425
|
+
* again, but reached from a fact about the two DECLARATIONS rather than from a
|
|
2426
|
+
* method body.
|
|
2427
|
+
*
|
|
2428
|
+
* hono's `RegExpRouter` writes `match: typeof match<Router<T>, T> = match`
|
|
2429
|
+
* over `function match<R extends Router<T>, T>(this: R, method, path)`, while
|
|
2430
|
+
* `TrieRouter` and `SmartRouter` declare `match` as an ordinary method -- and
|
|
2431
|
+
* `Router<T>` itself declares it as a method, with no `this` at all. So the
|
|
2432
|
+
* field's carrier names a receiver and every read through the interface (or
|
|
2433
|
+
* through the union of the three implementors) publishes one that does not,
|
|
2434
|
+
* and nothing converted between them.
|
|
2435
|
+
*
|
|
2436
|
+
* Binding the read's own receiver is the only call the language admits here,
|
|
2437
|
+
* which is what makes this sound where the detached-METHOD case next door
|
|
2438
|
+
* needs `readsReceiver` to hold: a `this` parameter is a parameter, and
|
|
2439
|
+
* TypeScript refuses a call through the value that does not supply one
|
|
2440
|
+
* (`The 'this' context of type 'void' is not assignable...`). A method has no
|
|
2441
|
+
* such annotation, so a detached call really can reach it with `this`
|
|
2442
|
+
* undefined, and binding there would answer where the program would have
|
|
2443
|
+
* thrown.
|
|
2444
|
+
*/
|
|
2445
|
+
export const receiverBoundFieldText = (ctx, stored, published, storedText, receiver, receiverText) => {
|
|
2446
|
+
if (!bindsReceiver(stored, published))
|
|
2447
|
+
return null;
|
|
2448
|
+
const abi = callableObjectAbi(stored);
|
|
2449
|
+
if (abi === null || abi.receiver === null)
|
|
2450
|
+
return null;
|
|
2451
|
+
const bound = alignedValueText(ctx, 'emit-narrowing.ts:receiver-bound-field', receiver, abi.receiver, receiverText);
|
|
2452
|
+
if (bound === null)
|
|
2453
|
+
return null;
|
|
2454
|
+
return `${cppTypeOf(published)}::bindReceiver(${storedText}, ${bound})`;
|
|
2455
|
+
};
|
|
2456
|
+
export const bindsReceiver = (source, target) => {
|
|
2457
|
+
const from = callableObjectAbi(source);
|
|
2458
|
+
const to = callableObjectAbi(target);
|
|
2459
|
+
if (!from || !to || from.receiver === null || to.receiver !== null)
|
|
2460
|
+
return false;
|
|
2461
|
+
return cppAbiType({ ...from, receiver: null }) === cppAbiType(to);
|
|
2462
|
+
};
|
|
2463
|
+
export const dropsUnboundParameters = (source, target) => {
|
|
2464
|
+
const from = callableObjectAbi(source);
|
|
2465
|
+
const to = callableObjectAbi(target);
|
|
2466
|
+
if (!from || !to)
|
|
2467
|
+
return false;
|
|
2468
|
+
// Receivers are not part of the runtime constructor's own proof (it
|
|
2469
|
+
// matches on `Arguments...` alone), so a callable wearing one on either
|
|
2470
|
+
// side is not this shape.
|
|
2471
|
+
if (from.receiver !== null || to.receiver !== null)
|
|
2472
|
+
return false;
|
|
2473
|
+
if (from.parameters.length >= to.parameters.length)
|
|
2474
|
+
return false;
|
|
2475
|
+
if (representationKey(from.result) !== representationKey(to.result))
|
|
2476
|
+
return false;
|
|
2477
|
+
return from.parameters.every((parameter, index) => representationKey(parameter.value) === representationKey(to.parameters[index].value));
|
|
2478
|
+
};
|
|
2479
|
+
/**
|
|
2480
|
+
* A zero-parameter callable filling a slot that declares at least one
|
|
2481
|
+
* parameter AND whose result is a tagged union naming the source's own
|
|
2482
|
+
* result as one of its arms -- `dropsUnboundParameters`'s zero-arity special
|
|
2483
|
+
* case (`gea_runtime.h`'s `dropArguments`) composed with
|
|
2484
|
+
* `gea::TaggedUnion::ofArm`, for a source neither alone accepts:
|
|
2485
|
+
* `dropsUnboundParameters` requires an IDENTICAL result, and a bare `ofArm`
|
|
2486
|
+
* conversion has no arity adaptation of its own.
|
|
2487
|
+
*
|
|
2488
|
+
* hono's `Context.notFound`: `this.#notFoundHandler ??=
|
|
2489
|
+
* () => createResponseInstance()` merges a `() => Response` into a field
|
|
2490
|
+
* declared `(c: Context) => Response | Promise<Response>`.
|
|
2491
|
+
* `gea_runtime.h`'s `ResultWidensIntoArm` proves the identical pairing at
|
|
2492
|
+
* the type level; this re-asks the same two-part question so the two cannot
|
|
2493
|
+
* disagree about which pairs the runtime constructor actually accepts.
|
|
2494
|
+
*
|
|
2495
|
+
* Deliberately narrower than `dropsUnboundParameters`: a nonzero source
|
|
2496
|
+
* arity is left entirely to that function (its PREFIX proof does not carry
|
|
2497
|
+
* over once the result itself also changes), and a target result that is
|
|
2498
|
+
* not a tagged union, or a source result that is not EXACTLY one of its
|
|
2499
|
+
* arms, is refused -- the same fail-closed discipline `ResultWidensIntoArm`
|
|
2500
|
+
* documents in the runtime.
|
|
2501
|
+
*/
|
|
2502
|
+
export const dropsAllParametersIntoResultArm = (source, target) => {
|
|
2503
|
+
const from = callableObjectAbi(source);
|
|
2504
|
+
const to = callableObjectAbi(target);
|
|
2505
|
+
if (!from || !to)
|
|
2506
|
+
return false;
|
|
2507
|
+
if (from.receiver !== null || to.receiver !== null)
|
|
2508
|
+
return false;
|
|
2509
|
+
if (from.parameters.length !== 0 || to.parameters.length === 0)
|
|
2510
|
+
return false;
|
|
2511
|
+
if (to.result.kind !== 'tagged-union')
|
|
2512
|
+
return false;
|
|
2513
|
+
const resultKey = representationKey(from.result);
|
|
2514
|
+
return to.result.arms.some((arm) => representationKey(arm.value) === resultKey);
|
|
2515
|
+
};
|
|
2516
|
+
/**
|
|
2517
|
+
* A callable filling a slot whose declared result is `void`, its own result
|
|
2518
|
+
* being anything else -- same parameters, in the same order, with the same
|
|
2519
|
+
* types; only the RESULT differs, and only in the direction of being
|
|
2520
|
+
* discarded.
|
|
2521
|
+
*
|
|
2522
|
+
* ECMA-262 makes this sound for a reason distinct from the other two
|
|
2523
|
+
* predicates above: a call's result is simply not read unless something
|
|
2524
|
+
* reads it, so a function returning `T` really is a valid `() => void` (or
|
|
2525
|
+
* any arity's `void`-returning form) wherever the caller never names the
|
|
2526
|
+
* result -- `Array.prototype.forEach`'s callback contract is the textbook
|
|
2527
|
+
* case, and this port's own is `node:http`'s `RequestListener = (req, res)
|
|
2528
|
+
* => void`: `createServer(async (req, res) => { ... })` is an everyday Node
|
|
2529
|
+
* idiom (`apps/hono-hello/server.ts` is one), and TypeScript accepts a
|
|
2530
|
+
* `Promise<void>`-returning callback there for the identical reason -- a
|
|
2531
|
+
* `void`-returning function TYPE is satisfied by ANY return type, precisely
|
|
2532
|
+
* so a caller who ignores the result is never forced to wrap it.
|
|
2533
|
+
*
|
|
2534
|
+
* `gea_runtime.h`'s `CallableObject` composed converting constructor this
|
|
2535
|
+
* asks for discards whatever the source returns; see that constructor's own
|
|
2536
|
+
* comment for why doing so is never lossy for an async body specifically --
|
|
2537
|
+
* this runtime's `Promise<V>` is a settled-value box, never a coroutine
|
|
2538
|
+
* frame, so by the time a call returns here its entire body, `await`s
|
|
2539
|
+
* included, has already run to completion.
|
|
2540
|
+
*
|
|
2541
|
+
* Gated the mirror of `dropsAllParametersIntoResultArm` above: parameters
|
|
2542
|
+
* must agree in COUNT and in every position's TYPE (this is not an arity
|
|
2543
|
+
* change, `dropsUnboundParameters`'s own axis -- it is a pure result
|
|
2544
|
+
* change), the receiver must agree exactly on both sides (a call's receiver
|
|
2545
|
+
* is never dropped), and the target's result must actually BE `void` --
|
|
2546
|
+
* anything else stays a hard compile error, this file's shared fail-closed
|
|
2547
|
+
* rule. Source and target already sharing `void` needs no constructor at
|
|
2548
|
+
* all: `cppTypeOf` equality above already renders that as the identity
|
|
2549
|
+
* text, so this predicate is never even asked.
|
|
2550
|
+
*
|
|
2551
|
+
* Zero parameters is excluded on the runtime constructor's own account, not
|
|
2552
|
+
* this predicate's: `gea_runtime.h`'s matching constructor collapses to the
|
|
2553
|
+
* textually identical signature the `ResultWidensIntoArm`-gated one above it
|
|
2554
|
+
* already declares once `Arguments...` is empty, which fails to COMPILE
|
|
2555
|
+
* (every zero-arity `CallableObject<Result()>` in the header, `Result=void`
|
|
2556
|
+
* or not) rather than merely fails to be selected -- see that constructor's
|
|
2557
|
+
* own comment. Asking this predicate to answer `false` there keeps the two
|
|
2558
|
+
* files agreeing about which pairs the runtime actually accepts, the same
|
|
2559
|
+
* discipline `dropsUnboundParameters` and `dropsAllParametersIntoResultArm`
|
|
2560
|
+
* both already state for their own exclusions.
|
|
2561
|
+
*/
|
|
2562
|
+
/**
|
|
2563
|
+
* A callable whose parameters already agree with the slot's, position for
|
|
2564
|
+
* position, and whose RESULT is exactly one arm of the tagged union the slot
|
|
2565
|
+
* declares -- `dropsAllParametersIntoResultArm` with the arity change taken
|
|
2566
|
+
* back out, leaving only the widening half.
|
|
2567
|
+
*
|
|
2568
|
+
* That predicate requires a ZERO-parameter source and `dropsUnboundParameters`
|
|
2569
|
+
* an IDENTICAL result, so a pair moving along only the result axis at a nonzero
|
|
2570
|
+
* arity satisfies neither -- hono's `Hono.fetch` is one, a `(Context) =>
|
|
2571
|
+
* Response` assigned into a field declared `(Context) => Response |
|
|
2572
|
+
* Promise<Response>`. Sound for exactly the reason `gea_runtime.h`'s
|
|
2573
|
+
* `ResultWidensIntoArm` states, and asked in the same terms so the two cannot
|
|
2574
|
+
* disagree: the result must equal one arm EXACTLY and every other position --
|
|
2575
|
+
* receiver, arity, each parameter's carrier, the rest slot -- exactly, none
|
|
2576
|
+
* being adapted here. `widenResultIntoArm` is the constructor performing it.
|
|
2577
|
+
*/
|
|
2578
|
+
export const widensResultIntoArm = (source, target) => {
|
|
2579
|
+
const from = callableObjectAbi(source);
|
|
2580
|
+
const to = callableObjectAbi(target);
|
|
2581
|
+
if (!from || !to)
|
|
2582
|
+
return false;
|
|
2583
|
+
if (from.receiver !== null || to.receiver !== null)
|
|
2584
|
+
return false;
|
|
2585
|
+
if (from.parameters.length === 0 || from.parameters.length !== to.parameters.length)
|
|
2586
|
+
return false;
|
|
2587
|
+
if (from.restFrom !== to.restFrom || to.result.kind !== 'tagged-union')
|
|
2588
|
+
return false;
|
|
2589
|
+
if (!from.parameters.every((parameter, index) => representationKey(parameter.value) === representationKey(to.parameters[index].value)))
|
|
2590
|
+
return false;
|
|
2591
|
+
const resultKey = representationKey(from.result);
|
|
2592
|
+
return to.result.arms.some((arm) => representationKey(arm.value) === resultKey);
|
|
2593
|
+
};
|
|
2594
|
+
/**
|
|
2595
|
+
* A source whose rest parameter sits at a LATER position than the target's.
|
|
2596
|
+
*
|
|
2597
|
+
* ECMAScript has one calling convention, so `(base?: string, sub?: string,
|
|
2598
|
+
* ...rest: string[]) => string` and `(...paths: string[]) => string` are the
|
|
2599
|
+
* same function -- hono's `utils/url.ts` declares exactly that pair on one
|
|
2600
|
+
* `const mergePath`, the annotation stating the second and the initializer
|
|
2601
|
+
* being the first. C++ sees two unrelated function types, and the store
|
|
2602
|
+
* refused.
|
|
2603
|
+
*
|
|
2604
|
+
* The target has to be a single rest parameter, because that array is the
|
|
2605
|
+
* whole of what the thunk has to unpack. Every leading source parameter has
|
|
2606
|
+
* to be `optional(E, undefined)` over the same element the rest carries: a
|
|
2607
|
+
* position the call never reached has to be ABSENT, and a leading slot must
|
|
2608
|
+
* never be handed an element of a type it did not declare. The source's own
|
|
2609
|
+
* rest must sit exactly after its leading run, since the tail is what is
|
|
2610
|
+
* left when those are taken.
|
|
2611
|
+
*
|
|
2612
|
+
* `gea_runtime.h`'s `RestRebaseAdmits` states the identical conditions over
|
|
2613
|
+
* the C++ types, so a pair this admits and that one does not is a compile
|
|
2614
|
+
* error rather than a wrong call -- the same fail-closed pairing every other
|
|
2615
|
+
* predicate here has with its constructor.
|
|
2616
|
+
*/
|
|
2617
|
+
export const rebasesRestOverLeadingParameters = (source, target) => {
|
|
2618
|
+
const from = callableObjectAbi(source);
|
|
2619
|
+
const to = callableObjectAbi(target);
|
|
2620
|
+
if (!from || !to)
|
|
2621
|
+
return false;
|
|
2622
|
+
if (from.receiver !== null || to.receiver !== null)
|
|
2623
|
+
return false;
|
|
2624
|
+
if (representationKey(from.result) !== representationKey(to.result))
|
|
2625
|
+
return false;
|
|
2626
|
+
if (to.restFrom !== 0 || to.parameters.length !== 1)
|
|
2627
|
+
return false;
|
|
2628
|
+
const leading = from.parameters.length - 1;
|
|
2629
|
+
if (leading < 1 || from.restFrom !== leading)
|
|
2630
|
+
return false;
|
|
2631
|
+
const slot = to.parameters[0]?.value;
|
|
2632
|
+
const tail = from.parameters[leading]?.value;
|
|
2633
|
+
if (!slot || !tail || slot.kind !== 'array-object')
|
|
2634
|
+
return false;
|
|
2635
|
+
if (representationKey(slot) !== representationKey(tail))
|
|
2636
|
+
return false;
|
|
2637
|
+
const elementKey = representationKey(slot.element);
|
|
2638
|
+
return from.parameters
|
|
2639
|
+
.slice(0, leading)
|
|
2640
|
+
.every((parameter) => parameter.value.kind === 'optional' &&
|
|
2641
|
+
parameter.value.absence === 'undefined' &&
|
|
2642
|
+
representationKey(parameter.value.payload) === elementKey);
|
|
2643
|
+
};
|
|
2644
|
+
export const discardsResultIntoVoid = (source, target) => {
|
|
2645
|
+
const from = callableObjectAbi(source);
|
|
2646
|
+
const to = callableObjectAbi(target);
|
|
2647
|
+
if (!from || !to)
|
|
2648
|
+
return false;
|
|
2649
|
+
if (from.receiver !== null || to.receiver !== null)
|
|
2650
|
+
return false;
|
|
2651
|
+
if (to.result.kind !== 'void')
|
|
2652
|
+
return false;
|
|
2653
|
+
if (to.parameters.length === 0)
|
|
2654
|
+
return false;
|
|
2655
|
+
if (from.parameters.length !== to.parameters.length)
|
|
2656
|
+
return false;
|
|
2657
|
+
return from.parameters.every((parameter, index) => representationKey(parameter.value) === representationKey(to.parameters[index].value));
|
|
2658
|
+
};
|
|
2659
|
+
const callableMembershipOf = (carrier) => {
|
|
2660
|
+
switch (carrier.kind) {
|
|
2661
|
+
case 'function':
|
|
2662
|
+
return [carrier.functionId];
|
|
2663
|
+
case 'function-family':
|
|
2664
|
+
case 'function-value-family':
|
|
2665
|
+
return carrier.members.length > 0 ? [...new Set(carrier.members)].sort() : null;
|
|
2666
|
+
case 'function-value-dispatch':
|
|
2667
|
+
case 'function-and-constructor':
|
|
2668
|
+
case 'constructor-family':
|
|
2669
|
+
case 'constructor-value-dispatch':
|
|
2670
|
+
case 'generic-function-set':
|
|
2671
|
+
return null;
|
|
2672
|
+
case 'dynamic':
|
|
2673
|
+
return carrier.reason === 'untyped-callable' ? null : undefined;
|
|
2674
|
+
default:
|
|
2675
|
+
return undefined;
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
export const boxDiscriminantsOf = (carrier) => {
|
|
2679
|
+
if (carrier.kind === 'tagged-union') {
|
|
2680
|
+
const parts = carrier.arms.map(boxDiscriminantsOfArm);
|
|
2681
|
+
return parts.some((part) => part === null) ? null : parts.flat();
|
|
2682
|
+
}
|
|
2683
|
+
if (carrier.kind === 'optional') {
|
|
2684
|
+
const payload = boxDiscriminantsOf(carrier.payload);
|
|
2685
|
+
if (payload === null)
|
|
2686
|
+
return null;
|
|
2687
|
+
return [{ tag: carrier.absence === 'null' ? 'Null' : 'Undefined', payload: null, nominal: null, callableMembers: null }, ...payload];
|
|
2688
|
+
}
|
|
2689
|
+
const callableMembers = callableMembershipOf(carrier);
|
|
2690
|
+
// A Function tag and a C++ signature authenticate only callability and an
|
|
2691
|
+
// ABI. They cannot select a source union member. Generic/evaluated callable
|
|
2692
|
+
// arms therefore fail closed; exact functions and closed families carry the
|
|
2693
|
+
// FunctionId tokens installed on their shared Function object.
|
|
2694
|
+
if (callableMembers === null)
|
|
2695
|
+
return null;
|
|
2696
|
+
// The static Ref wrapper recorded in payloadType() is not an object brand:
|
|
2697
|
+
// a Derived allocation can cross the dynamic boundary through Ref<Base>.
|
|
2698
|
+
// Nominal union selection therefore reads the authenticated allocation
|
|
2699
|
+
// family retained by Value, which is the same fact class projection uses.
|
|
2700
|
+
if (carrier.kind === 'class-ref' && carrier.ownership === 'shared-refcount')
|
|
2701
|
+
return [{ tag: 'Object', payload: null, nominal: cppClassName(carrier.declaration), callableMembers: null }];
|
|
2702
|
+
const tag = dynamicTagFor(carrier);
|
|
2703
|
+
if (tag === null)
|
|
2704
|
+
return null;
|
|
2705
|
+
return [
|
|
2706
|
+
{
|
|
2707
|
+
tag,
|
|
2708
|
+
payload: tag === 'Undefined' || tag === 'Null' ? null : cppTypeOf(carrier),
|
|
2709
|
+
nominal: null,
|
|
2710
|
+
callableMembers: callableMembers ?? null
|
|
2711
|
+
}
|
|
2712
|
+
];
|
|
2713
|
+
};
|
|
2714
|
+
/** The representation arm's carried discriminator and the executable classifier must agree. */
|
|
2715
|
+
export const boxDiscriminantsOfArm = (arm) => {
|
|
2716
|
+
// Broad `Function` is an already-dynamic boundary. It has no static ABI or
|
|
2717
|
+
// declaration identity to recover, but its Value tag is executable evidence
|
|
2718
|
+
// against a non-callable arm. A second callable arm shares this exact tag;
|
|
2719
|
+
// the pairwise collision check in unboxedLoadText then rejects the overlap
|
|
2720
|
+
// because this entry deliberately carries no invented FunctionId set.
|
|
2721
|
+
if (arm.runtimeDiscriminator.kind === 'callable-tag') {
|
|
2722
|
+
return arm.value.kind === 'dynamic' && arm.value.reason === 'untyped-callable'
|
|
2723
|
+
? [{ tag: 'Function', payload: null, nominal: null, callableMembers: null }]
|
|
2724
|
+
: null;
|
|
2725
|
+
}
|
|
2726
|
+
const discriminants = boxDiscriminantsOf(arm.value);
|
|
2727
|
+
if (arm.runtimeDiscriminator.kind === 'unverifiable-callable')
|
|
2728
|
+
return null;
|
|
2729
|
+
if (discriminants === null)
|
|
2730
|
+
return null;
|
|
2731
|
+
if (arm.runtimeDiscriminator.kind === 'carrier') {
|
|
2732
|
+
return discriminants.some((entry) => entry.callableMembers !== null) ? null : discriminants;
|
|
2733
|
+
}
|
|
2734
|
+
const expected = [...arm.runtimeDiscriminator.members].sort();
|
|
2735
|
+
if (expected.length === 0)
|
|
2736
|
+
return null;
|
|
2737
|
+
return discriminants.every((entry) => entry.callableMembers !== null && [...entry.callableMembers].sort().join('\0') === expected.join('\0'))
|
|
2738
|
+
? discriminants
|
|
2739
|
+
: null;
|
|
2740
|
+
};
|
|
2741
|
+
/**
|
|
2742
|
+
* The site string a failed unbox aborts with.
|
|
2743
|
+
*
|
|
2744
|
+
* `gea::detail::refusePayloadMismatch` prints this verbatim, and in a release
|
|
2745
|
+
* build it is the ONLY evidence the abort carries: the generated C++ ships with
|
|
2746
|
+
* no debug info, so the backtrace names an enclosing function and nothing more.
|
|
2747
|
+
* A function that unboxes twice then has two indistinguishable aborts, and
|
|
2748
|
+
* finding which one fired costs a rebuild. Naming the carrier the read demanded
|
|
2749
|
+
* makes the message itself the answer.
|
|
2750
|
+
*/
|
|
2751
|
+
const assertionSite = (target) => `an assertion out of a dynamic value to ${cppTypeOf(target)}`;
|
|
2752
|
+
export const unboxedLoadText = (target, text) => {
|
|
2753
|
+
// A `Function` arm remains a Value because its ABI is unknown. Loading it
|
|
2754
|
+
// out of a broader dynamic boundary is an identity-preserving copy guarded
|
|
2755
|
+
// by the Function tag, not an adaptation to an invented signature.
|
|
2756
|
+
if (target.kind === 'dynamic' && target.reason === 'untyped-callable') {
|
|
2757
|
+
return `gea::detail::unboxFunctionValue(${text}, ${cppStringLiteral(assertionSite(target))})`;
|
|
2758
|
+
}
|
|
2759
|
+
// A dynamic Function does not carry one statically recoverable declaration
|
|
2760
|
+
// identity, but an evaluated dispatch needs only callability plus its own
|
|
2761
|
+
// ABI. `DynamicCarrier<CallableObject<...>>::in` checks the Function tag and
|
|
2762
|
+
// either recovers an exact payload or builds the checked argument/result
|
|
2763
|
+
// adapter described in gea_runtime.h. This is deliberately separate from
|
|
2764
|
+
// the generic `unboxValue<T>` below: that exact-payload load cannot adapt a
|
|
2765
|
+
// callable whose source and reader ABIs differ.
|
|
2766
|
+
const callableAbi = callableObjectAbi(target);
|
|
2767
|
+
if (callableAbi !== null) {
|
|
2768
|
+
const type = cppTypeOf(target);
|
|
2769
|
+
const value = 'gea_callable_value';
|
|
2770
|
+
const restFrom = callableAbi.restFrom;
|
|
2771
|
+
const load = restFrom === null
|
|
2772
|
+
? callableAbi.receiver === null
|
|
2773
|
+
? `gea::detail::DynamicCarrier<${type}>::in(${value}, 0)`
|
|
2774
|
+
: `gea::detail::DynamicCarrier<${type}>::inWithReceiver(${value}, 0)`
|
|
2775
|
+
: callableAbi.receiver === null
|
|
2776
|
+
? `gea::detail::DynamicCarrier<${type}>::template inWithRest<${restFrom}>(${value}, 0)`
|
|
2777
|
+
: `gea::detail::DynamicCarrier<${type}>::template inWithReceiverAndRest<${restFrom + 1}>(${value}, 0)`;
|
|
2778
|
+
const members = target.kind === 'function'
|
|
2779
|
+
? [target.functionId]
|
|
2780
|
+
: target.kind === 'function-family' || target.kind === 'function-value-family'
|
|
2781
|
+
? target.members
|
|
2782
|
+
: null;
|
|
2783
|
+
if (members === null)
|
|
2784
|
+
return `[](const gea::Value& ${value}) -> ${type} { return ${load}; }(${text})`;
|
|
2785
|
+
if (members.length === 0)
|
|
2786
|
+
return null;
|
|
2787
|
+
const membership = [...new Set(members)]
|
|
2788
|
+
.sort()
|
|
2789
|
+
.map((member) => `${value}.callableDeclarationIdentity() == gea::detail::callableDeclarationTagFor<&${cppThunkName(member)}>()`)
|
|
2790
|
+
.join(' || ');
|
|
2791
|
+
return (`[](const gea::Value& ${value}) -> ${type} { ` +
|
|
2792
|
+
`if (${value}.tag() != gea::Value::Tag::Function || !(${membership})) ` +
|
|
2793
|
+
`gea::detail::refusePayloadMismatch("an assertion to an authenticated callable"); return ${load}; }(${text})`);
|
|
2794
|
+
}
|
|
2795
|
+
// A class assertion is a checked projection of the authenticated allocation
|
|
2796
|
+
// the box retained, not an exact match against the writer's static Ref<T>
|
|
2797
|
+
// wrapper. That distinction admits a Derived instance carried through an
|
|
2798
|
+
// `any` Base boundary while still refusing an arbitrary object-shaped Value.
|
|
2799
|
+
// Borrowed and owned class carriers never had a box-safe lifetime contract,
|
|
2800
|
+
// so keep them outside this path rather than rendering a plausible cast.
|
|
2801
|
+
if (target.kind === 'class-ref') {
|
|
2802
|
+
if (target.ownership !== 'shared-refcount')
|
|
2803
|
+
return null;
|
|
2804
|
+
// `T | null` never reaches here as an `optional` target at all --
|
|
2805
|
+
// `representation/optional.ts` folds it onto this same bare `class-ref`,
|
|
2806
|
+
// since `gea::Ref<T>` already default-constructs to the JS `null` state
|
|
2807
|
+
// (the identical fact `widenedStoreText`'s own `written.kind === 'null'`
|
|
2808
|
+
// branch relies on for the store direction). A dynamic boundary must
|
|
2809
|
+
// honor that folding on the READ side too: a box tagged `Null` is this
|
|
2810
|
+
// carrier's own absence, not a payload mismatch, so it is read first and
|
|
2811
|
+
// produces the empty `Ref`, exactly as the `optional` branch above reads
|
|
2812
|
+
// its stated absence tag before falling through to the payload load.
|
|
2813
|
+
// Skipping this let `unknown` values that were legitimately `null`
|
|
2814
|
+
// (`x as SomeClass | null`) abort at `unboxClassRef`'s `Tag::Object`
|
|
2815
|
+
// check instead of producing the empty handle the type says they can be.
|
|
2816
|
+
const type = cppTypeOf(target);
|
|
2817
|
+
return `(${text}.tag() == gea::Value::Tag::Null ? ${type}() : gea::detail::unboxClassRef<${cppClassName(target.declaration)}>(${text}, ${cppStringLiteral(assertionSite(target))}))`;
|
|
2818
|
+
}
|
|
2819
|
+
// A sum has no single tag -- which JavaScript type the box holds depends on
|
|
2820
|
+
// the arm that is live -- so `dynamicTagFor` answers `null` for it and this
|
|
2821
|
+
// used to refuse. Refusing here was not a refusal anywhere the caller could
|
|
2822
|
+
// see: `emit-return.ts` fell through to the bare operand text and emitted
|
|
2823
|
+
// `return <gea::Value>;` from a function declared to return
|
|
2824
|
+
// `gea::TaggedUnion<...>`, which certifies, passes preflight, and is
|
|
2825
|
+
// rejected by clang. A JSDoc `@returns {number|string}` on a JS function
|
|
2826
|
+
// whose parameter's call sites disagree is enough to reach it.
|
|
2827
|
+
//
|
|
2828
|
+
// The recipe is the exact mirror of the per-arm boxing chain
|
|
2829
|
+
// `widenedStoreText` renders in the other direction: discriminate on the
|
|
2830
|
+
// BOX's own tag and build the matching arm. Written as a chain rather than a
|
|
2831
|
+
// runtime helper for the same reason boxing is -- each arm needs its
|
|
2832
|
+
// payload's STATIC C++ type, which only an arm-indexed `ofArm<I>` has.
|
|
2833
|
+
if (target.kind === 'tagged-union') {
|
|
2834
|
+
// A union with exactly one boxed arm is a typed sum with a dynamic
|
|
2835
|
+
// REMAINDER: the descriptor `objectDescriptorReturnTypeAt` mints for a
|
|
2836
|
+
// record read through a runtime key is the measured shape -- `number`
|
|
2837
|
+
// for the declared field, and whatever the object's dynamic-property
|
|
2838
|
+
// sidecar holds for any other key. A box whose tag names a typed arm
|
|
2839
|
+
// lands in that arm (a number never lives boxed beside the number arm,
|
|
2840
|
+
// so the union's own equality keeps working), and every other box is
|
|
2841
|
+
// the remainder. Two boxed arms would leave the remainder ambiguous.
|
|
2842
|
+
const boxedArms = target.arms.filter((arm) => arm.value.kind === 'dynamic' && arm.value.reason !== 'untyped-callable');
|
|
2843
|
+
if (boxedArms.length > 1)
|
|
2844
|
+
return null;
|
|
2845
|
+
const catchAll = target.arms.findIndex((arm) => arm.value.kind === 'dynamic' && arm.value.reason !== 'untyped-callable');
|
|
2846
|
+
const arms = target.arms.map((arm, index) => (index === catchAll ? [] : boxDiscriminantsOfArm(arm)));
|
|
2847
|
+
// One arm this backend cannot name a tag for: the box cannot say which
|
|
2848
|
+
// arm is live, and guessing would reinterpret bytes. Refuse, and let the
|
|
2849
|
+
// obligation say so out loud.
|
|
2850
|
+
if (arms.some((arm) => arm === null))
|
|
2851
|
+
return null;
|
|
2852
|
+
const sets = arms;
|
|
2853
|
+
// Two arms sharing a JS tag -- `Response` and `Promise<Response>` both
|
|
2854
|
+
// read `typeof x === 'object'` -- are still distinguishable if they
|
|
2855
|
+
// record DIFFERENT C++ payload types: `Value::payloadType()`, the second
|
|
2856
|
+
// half of the exact check `gea::detail::unboxValue` performs on every
|
|
2857
|
+
// read below. Only when two arms agree on BOTH the tag AND the payload
|
|
2858
|
+
// type is there nothing left to discriminate on; refuse there, the one
|
|
2859
|
+
// case the tag-only check below used to refuse broadly for any collision.
|
|
2860
|
+
for (let i = 0; i < sets.length; i++)
|
|
2861
|
+
for (let j = i + 1; j < sets.length; j++)
|
|
2862
|
+
for (const left of sets[i])
|
|
2863
|
+
for (const right of sets[j])
|
|
2864
|
+
if (left.tag === right.tag && left.payload === right.payload && left.nominal === right.nominal) {
|
|
2865
|
+
if (left.callableMembers === null || right.callableMembers === null)
|
|
2866
|
+
return null;
|
|
2867
|
+
if (left.callableMembers.some((member) => right.callableMembers.includes(member)))
|
|
2868
|
+
return null;
|
|
2869
|
+
}
|
|
2870
|
+
const targetType = cppTypeOf(target);
|
|
2871
|
+
// An arm classifier selects one executable runtime member before its exact
|
|
2872
|
+
// payload load runs. TypeScript union annotations do not license
|
|
2873
|
+
// ToNumber/ToString coercion at this boundary.
|
|
2874
|
+
const loads = target.arms.map((arm, index) => index === catchAll ? text : convertedValueText({ kind: 'dynamic', reason: 'declared-any-never-narrowed' }, arm.value, text));
|
|
2875
|
+
if (loads.some((load) => load === null))
|
|
2876
|
+
return null;
|
|
2877
|
+
const armText = (index) => `${targetType}::ofArm<${index}>(${loads[index]})`;
|
|
2878
|
+
// A tag shared with another arm is not enough to pick this one -- the
|
|
2879
|
+
// payload's own recorded C++ type is the tiebreaker, asked here as a
|
|
2880
|
+
// CONDITION (rather than left to `unboxValue`'s internal abort) so
|
|
2881
|
+
// dispatch lands on the right arm instead of merely refusing the wrong
|
|
2882
|
+
// one once already committed to it.
|
|
2883
|
+
//
|
|
2884
|
+
// An arm claims a SET of tags, not one, because an arm can itself be a
|
|
2885
|
+
// union or an optional -- `number | string | null` reaching a cell typed
|
|
2886
|
+
// `undefined | null | (number | string)` is ordinary TypeScript, and the
|
|
2887
|
+
// outer sum's third arm answers to both `Number` and `String`. Asking for
|
|
2888
|
+
// one tag per arm refused every such shape outright, and refusing here is
|
|
2889
|
+
// invisible to the caller: `emit-return.ts` used to fall through to the
|
|
2890
|
+
// bare operand and hand clang a `gea::Value` where a `gea::TaggedUnion`
|
|
2891
|
+
// was declared. The condition is the disjunction over the arm's own
|
|
2892
|
+
// discriminants, and the recursive load below builds the inner chain.
|
|
2893
|
+
const test = (discriminant) => {
|
|
2894
|
+
if (discriminant.callableMembers !== null) {
|
|
2895
|
+
const membership = discriminant.callableMembers
|
|
2896
|
+
.map((member) => `${text}.callableDeclarationIdentity() == gea::detail::callableDeclarationTagFor<&${cppThunkName(member)}>()`)
|
|
2897
|
+
.join(' || ');
|
|
2898
|
+
return `(${text}.tag() == gea::Value::Tag::Function && (${membership}))`;
|
|
2899
|
+
}
|
|
2900
|
+
return discriminant.nominal !== null
|
|
2901
|
+
? `(${text}.tag() == gea::Value::Tag::Object && ${text}.classObject() && ` +
|
|
2902
|
+
`gea::detail::classIdentityExtends(${text}.classIdentity(), &gea::detail::RefOperationsFor<${discriminant.nominal}>::table))`
|
|
2903
|
+
: discriminant.payload !== null
|
|
2904
|
+
? `(${text}.tag() == gea::Value::Tag::${discriminant.tag} && ` +
|
|
2905
|
+
`${text}.payloadType() == gea::detail::payloadTypeTagFor<${discriminant.payload}>())`
|
|
2906
|
+
: `${text}.tag() == gea::Value::Tag::${discriminant.tag}`;
|
|
2907
|
+
};
|
|
2908
|
+
const condition = (index) => sets[index].map((entry) => test(entry)).join(' || ');
|
|
2909
|
+
// The final arm must not be an implicit fallback. An exact load used to
|
|
2910
|
+
// refuse a nonmatching tag by itself, but a coercive Number/String arm
|
|
2911
|
+
// would otherwise turn (for example) Boolean into whichever arm happens
|
|
2912
|
+
// to be last. A box matching no published member is invalid for this
|
|
2913
|
+
// union and refuses before any member coercion can run.
|
|
2914
|
+
const refused = `([]() -> ${targetType} { gea::detail::refusePayloadMismatch("a dynamic value admitted by no union arm"); }())`;
|
|
2915
|
+
const typed = target.arms.map((_, index) => index).filter((index) => index !== catchAll);
|
|
2916
|
+
let result = catchAll >= 0 ? armText(catchAll) : refused;
|
|
2917
|
+
for (let position = typed.length - 1; position >= 0; position--) {
|
|
2918
|
+
const index = typed[position];
|
|
2919
|
+
result =
|
|
2920
|
+
position === typed.length - 1 && catchAll < 0
|
|
2921
|
+
? `${condition(index)} ? ${armText(index)} : ${result}`
|
|
2922
|
+
: `${condition(index)} ? ${armText(index)} : (${result})`;
|
|
2923
|
+
}
|
|
2924
|
+
return typed.length > 1 || (typed.length === 1 && catchAll >= 0) ? `(${result})` : result;
|
|
2925
|
+
}
|
|
2926
|
+
// `gea::Optional<T>` read straight out of a box: the tag decides which of
|
|
2927
|
+
// the two states the box holds, exactly the way `dynamicTagFor` already
|
|
2928
|
+
// decides it for the boxing direction (`Value::box` writes `Tag::Null`/
|
|
2929
|
+
// `Tag::Undefined` for the two absent values and nothing else needs
|
|
2930
|
+
// recording, since an absent optional carries no payload). A box tagged
|
|
2931
|
+
// with the OTHER absent value, or any present value, unboxes the payload
|
|
2932
|
+
// exactly as a bare (non-optional) target would -- the whole reason this is
|
|
2933
|
+
// an `if`, not a further tag branch, is that only ONE tag reads as empty and
|
|
2934
|
+
// every other live tag still has to reach the ordinary payload load below.
|
|
2935
|
+
if (target.kind === 'optional') {
|
|
2936
|
+
// The wrapper owns only its stated absence. Every other dynamic tag is an
|
|
2937
|
+
// exact assertion into its payload; `null` must not become an absent
|
|
2938
|
+
// `undefined` optional (or vice versa), and a typed payload must never
|
|
2939
|
+
// acquire ToNumber/ToString semantics merely by crossing this boundary.
|
|
2940
|
+
const payload = target.payload.kind === 'dynamic' ? text : unboxedLoadText(target.payload, text);
|
|
2941
|
+
if (payload === null)
|
|
2942
|
+
return null;
|
|
2943
|
+
const targetType = cppTypeOf(target);
|
|
2944
|
+
const absentTag = target.absence === 'null' ? 'Null' : 'Undefined';
|
|
2945
|
+
return `(${text}.tag() == gea::Value::Tag::${absentTag} ? ${targetType}() : ${targetType}(${payload}))`;
|
|
2946
|
+
}
|
|
2947
|
+
// A dynamic container commonly carries dynamic elements even when the
|
|
2948
|
+
// assertion names a typed Array. BSON deserialization is the concrete case:
|
|
2949
|
+
// it builds `Array<Value>` because each element is decided by the wire type,
|
|
2950
|
+
// then its public result is asserted as `string[]`, `Document[]`, and so on.
|
|
2951
|
+
// The runtime bridge preserves an exact typed payload by identity and
|
|
2952
|
+
// otherwise rebuilds it element by element through `DynamicCarrier`, so the
|
|
2953
|
+
// result is a native `ArrayObject<Element>` and a bad element refuses rather
|
|
2954
|
+
// than being reinterpreted or leaving the whole Array boxed.
|
|
2955
|
+
if (target.kind === 'array-object') {
|
|
2956
|
+
return `gea::detail::unboxDynamicArray<${cppTypeOf(target.element)}>(${text}, ${cppStringLiteral(assertionSite(target))})`;
|
|
2957
|
+
}
|
|
2958
|
+
if (target.kind === 'dictionary' && target.key === 'string' && target.ownership === 'shared-refcount') {
|
|
2959
|
+
return `gea::detail::unboxDynamicDictionary<${cppTypeOf(target.value)}>(${text}, ${cppStringLiteral(assertionSite(target))})`;
|
|
2960
|
+
}
|
|
2961
|
+
// An open document deserializer cannot know the concrete interface a later
|
|
2962
|
+
// assertion will name, so it stores a native `Dictionary<Value>`. Requiring
|
|
2963
|
+
// that payload to already be the target record's C++ struct rejects valid
|
|
2964
|
+
// BSON/JSON document assertions. Rebuild the declared record from checked
|
|
2965
|
+
// dynamic field reads instead. The exact-payload branch preserves identity
|
|
2966
|
+
// when the box already carries this record; the fallback evaluates the
|
|
2967
|
+
// source once and leaves extra dictionary keys outside the statically
|
|
2968
|
+
// declared result, matching the assertion's structural view.
|
|
2969
|
+
if (target.kind === 'record' && target.accessors.length === 0) {
|
|
2970
|
+
if (target.ownership === 'borrowed')
|
|
2971
|
+
return null;
|
|
2972
|
+
const targetType = cppTypeOf(target);
|
|
2973
|
+
const holder = 'gea_dynamic_record';
|
|
2974
|
+
const fields = target.fields.map((field, index) => {
|
|
2975
|
+
const present = `gea_dynamic_record_present_${index}`;
|
|
2976
|
+
const value = `gea_dynamic_record_value_${index}`;
|
|
2977
|
+
const load = field.value.kind === 'dynamic' ? value : unboxedLoadText(field.value, value);
|
|
2978
|
+
return { field, present, value, load };
|
|
2979
|
+
});
|
|
2980
|
+
if (fields.some((field) => field.load === null))
|
|
2981
|
+
return null;
|
|
2982
|
+
const checks = fields
|
|
2983
|
+
.filter(({ field }) => field.required)
|
|
2984
|
+
.map(({ field, present }) => `if (!${present}) gea::detail::refusePayloadMismatch("a dynamic record lacks required field ${field.key}");`);
|
|
2985
|
+
const values = fields.map(({ field, present, value }) => `const gea::Value ${value} = ${present} ? gea::detail::dynamicRecordField(${holder}, ${cppStringLiteral(field.key)}) : gea::Value();`);
|
|
2986
|
+
const reads = fields.map(({ field, present, load }) => {
|
|
2987
|
+
const materialized = load;
|
|
2988
|
+
return field.required ? materialized : `(${present} ? ${materialized} : ${cppTypeOf(field.value)}{})`;
|
|
2989
|
+
});
|
|
2990
|
+
const presences = fields.filter(({ field }) => !field.required).map(({ present }) => present);
|
|
2991
|
+
const structure = `${cppRecordStructName(target.shapeId)}{${[...reads, ...presences].join(', ')}}`;
|
|
2992
|
+
const built = target.ownership === 'shared-refcount' ? `gea::makeRef<${cppRecordStructName(target.shapeId)}>(${structure})` : structure;
|
|
2993
|
+
const exactPayload = target.ownership === 'shared-refcount'
|
|
2994
|
+
? `if (${holder}.tag() == gea::Value::Tag::Object && ${holder}.payloadType() == gea::detail::payloadTypeTagFor<${targetType}>()) ` +
|
|
2995
|
+
`return gea::detail::unboxValue<${targetType}>(${holder}, gea::Value::Tag::Object, ${cppStringLiteral(assertionSite(target))}); `
|
|
2996
|
+
: '';
|
|
2997
|
+
return (`[](const gea::Value& ${holder}) -> ${targetType} { ` +
|
|
2998
|
+
exactPayload +
|
|
2999
|
+
`if (${holder}.tag() != gea::Value::Tag::Object && ${holder}.tag() != gea::Value::Tag::Function) ` +
|
|
3000
|
+
`gea::detail::refusePayloadMismatch("an assertion to a record requires an object"); ` +
|
|
3001
|
+
`${fields.map(({ field, present }) => `const bool ${present} = gea::detail::dynamicRecordHasField(${holder}, ${cppStringLiteral(field.key)});`).join(' ')} ` +
|
|
3002
|
+
`${checks.join(' ')} ${values.join(' ')} return ${built}; }(${text})`);
|
|
3003
|
+
}
|
|
3004
|
+
// The exact inverse of the boxing table, asked of the same function, so a
|
|
3005
|
+
// carrier can never be boxed under one tag and read back under another.
|
|
3006
|
+
const tag = dynamicTagFor(target);
|
|
3007
|
+
if (tag === null)
|
|
3008
|
+
return null;
|
|
3009
|
+
// `undefined` has no payload to read out of the box at all -- the tag IS
|
|
3010
|
+
// the whole value -- so this is a verify-then-produce, never a load:
|
|
3011
|
+
// `gea::detail::unboxUndefinedValue` checks the tag alone (there is no
|
|
3012
|
+
// payload-type half of the check `unboxValue` performs for everything
|
|
3013
|
+
// else, because there is no payload to have recorded one for) and hands
|
|
3014
|
+
// back the one value `undefined` ever is, `gea::Undefined{}`
|
|
3015
|
+
// (`cppConstantLiteral`'s own spelling, unreachable from here without a
|
|
3016
|
+
// `ConstantLiteral` this function was never handed).
|
|
3017
|
+
if (tag === 'Undefined')
|
|
3018
|
+
return `gea::detail::unboxUndefinedValue(${text}, ${cppStringLiteral(assertionSite(target))})`;
|
|
3019
|
+
// `null` is the same verify-then-produce, and this used to refuse it on a
|
|
3020
|
+
// false premise ("neither has a C++ value this read can produce"). No
|
|
3021
|
+
// PAYLOAD is not no VALUE: `null`'s carrier is `std::nullptr_t`, whose one
|
|
3022
|
+
// inhabitant is `nullptr`, so the tag check plus that inhabitant is a
|
|
3023
|
+
// complete read. BigInt has an ordinary payload: `Value::box` records the
|
|
3024
|
+
// `gea::BigInt` type and `unboxValue` checks it like every other scalar.
|
|
3025
|
+
if (tag === 'Null')
|
|
3026
|
+
return `gea::detail::unboxNullValue(${text}, ${cppStringLiteral(assertionSite(target))})`;
|
|
3027
|
+
// `gea::detail::unboxValue` checks BOTH the tag and the payload's recorded
|
|
3028
|
+
// C++ type (`payloadTypeTagFor`), so a box holding a different struct than
|
|
3029
|
+
// the narrowing claimed refuses by name instead of reinterpreting bytes.
|
|
3030
|
+
// That is what makes reading an object out of a box safe here at all: the
|
|
3031
|
+
// tag alone says only "some object".
|
|
3032
|
+
return `gea::detail::unboxValue<${cppTypeOf(target)}>(${text}, gea::Value::Tag::${tag}, ${cppStringLiteral(assertionSite(target))})`;
|
|
3033
|
+
};
|
|
3034
|
+
/**
|
|
3035
|
+
* `generic-function-set` into a superset: each source index becomes the
|
|
3036
|
+
* target's index of the same member. A one-member source is a constant; a
|
|
3037
|
+
* wider one is a table lookup. `null` when the pair is not a set widening
|
|
3038
|
+
* (a member the target lacks is a narrowing, and no store performs one).
|
|
3039
|
+
*/
|
|
3040
|
+
export const genericFunctionSetWideningText = (source, target, text) => {
|
|
3041
|
+
if (target.kind !== 'generic-function-set')
|
|
3042
|
+
return null;
|
|
3043
|
+
// A member's own name, read as a callable: the one member is index 0. The
|
|
3044
|
+
// callable's text is a cell read with no effect to keep.
|
|
3045
|
+
if (source.kind !== 'generic-function-set') {
|
|
3046
|
+
return (source.kind === 'function' ||
|
|
3047
|
+
source.kind === 'function-family' ||
|
|
3048
|
+
source.kind === 'function-value-family' ||
|
|
3049
|
+
source.kind === 'function-value-dispatch') &&
|
|
3050
|
+
target.members.length === 1
|
|
3051
|
+
? 'static_cast<std::uint8_t>(0)'
|
|
3052
|
+
: null;
|
|
3053
|
+
}
|
|
3054
|
+
const indexes = source.members.map((member) => target.members.indexOf(member));
|
|
3055
|
+
if (indexes.some((index) => index < 0))
|
|
3056
|
+
return null;
|
|
3057
|
+
if (indexes.every((index, position) => index === position))
|
|
3058
|
+
return text;
|
|
3059
|
+
if (indexes.length === 1)
|
|
3060
|
+
return `static_cast<std::uint8_t>(${indexes[0]})`;
|
|
3061
|
+
return `([&]() -> std::uint8_t { static constexpr std::uint8_t gea_set_remap[] = {${indexes.join(', ')}}; return gea_set_remap[${text}]; }())`;
|
|
3062
|
+
};
|
|
3063
|
+
/**
|
|
3064
|
+
* `never[]` read where `E[]` is wanted: the one empty array of `E`. See
|
|
3065
|
+
* `conversions.ts`'s `gea::emptyArraySentinel` recipe for why identity, not a
|
|
3066
|
+
* fresh `[]`, is the answer, and which pairs it admits. The dynamic target
|
|
3067
|
+
* decides at runtime (`emptyArraySentinelOrBoxed`): an empty source is the
|
|
3068
|
+
* sentinel, a populated one -- a real `undefined[]`, which the carrier cannot
|
|
3069
|
+
* tell apart -- is boxed element by element.
|
|
3070
|
+
*/
|
|
3071
|
+
export const emptyArraySentinelText = (source, target, text) => {
|
|
3072
|
+
if (source.kind !== 'array-object' || target.kind !== 'array-object' || source.element.kind !== 'undefined')
|
|
3073
|
+
return null;
|
|
3074
|
+
if (source.ownership !== 'shared-refcount' || target.ownership !== 'shared-refcount')
|
|
3075
|
+
return null;
|
|
3076
|
+
if (target.element.kind === 'dynamic')
|
|
3077
|
+
return `gea::emptyArraySentinelOrBoxed(${text})`;
|
|
3078
|
+
if (carriesUndefined(target.element))
|
|
3079
|
+
return null;
|
|
3080
|
+
return `gea::emptyArraySentinel<${cppTypeOf(target.element)}>(${text})`;
|
|
3081
|
+
};
|
|
3082
|
+
/**
|
|
3083
|
+
* Any callable read where a function IDENTITY is wanted -- the carrier of
|
|
3084
|
+
* `(...args: never[]) => R`, TypeScript's `AnyFunction`.
|
|
3085
|
+
*
|
|
3086
|
+
* Nothing is fabricated: `gea::CallableObject` already stores a
|
|
3087
|
+
* `Ref<FunctionObjectIdentity>` beside its thunk, precisely so that copying or
|
|
3088
|
+
* adapting a callable cannot mint a second ECMAScript identity, and this reads
|
|
3089
|
+
* that field. Offered for the carriers `cppTypeOf` spells as a `CallableObject`
|
|
3090
|
+
* or a `CallableConstructorObject`; `gea::ConstructorObject` carries no such
|
|
3091
|
+
* field, so the constructor-only kinds keep the refusal rather than get a
|
|
3092
|
+
* fabricated one.
|
|
3093
|
+
*
|
|
3094
|
+
* There is deliberately no reverse: an identity carries no calling convention,
|
|
3095
|
+
* so a call through it has nothing to use and must refuse.
|
|
3096
|
+
*/
|
|
3097
|
+
const callableIdentityText = (source, target, text) => {
|
|
3098
|
+
if (target.kind !== 'callable-identity')
|
|
3099
|
+
return null;
|
|
3100
|
+
switch (source.kind) {
|
|
3101
|
+
case 'function':
|
|
3102
|
+
case 'function-family':
|
|
3103
|
+
case 'function-value-family':
|
|
3104
|
+
case 'function-value-dispatch':
|
|
3105
|
+
case 'function-and-constructor':
|
|
3106
|
+
return `(${text}).functionObjectIdentity()`;
|
|
3107
|
+
default:
|
|
3108
|
+
return null;
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
/**
|
|
3112
|
+
* A call-only function object viewed under a type that ALSO names its
|
|
3113
|
+
* `[[Construct]]` -- `Factory as typeof Factory & (new (v: number) => T)`.
|
|
3114
|
+
*
|
|
3115
|
+
* Nothing is rebuilt. ECMA-262 gives a pre-`class` constructor function both
|
|
3116
|
+
* internal methods when it is created; the assertion changes only which of
|
|
3117
|
+
* them the program may name, so the widened view keeps the source's
|
|
3118
|
+
* environment, its owner and its `FunctionObjectIdentity` -- one own-property
|
|
3119
|
+
* table, one `===` answer. `gea::withConstructEntry` is that one step.
|
|
3120
|
+
*
|
|
3121
|
+
* The construct entry is looked up at run time by the source's own invoke
|
|
3122
|
+
* pointer, published beside the construct thunk
|
|
3123
|
+
* (`translation-unit.ts`'s `constructThunkOf`). That keeps the conversion a
|
|
3124
|
+
* property of the VALUE: this step never has to decide which declaration a
|
|
3125
|
+
* cell holds, and a function whose declaration published no such entry refuses
|
|
3126
|
+
* by name instead of constructing through another body.
|
|
3127
|
+
*
|
|
3128
|
+
* The call halves must be the SAME frame. An adapted one is a different
|
|
3129
|
+
* function object, and the identity this preserves is the whole point.
|
|
3130
|
+
*/
|
|
3131
|
+
const callableConstructEntryText = (source, target, text) => {
|
|
3132
|
+
if (target.kind !== 'function-and-constructor')
|
|
3133
|
+
return null;
|
|
3134
|
+
const call = source.kind === 'function' ||
|
|
3135
|
+
source.kind === 'function-family' ||
|
|
3136
|
+
source.kind === 'function-value-family' ||
|
|
3137
|
+
source.kind === 'function-value-dispatch'
|
|
3138
|
+
? source.abi
|
|
3139
|
+
: null;
|
|
3140
|
+
if (call === null || abiKey(call) !== abiKey(target.call))
|
|
3141
|
+
return null;
|
|
3142
|
+
return `gea::withConstructEntry<${cppTypeOf(target)}>(${text})`;
|
|
3143
|
+
};
|
|
3144
|
+
const claimed = (text) => (text === null ? undefined : text);
|
|
3145
|
+
/**
|
|
3146
|
+
* The chain, in the order it has always run. Each entry is one recipe with a
|
|
3147
|
+
* stable id; `conversionRecipeOf` names the entry that claims a pair without
|
|
3148
|
+
* rendering it, so the conversion census (`conversion/nodes.ts`) can state
|
|
3149
|
+
* the same answer this printer gives, from the same table.
|
|
3150
|
+
*/
|
|
3151
|
+
export const conversionChain = [
|
|
3152
|
+
{ id: 'native-sum-widening', apply: (source, target, text) => claimed(widenedNativeSumText(source, target, text)) },
|
|
3153
|
+
// A choice among generic functions widening into a superset: the index is
|
|
3154
|
+
// remapped, member by member. Before the identical-spelling identity below,
|
|
3155
|
+
// which would pass the SOURCE's index through unchanged -- both sets are one
|
|
3156
|
+
// `std::uint8_t`, and `0` means a different member in each.
|
|
3157
|
+
{ id: 'generic-set-remap', apply: (source, target, text) => claimed(genericFunctionSetWideningText(source, target, text)) },
|
|
3158
|
+
// Two carriers this backend spells identically convert by identity. The
|
|
3159
|
+
// representation tells them apart for reasons the C++ type does not carry --
|
|
3160
|
+
// `constructor-family` names the class its `new` reaches and
|
|
3161
|
+
// `constructor-value-dispatch` does not -- but both are one
|
|
3162
|
+
// `gea::ConstructorObject<...>`, and a value of one already IS a value of the
|
|
3163
|
+
// other. Checking the spelling rather than enumerating the pairs is what keeps
|
|
3164
|
+
// this from becoming a second table that could disagree with `cppTypeOf`.
|
|
3165
|
+
{ id: 'same-cpp-type', apply: (source, target, text) => (cppTypeOf(source) === cppTypeOf(target) ? text : undefined) },
|
|
3166
|
+
{ id: 'empty-array-sentinel', apply: (source, target, text) => claimed(emptyArraySentinelText(source, target, text)) },
|
|
3167
|
+
{ id: 'callable-identity', apply: (source, target, text) => claimed(callableIdentityText(source, target, text)) },
|
|
3168
|
+
{ id: 'callable-construct-entry', apply: (source, target, text) => claimed(callableConstructEntryText(source, target, text)) },
|
|
3169
|
+
{
|
|
3170
|
+
id: 'constructor-upcast',
|
|
3171
|
+
apply: (source, target, text) => {
|
|
3172
|
+
const member = constructorUpcastMember(source, target);
|
|
3173
|
+
return member === null ? undefined : `gea::upcastConstructor<${cppTypeOf(target)}, &${cppConstructThunkName(member)}>(${text})`;
|
|
3174
|
+
}
|
|
3175
|
+
},
|
|
3176
|
+
{
|
|
3177
|
+
// The same store when the two conventions also disagree on a parameter
|
|
3178
|
+
// (`global.Request = LightweightRequest`, whose `input` names its own class
|
|
3179
|
+
// where the base's names the base): the wrapper converts each argument the
|
|
3180
|
+
// base convention hands it into the one the derived thunk takes.
|
|
3181
|
+
id: 'constructor-adapter',
|
|
3182
|
+
apply: (source, target, text) => {
|
|
3183
|
+
if (source.kind !== 'constructor-family' || target.kind !== 'constructor-family')
|
|
3184
|
+
return undefined;
|
|
3185
|
+
const [member] = source.members;
|
|
3186
|
+
if (member === undefined || source.members.length !== 1 || !target.members.includes(member))
|
|
3187
|
+
return undefined;
|
|
3188
|
+
if (source.abi.receiver !== null || target.abi.receiver !== null)
|
|
3189
|
+
return undefined;
|
|
3190
|
+
if (source.abi.restFrom !== target.abi.restFrom || source.abi.parameters.length !== target.abi.parameters.length)
|
|
3191
|
+
return undefined;
|
|
3192
|
+
if (classRefTransportKind(source.abi.result, target.abi.result) !== 'upcast')
|
|
3193
|
+
return undefined;
|
|
3194
|
+
const formals = [];
|
|
3195
|
+
const actuals = [];
|
|
3196
|
+
for (const [index, parameter] of target.abi.parameters.entries()) {
|
|
3197
|
+
const slot = source.abi.parameters[index];
|
|
3198
|
+
const name = `gea_argument_${index}`;
|
|
3199
|
+
formals.push(`${cppAbiParameterType(parameter)} ${name}`);
|
|
3200
|
+
if (cppAbiParameterType(parameter) === cppAbiParameterType(slot)) {
|
|
3201
|
+
actuals.push(`std::forward<${cppAbiParameterType(parameter)}>(${name})`);
|
|
3202
|
+
continue;
|
|
3203
|
+
}
|
|
3204
|
+
const converted = tryCandidateText(() => convertedValueText(parameter.value, slot.value, name));
|
|
3205
|
+
if (converted === null)
|
|
3206
|
+
return undefined;
|
|
3207
|
+
actuals.push(converted);
|
|
3208
|
+
}
|
|
3209
|
+
const result = cppResultTypeOf(target.abi.result);
|
|
3210
|
+
return (`[](const ${cppTypeOf(source)}& gea_from) { ${cppTypeOf(target)} gea_to; ` +
|
|
3211
|
+
`gea_to.construct_ = +[](void* gea_environment${formals.map((formal) => `, ${formal}`).join('')}) -> ${result} { ` +
|
|
3212
|
+
`return ${result}(${cppConstructThunkName(member)}(gea_environment${actuals.map((actual) => `, ${actual}`).join('')})); }; ` +
|
|
3213
|
+
`gea_to.environment = gea_from.environment; gea_to.environmentOwner = gea_from.environmentOwner; return gea_to; }(${text})`);
|
|
3214
|
+
}
|
|
3215
|
+
},
|
|
3216
|
+
{ id: 'regexp-match-array-base', apply: (source, target, text) => claimed(regexpMatchArrayBaseText(source, target, text)) },
|
|
3217
|
+
// A derived class-ref stored where a base class-ref is declared, rendered as
|
|
3218
|
+
// an EXPLICIT construction of the target. Checks the same ancestry direction as
|
|
3219
|
+
// the conversion authority: this chain is also queried speculatively while
|
|
3220
|
+
// matching union arms, where a BASE arm and a DERIVED target do not imply the
|
|
3221
|
+
// base value survived the control-flow guard. Treating that downcast as a store
|
|
3222
|
+
// would make a union recast consume an arm the narrowing excluded, then ask
|
|
3223
|
+
// `gea::Ref` for a constructor C++ does not provide. A real base-to-derived
|
|
3224
|
+
// narrowing falls through to `narrowedLoadText`'s checked downcast.
|
|
3225
|
+
{
|
|
3226
|
+
id: 'class-upcast',
|
|
3227
|
+
apply: (source, target, text) => source.kind === 'class-ref' &&
|
|
3228
|
+
target.kind === 'class-ref' &&
|
|
3229
|
+
source.ownership === target.ownership &&
|
|
3230
|
+
source.ancestors.includes(target.declaration)
|
|
3231
|
+
? `${cppTypeOf(target)}(${text})`
|
|
3232
|
+
: undefined
|
|
3233
|
+
},
|
|
3234
|
+
// A host handle stored where one of its own base types is declared, the same
|
|
3235
|
+
// rule as `class-upcast` above for a hierarchy this compiler did not lay out.
|
|
3236
|
+
// An `NSStackView` IS an `NSView`: the host stated the inheritance
|
|
3237
|
+
// (`PluginCapabilities.nativeBases`, carried here on the carrier itself) and
|
|
3238
|
+
// the generated bridge gives the wrapper structs that same inheritance, so
|
|
3239
|
+
// the target performs this one implicitly and for free. Rendered as an
|
|
3240
|
+
// explicit construction of the base for `class-upcast`'s reason -- this chain
|
|
3241
|
+
// is also probed speculatively while matching union arms, where the opposite
|
|
3242
|
+
// direction is a downcast no store may perform, and the ancestry check is
|
|
3243
|
+
// what tells the two apart.
|
|
3244
|
+
//
|
|
3245
|
+
// Before this step the pair had no node in the conversion census, so every
|
|
3246
|
+
// program that passed a derived host handle to a base-typed parameter was
|
|
3247
|
+
// refused by the certificate ("no runtime conversion is installed") while the
|
|
3248
|
+
// printer sitting behind it had known how to render it all along.
|
|
3249
|
+
{
|
|
3250
|
+
id: 'native-handle-upcast',
|
|
3251
|
+
apply: (source, target, text) => source.kind === 'native-handle' && target.kind === 'native-handle' && target.native !== null && source.bases.includes(target.native)
|
|
3252
|
+
? `${cppTypeOf(target)}(${text})`
|
|
3253
|
+
: undefined
|
|
3254
|
+
},
|
|
3255
|
+
{ id: 'callable-part', apply: (source, target, text) => claimed(callablePartText(source, target, text)) },
|
|
3256
|
+
// An EMPTY array literal flowing into a native array-like carrier. `[]` with
|
|
3257
|
+
// no contextual array type checks as the empty tuple, which derives a record
|
|
3258
|
+
// with no fields; `(path.match(/.../g) || [])` is the shape. A
|
|
3259
|
+
// value-initialized native handle IS what the source names -- an array-like
|
|
3260
|
+
// holding nothing -- so the conversion mints one rather than refusing a program
|
|
3261
|
+
// the checker already proved compatible. Gated on the source declaring NOTHING:
|
|
3262
|
+
// a record with even one field would be dropping data.
|
|
3263
|
+
{
|
|
3264
|
+
id: 'empty-record-into-native',
|
|
3265
|
+
apply: (source, target) => source.kind === 'record' && source.fields.length === 0 && source.accessors.length === 0 && target.kind === 'native-record-ref'
|
|
3266
|
+
? target.ownership === 'shared-refcount'
|
|
3267
|
+
? `gea::makeRef<${target.native ?? cppTypeOf(target, 'owned')}>()`
|
|
3268
|
+
: `${cppTypeOf(target, 'owned')}{}`
|
|
3269
|
+
: undefined
|
|
3270
|
+
},
|
|
3271
|
+
// The next five are implicit converting constructors `gea::CallableObject`
|
|
3272
|
+
// declares, so the identity text renders each -- see each predicate's own doc
|
|
3273
|
+
// for why that is sound. `resultAdaptedCallableText` after them is the pair
|
|
3274
|
+
// that is NOT an implicit constructor: same frame, a result the header cannot
|
|
3275
|
+
// convert on its own (`resultAdapterOf`).
|
|
3276
|
+
{ id: 'callable-drops-unbound-parameters', apply: (source, target, text) => (dropsUnboundParameters(source, target) ? text : undefined) },
|
|
3277
|
+
{
|
|
3278
|
+
id: 'callable-drops-all-parameters-into-result-arm',
|
|
3279
|
+
apply: (source, target, text) => (dropsAllParametersIntoResultArm(source, target) ? text : undefined)
|
|
3280
|
+
},
|
|
3281
|
+
{ id: 'callable-widens-result-into-arm', apply: (source, target, text) => (widensResultIntoArm(source, target) ? text : undefined) },
|
|
3282
|
+
{
|
|
3283
|
+
id: 'callable-discards-result-into-void',
|
|
3284
|
+
apply: (source, target, text) => (discardsResultIntoVoid(source, target) ? text : undefined)
|
|
3285
|
+
},
|
|
3286
|
+
{ id: 'callable-rebases-rest', apply: (source, target, text) => (rebasesRestOverLeadingParameters(source, target) ? text : undefined) },
|
|
3287
|
+
{ id: 'result-adapted-callable', apply: (source, target, text) => claimed(resultAdaptedCallableText(source, target, text)) },
|
|
3288
|
+
// `gea::Optional<T>` declares a converting constructor from `T`, so a present
|
|
3289
|
+
// payload widens by being written where the optional is expected.
|
|
3290
|
+
{
|
|
3291
|
+
id: 'optional-wrap-identity',
|
|
3292
|
+
apply: (source, target, text) => target.kind === 'optional' && representationKey(target.payload) === representationKey(source) ? text : undefined
|
|
3293
|
+
},
|
|
3294
|
+
// A DYNAMIC source must be asked whether the box itself is empty before any
|
|
3295
|
+
// payload conversion happens. Falling through to the payload-only wrap below
|
|
3296
|
+
// rendered an unconditional `unboxValue<T>` that C++ then widened into the
|
|
3297
|
+
// optional, reading the box as if it could never be empty: `asNullable(null)`
|
|
3298
|
+
// for a declared `string | null` return certified, compiled, and aborted at
|
|
3299
|
+
// runtime inside `unboxValue`.
|
|
3300
|
+
{
|
|
3301
|
+
id: 'optional-from-dynamic',
|
|
3302
|
+
apply: (source, target, text) => (target.kind === 'optional' && source.kind === 'dynamic' ? unboxedLoadText(target, text) : undefined)
|
|
3303
|
+
},
|
|
3304
|
+
// A source that is ITSELF optional keeps its own presence here: absent stays
|
|
3305
|
+
// absent, present converts one payload down, and the empty branch is a
|
|
3306
|
+
// default-constructed target. Unwrapping the target and converting into the
|
|
3307
|
+
// payload alone rendered an unconditional `(*text)`, and `flag && image` over
|
|
3308
|
+
// an `Img | null` segfaulted on `null` having certified clean. It is the ONLY
|
|
3309
|
+
// answer for an optional source: a payload this cannot convert refuses rather
|
|
3310
|
+
// than falling through, because the fallthrough was that same unwrap one frame
|
|
3311
|
+
// deeper (hono's `c.json` answered `HTTP/1.1 0 unknown` through it).
|
|
3312
|
+
{
|
|
3313
|
+
id: 'optional-payload-convert',
|
|
3314
|
+
apply: (source, target, text) => {
|
|
3315
|
+
if (target.kind !== 'optional' || source.kind !== 'optional')
|
|
3316
|
+
return undefined;
|
|
3317
|
+
const payload = convertedValueText(source.payload, target.payload, `(*${text})`);
|
|
3318
|
+
if (payload === null)
|
|
3319
|
+
return null;
|
|
3320
|
+
return `(${text}.has_value() ? ${cppTypeOf(target)}{${cppTypeOf(target.payload)}{${payload}}} : ${cppTypeOf(target)}{})`;
|
|
3321
|
+
}
|
|
3322
|
+
},
|
|
3323
|
+
{
|
|
3324
|
+
id: 'union-into-optional-payload',
|
|
3325
|
+
apply: (source, target, text) => target.kind === 'optional' && source.kind === 'tagged-union'
|
|
3326
|
+
? claimed(sumIntoPayloadText(source, target.payload, text, (arm) => `${cppTypeOf(target)}{${cppTypeOf(target.payload)}{${arm}}}`))
|
|
3327
|
+
: undefined
|
|
3328
|
+
},
|
|
3329
|
+
// A UNION source that still carries the target's own absence as a bare arm
|
|
3330
|
+
// has proven nothing about presence: `number | string | null | undefined`
|
|
3331
|
+
// narrowed past the `null` guard alone is `optional(number | string,
|
|
3332
|
+
// undefined)`, and the undefined arm is as live as either payload arm. The
|
|
3333
|
+
// payload-only wrap below would load the payload's arm unconditionally into a
|
|
3334
|
+
// PRESENT optional, so `value === undefined` never held. `narrowedLoadText`'s
|
|
3335
|
+
// optional case tests the absent arm; a payload none of the arms carries whole
|
|
3336
|
+
// makes it refuse, and that refusal is not an answer.
|
|
3337
|
+
{
|
|
3338
|
+
id: 'union-absence-narrow',
|
|
3339
|
+
apply: (source, target, text) => target.kind === 'optional' && source.kind === 'tagged-union' && source.arms.some((arm) => arm.value.kind === target.absence)
|
|
3340
|
+
? claimed(tryCandidateText(() => narrowedLoadText(source, target, text)))
|
|
3341
|
+
: undefined
|
|
3342
|
+
},
|
|
3343
|
+
// A source that CONVERTS to the payload: `((gl, v) => void)` written where
|
|
3344
|
+
// `((gl, v, textures) => void) | undefined` is expected. The payload is named
|
|
3345
|
+
// EXPLICITLY rather than letting two constructors chain, because C++ permits
|
|
3346
|
+
// one user-defined conversion per implicit sequence. Both constructions are
|
|
3347
|
+
// spelled so the target's presence carrier survives: a narrowed
|
|
3348
|
+
// `Error | null | undefined` must remain `Optional<Error>` after the undefined
|
|
3349
|
+
// guard so a following truthiness test can read its presence flag.
|
|
3350
|
+
{
|
|
3351
|
+
id: 'optional-wrap-converted',
|
|
3352
|
+
apply: (source, target, text) => {
|
|
3353
|
+
if (target.kind !== 'optional')
|
|
3354
|
+
return undefined;
|
|
3355
|
+
const payload = convertedValueText(source, target.payload, text);
|
|
3356
|
+
return payload !== null ? `${cppTypeOf(target)}{${cppTypeOf(target.payload)}{${payload}}}` : undefined;
|
|
3357
|
+
}
|
|
3358
|
+
},
|
|
3359
|
+
// Two real sums, neither one arm of the other: `narrowedLoadText` would
|
|
3360
|
+
// misread the target as an arm value to search FOR inside the source and
|
|
3361
|
+
// refuse. A proper subset is control-flow narrowing, not a recast, and renders
|
|
3362
|
+
// from the TARGET arms so arms excluded by the guard never appear: a recast
|
|
3363
|
+
// searches for a home for every source arm and would emit a downcast of a base
|
|
3364
|
+
// arm the guard proved dead.
|
|
3365
|
+
//
|
|
3366
|
+
// Admission is `narrowingReachesTarget` (`conversion/build.ts`) -- the same
|
|
3367
|
+
// recursive reachability the census's own registry admission already asks
|
|
3368
|
+
// (`conversions.ts`'s `narrowing` sub-union branch) -- and not a flat
|
|
3369
|
+
// top-level key-set comparison, because a NARROWING can be NESTED: `typeof
|
|
3370
|
+
// value === 'string'` proven false inside `undefined | null |
|
|
3371
|
+
// (string|number|bigint|boolean|symbol|object|function)` leaves the same
|
|
3372
|
+
// three-arm outer shape with a smaller seven-minus-one inner union, which
|
|
3373
|
+
// shares no top-level key with its target at all. The flat check refused
|
|
3374
|
+
// that pair and let `union-recast` answer instead, which searches for a
|
|
3375
|
+
// home for the whole nested arm as ONE value; finding none, it fell through
|
|
3376
|
+
// `narrowedLoadText`'s per-leaf search into `unreachable-value`'s
|
|
3377
|
+
// dead-branch discard, which cannot tell "this leaf is provably absent"
|
|
3378
|
+
// from "no leaf of this live arm equals bare `undefined`" and collapsed
|
|
3379
|
+
// every live arm of the inner union into one bogus `Undefined` home.
|
|
3380
|
+
// Certified, emitted, and wrong for every arm past the first guard. Using
|
|
3381
|
+
// the registry's own predicate here is what keeps the two from disagreeing
|
|
3382
|
+
// again the way its doc comment says they cannot.
|
|
3383
|
+
{
|
|
3384
|
+
id: 'union-subset-narrow',
|
|
3385
|
+
apply: (source, target, text) => {
|
|
3386
|
+
if (source.kind !== 'tagged-union' || target.kind !== 'tagged-union')
|
|
3387
|
+
return undefined;
|
|
3388
|
+
return narrowingReachesTarget(source, target) ? claimed(narrowedUnionSubsetText(source, target, text)) : undefined;
|
|
3389
|
+
}
|
|
3390
|
+
},
|
|
3391
|
+
{
|
|
3392
|
+
id: 'union-recast',
|
|
3393
|
+
apply: (source, target, text) => source.kind === 'tagged-union' && target.kind === 'tagged-union' ? claimed(recastedUnionText(source, target, text)) : undefined
|
|
3394
|
+
},
|
|
3395
|
+
// The same shape one level out: two records declaring the same fields under
|
|
3396
|
+
// two shape ids. Tried, not committed to -- a pair this cannot rebuild is not
|
|
3397
|
+
// necessarily a pair nothing can convert.
|
|
3398
|
+
{
|
|
3399
|
+
id: 'record-recast',
|
|
3400
|
+
apply: (source, target, text) => source.kind === 'record' && target.kind === 'record' ? claimed(recastedRecordText(source, target, text)) : undefined
|
|
3401
|
+
},
|
|
3402
|
+
// The record recast still wrapped in its presence bit, admitted by
|
|
3403
|
+
// `conversions.ts`'s `gea::Optional::recastPayload` on the identical
|
|
3404
|
+
// `recordsRecastable` question. An IIFE with a local rather than a conditional
|
|
3405
|
+
// over `text`: `text` is an arbitrary expression and must be evaluated once.
|
|
3406
|
+
{
|
|
3407
|
+
id: 'optional-record-recast',
|
|
3408
|
+
apply: (source, target, text) => {
|
|
3409
|
+
if (source.kind !== 'optional' ||
|
|
3410
|
+
target.kind !== 'optional' ||
|
|
3411
|
+
source.payload.kind !== 'record' ||
|
|
3412
|
+
target.payload.kind !== 'record' ||
|
|
3413
|
+
source.absence !== target.absence)
|
|
3414
|
+
return undefined;
|
|
3415
|
+
const payload = recastedRecordText(source.payload, target.payload, '(*gea_present)');
|
|
3416
|
+
if (payload === null)
|
|
3417
|
+
return undefined;
|
|
3418
|
+
const targetType = cppTypeOf(target);
|
|
3419
|
+
return (`([&]() -> ${targetType} { const auto& gea_present = ${text}; ` +
|
|
3420
|
+
`if (!gea_present.has_value()) return ${targetType}(); ` +
|
|
3421
|
+
`return ${targetType}(${payload}); }())`);
|
|
3422
|
+
}
|
|
3423
|
+
},
|
|
3424
|
+
// A closed record's fields poured into an open dictionary; see
|
|
3425
|
+
// `recordCastableToDictionary` for why only this direction is sound.
|
|
3426
|
+
// `record-to-array` is the same distance: an unannotated rest parameter's two
|
|
3427
|
+
// views (`recordCastableToArray`).
|
|
3428
|
+
{
|
|
3429
|
+
id: 'record-to-dictionary',
|
|
3430
|
+
apply: (source, target, text) => source.kind === 'record' && target.kind === 'dictionary' ? claimed(recastedRecordToDictionaryText(source, target, text)) : undefined
|
|
3431
|
+
},
|
|
3432
|
+
// One shape wider: an open dictionary's values widened into another's
|
|
3433
|
+
// (`dictionaryCastableToDictionary`).
|
|
3434
|
+
{
|
|
3435
|
+
id: 'dictionary-to-dictionary',
|
|
3436
|
+
apply: (source, target, text) => source.kind === 'dictionary' && target.kind === 'dictionary' ? claimed(recastedDictionaryText(source, target, text)) : undefined
|
|
3437
|
+
},
|
|
3438
|
+
// An Array of one element carrier copied into an Array of another, each
|
|
3439
|
+
// element through its own conversion. A copy, so only for an array that is
|
|
3440
|
+
// not written through both names afterwards: a matcher table built once
|
|
3441
|
+
// (hono's `[handlers.map(...), emptyParam]` literal) or a shared empty one.
|
|
3442
|
+
{
|
|
3443
|
+
id: 'array-copy-recast',
|
|
3444
|
+
apply: (source, target, text) => {
|
|
3445
|
+
if (source.kind !== 'array-object' || target.kind !== 'array-object')
|
|
3446
|
+
return undefined;
|
|
3447
|
+
if (source.recursive || target.recursive || source.extension || target.extension)
|
|
3448
|
+
return undefined;
|
|
3449
|
+
if (source.ownership !== 'shared-refcount' || target.ownership !== 'shared-refcount')
|
|
3450
|
+
return undefined;
|
|
3451
|
+
// Never across the dynamic boundary. An element boxed or unboxed on the
|
|
3452
|
+
// way marks a container one authority typed `dynamic` -- an evolving
|
|
3453
|
+
// `var array = []` whose pushes the census could not type -- and such an
|
|
3454
|
+
// array is a shared MUTABLE local passed by reference: copying it hands
|
|
3455
|
+
// the callee a twin, so its pushes never reach the caller
|
|
3456
|
+
// (`test/runtime/array-object-call-argument-aliasing.ts`, and the
|
|
3457
|
+
// three.js `getProgramCacheKey` glyph miscompile its `.runtime.js`
|
|
3458
|
+
// sibling reproduces). Both polarities were live miscompiles that the
|
|
3459
|
+
// refusal fixed on 2026-09-12; re-admitting them here silently undid
|
|
3460
|
+
// that fix. The record- and union-element copies this recipe was added
|
|
3461
|
+
// for (hono's matcher tables, built from literals) do not cross it --
|
|
3462
|
+
// and the test is the ELEMENT's own carrier, not a field somewhere
|
|
3463
|
+
// inside a record element: a record field boxed on the way is a shape
|
|
3464
|
+
// change, not the census-refused evolving array this rule names.
|
|
3465
|
+
const dynamicElement = (element) => element.kind === 'dynamic' || (element.kind === 'optional' && element.payload.kind === 'dynamic');
|
|
3466
|
+
if (dynamicElement(source.element) !== dynamicElement(target.element))
|
|
3467
|
+
return undefined;
|
|
3468
|
+
const element = cppTypeOf(source.element);
|
|
3469
|
+
const converted = tryCandidateText(() => convertedValueText(source.element, target.element, 'gea_element'));
|
|
3470
|
+
if (converted === null)
|
|
3471
|
+
return null;
|
|
3472
|
+
const storage = cppTypeOf(target, 'owned');
|
|
3473
|
+
return (`[](const ${cppTypeOf(source)}& gea_from) { auto gea_array = gea::makeRef<${storage}>(); ` +
|
|
3474
|
+
`if (gea_from.get() != nullptr) gea_array->appendRangeConverted(*gea_from, 0, [](const ${element}& gea_element) { return ${converted}; }); ` +
|
|
3475
|
+
`return gea_array; }(${text})`);
|
|
3476
|
+
}
|
|
3477
|
+
},
|
|
3478
|
+
{
|
|
3479
|
+
// An array asserted to a tuple (`ownRoute as [string, H[]][]`): the tuple's
|
|
3480
|
+
// positions are read out of the array and converted one by one. A shorter
|
|
3481
|
+
// array than the tuple has no value for a required position, which the
|
|
3482
|
+
// assertion claimed away; it fails loudly rather than reading past the end.
|
|
3483
|
+
id: 'array-to-tuple',
|
|
3484
|
+
apply: (source, target, text) => {
|
|
3485
|
+
if (source.kind !== 'array-object' || target.kind !== 'record' || source.ownership !== 'shared-refcount')
|
|
3486
|
+
return undefined;
|
|
3487
|
+
if (source.recursive || source.extension || target.accessors.length > 0 || target.fields.length === 0)
|
|
3488
|
+
return undefined;
|
|
3489
|
+
if (!target.fields.every((field, index) => field.key === String(index) && field.required))
|
|
3490
|
+
return undefined;
|
|
3491
|
+
const reads = [];
|
|
3492
|
+
for (const [index, field] of target.fields.entries()) {
|
|
3493
|
+
const converted = tryCandidateText(() => convertedValueText(source.element, field.value, `gea_from->at(${index})`));
|
|
3494
|
+
if (converted === null)
|
|
3495
|
+
return null;
|
|
3496
|
+
reads.push(field.value.kind === 'scalar' && field.value.domain !== 'bigint'
|
|
3497
|
+
? `static_cast<decltype(${cppRecordStructName(target.shapeId)}::${cppRecordFieldName(field.key)})>(${converted})`
|
|
3498
|
+
: converted);
|
|
3499
|
+
}
|
|
3500
|
+
const structure = `${cppRecordStructName(target.shapeId)}{${reads.join(', ')}}`;
|
|
3501
|
+
const built = target.ownership === 'shared-refcount' ? `gea::makeRef<${cppRecordStructName(target.shapeId)}>(${structure})` : structure;
|
|
3502
|
+
return (`[](const ${cppTypeOf(source)}& gea_from) { ` +
|
|
3503
|
+
`if (gea_from.get() == nullptr || gea_from->size() < ${target.fields.length}) gea::host::throwRuntimeError("TypeError", "an array asserted to a tuple is shorter than the tuple"); ` +
|
|
3504
|
+
`return ${built}; }(${text})`);
|
|
3505
|
+
}
|
|
3506
|
+
},
|
|
3507
|
+
{
|
|
3508
|
+
id: 'record-to-array',
|
|
3509
|
+
apply: (source, target, text) => source.kind === 'record' && target.kind === 'array-object' ? claimed(recastedRecordToArrayText(source, target, text)) : undefined
|
|
3510
|
+
},
|
|
3511
|
+
// TWO PROMISES WHOSE PAYLOADS DIFFER. `widenedStoreText` reads a `promise`
|
|
3512
|
+
// target as "widen the written VALUE into a promise of it", correct for the
|
|
3513
|
+
// `return` terminator and wrong here, where the source is already a promise.
|
|
3514
|
+
// All three `[[PromiseState]]` values are carried: a rejection moves the
|
|
3515
|
+
// `exception_ptr` (the thrown carrier is the same on both sides), an unsettled
|
|
3516
|
+
// promise stays unsettled, and a `Promise<void>` on either side has no
|
|
3517
|
+
// `value()`, which is why a void payload against a richer one refuses.
|
|
3518
|
+
//
|
|
3519
|
+
// `Promise<void>` against `Promise<undefined>` is the exception, and not a
|
|
3520
|
+
// hole being papered over: the two are one run-time promise wearing two
|
|
3521
|
+
// TypeScript types (ECMA-262 27.2.1.4 fulfils a promise resolved with
|
|
3522
|
+
// nothing with `undefined`, which `cppResultTypeOf` elides for `void`), so
|
|
3523
|
+
// the state crosses and the unit value is supplied or dropped. That recipe
|
|
3524
|
+
// links through `observe` rather than snapshotting a settled state, because
|
|
3525
|
+
// its source is routinely still PENDING -- `@hono/node-server`'s
|
|
3526
|
+
// `responseViaCache` awaits inside -- and a snapshot of a pending promise is
|
|
3527
|
+
// a target nothing ever settles.
|
|
3528
|
+
//
|
|
3529
|
+
// `Promise<never>` is the one void-payload SOURCE that does not refuse, and
|
|
3530
|
+
// it is not a hole being papered over: a promise that cannot fulfil has no
|
|
3531
|
+
// fulfilment value for the target to be missing. Only the rejection has to
|
|
3532
|
+
// cross, so the recipe forwards it rather than reading a `value()` that does
|
|
3533
|
+
// not exist -- and it forwards a rejection that has not happened YET as
|
|
3534
|
+
// well, through `observe`, because a pending `Promise<never>` may still
|
|
3535
|
+
// reject and dropping that would swallow the error the whole carrier exists
|
|
3536
|
+
// to deliver.
|
|
3537
|
+
{
|
|
3538
|
+
id: 'promise-payload',
|
|
3539
|
+
apply: (source, target, text) => {
|
|
3540
|
+
if (source.kind !== 'promise' || target.kind !== 'promise')
|
|
3541
|
+
return undefined;
|
|
3542
|
+
const bottomSource = source.value.kind === 'void' && source.value.bottom === true;
|
|
3543
|
+
if (!bottomSource && (source.value.kind === 'void' || target.value.kind === 'void')) {
|
|
3544
|
+
if (!isUnitPromisePayload(source.value) || !isUnitPromisePayload(target.value))
|
|
3545
|
+
return undefined;
|
|
3546
|
+
return (`([&]() -> ${cppTypeOf(target)} { const auto& ${promiseSourceName} = ${text}; ` +
|
|
3547
|
+
`if (${promiseSourceName}.rejected()) return ${cppTypeOf(target)}::rejected_with(${promiseSourceName}.rejection()); ` +
|
|
3548
|
+
`${cppTypeOf(target)} ${promiseTargetName}; ` +
|
|
3549
|
+
`${promiseSourceName}.observe([${promiseTargetName}](auto&&...) mutable ` +
|
|
3550
|
+
`{ ${promiseTargetName}.resolve(${target.value.kind === 'void' ? '' : cppUndefinedValue}); }, ` +
|
|
3551
|
+
`[${promiseTargetName}](const std::exception_ptr& ${promiseRejectionName}) mutable ` +
|
|
3552
|
+
`{ ${promiseTargetName}.reject(${promiseRejectionName}); }); ` +
|
|
3553
|
+
`return ${promiseTargetName}; }())`);
|
|
3554
|
+
}
|
|
3555
|
+
if (source.value.kind === 'void') {
|
|
3556
|
+
if (source.value.bottom !== true)
|
|
3557
|
+
return undefined;
|
|
3558
|
+
return (`([&]() -> ${cppTypeOf(target)} { const auto& ${promiseSourceName} = ${text}; ` +
|
|
3559
|
+
`if (${promiseSourceName}.rejected()) return ${cppTypeOf(target)}::rejected_with(${promiseSourceName}.rejection()); ` +
|
|
3560
|
+
`${cppTypeOf(target)} ${promiseTargetName}; ` +
|
|
3561
|
+
`${promiseSourceName}.observe([]() {}, [${promiseTargetName}](const std::exception_ptr& ${promiseRejectionName}) mutable ` +
|
|
3562
|
+
`{ ${promiseTargetName}.reject(${promiseRejectionName}); }); ` +
|
|
3563
|
+
`return ${promiseTargetName}; }())`);
|
|
3564
|
+
}
|
|
3565
|
+
const settledPayload = convertedValueText(source.value, target.value, `${promiseSourceName}.value()`);
|
|
3566
|
+
if (settledPayload === null)
|
|
3567
|
+
return undefined;
|
|
3568
|
+
const pendingPayload = convertedValueText(source.value, target.value, promiseValueName);
|
|
3569
|
+
if (pendingPayload === null)
|
|
3570
|
+
return undefined;
|
|
3571
|
+
// A source still PENDING links through `observe`, exactly as the unit
|
|
3572
|
+
// recipe above does and for the same reason: hono's `#cachedBody` is one
|
|
3573
|
+
// callable returning `Promise<string | ArrayBuffer | Blob | ...>` that
|
|
3574
|
+
// every reader (`text()`, `json()`, `formData()`) adapts to its own
|
|
3575
|
+
// `Promise<T>` at the call, and the promise it hands back is the body
|
|
3576
|
+
// read that has not arrived yet. Snapshotting it (`if (!settled()) return
|
|
3577
|
+
// Target()`) minted a target nothing ever settled -- every `await
|
|
3578
|
+
// c.req.json()` hung and the POST never answered. The conversion runs
|
|
3579
|
+
// when the value exists; a conversion that throws (a dynamic arm not of
|
|
3580
|
+
// the declared type) becomes the target's rejection, which is what the
|
|
3581
|
+
// same failure inside a `then` handler is.
|
|
3582
|
+
return (`([&]() -> ${cppTypeOf(target)} { const auto& ${promiseSourceName} = ${text}; ` +
|
|
3583
|
+
`if (${promiseSourceName}.rejected()) return ${cppTypeOf(target)}::rejected_with(${promiseSourceName}.rejection()); ` +
|
|
3584
|
+
`if (${promiseSourceName}.settled()) return ${cppTypeOf(target)}(${settledPayload}); ` +
|
|
3585
|
+
`${cppTypeOf(target)} ${promiseTargetName}; ` +
|
|
3586
|
+
`${promiseSourceName}.observe([${promiseTargetName}](const ${cppTypeOf(source.value)}& ${promiseValueName}) mutable ` +
|
|
3587
|
+
`{ try { ${promiseTargetName}.resolve(${pendingPayload}); } catch (...) { ${promiseTargetName}.reject(std::current_exception()); } }, ` +
|
|
3588
|
+
`[${promiseTargetName}](const std::exception_ptr& ${promiseRejectionName}) mutable ` +
|
|
3589
|
+
`{ ${promiseTargetName}.reject(${promiseRejectionName}); }); ` +
|
|
3590
|
+
`return ${promiseTargetName}; }())`);
|
|
3591
|
+
}
|
|
3592
|
+
},
|
|
3593
|
+
// A TypeScript boundary does not call an ECMAScript abstract operation. A
|
|
3594
|
+
// dynamic source enters every typed carrier through its exact checked load;
|
|
3595
|
+
// String/Number, arithmetic, templates and property keys render their own
|
|
3596
|
+
// coercions at their semantic sites.
|
|
3597
|
+
{ id: 'dynamic-unbox', apply: (source, target, text) => (source.kind === 'dynamic' ? unboxedLoadText(target, text) : undefined) },
|
|
3598
|
+
// A String OBJECT reconciled against the `string` primitive its alias widens
|
|
3599
|
+
// to (hono's `HtmlEscapedString`). Before the two general paths for the same
|
|
3600
|
+
// reason the dynamic case is: `narrowedLoadText` would read this as a search
|
|
3601
|
+
// through a union's arms and refuse, and `widenedStoreText` reads it backwards.
|
|
3602
|
+
{
|
|
3603
|
+
id: 'string-object-stringify',
|
|
3604
|
+
apply: (source, target, text) => (target.kind === 'string' ? claimed(stringObjectStringifyText(source, text)) : undefined)
|
|
3605
|
+
},
|
|
3606
|
+
{ id: 'narrowed-load', apply: (source, target, text) => claimed(narrowedLoadText(source, target, text)) },
|
|
3607
|
+
// Ahead of `widened-store`, which claims every pair it reaches and refuses
|
|
3608
|
+
// the ones it cannot widen -- so nothing after it ever runs. A storage-free
|
|
3609
|
+
// `undefined` entering a carrier with no room for an absence is a branch
|
|
3610
|
+
// flow analysis proved dead, not a missing load: `undefined` is also
|
|
3611
|
+
// `never`'s carrier (`representation/primitives.ts`), and a pair the loads
|
|
3612
|
+
// refuse can only be reached when the source holds nothing. `var [d = 7] =
|
|
3613
|
+
// []` is the standing case -- the extraction past an empty tuple is
|
|
3614
|
+
// `undefined` outright, the default's present arm converts it to the bound
|
|
3615
|
+
// `number`, and the `is-defined` test guarding that arm is the constant
|
|
3616
|
+
// `false`. Rendered as the throw the call-site argument path already
|
|
3617
|
+
// renders (`emit-callable.ts`) so the dead arm does not block the live one.
|
|
3618
|
+
// The other direction is a read flow analysis proved yields `undefined` out
|
|
3619
|
+
// of a cell that holds a value on every other path: the value is
|
|
3620
|
+
// discarded, not converted. A carrier that CAN hold an absence -- an
|
|
3621
|
+
// optional, a sum, a box -- is `widened-store`'s to fill with one.
|
|
3622
|
+
{
|
|
3623
|
+
id: 'unreachable-value',
|
|
3624
|
+
apply: (source, target, text) => {
|
|
3625
|
+
const holdsAbsence = (carrier) => carrier.kind === 'optional' ||
|
|
3626
|
+
carrier.kind === 'tagged-union' ||
|
|
3627
|
+
carrier.kind === 'dynamic' ||
|
|
3628
|
+
carrier.kind === 'undefined' ||
|
|
3629
|
+
carrier.kind === 'null' ||
|
|
3630
|
+
carrier.kind === 'void';
|
|
3631
|
+
if (source.kind === 'undefined' && !holdsAbsence(target))
|
|
3632
|
+
return `gea::host::unreachableValue<${cppTypeOf(target)}>()`;
|
|
3633
|
+
if (target.kind === 'undefined' && !holdsAbsence(source))
|
|
3634
|
+
return `(static_cast<void>(${text}), gea::Undefined{})`;
|
|
3635
|
+
return undefined;
|
|
3636
|
+
}
|
|
3637
|
+
},
|
|
3638
|
+
{ id: 'widened-store', apply: (source, target, text) => widenedStoreText(target, source, text) }
|
|
3639
|
+
];
|
|
3640
|
+
const runConversionChain = (source, target, text) => {
|
|
3641
|
+
for (const step of conversionChain) {
|
|
3642
|
+
const rendered = step.apply(source, target, text);
|
|
3643
|
+
if (rendered === undefined)
|
|
3644
|
+
continue;
|
|
3645
|
+
return { step, text: rendered };
|
|
3646
|
+
}
|
|
3647
|
+
return { step: null, text: null };
|
|
3648
|
+
};
|
|
3649
|
+
export const alignedValueText = (ctx, site, source, target, text) => {
|
|
3650
|
+
if (representationKey(source) === representationKey(target))
|
|
3651
|
+
return text;
|
|
3652
|
+
const node = ctx.conversions.nodeFor(source, target);
|
|
3653
|
+
const refused = node.capability.kind === 'never';
|
|
3654
|
+
ctx.printerDrift.push({
|
|
3655
|
+
owner: String(ctx.owner),
|
|
3656
|
+
site,
|
|
3657
|
+
kind: refused ? 'refused' : 'converted',
|
|
3658
|
+
source: representationKey(source),
|
|
3659
|
+
target: representationKey(target),
|
|
3660
|
+
reason: node.capability.kind === 'never' ? node.capability.reason : node.capability.kind,
|
|
3661
|
+
sourceRepresentation: source,
|
|
3662
|
+
targetRepresentation: target
|
|
3663
|
+
});
|
|
3664
|
+
return refused ? convertedValueText(source, target, text) : recipeText(ctx, node, text);
|
|
3665
|
+
};
|
|
3666
|
+
/**
|
|
3667
|
+
* A census node's recipe, rendered by one of the two renderers the census
|
|
3668
|
+
* owns. The chain first: the registry's atoms ARE its narrowing, widening
|
|
3669
|
+
* and recasting steps, and its static recipes are named by the step that
|
|
3670
|
+
* claimed the pair (`conversions.ts`'s `staticRecipe`). Then the structural
|
|
3671
|
+
* record view (`conversion/record-view.ts`), which renders both the
|
|
3672
|
+
* `view:structural-record` recipe and the registry's `gea::record::recast`
|
|
3673
|
+
* atom -- the eager graph minted that atom for a by-value record entering
|
|
3674
|
+
* an interface's shared shape before the view plan existed, and the chain's
|
|
3675
|
+
* `record-recast` step spells only the by-reference recast, so the view is
|
|
3676
|
+
* that atom's renderer. Keyed by capability KIND rather than by materializer
|
|
3677
|
+
* id on purpose: a node's id says which table answered, not which text
|
|
3678
|
+
* spells it, and every non-`never` node has exactly one of these two.
|
|
3679
|
+
*
|
|
3680
|
+
* `class-family` is a third renderer, asked before the chain rather than
|
|
3681
|
+
* folded into it: `classFamilyLoadText` orders its cascade over
|
|
3682
|
+
* `ctx.classes`, the whole-program class table, and the chain
|
|
3683
|
+
* (`convertedValueText`) is deliberately ctx-free -- see `algebra.ts`'s doc
|
|
3684
|
+
* on the kind for why an `atom` cannot carry this recipe.
|
|
3685
|
+
*/
|
|
3686
|
+
export const recipeText = (ctx, node, text) => {
|
|
3687
|
+
if (node.capability.kind === 'identity')
|
|
3688
|
+
return text;
|
|
3689
|
+
if (node.capability.kind === 'never')
|
|
3690
|
+
return null;
|
|
3691
|
+
if (node.capability.kind === 'coercion')
|
|
3692
|
+
return coercionText(node.capability.operation, text, node.source, ctx.layouts);
|
|
3693
|
+
if (node.capability.kind === 'class-family')
|
|
3694
|
+
return classFamilyLoadText(ctx, node.source, node.target, text);
|
|
3695
|
+
// `conversions.ts`'s read of a structural value as a class nothing instantiates.
|
|
3696
|
+
if (node.capability.kind === 'atom' && node.capability.materializer.id === 'gea::host::unreachableValue')
|
|
3697
|
+
return `((void)(${text}), gea::host::unreachableValue<${cppTypeOf(node.target)}>())`;
|
|
3698
|
+
// `conversions.ts`'s read of a class instance's structural view as the class: the boxed origin, narrowed.
|
|
3699
|
+
if (node.capability.kind === 'atom' && node.capability.materializer.id === 'gea::record::viewOrigin')
|
|
3700
|
+
return narrowedLoadText({ kind: 'dynamic', reason: 'declared-any-never-narrowed' }, node.target, node.source.kind === 'optional'
|
|
3701
|
+
? `((${text}).has_value() ? gea::record::viewOrigin(*(${text})) : gea::Value())`
|
|
3702
|
+
: `gea::record::viewOrigin(${text})`);
|
|
3703
|
+
if ((node.capability.kind === 'atom' || node.capability.kind === 'static') && node.capability.materializer.nativeSelection) {
|
|
3704
|
+
const helper = ctx.nativeSelectionHelpers?.get(node.id);
|
|
3705
|
+
return helper
|
|
3706
|
+
? `${helper.name}(${text})`
|
|
3707
|
+
: nativeSelectionText(node.capability.materializer.nativeSelection, node.source, node.target, text);
|
|
3708
|
+
}
|
|
3709
|
+
return convertedValueText(node.source, node.target, text) ?? structuralRecordViewText(ctx, node.source, node.target, text);
|
|
3710
|
+
};
|
|
3711
|
+
/**
|
|
3712
|
+
* The text of a node a `convert` instruction NAMES, rather than of the pair
|
|
3713
|
+
* it spans: a coercion node shares its (source, target) pair with the store
|
|
3714
|
+
* node `alignedValueText` would look up -- `string -> scalar(number)` is an
|
|
3715
|
+
* exact tag read as a store and StringToNumber as a coercion -- so the
|
|
3716
|
+
* instruction's own node id is the only thing that says which one runs.
|
|
3717
|
+
*/
|
|
3718
|
+
export const namedConversionText = (ctx, site, node, text) => {
|
|
3719
|
+
const refused = node.capability.kind === 'never';
|
|
3720
|
+
ctx.printerDrift.push({
|
|
3721
|
+
owner: String(ctx.owner),
|
|
3722
|
+
site,
|
|
3723
|
+
kind: refused ? 'refused' : 'converted',
|
|
3724
|
+
source: representationKey(node.source),
|
|
3725
|
+
target: representationKey(node.target),
|
|
3726
|
+
reason: node.capability.kind === 'never' ? node.capability.reason : node.capability.kind,
|
|
3727
|
+
sourceRepresentation: node.source,
|
|
3728
|
+
targetRepresentation: node.target
|
|
3729
|
+
});
|
|
3730
|
+
return recipeText(ctx, node, text);
|
|
3731
|
+
};
|
|
3732
|
+
export const convertedValueText = (source, target, text) => {
|
|
3733
|
+
if (representationKey(source) === representationKey(target))
|
|
3734
|
+
return text;
|
|
3735
|
+
// A void TARGET is the language's explicit discard conversion. The source
|
|
3736
|
+
// operation has already evaluated its effects; this expression preserves a
|
|
3737
|
+
// valid C++ void value for callers that return or forward the conversion,
|
|
3738
|
+
// without naming a storage type that cannot exist. A void SOURCE cannot
|
|
3739
|
+
// produce any non-void target and remains a named refusal.
|
|
3740
|
+
if (target.kind === 'void')
|
|
3741
|
+
return `(void)(${text})`;
|
|
3742
|
+
if (source.kind === 'void') {
|
|
3743
|
+
throw createCppEmitBlockedError(`conversion:${representationKey(source)}->${representationKey(target)}`, `reconciles a ${source.kind} source with a ${target.kind} target`);
|
|
3744
|
+
}
|
|
3745
|
+
// Refused HERE: `cppTypeOf` throws a bare `Error` on lattice bottom that
|
|
3746
|
+
// escapes the per-body catch and kills the whole compile otherwise.
|
|
3747
|
+
if (containsUnresolved(source) || containsUnresolved(target)) {
|
|
3748
|
+
const bottom = [...walkRepresentation(source), ...walkRepresentation(target)].find((f) => f.kind === 'unresolved');
|
|
3749
|
+
throw createCppEmitBlockedError(`conversion:${representationKey(source)}->${representationKey(target)}`, `reconciles against ${bottom?.reason ?? 'an unresolved representation'}`);
|
|
3750
|
+
}
|
|
3751
|
+
return runConversionChain(source, target, text).text;
|
|
3752
|
+
};
|
|
3753
|
+
/**
|
|
3754
|
+
* Which chain step answers a pair, decided by running the chain on a
|
|
3755
|
+
* placeholder: the steps decide from the two carriers alone and only thread
|
|
3756
|
+
* the text through, so the answer is the one `convertedValueText` gives. A
|
|
3757
|
+
* step that claims the pair but cannot render it is reported with
|
|
3758
|
+
* `renders: false`; a pair no step claims is `null`.
|
|
3759
|
+
*/
|
|
3760
|
+
/**
|
|
3761
|
+
* Whether this backend can spell a carrier at all. Asked about every pair the
|
|
3762
|
+
* registry is probed with -- spelling the same target ten thousand times,
|
|
3763
|
+
* catching a throw each time it cannot be, was a measurable share of that
|
|
3764
|
+
* loop -- so remembered by identity, as `representationKey`'s own memo is.
|
|
3765
|
+
* The mapping's throw is still the answer; `manifest.ts`'s `isSpellable`
|
|
3766
|
+
* reads it the same way.
|
|
3767
|
+
*/
|
|
3768
|
+
const spellability = new WeakMap();
|
|
3769
|
+
export const isSpellable = (representation) => {
|
|
3770
|
+
const known = spellability.get(representation);
|
|
3771
|
+
if (known !== undefined)
|
|
3772
|
+
return known;
|
|
3773
|
+
let spellable;
|
|
3774
|
+
try {
|
|
3775
|
+
cppTypeOf(representation);
|
|
3776
|
+
spellable = true;
|
|
3777
|
+
}
|
|
3778
|
+
catch {
|
|
3779
|
+
spellable = false;
|
|
3780
|
+
}
|
|
3781
|
+
spellability.set(representation, spellable);
|
|
3782
|
+
return spellable;
|
|
3783
|
+
};
|
|
3784
|
+
/**
|
|
3785
|
+
* The chain's answer for a pair, as a predicate: the registry's static recipe
|
|
3786
|
+
* and a record view's field and arm pairs (`conversion/record-view.ts`) ask
|
|
3787
|
+
* this one question, so a view the census admits is a view the printer
|
|
3788
|
+
* renders.
|
|
3789
|
+
*/
|
|
3790
|
+
export const chainConverts = (source, target) => isSpellable(source) && isSpellable(target) && (conversionRecipeOf(source, target)?.renders ?? false);
|
|
3791
|
+
export const conversionRecipeOf = (source, target) => {
|
|
3792
|
+
if (representationKey(source) === representationKey(target))
|
|
3793
|
+
return { id: 'identity', renders: true };
|
|
3794
|
+
if (target.kind === 'void')
|
|
3795
|
+
return { id: 'discard-into-void', renders: true };
|
|
3796
|
+
if (source.kind === 'void' || containsUnresolved(source) || containsUnresolved(target))
|
|
3797
|
+
return null;
|
|
3798
|
+
const probed = tryCandidateText(() => {
|
|
3799
|
+
const outcome = runConversionChain(source, target, 'gea_conversion_probe');
|
|
3800
|
+
return outcome.step === null ? null : `${outcome.step.id}\u0000${outcome.text === null ? '' : 'renders'}`;
|
|
3801
|
+
});
|
|
3802
|
+
if (probed === null)
|
|
3803
|
+
return null;
|
|
3804
|
+
const [id, renders] = probed.split('\u0000');
|
|
3805
|
+
return { id: id ?? 'unknown', renders: renders === 'renders' };
|
|
3806
|
+
};
|
|
3807
|
+
/**
|
|
3808
|
+
* Whether this carrier OWNS what it holds, so handing it over is a move rather
|
|
3809
|
+
* than a copy.
|
|
3810
|
+
*
|
|
3811
|
+
* A `string` owns a heap buffer the same way a counted reference owns a count,
|
|
3812
|
+
* and hands it over the same way. `JSON.stringify`'s result is the case that
|
|
3813
|
+
* made this visible: a 300 KB text was copied out of the value the serializer
|
|
3814
|
+
* had just built into the cell that names it, once per call.
|
|
3815
|
+
*
|
|
3816
|
+
* An `optional` owns whatever its payload owns and nothing else --
|
|
3817
|
+
* `gea::Optional<gea::Ref<T>>` is a `Ref` plus a presence bit, and moving it
|
|
3818
|
+
* moves the `Ref`. `binary_trees` is the case: `sum(node.left)` reads a
|
|
3819
|
+
* `TreeNode | null` field into a temporary and passes it by value, which is an
|
|
3820
|
+
* increment on the way in and a decrement, a branch and an out-of-line
|
|
3821
|
+
* destructor on the way out, twice for every one of a million nodes. The
|
|
3822
|
+
* hand-written baseline it is measured against passes a raw pointer.
|
|
3823
|
+
*/
|
|
3824
|
+
const ownsItsStorage = (carrier) => {
|
|
3825
|
+
if (carrier.kind === 'string')
|
|
3826
|
+
return true;
|
|
3827
|
+
if (carrier.kind === 'class-ref')
|
|
3828
|
+
return carrier.ownership === 'shared-refcount';
|
|
3829
|
+
if (carrier.kind === 'optional')
|
|
3830
|
+
return ownsItsStorage(carrier.payload);
|
|
3831
|
+
return false;
|
|
3832
|
+
};
|
|
3833
|
+
/**
|
|
3834
|
+
* A DYING refcounted value moves into the slot that takes it rather than being
|
|
3835
|
+
* copied into it -- see `ir/transfer.ts`'s `buildDyingArgumentIndex` for what
|
|
3836
|
+
* makes a value dying, which is the whole of the safety argument.
|
|
3837
|
+
*
|
|
3838
|
+
* Two conditions here, both about the CARRIER rather than the value's life:
|
|
3839
|
+
* only a carrier that owns a count has anything to hand over, and the slot must
|
|
3840
|
+
* hold exactly what the value already holds. `gea::Ref` has no converting move
|
|
3841
|
+
* constructor, so an upcast to a base class binds `std::move(x)` right back to
|
|
3842
|
+
* the copying `Ref(const Ref<Other>&)` -- and any slot needing a real
|
|
3843
|
+
* conversion has already built a temporary of its own, which moving cannot
|
|
3844
|
+
* make cheaper.
|
|
3845
|
+
*/
|
|
3846
|
+
export const movedValueText = (ctx, value, held, text) => {
|
|
3847
|
+
if (transferOf(ctx.dyingArguments, ctx.ownedValues, ctx.ownedDyingValues, value.value) !== 'move')
|
|
3848
|
+
return text;
|
|
3849
|
+
// A value the emitter withheld renders as its own expression, not as a cell:
|
|
3850
|
+
// `f(std::move((a->elementAtIndex(i))))` moves a prvalue, which gcc rejects
|
|
3851
|
+
// as a pessimizing move under -Werror, and there is nothing to gain -- the
|
|
3852
|
+
// temporary already binds to the by-value slot without a copy.
|
|
3853
|
+
if (isDeferredValue(ctx, value.value))
|
|
3854
|
+
return text;
|
|
3855
|
+
const carrier = value.representation;
|
|
3856
|
+
// A `string` owns a heap buffer the same way a counted reference owns a
|
|
3857
|
+
// count, and hands it over the same way. `JSON.stringify`'s result is the
|
|
3858
|
+
// case that made this visible: a 300 KB text was copied out of the value the
|
|
3859
|
+
// serializer had just built into the cell that names it, once per call.
|
|
3860
|
+
if (!ownsItsStorage(carrier))
|
|
3861
|
+
return text;
|
|
3862
|
+
if (held !== null && representationKey(held) !== representationKey(carrier))
|
|
3863
|
+
return text;
|
|
3864
|
+
return `std::move(${text})`;
|
|
3865
|
+
};
|
|
3866
|
+
/**
|
|
3867
|
+
* The proven-partial-dead-arm sibling of `emit.ts`'s `emitConvert` -- see
|
|
3868
|
+
* `MergeLiveArmRebuildOperation`'s doc comment (`ir/model.ts`) for why a
|
|
3869
|
+
* general `convert` cannot express this.
|
|
3870
|
+
*
|
|
3871
|
+
* `operation.liveArms` is the proof: which of the source's (optional-
|
|
3872
|
+
* unwrapped) tagged-union arms are not proven dead at this merge, computed
|
|
3873
|
+
* once by the control-flow lowering that owns the guard and carried here
|
|
3874
|
+
* unchanged -- never recomputed. `ir/lower-narrow.ts` derives it from
|
|
3875
|
+
* `partialDeadMergeArms` for `&&`; `ir/lower-destructuring.ts` derives it from
|
|
3876
|
+
* the top-level arms surviving a default's `is-defined` guard. Only those
|
|
3877
|
+
* indices are tested, in source-arm order, on the source's own discriminant
|
|
3878
|
+
* (`.is<N>()`/`.get<N>()`, the
|
|
3879
|
+
* identical accessors this file already renders for a narrowed tagged
|
|
3880
|
+
* union above), and the chain falls through to the last live index without
|
|
3881
|
+
* testing it -- sound because reaching this expression at all already proves
|
|
3882
|
+
* the held arm is one of them. The source's own absence (if it is optional)
|
|
3883
|
+
* reads the result's own absent value instead of testing any arm at all.
|
|
3884
|
+
*
|
|
3885
|
+
* Each live arm converts into the result's carrier through `convertedValueText`
|
|
3886
|
+
* (below) FIRST, unmodified, and only when that finds no installed load AND
|
|
3887
|
+
* the result is boolean-shaped (`isBooleanShapedMergeTarget`) does it fall
|
|
3888
|
+
* back to `ToBoolean` (`emit-presence.ts`'s `booleanTestText`) instead. That
|
|
3889
|
+
* fallback is not a general "any carrier converts to boolean" capability -- it
|
|
3890
|
+
* is not added to `convertedValueText`'s own representation-keyed dispatch,
|
|
3891
|
+
* which would let it fire for any unrelated occurrence of the same
|
|
3892
|
+
* representation pair. It fires only here, for an arm THIS merge's own
|
|
3893
|
+
* partial-dead-arm proof already marked live, because a `&&` merge's own
|
|
3894
|
+
* published type collapsing to `boolean | undefined` is the checker having
|
|
3895
|
+
* already proven the kept contribution meaningful only as its truthiness:
|
|
3896
|
+
* ECMA-262's `&&` picks its kept operand by `ToBoolean` in the first place, so
|
|
3897
|
+
* restating that answer as a plain `bool` is not a new fact about the arm's
|
|
3898
|
+
* carrier, it is the exact question this merge already asked of it.
|
|
3899
|
+
*/
|
|
3900
|
+
export const emitMergeLiveArmRebuild = (ctx, lines, operation) => {
|
|
3901
|
+
const sourceRepresentation = operation.source.representation;
|
|
3902
|
+
const payload = sourceRepresentation.kind === 'optional' ? sourceRepresentation.payload : sourceRepresentation;
|
|
3903
|
+
if (payload.kind !== 'tagged-union') {
|
|
3904
|
+
throw createCppEmitBlockedError(`conversion:${representationKey(sourceRepresentation)}->tagged-union`, `rebuilds live arms of ${representationKey(sourceRepresentation)}, which is not a tagged union`);
|
|
3905
|
+
}
|
|
3906
|
+
const target = operation.result.representation;
|
|
3907
|
+
const sourceText = operandText(ctx, operation.source);
|
|
3908
|
+
const unwrapped = sourceRepresentation.kind === 'optional' ? `(*${sourceText})` : sourceText;
|
|
3909
|
+
const booleanPayload = { kind: 'scalar', domain: 'boolean' };
|
|
3910
|
+
const armText = (index) => {
|
|
3911
|
+
const arm = payload.arms[index];
|
|
3912
|
+
if (!arm) {
|
|
3913
|
+
throw createCppEmitBlockedError(`conversion:${representationKey(sourceRepresentation)}->arm-${index}`, `cites live arm ${index} of ${representationKey(sourceRepresentation)}, which has no such arm`);
|
|
3914
|
+
}
|
|
3915
|
+
const loaded = `${unwrapped}.get<${index}>()`;
|
|
3916
|
+
if (operation.nativeTransport) {
|
|
3917
|
+
const citation = operation.nativeTransport.arms.find((entry) => entry.index === index);
|
|
3918
|
+
const node = citation ? ctx.conversions.nodeById(citation.conversion) : null;
|
|
3919
|
+
const text = node ? namedConversionText(ctx, 'emit-narrowing.ts:merge-native-arm', node, loaded) : null;
|
|
3920
|
+
if (text !== null)
|
|
3921
|
+
return text;
|
|
3922
|
+
throw createCppEmitBlockedError(`conversion:${citation?.conversion ?? 'merge-native-arm'}`, 'a sealed native merge arm has no conversion spelling');
|
|
3923
|
+
}
|
|
3924
|
+
const converted = convertedValueText(arm.value, target, loaded);
|
|
3925
|
+
if (converted !== null)
|
|
3926
|
+
return converted;
|
|
3927
|
+
if (isBooleanShapedMergeTarget(target)) {
|
|
3928
|
+
const boolText = booleanTestText(loaded, arm.value);
|
|
3929
|
+
if (representationKey(target) === representationKey(booleanPayload))
|
|
3930
|
+
return boolText;
|
|
3931
|
+
const wrapped = convertedValueText(booleanPayload, target, boolText);
|
|
3932
|
+
if (wrapped !== null)
|
|
3933
|
+
return wrapped;
|
|
3934
|
+
}
|
|
3935
|
+
throw createCppEmitBlockedError(`conversion:${representationKey(arm.value)}->${representationKey(target)}`, `rebuilds live arm ${index} (${representationKey(arm.value)}) of ${representationKey(sourceRepresentation)} into ` +
|
|
3936
|
+
`${representationKey(target)}, which no installed load performs`);
|
|
3937
|
+
};
|
|
3938
|
+
const indices = operation.liveArms;
|
|
3939
|
+
const last = indices[indices.length - 1];
|
|
3940
|
+
if (last === undefined) {
|
|
3941
|
+
throw createCppEmitBlockedError(`conversion:${representationKey(sourceRepresentation)}->no-live-arms`, `rebuilds ${representationKey(sourceRepresentation)} with no live arms recorded`);
|
|
3942
|
+
}
|
|
3943
|
+
let dispatch = armText(last);
|
|
3944
|
+
for (const index of indices.slice(0, -1).reverse()) {
|
|
3945
|
+
dispatch = `${unwrapped}.is<${index}>() ? ${armText(index)} : (${dispatch})`;
|
|
3946
|
+
}
|
|
3947
|
+
if (indices.length > 1)
|
|
3948
|
+
dispatch = `(${dispatch})`;
|
|
3949
|
+
const absenceText = () => {
|
|
3950
|
+
const citation = operation.nativeTransport?.absence;
|
|
3951
|
+
if (citation === undefined || sourceRepresentation.kind !== 'optional')
|
|
3952
|
+
return sourceRepresentation.kind === 'optional'
|
|
3953
|
+
? cppConstantLiteral(sourceRepresentation.absence, sourceRepresentation.absence, target)
|
|
3954
|
+
: '';
|
|
3955
|
+
const node = ctx.conversions.nodeById(citation);
|
|
3956
|
+
const text = node
|
|
3957
|
+
? namedConversionText(ctx, 'emit-narrowing.ts:merge-native-absence', node, cppConstantLiteral(sourceRepresentation.absence, sourceRepresentation.absence, { kind: sourceRepresentation.absence }))
|
|
3958
|
+
: null;
|
|
3959
|
+
if (text !== null)
|
|
3960
|
+
return text;
|
|
3961
|
+
throw createCppEmitBlockedError(`conversion:${citation}`, 'a sealed native merge absence has no conversion spelling');
|
|
3962
|
+
};
|
|
3963
|
+
const text = sourceRepresentation.kind === 'optional' && operation.sourceAbsenceLive
|
|
3964
|
+
? `(${sourceText}.has_value() ? ${dispatch} : ${absenceText()})`
|
|
3965
|
+
: dispatch;
|
|
3966
|
+
lines.push(`${defineValue(ctx, operation.result)} = ${text};`);
|
|
3967
|
+
};
|