@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,3463 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { logicalResultTypeOf } from './logical-result-type.js';
|
|
3
|
+
export { isOpenTypeForm } from './open-type-form.js';
|
|
4
|
+
import { forEachReachableStatement } from './reachability.js';
|
|
5
|
+
import { isClassSpelledSourceClass } from './flow/model.js';
|
|
6
|
+
import { ownedClassReceiverInventoryOf } from './flow/owned-class-receivers.js';
|
|
7
|
+
import { closedValueOriginAuthorityOf } from './flow/callable-reach.js';
|
|
8
|
+
import { seededOriginSolver } from './flow/seeded-origins.js';
|
|
9
|
+
import { deferredIntrinsicProtocolLedgerOf, intrinsicProtocolRequirementKind } from './deferred-intrinsic-protocols.js';
|
|
10
|
+
import { createPropertyKeyDomains, domainMayNameNumeric } from './property-key-domain.js';
|
|
11
|
+
import { unwrapErasedExpression } from './producers/erasure.js';
|
|
12
|
+
/**
|
|
13
|
+
* The type a COMPOSITE expression's own operator produces, read from its
|
|
14
|
+
* operands rather than invented.
|
|
15
|
+
*
|
|
16
|
+
* Extracted so `parameter-bindings.ts` and `return-bindings.ts` -- two
|
|
17
|
+
* censuses that both walk an expression the checker gave up on, resolving it
|
|
18
|
+
* from evidence the checker DOES have elsewhere -- answer a ternary's arms or
|
|
19
|
+
* a `+`'s operands the SAME way once, rather than each growing its own
|
|
20
|
+
* second, silently-drifting copy of the same three rules. That drift is this
|
|
21
|
+
* compiler's own named defect class (`CLAUDE.md`: "two authorities answering
|
|
22
|
+
* the same question differently"); this file exists so there is one.
|
|
23
|
+
*
|
|
24
|
+
* `parameter-bindings.ts`'s own `compute` does not yet call this -- this
|
|
25
|
+
* module does not own that file and cannot wire it in. The call this module
|
|
26
|
+
* was written to receive, added at the same point every other node kind is
|
|
27
|
+
* dispatched in that file's `compute`, is:
|
|
28
|
+
*
|
|
29
|
+
* if (ts.isConditionalExpression(node) || ts.isBinaryExpression(node) || ts.isTemplateExpression(node)) {
|
|
30
|
+
* return derivedExpressionType(checker, node, (operand) => known(operand) ?? resolve(operand))
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* placed anywhere before the function's final `return null`. `known`/
|
|
34
|
+
* `resolve` are that file's own existing closures (`createResolver`'s
|
|
35
|
+
* locals) -- passing them through as the `read` callback is what keeps this
|
|
36
|
+
* module free of any recursion or memoization of its own: every operand it
|
|
37
|
+
* asks about is resolved by the CALLER's own resolver, under the caller's
|
|
38
|
+
* own cycle guard and round-based memo, so wiring this in changes nothing
|
|
39
|
+
* about either file's termination or fixpoint behaviour. It only adds
|
|
40
|
+
* outcomes to `compute`'s dispatch that were previously `null`.
|
|
41
|
+
*
|
|
42
|
+
* ## What it refuses
|
|
43
|
+
*
|
|
44
|
+
* - A conditional (`a ? b : c`) whose arms disagree -- tested the identical
|
|
45
|
+
* way two call sites or two `return`s are tested elsewhere in these two
|
|
46
|
+
* censuses (`widestOf`): agreement is never spelling, and a union of this
|
|
47
|
+
* compiler's own making is a guess nobody wrote.
|
|
48
|
+
* - `+` over two operands where neither is provably `string`: ECMA-262
|
|
49
|
+
* 12.15.5 makes `+` do string concatenation the moment EITHER operand's
|
|
50
|
+
* runtime value is a string, and numeric addition otherwise -- so a `+`
|
|
51
|
+
* between an unresolved operand and anything is refused rather than
|
|
52
|
+
* assumed numeric. `hue2rgb`'s `p + ( q - p ) * 6 * t` is the shape this
|
|
53
|
+
* exists for: real arithmetic over real (parameter-derived) numbers, once
|
|
54
|
+
* every operand resolves, refused whole the moment one does not.
|
|
55
|
+
* - Every other arithmetic/bitwise operator (`-`, `*`, `/`, `%`, `**`, `&`,
|
|
56
|
+
* `|`, `^`, `<<`, `>>`, `>>>`) unless BOTH operands are provably in the
|
|
57
|
+
* number domain.
|
|
58
|
+
* - `&&`/`||`/`??` use `logicalResultTypeOf`, the shared partition of which
|
|
59
|
+
* left values the operator keeps and whether the right can contribute.
|
|
60
|
+
* A known class or absence combined with a boolean remains a native union;
|
|
61
|
+
* disagreement between those types does not erase both into checker `any`.
|
|
62
|
+
* Dynamic operands retain their uncertainty, and Number's falsy part keeps
|
|
63
|
+
* its Number type because it includes NaN as well as zero.
|
|
64
|
+
* - Comparison operators (`<`, `<=`, `>`, `>=`, `==`, `===`, `!=`, `!==`,
|
|
65
|
+
* `instanceof`, `in`) are not handled here at all: ECMA-262 fixes their
|
|
66
|
+
* result at `boolean` regardless of operand types, so the checker already
|
|
67
|
+
* answers them directly and this function is never reached for one --
|
|
68
|
+
* `read`/`known` at the BinaryExpression node itself already succeeded
|
|
69
|
+
* before either caller ever calls in here.
|
|
70
|
+
* - A template expression is always `string` (`ToString` is total), which
|
|
71
|
+
* the checker already reports without help for the identical reason; this
|
|
72
|
+
* module's own handling of it is a defensive fallback, never the primary
|
|
73
|
+
* path.
|
|
74
|
+
*/
|
|
75
|
+
const numberDomain = (type) => (type.flags & ts.TypeFlags.NumberLike) !== 0;
|
|
76
|
+
const stringDomain = (type) => (type.flags & ts.TypeFlags.StringLike) !== 0;
|
|
77
|
+
/**
|
|
78
|
+
* Whether a type says nothing about STORAGE, and so is not evidence.
|
|
79
|
+
*
|
|
80
|
+
* The ONE copy. `parameter-bindings.ts` and `return-bindings.ts` each carried
|
|
81
|
+
* their own before this, with `parameter-bindings.ts`'s adding bare
|
|
82
|
+
* `Function` on top -- it still does, by composing with this rather than
|
|
83
|
+
* restating it, so the base three flags cannot drift between the three
|
|
84
|
+
* modules that ask this question. That module's own `isUnusableEvidence`
|
|
85
|
+
* carries the long-form reasoning for `any`, `void` and `never`.
|
|
86
|
+
*
|
|
87
|
+
* `annotationStatesNothing` is the fourth member and the reason this became
|
|
88
|
+
* shared: a value typed `object` or `{}` is not `any`, so every one of these
|
|
89
|
+
* copies read it as real evidence and published it -- and the parameter
|
|
90
|
+
* census's answer SHADOWS the return census's in the composed view
|
|
91
|
+
* (`composeReturnBindings`), so `function makesBag(): object { return { a: 1,
|
|
92
|
+
* b: 2 } }` had its call answered `object` by one census while the other had
|
|
93
|
+
* already derived the real record. Two authorities, one call, and
|
|
94
|
+
* `model/selected-signature.ts`'s fail-closed guard caught it as a withheld
|
|
95
|
+
* producer. A type that states nothing is not evidence here for exactly the
|
|
96
|
+
* reason `any` is not.
|
|
97
|
+
*/
|
|
98
|
+
/**
|
|
99
|
+
* The destructuring TARGET an object or array literal is written against,
|
|
100
|
+
* when that target is a binding pattern whose type TypeScript merely IMPLIED
|
|
101
|
+
* from the pattern's shape: `const [a, b] = [1, 2]`, `[a, b] = [1, 2]`,
|
|
102
|
+
* `f([1, 2])` for `function f([x, y])`, `function g({ x } = { x: 1 })`. The
|
|
103
|
+
* checker contextually types such a literal from the pattern -- a tuple, or
|
|
104
|
+
* a record of `any` fields -- while the value is exactly what was written:
|
|
105
|
+
* nothing in the program stated a type, and the language binds the pattern
|
|
106
|
+
* off whatever arrives. `null` for a literal in any other position,
|
|
107
|
+
* including one whose target DECLARES its type, which stays what it states.
|
|
108
|
+
* The returned parameter is the census's cell for the value when the target
|
|
109
|
+
* is a parameter, so an empty literal can take its element from what the
|
|
110
|
+
* other call sites pass rather than from nothing. `element` names the nested
|
|
111
|
+
* binding element whose DEFAULT the literal is: that literal is stored in
|
|
112
|
+
* the element's own slot, never in the root parameter's.
|
|
113
|
+
*/
|
|
114
|
+
export const impliedPatternTargetOf = (checker, node) => {
|
|
115
|
+
const parent = node.parent;
|
|
116
|
+
const shapeMatches = (name) => ts.isArrayLiteralExpression(node) ? ts.isArrayBindingPattern(name) : ts.isObjectBindingPattern(name);
|
|
117
|
+
const impliedPattern = (declaration) => shapeMatches(declaration.name) &&
|
|
118
|
+
declaration.type === undefined &&
|
|
119
|
+
(!ts.isParameter(declaration) || impliedPatternParameterOf(checker, declaration) !== null);
|
|
120
|
+
if (ts.isVariableDeclaration(parent) && parent.initializer === node && impliedPattern(parent))
|
|
121
|
+
return { parameter: null };
|
|
122
|
+
if (ts.isParameter(parent) && parent.initializer === node && impliedPattern(parent))
|
|
123
|
+
return { parameter: parent };
|
|
124
|
+
// A nested element's default (`[{ x } = { x: 44 }]`) is read by that
|
|
125
|
+
// element's own pattern, and is typed by the checker from the SAME
|
|
126
|
+
// silhouette the root pattern is: the root decides whether anything was
|
|
127
|
+
// stated.
|
|
128
|
+
if (ts.isBindingElement(parent) && parent.initializer === node && shapeMatches(parent.name)) {
|
|
129
|
+
let root = parent;
|
|
130
|
+
while (ts.isBindingElement(root) || ts.isArrayBindingPattern(root) || ts.isObjectBindingPattern(root))
|
|
131
|
+
root = root.parent;
|
|
132
|
+
if (ts.isParameter(root))
|
|
133
|
+
return impliedPatternParameterOf(checker, root) ? { parameter: root, element: parent } : null;
|
|
134
|
+
if (ts.isVariableDeclaration(root))
|
|
135
|
+
return root.type === undefined ? { parameter: null, element: parent } : null;
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && parent.right === node) {
|
|
139
|
+
const left = ts.isParenthesizedExpression(parent.left) ? parent.left.expression : parent.left;
|
|
140
|
+
const targetMatches = ts.isArrayLiteralExpression(node) ? ts.isArrayLiteralExpression(left) : ts.isObjectLiteralExpression(left);
|
|
141
|
+
return targetMatches ? { parameter: null } : null;
|
|
142
|
+
}
|
|
143
|
+
if ((ts.isCallExpression(parent) || ts.isNewExpression(parent)) && parent.arguments?.includes(node)) {
|
|
144
|
+
const position = parent.arguments.indexOf(node);
|
|
145
|
+
const signature = checker.getResolvedSignature(parent);
|
|
146
|
+
const symbol = signature?.getParameters()[position];
|
|
147
|
+
const declaration = symbol?.valueDeclaration;
|
|
148
|
+
if (!declaration || !ts.isParameter(declaration) || declaration.dotDotDotToken || !impliedPattern(declaration))
|
|
149
|
+
return null;
|
|
150
|
+
return { parameter: declaration };
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* The parameter whose DESTRUCTURING PATTERN `node` is or names, when the
|
|
156
|
+
* pattern's type is one TypeScript merely IMPLIED from the pattern's shape --
|
|
157
|
+
* `function f([x, y])`, `function g({ a, b })` -- with no annotation and no
|
|
158
|
+
* JSDoc tag. That implied type (`[any, any]`, `{ a: any; b: any }`) is a
|
|
159
|
+
* statement about the binding's SYNTAX, not about any value a caller passes:
|
|
160
|
+
* the language binds an array pattern off any iterable of any length, and an
|
|
161
|
+
* object pattern off any object. So for every question this compiler asks --
|
|
162
|
+
* does the parameter state a type (no: the census binds it from the call
|
|
163
|
+
* sites), is the pattern's own type real (no: it is its source's) -- the
|
|
164
|
+
* pattern is "unannotated", and this one predicate is what every site asks so
|
|
165
|
+
* the census, the layout resolver and the ABI projection agree by
|
|
166
|
+
* construction. `null` for a named parameter, an annotated pattern, or a
|
|
167
|
+
* pattern that is not a parameter's own name.
|
|
168
|
+
*/
|
|
169
|
+
export const impliedPatternParameterOf = (checker, node) => {
|
|
170
|
+
const parameter = ts.isParameter(node)
|
|
171
|
+
? node
|
|
172
|
+
: (ts.isArrayBindingPattern(node) || ts.isObjectBindingPattern(node)) && ts.isParameter(node.parent) && node.parent.name === node
|
|
173
|
+
? node.parent
|
|
174
|
+
: null;
|
|
175
|
+
// A JSDoc function-type literal spells its parameters positionally --
|
|
176
|
+
// `@type {function(...number): void}` parses to a ParameterDeclaration whose
|
|
177
|
+
// `name` node is absent. Every predicate below reads `parameter.name`, so the
|
|
178
|
+
// absence has to be answered here rather than crashing the producer.
|
|
179
|
+
if (!parameter || !parameter.name || ts.isIdentifier(parameter.name) || parameter.type || parameter.dotDotDotToken)
|
|
180
|
+
return null;
|
|
181
|
+
if (ts.getJSDocParameterTags(parameter).length > 0 || ts.getJSDocType(parameter) !== undefined)
|
|
182
|
+
return null;
|
|
183
|
+
// A function literal can receive a real parameter type from its surrounding
|
|
184
|
+
// call or assignment even though the parameter has no annotation of its
|
|
185
|
+
// own. `pairs.map(([value, count]) => ...)` is the common case: Array#map's
|
|
186
|
+
// contextual signature states the tuple held by the callback parameter.
|
|
187
|
+
// That tuple is not the checker's pattern-shaped `[any, any]` placeholder
|
|
188
|
+
// and must keep its fixed positions. Otherwise the slot is flattened to an
|
|
189
|
+
// `Array<any>` while each binding element retains its contextual tuple
|
|
190
|
+
// field, leaving one value with two incompatible carriers.
|
|
191
|
+
const callable = parameter.parent;
|
|
192
|
+
if (ts.isArrowFunction(callable) || ts.isFunctionExpression(callable)) {
|
|
193
|
+
const contextual = checker.getContextualType(callable);
|
|
194
|
+
if (contextual) {
|
|
195
|
+
const position = callable.parameters.indexOf(parameter);
|
|
196
|
+
const signatures = checker.getNonNullableType(contextual).getCallSignatures();
|
|
197
|
+
if (signatures.length > 0 &&
|
|
198
|
+
signatures.every((signature) => {
|
|
199
|
+
const symbol = signature.getParameters()[position];
|
|
200
|
+
if (!symbol)
|
|
201
|
+
return false;
|
|
202
|
+
const type = checker.getTypeOfSymbolAtLocation(symbol, callable);
|
|
203
|
+
return (type.flags & ts.TypeFlags.Any) === 0;
|
|
204
|
+
}))
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return parameter;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* The implied-pattern parameter a binding element belongs to, or `null`. The
|
|
212
|
+
* checker types every name in `function f([w = c()])` from the pattern's own
|
|
213
|
+
* silhouette -- `void` here, because the default is the only evidence it has
|
|
214
|
+
* -- and that silhouette is the thing `impliedPatternParameterOf` exists to
|
|
215
|
+
* see through: a layout consult that trusts it skips the census that holds
|
|
216
|
+
* the callers' actual writes. An identifier declared by such an element is
|
|
217
|
+
* the same question asked at a read.
|
|
218
|
+
*/
|
|
219
|
+
export const impliedPatternElementRootOf = (checker, node) => {
|
|
220
|
+
let current = node;
|
|
221
|
+
if (ts.isIdentifier(current)) {
|
|
222
|
+
const declarations = checker.getSymbolAtLocation(current)?.declarations;
|
|
223
|
+
current = declarations && declarations.length === 1 && declarations[0] && ts.isBindingElement(declarations[0]) ? declarations[0] : null;
|
|
224
|
+
}
|
|
225
|
+
if (!current || !ts.isBindingElement(current))
|
|
226
|
+
return null;
|
|
227
|
+
while (ts.isBindingElement(current) || ts.isArrayBindingPattern(current) || ts.isObjectBindingPattern(current))
|
|
228
|
+
current = current.parent;
|
|
229
|
+
return ts.isParameter(current) ? impliedPatternParameterOf(checker, current) : null;
|
|
230
|
+
};
|
|
231
|
+
export const isUnusableEvidence = (type) => (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Void | ts.TypeFlags.Never)) !== 0;
|
|
232
|
+
/**
|
|
233
|
+
* The single-signature call arity of a type, or `null` when it is not a
|
|
234
|
+
* callable with exactly one signature -- `widestOf`'s own guard against
|
|
235
|
+
* joining a CALLING CONVENTION by plain assignability, below.
|
|
236
|
+
*/
|
|
237
|
+
const soleCallArity = (type) => {
|
|
238
|
+
const signatures = type.getCallSignatures();
|
|
239
|
+
return signatures.length === 1 ? (signatures[0]?.getParameters().length ?? null) : null;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* The one type among these that every other fits into, or `null` when no
|
|
243
|
+
* single member holds them all.
|
|
244
|
+
*
|
|
245
|
+
* The ONE join every census in this compiler uses -- `parameter-bindings.ts`
|
|
246
|
+
* (call-site arguments, write-sets, return expressions), `return-bindings.ts`
|
|
247
|
+
* and `local-bindings.ts` (both by way of `derivedExpressionType`, below).
|
|
248
|
+
* Kept to one copy deliberately: two joins answering the same "do these
|
|
249
|
+
* agree" question two ways is the defect class this compiler keeps
|
|
250
|
+
* rediscovering (see this file's own header comment on `derivedExpressionType`).
|
|
251
|
+
*
|
|
252
|
+
* Two writes to one cell, two calls to one parameter, two `return`s from one
|
|
253
|
+
* function: each has to settle on a single carrier, and requiring the observed
|
|
254
|
+
* types to be IDENTICAL refuses a case the program answers itself. In three's
|
|
255
|
+
* renderer `let _gl = context;` is followed by
|
|
256
|
+
* `if ( _gl === null ) _gl = getContext( ... );` -- the writes are
|
|
257
|
+
* `NativeWebGL2RenderingContext` and `NativeWebGL2RenderingContext | null`, and
|
|
258
|
+
* the null check on the very next line says which one the cell is.
|
|
259
|
+
*
|
|
260
|
+
* Nothing is invented here. The answer is always one of the types the program
|
|
261
|
+
* itself produced, admitted only because the checker says every other observed
|
|
262
|
+
* type is assignable to it -- so the storage provably holds all of them. Two
|
|
263
|
+
* unrelated types stay refused, because there the honest answer is neither, and
|
|
264
|
+
* a union of this compiler's own making would be a source-shaped guess at a
|
|
265
|
+
* carrier rather than a reading of one.
|
|
266
|
+
*
|
|
267
|
+
* A CALLING CONVENTION is the one case plain assignability gets wrong. A
|
|
268
|
+
* function declaring FEWER parameters is assignable wherever one declaring
|
|
269
|
+
* more is expected -- TypeScript lets a callee ignore an argument it never
|
|
270
|
+
* named -- so joining `(event) => void` and `() => void` by assignability
|
|
271
|
+
* alone picks `() => void`: the least informative signature, with the
|
|
272
|
+
* parameter every real call site actually passes silently dropped.
|
|
273
|
+
* `EventDispatcher.addEventListener`'s `listener` parameter, bound once bare
|
|
274
|
+
* `Function` stopped being usable evidence (`parameter-bindings.ts`'s
|
|
275
|
+
* `isUnusableEvidence`), is exactly this: joined by assignability it
|
|
276
|
+
* collapsed to `() => void` while `dispatchEvent` calls every listener with
|
|
277
|
+
* one argument, and that reached the emitter as a real ABI mismatch. Requiring
|
|
278
|
+
* the SAME arity when either side is a single-signature callable is the
|
|
279
|
+
* narrow fix -- compatible same-arity callables still agree exactly as
|
|
280
|
+
* `isTypeAssignableTo` already decided; only arity itself stops being
|
|
281
|
+
* something a join can widen away.
|
|
282
|
+
*/
|
|
283
|
+
/**
|
|
284
|
+
* Whether a JSDoc type node states nothing this compiler can read -- it
|
|
285
|
+
* resolves to `any`, `unknown`, or the checker's internal error type (which
|
|
286
|
+
* carries the `Any` flag).
|
|
287
|
+
*
|
|
288
|
+
* `checker.getTypeFromTypeNode` degrades to this when the node NAMES something
|
|
289
|
+
* the checker cannot bind at that site, and three.js's source does it
|
|
290
|
+
* constantly: `Box3.js` JSDoc-references `Sphere`/`Object3D`/`Triangle`/
|
|
291
|
+
* `Line3`/`Plane`/`Matrix3`/`Matrix4`/`Raycaster`/`Sprite` while its only
|
|
292
|
+
* import is `Vector3`, and every one of those resolves to `any` here.
|
|
293
|
+
*
|
|
294
|
+
* The distinction this draws is the whole point: a tag the checker CAN read is
|
|
295
|
+
* the program stating a type, and stops a census exactly as a TS annotation
|
|
296
|
+
* does. A tag that resolves to nothing is the program stating a type this
|
|
297
|
+
* compiler failed to resolve -- refusing the declaration for it is refusing it
|
|
298
|
+
* for a fact that is not there. `parameter-bindings.ts` has drawn this
|
|
299
|
+
* distinction since the call-site census landed; `return-bindings.ts` and
|
|
300
|
+
* `local-bindings.ts` did not, and bailed on the mere PRESENCE of a tag, which
|
|
301
|
+
* is why 48 of the app's three.js returns stayed `any` while the identical
|
|
302
|
+
* shape on a parameter was rescued. One predicate, three censuses.
|
|
303
|
+
*/
|
|
304
|
+
/**
|
|
305
|
+
* The type one member of `receiver` produces, or `null` when nothing usable
|
|
306
|
+
* comes back.
|
|
307
|
+
*
|
|
308
|
+
* ONE copy, shared by all three censuses. `parameter-bindings.ts`,
|
|
309
|
+
* `local-bindings.ts` and `field-bindings.ts` each carried a private,
|
|
310
|
+
* byte-identical version of this -- three authorities on one question, which
|
|
311
|
+
* is the defect class this compiler keeps rediscovering. The bug below lived
|
|
312
|
+
* in all three at once precisely because fixing it in one would not have been
|
|
313
|
+
* visible in the others.
|
|
314
|
+
*
|
|
315
|
+
* ## The receiver's own null is not part of the question
|
|
316
|
+
*
|
|
317
|
+
* `getPropertyOfType` on a UNION answers only with properties every member
|
|
318
|
+
* has, and `null` has none -- so `_gl.R32F`, where `_gl` is
|
|
319
|
+
* `NativeWebGL2RenderingContext | null`, came back with no property at all and
|
|
320
|
+
* the whole chain below it refused. That is not the program being unreadable;
|
|
321
|
+
* it is the question being asked of the wrong type. A member read either runs
|
|
322
|
+
* with a non-null receiver or does not run, so what the read PRODUCES is the
|
|
323
|
+
* member of the non-null receiver -- exactly what TypeScript itself answers
|
|
324
|
+
* once flow narrowing has done its work, and `structural-layout-type.ts`
|
|
325
|
+
* already asks it this way.
|
|
326
|
+
*
|
|
327
|
+
* Stripping the null cannot make an answer worse: where the receiver has no
|
|
328
|
+
* null, `getNonNullableType` is the identity, and where it does, the
|
|
329
|
+
* alternative was `null` -- the boxed carrier -- rather than some other type.
|
|
330
|
+
*
|
|
331
|
+
* Measured on the three.js app: 52 boxed identifier reads hang off `WebGLTextures.js`'s
|
|
332
|
+
* `internalFormat` cell alone, whose every write is a `_gl.<CONSTANT>` read.
|
|
333
|
+
*/
|
|
334
|
+
/**
|
|
335
|
+
* `resolved` when it names ONE calling convention, the convention this call
|
|
336
|
+
* site actually selected when it does not, and `null` when neither.
|
|
337
|
+
*
|
|
338
|
+
* A member can land on a method declaring more than one signature, and there is
|
|
339
|
+
* no primitive joining two overloads into one: substituting the overloaded type
|
|
340
|
+
* trades a `dynamic` carrier for an `unresolved` one -- lattice BOTTOM -- which
|
|
341
|
+
* trips `representation/verify.ts`'s `unresolved-reaches-materialization`
|
|
342
|
+
* guard. That guard is right; the defect is asking the question of the wrong
|
|
343
|
+
* node.
|
|
344
|
+
*
|
|
345
|
+
* A member read that IS a call's callee does not need its overloads joined: the
|
|
346
|
+
* program picked one, with its arguments, and TypeScript resolves exactly that
|
|
347
|
+
* with `getResolvedSignature`. Reading the type back off the selected
|
|
348
|
+
* signature's own declaration yields a single-signature function type -- the
|
|
349
|
+
* real convention this call uses. `string.split( '\n' )` in `WebGLProgram.js`
|
|
350
|
+
* is the whole of it: two `String.prototype.split` overloads, one call.
|
|
351
|
+
*
|
|
352
|
+
* NOT the refuted overload-selector. That one lived inside
|
|
353
|
+
* `resolvedCalleeSignatureType`, where the receiver had collapsed to `never` --
|
|
354
|
+
* so the overload set was a phantom and joining its arms answered a question
|
|
355
|
+
* the program never asked. Here the receiver is a real type a census bound, the
|
|
356
|
+
* overloads are real, and the SELECTION is TypeScript's own rather than one
|
|
357
|
+
* reimplemented here.
|
|
358
|
+
*
|
|
359
|
+
* An overloaded member NOT in callee position keeps its box: nothing at that
|
|
360
|
+
* site says which signature the value stands for, and guessing would give it a
|
|
361
|
+
* convention the program never chose.
|
|
362
|
+
*/
|
|
363
|
+
/**
|
|
364
|
+
* The type an ELEMENT read produces, when the key is not a name.
|
|
365
|
+
*
|
|
366
|
+
* `lights[ i ]`, `state.probe[ j ]`, `array[ i ]` -- the census resolvers
|
|
367
|
+
* handled only a STRING-LITERAL key, which is a named member spelled with
|
|
368
|
+
* brackets. Every other key was refused, and on the three.js app that is 470 boxed
|
|
369
|
+
* identifier reads at the root of the chain plus everything downstream of
|
|
370
|
+
* them: `const light = lights[ i ]` is the single largest one.
|
|
371
|
+
*
|
|
372
|
+
* A key not known until runtime is answered by the receiver's INDEX signature,
|
|
373
|
+
* which is the only thing that CAN answer it -- and an array's element type is
|
|
374
|
+
* exactly its numeric index signature, so `Light[]` indexed by a `number`
|
|
375
|
+
* yields `Light` with no array special-case. Which signature to ask is decided
|
|
376
|
+
* by the key's own type, resolved by the caller's own operand resolver so each
|
|
377
|
+
* census keeps its own view of what a key expression holds.
|
|
378
|
+
*
|
|
379
|
+
* A receiver with no index signature for that key comes back `null` and stays
|
|
380
|
+
* boxed: the read is then something this cannot describe, and an array whose
|
|
381
|
+
* element type TypeScript inferred as `any` is `any` again by the
|
|
382
|
+
* `isUnusableEvidence` test below rather than an improvement -- except where
|
|
383
|
+
* the caller hands over the program's value-flow index and that index proves
|
|
384
|
+
* a NUMBER key can name nothing any instance of the receiver's class family
|
|
385
|
+
* will ever hold (`absentNumericIndexTypeOf`): the read is then `undefined`.
|
|
386
|
+
*/
|
|
387
|
+
export const indexedTypeOf = (checker, receiver, key, at, flow, census = null) => {
|
|
388
|
+
const numeric = (key.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral)) !== 0;
|
|
389
|
+
const nonNullReceiver = checker.getNonNullableType(receiver);
|
|
390
|
+
// Numeric property keys also address a string index signature. Prefer an
|
|
391
|
+
// explicit numeric signature when present; its narrower value contract
|
|
392
|
+
// must survive alongside the string signature's broader one.
|
|
393
|
+
const indexed = numeric
|
|
394
|
+
? (checker.getIndexTypeOfType(nonNullReceiver, ts.IndexKind.Number) ?? checker.getIndexTypeOfType(nonNullReceiver, ts.IndexKind.String))
|
|
395
|
+
: checker.getIndexTypeOfType(nonNullReceiver, ts.IndexKind.String);
|
|
396
|
+
if (indexed)
|
|
397
|
+
return isUnusableEvidence(indexed) ? null : indexed;
|
|
398
|
+
return (closedObjectLiteralIndexTypeOf(checker, nonNullReceiver, at) ??
|
|
399
|
+
(flow ? absentNumericIndexTypeOf(checker, flow, nonNullReceiver, key, census) : null));
|
|
400
|
+
};
|
|
401
|
+
/**
|
|
402
|
+
* A closed object LITERAL declares no index signature, but every string (or
|
|
403
|
+
* number) key reading it either lands on one of its own properties or misses
|
|
404
|
+
* -- exactly the fact an index signature states. `const shaderIDs = { a: '1',
|
|
405
|
+
* b: '2' }; shaderIDs[ material.type ]` is not a different question from
|
|
406
|
+
* `Record<string, string>[ k ]`; TypeScript answers it `any` only because
|
|
407
|
+
* nobody wrote the signature down, and the checker's own `getIndexTypeOfType`
|
|
408
|
+
* has nothing to hand back. Synthesizing `(join of the literal's own property
|
|
409
|
+
* types) | undefined` from the literal's OWN declaration is citing the object,
|
|
410
|
+
* not guessing at it -- the same standing `joinOfWrites` already gives a
|
|
411
|
+
* cell's disagreeing writes, applied to a different evidence set.
|
|
412
|
+
*
|
|
413
|
+
* Fires only when EVERY declaration of the receiver's own symbol is an object
|
|
414
|
+
* LITERAL expression: a class instance or a declared interface can grow
|
|
415
|
+
* properties (or hold others already) this reading never sees, so those stay
|
|
416
|
+
* refused. A missing key answers `undefined` -- the language's own reading of
|
|
417
|
+
* an absent property, the same precedent `structural-indexed-access.ts`
|
|
418
|
+
* already relies on for `T[K]` -- so the synthesized type always carries it,
|
|
419
|
+
* matching how the source itself is written: every real call site of this
|
|
420
|
+
* shape (`shaderIDs[ material.type ]` chained into `if ( shaderID )`, a
|
|
421
|
+
* dispatch-table `handlers[ event ]?.()`) already treats the read as
|
|
422
|
+
* possibly-absent.
|
|
423
|
+
*/
|
|
424
|
+
const closedObjectLiteralIndexTypeOf = (checker, receiver, at) => {
|
|
425
|
+
const declarations = receiver.getSymbol()?.declarations;
|
|
426
|
+
if (!declarations || declarations.length === 0 || !declarations.every(ts.isObjectLiteralExpression))
|
|
427
|
+
return null;
|
|
428
|
+
const properties = checker.getPropertiesOfType(receiver);
|
|
429
|
+
if (properties.length === 0)
|
|
430
|
+
return null;
|
|
431
|
+
const valueTypes = [];
|
|
432
|
+
for (const property of properties) {
|
|
433
|
+
const type = checker.getTypeOfSymbolAtLocation(property, at);
|
|
434
|
+
if (isUnusableEvidence(type))
|
|
435
|
+
return null;
|
|
436
|
+
valueTypes.push(type);
|
|
437
|
+
}
|
|
438
|
+
const joined = joinOfWrites(checker, valueTypes);
|
|
439
|
+
return joined ? checker.getNullableType(joined, ts.TypeFlags.Undefined) : null;
|
|
440
|
+
};
|
|
441
|
+
/** The source class an INSTANCE type is an instance of -- never `typeof C`, and never a library class whose instances the host shapes. */
|
|
442
|
+
const sourceClassOfInstance = (type) => {
|
|
443
|
+
if ((type.flags & ts.TypeFlags.Object) === 0)
|
|
444
|
+
return null;
|
|
445
|
+
const target = (type.objectFlags & ts.ObjectFlags.Reference) !== 0 ? type.target : type;
|
|
446
|
+
if (!target.isClassOrInterface() || (target.objectFlags & ts.ObjectFlags.Class) === 0)
|
|
447
|
+
return null;
|
|
448
|
+
const declaration = target.getSymbol()?.valueDeclaration;
|
|
449
|
+
if (!declaration || (!ts.isClassDeclaration(declaration) && !ts.isClassExpression(declaration)))
|
|
450
|
+
return null;
|
|
451
|
+
return declaration.getSourceFile().isDeclarationFile ? null : declaration;
|
|
452
|
+
};
|
|
453
|
+
/** `ToPropertyKey` of a Number is its `ToString`: exactly the names a number key can spell. */
|
|
454
|
+
const isCanonicalNumericName = (name) => String(Number(name)) === name;
|
|
455
|
+
const KEYED_WRITE_EDGES = new Set([
|
|
456
|
+
'index-assignment',
|
|
457
|
+
'compound-assignment',
|
|
458
|
+
'logical-assignment',
|
|
459
|
+
'destructuring',
|
|
460
|
+
'destructuring-default',
|
|
461
|
+
'iteration-binding'
|
|
462
|
+
]);
|
|
463
|
+
/** `Object.<method>` / `Reflect.<method>` calls that add own properties or replace a prototype. */
|
|
464
|
+
const OBJECT_PROPERTY_MUTATORS = ['assign', 'defineProperty', 'defineProperties', 'setPrototypeOf'];
|
|
465
|
+
const REFLECT_PROPERTY_MUTATORS = ['set', 'defineProperty', 'setPrototypeOf'];
|
|
466
|
+
const INSTANCE_PRIMITIVE_FLAGS = ts.TypeFlags.StringLike |
|
|
467
|
+
ts.TypeFlags.NumberLike |
|
|
468
|
+
ts.TypeFlags.BigIntLike |
|
|
469
|
+
ts.TypeFlags.BooleanLike |
|
|
470
|
+
ts.TypeFlags.EnumLike |
|
|
471
|
+
ts.TypeFlags.ESSymbolLike |
|
|
472
|
+
ts.TypeFlags.Void |
|
|
473
|
+
ts.TypeFlags.Undefined |
|
|
474
|
+
ts.TypeFlags.Null |
|
|
475
|
+
ts.TypeFlags.Never;
|
|
476
|
+
const EQUALITY_OPERATORS = new Set([
|
|
477
|
+
ts.SyntaxKind.EqualsEqualsEqualsToken,
|
|
478
|
+
ts.SyntaxKind.ExclamationEqualsEqualsToken,
|
|
479
|
+
ts.SyntaxKind.EqualsEqualsToken,
|
|
480
|
+
ts.SyntaxKind.ExclamationEqualsToken
|
|
481
|
+
]);
|
|
482
|
+
const unwrapValueExpression = (expression) => {
|
|
483
|
+
let value = expression;
|
|
484
|
+
while (ts.isParenthesizedExpression(value) ||
|
|
485
|
+
ts.isAsExpression(value) ||
|
|
486
|
+
ts.isTypeAssertionExpression(value) ||
|
|
487
|
+
ts.isSatisfiesExpression(value) ||
|
|
488
|
+
ts.isNonNullExpression(value))
|
|
489
|
+
value = value.expression;
|
|
490
|
+
return value;
|
|
491
|
+
};
|
|
492
|
+
/** The outermost parenthesis or assertion wrapping `node`: where its value is actually USED. */
|
|
493
|
+
const outermostValueWrapperOf = (node) => {
|
|
494
|
+
let use = node;
|
|
495
|
+
while (ts.isParenthesizedExpression(use.parent) ||
|
|
496
|
+
ts.isAsExpression(use.parent) ||
|
|
497
|
+
ts.isTypeAssertionExpression(use.parent) ||
|
|
498
|
+
ts.isSatisfiesExpression(use.parent) ||
|
|
499
|
+
ts.isNonNullExpression(use.parent))
|
|
500
|
+
use = use.parent;
|
|
501
|
+
return use;
|
|
502
|
+
};
|
|
503
|
+
const NO_SETTLED_CENSUS = {};
|
|
504
|
+
// Keyed by the census too: a proof that consulted a settled census may pass
|
|
505
|
+
// where the checker-only proof refused, and the two must never share a slot.
|
|
506
|
+
const numericAbsenceProofs = new WeakMap();
|
|
507
|
+
const activeNumericAbsenceProofs = new WeakMap();
|
|
508
|
+
const numericAbsenceKeyDomains = new WeakMap();
|
|
509
|
+
const numericAbsenceDebug = process.env['GEA_INDEXED_ABSENCE_DEBUG'];
|
|
510
|
+
/**
|
|
511
|
+
* ⛔ UNSOUND MEASUREMENT ARM. `GEA_INDEXED_ABSENCE_FORCE=<class>|*` drops the
|
|
512
|
+
* KEYED-WRITE clause of the proof below and nothing else, so the carriers that
|
|
513
|
+
* clause alone is holding open can be counted before any work is spent making
|
|
514
|
+
* it discharge honestly. It admits a numeric-named property this program may
|
|
515
|
+
* really create; never set it for a build whose output is kept.
|
|
516
|
+
*/
|
|
517
|
+
const numericAbsenceForce = process.env['GEA_INDEXED_ABSENCE_FORCE'];
|
|
518
|
+
/** `GEA_NUMERIC_ABSENCE=0` refuses the numeric-absence proof; see its use for why. */
|
|
519
|
+
const numericAbsenceEnabled = process.env['GEA_NUMERIC_ABSENCE'];
|
|
520
|
+
/**
|
|
521
|
+
* `undefined`, when a NUMBER key can name no property that any instance of the
|
|
522
|
+
* receiver's class family will ever hold; `null` when that is not proved.
|
|
523
|
+
*
|
|
524
|
+
* three's `WebGLUtils.convert( p )` ends `return ( gl[ p ] !== undefined ) ?
|
|
525
|
+
* gl[ p ] : null;`, `gl` a `NativeWebGL2RenderingContext` and `p` a numeric
|
|
526
|
+
* format constant. The context declares no member a number can name, so the
|
|
527
|
+
* read is `undefined` on every call -- but the checker reads it `any`, the
|
|
528
|
+
* return census refused `convert` whole (`return-index-signature-absent`), and
|
|
529
|
+
* everything downstream of it (`getInternalFormat` and the texture upload
|
|
530
|
+
* paths) carried a dynamic value.
|
|
531
|
+
*
|
|
532
|
+
* JavaScript reads an absent property as `undefined`, and a number key spells
|
|
533
|
+
* only canonical numeric strings. So the read is absent on every instance when:
|
|
534
|
+
*
|
|
535
|
+
* - every value the receiver can hold is an instance of an ENUMERATED set of
|
|
536
|
+
* source classes (`ownedClassReceiverInventoryOf`), none declaring an index
|
|
537
|
+
* signature, a member whose name is a canonical numeric string anywhere on
|
|
538
|
+
* its prototype chain, or a computed member whose key could spell one -- and
|
|
539
|
+
* none with a library base, whose instances the host shapes;
|
|
540
|
+
* - no write can create such a property on a family member: no named write of
|
|
541
|
+
* a numeric name (`o[ 3 ] = v` is one), no computed write whose key domain
|
|
542
|
+
* (`property-key-domain.ts`) may spell one, no `__proto__` write, and no
|
|
543
|
+
* reflective definition or prototype replacement -- each unless its receiver
|
|
544
|
+
* provably cannot hold a family member. That is
|
|
545
|
+
* `flow/class-family-member-read.ts`'s closure rule, restated for a NAME SET
|
|
546
|
+
* (it answers one identifier-shaped name and refuses numeric ones up front);
|
|
547
|
+
* - `Object.prototype`, the one intrinsic every such chain ends in, is never
|
|
548
|
+
* handed anywhere that could give it a numeric property: every mention of it
|
|
549
|
+
* is a member read off it or an identity comparison, every `getPrototypeOf`
|
|
550
|
+
* result is used the same way, and no reflective mutator is referenced other
|
|
551
|
+
* than as a direct callee this scan reads. `global-host-mutations.ts` states
|
|
552
|
+
* the general form of this taint, but it reads the settled structural mapper
|
|
553
|
+
* and so cannot be consulted from inside the binding fixpoint that asks this.
|
|
554
|
+
*
|
|
555
|
+
* A string key refuses outright: it can spell a declared member's own name.
|
|
556
|
+
* `GEA_INDEXED_ABSENCE_DEBUG=<class>|*` prints the first failing proof.
|
|
557
|
+
*/
|
|
558
|
+
const absentNumericIndexTypeOf = (checker, flow, receiver, key, census) => {
|
|
559
|
+
const keys = key.isUnion() ? key.types : [key];
|
|
560
|
+
if (!keys.every((part) => (part.flags & ts.TypeFlags.NumberLike) !== 0))
|
|
561
|
+
return null;
|
|
562
|
+
const arms = receiver.isUnion() ? receiver.types : [receiver];
|
|
563
|
+
const censusKey = census ?? NO_SETTLED_CENSUS;
|
|
564
|
+
for (const arm of arms) {
|
|
565
|
+
const root = sourceClassOfInstance(arm);
|
|
566
|
+
if (!root)
|
|
567
|
+
return null;
|
|
568
|
+
let proofsByCensus = numericAbsenceProofs.get(flow);
|
|
569
|
+
if (!proofsByCensus)
|
|
570
|
+
numericAbsenceProofs.set(flow, (proofsByCensus = new WeakMap()));
|
|
571
|
+
let proofs = proofsByCensus.get(censusKey);
|
|
572
|
+
if (!proofs)
|
|
573
|
+
proofsByCensus.set(censusKey, (proofs = new Map()));
|
|
574
|
+
const ledger = deferredIntrinsicProtocolLedgerOf(flow);
|
|
575
|
+
let proved = proofs.get(root);
|
|
576
|
+
if (proved === undefined) {
|
|
577
|
+
let activeByCensus = activeNumericAbsenceProofs.get(flow);
|
|
578
|
+
if (!activeByCensus)
|
|
579
|
+
activeNumericAbsenceProofs.set(flow, (activeByCensus = new WeakMap()));
|
|
580
|
+
let active = activeByCensus.get(censusKey);
|
|
581
|
+
if (!active)
|
|
582
|
+
activeByCensus.set(censusKey, (active = new Set()));
|
|
583
|
+
if (active.has(root))
|
|
584
|
+
return null;
|
|
585
|
+
active.add(root);
|
|
586
|
+
try {
|
|
587
|
+
const compute = () => {
|
|
588
|
+
// The numeric twin of `class-family-member-read.ts`'s
|
|
589
|
+
// `GEA_FAMILY_MEMBER_ABSENT_KEYS`, and it exists for the same reason.
|
|
590
|
+
// The obligation below is DEFERRED: `requirePrototypeKeys` admits it
|
|
591
|
+
// on the spot (it only records into the active capture) and the
|
|
592
|
+
// sealed host census judges it much later. Under the `*` wildcard it
|
|
593
|
+
// cannot be judged true, and the result is a certification
|
|
594
|
+
// diagnostic that costs the WHOLE program its certificate -- a sound
|
|
595
|
+
// outcome, but a worse one than the boxed read this proof replaced,
|
|
596
|
+
// since refusing here simply leaves the read as it was. `0` refuses
|
|
597
|
+
// the proof outright and keeps the program compiling; the default is
|
|
598
|
+
// unchanged. Remove this switch when the census stops taking the
|
|
599
|
+
// wildcard, exactly as that sibling switch says.
|
|
600
|
+
// ... and the installed hosts state the same refusal for every build
|
|
601
|
+
// that loads them: `PluginCapabilities.refusesObjectPrototypeAbsenceProofs`.
|
|
602
|
+
if (numericAbsenceEnabled === '0' || ledger?.refusesObjectPrototypeAbsenceProofs === true)
|
|
603
|
+
return false;
|
|
604
|
+
// Numeric absence also depends on the complete numeric key domain of
|
|
605
|
+
// Object.prototype. Record that dependency in the same deferred
|
|
606
|
+
// ledger as the class-family proof; otherwise an opaque call may
|
|
607
|
+
// mutate a numeric prototype key after this provisional result.
|
|
608
|
+
if (ledger?.requirePrototypeKeys('Object', { numeric: true }, root) !== true)
|
|
609
|
+
return false;
|
|
610
|
+
return numericNamesAbsentFrom(checker, flow, root, census);
|
|
611
|
+
};
|
|
612
|
+
proved = ledger ? ledger.capture(compute) : { value: compute(), requirements: [] };
|
|
613
|
+
proofs.set(root, proved);
|
|
614
|
+
}
|
|
615
|
+
finally {
|
|
616
|
+
active.delete(root);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (!proved.value || (proved.requirements.length > 0 && ledger?.include(proved.requirements) !== true))
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
return checker.getUndefinedType();
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* Whether a numeric element read is proven to miss every owned class-family
|
|
626
|
+
* instance. This is deliberately separate from `indexedTypeOf`: an
|
|
627
|
+
* `undefined` result can also come from an ordinary index signature or a
|
|
628
|
+
* checker-derived type, neither of which licenses bypassing the runtime
|
|
629
|
+
* property lookup.
|
|
630
|
+
*/
|
|
631
|
+
export const numericIndexAbsenceProven = (checker, flow, receiver, key, census = null) => deferredIntrinsicProtocolLedgerOf(flow) !== null && absentNumericIndexTypeOf(checker, flow, receiver, key, census) !== null;
|
|
632
|
+
const numericNamesAbsentFrom = (checker, flow, root, census) => {
|
|
633
|
+
// `GEA_INDEXED_ABSENCE_DEBUG` used to report only the FIRST failing clause,
|
|
634
|
+
// because every refusal below was `return refuse(...)`: the function ended
|
|
635
|
+
// there, so a class refused by two keyed writes only ever showed one, and
|
|
636
|
+
// finding the second took a re-run with the first fixed. While watched,
|
|
637
|
+
// `refuse` still prints and records every site it is called at, but the loop
|
|
638
|
+
// and mutator-scan sites stop RETURNING on the first one and keep walking --
|
|
639
|
+
// the boolean this function hands back is unchanged either way, since a
|
|
640
|
+
// proof that refuses once is exactly as refused as one that refuses five
|
|
641
|
+
// times; only the reporting is exhaustive now.
|
|
642
|
+
const enumerating = numericAbsenceDebug !== undefined && (numericAbsenceDebug === '*' || numericAbsenceDebug === root.name?.text);
|
|
643
|
+
const clauseCounts = new Map();
|
|
644
|
+
let refused = 0;
|
|
645
|
+
const refuse = (reason, at) => {
|
|
646
|
+
if (numericAbsenceDebug !== undefined && (numericAbsenceDebug === '*' || numericAbsenceDebug === root.name?.text)) {
|
|
647
|
+
const file = at?.getSourceFile();
|
|
648
|
+
const where = at && file
|
|
649
|
+
? `${file.fileName.split('/').pop()}:${file.getLineAndCharacterOfPosition(at.getStart()).line + 1} ${at.getText().slice(0, 80)}`
|
|
650
|
+
: '';
|
|
651
|
+
process.stderr.write(`[INDEXED-ABSENCE] ${root.name?.text ?? '(anonymous)'} ${reason} ${where}\n`);
|
|
652
|
+
}
|
|
653
|
+
refused += 1;
|
|
654
|
+
clauseCounts.set(reason, (clauseCounts.get(reason) ?? 0) + 1);
|
|
655
|
+
return false;
|
|
656
|
+
};
|
|
657
|
+
// Every path that ends this proof funnels through here, so the summary line
|
|
658
|
+
// is emitted exactly once per call, whether the proof stopped at the first
|
|
659
|
+
// refusal (not enumerating) or walked every site (enumerating).
|
|
660
|
+
const summarize = () => {
|
|
661
|
+
if (enumerating && refused > 0)
|
|
662
|
+
process.stderr.write(`[INDEXED-ABSENCE-SUMMARY] ${root.name?.text ?? '(anonymous)'} refused=${refused} clauses=${[...clauseCounts]
|
|
663
|
+
.map(([clause, count]) => `${clause}:${count}`)
|
|
664
|
+
.join(',')}\n`);
|
|
665
|
+
return false;
|
|
666
|
+
};
|
|
667
|
+
const inventory = ownedClassReceiverInventoryOf(checker, flow, new Set([root]));
|
|
668
|
+
if (!inventory) {
|
|
669
|
+
refuse('family-not-closed');
|
|
670
|
+
return summarize();
|
|
671
|
+
}
|
|
672
|
+
let domains = numericAbsenceKeyDomains.get(flow);
|
|
673
|
+
// Every write the index holds is already reachable: the flow walk prunes
|
|
674
|
+
// unreachable members before it records anything.
|
|
675
|
+
if (!domains)
|
|
676
|
+
numericAbsenceKeyDomains.set(flow, (domains = createPropertyKeyDomains(checker, flow, () => true)));
|
|
677
|
+
const keys = domains;
|
|
678
|
+
// The family, each member's source ancestry, and what each declares.
|
|
679
|
+
const family = new Map();
|
|
680
|
+
const ancestorsOf = new Map();
|
|
681
|
+
const chain = new Set();
|
|
682
|
+
// Each member of the inventory is an independent question -- a bad member B
|
|
683
|
+
// says nothing about member A -- so while enumerating, a refused member is
|
|
684
|
+
// skipped (it contributes nothing to `family`/`ancestorsOf`/`chain`, which
|
|
685
|
+
// is safe: every later reader of those maps already tolerates an absent
|
|
686
|
+
// entry) rather than ending the whole proof before the rest are checked.
|
|
687
|
+
for (const member of inventory.classes) {
|
|
688
|
+
const symbol = member.name ? checker.getSymbolAtLocation(member.name) : checker.getTypeAtLocation(member).getSymbol();
|
|
689
|
+
const instance = symbol && checker.getDeclaredTypeOfSymbol(symbol);
|
|
690
|
+
if (!instance?.isClassOrInterface()) {
|
|
691
|
+
refuse('family-member-untyped', member);
|
|
692
|
+
if (!enumerating)
|
|
693
|
+
return summarize();
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (checker.getIndexInfosOfType(instance).length > 0) {
|
|
697
|
+
refuse('index-signature', member);
|
|
698
|
+
if (!enumerating)
|
|
699
|
+
return summarize();
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
const numeric = checker.getPropertiesOfType(instance).find((property) => isCanonicalNumericName(property.name));
|
|
703
|
+
if (numeric) {
|
|
704
|
+
refuse('numeric-member', numeric.valueDeclaration ?? member);
|
|
705
|
+
if (!enumerating)
|
|
706
|
+
return summarize();
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
const ancestors = new Set();
|
|
710
|
+
let libraryBase = null;
|
|
711
|
+
const visit = (type) => {
|
|
712
|
+
for (const base of checker.getBaseTypes(type)) {
|
|
713
|
+
const declaration = sourceClassOfInstance(base);
|
|
714
|
+
if (!declaration) {
|
|
715
|
+
libraryBase ??= member;
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (ancestors.has(declaration))
|
|
719
|
+
continue;
|
|
720
|
+
ancestors.add(declaration);
|
|
721
|
+
const target = (base.objectFlags & ts.ObjectFlags.Reference) !== 0 ? base.target : base;
|
|
722
|
+
if (target.isClassOrInterface())
|
|
723
|
+
visit(target);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
visit(instance);
|
|
727
|
+
if (libraryBase) {
|
|
728
|
+
refuse('library-base', libraryBase);
|
|
729
|
+
if (!enumerating)
|
|
730
|
+
return summarize();
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
family.set(member, instance);
|
|
734
|
+
ancestorsOf.set(member, ancestors);
|
|
735
|
+
chain.add(member);
|
|
736
|
+
for (const ancestor of ancestors)
|
|
737
|
+
chain.add(ancestor);
|
|
738
|
+
}
|
|
739
|
+
for (const owner of chain) {
|
|
740
|
+
// The question is whether any member of the chain is spelled with a
|
|
741
|
+
// COMPUTED name that may be numeric. A constructor function's equivalent is
|
|
742
|
+
// `this[ expr ] = v` in its body, which this walk does not read -- so it
|
|
743
|
+
// cannot say there is none.
|
|
744
|
+
if (!isClassSpelledSourceClass(owner)) {
|
|
745
|
+
refuse('constructor-function-computed-members', owner);
|
|
746
|
+
if (!enumerating)
|
|
747
|
+
return summarize();
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
for (const element of owner.members) {
|
|
751
|
+
const name = element.name;
|
|
752
|
+
if (!name || !ts.isComputedPropertyName(name))
|
|
753
|
+
continue;
|
|
754
|
+
if ((ts.getCombinedModifierFlags(element) & ts.ModifierFlags.Static) !== 0)
|
|
755
|
+
continue;
|
|
756
|
+
if (domainMayNameNumeric(keys.of(name.expression))) {
|
|
757
|
+
refuse('computed-member', element);
|
|
758
|
+
if (!enumerating)
|
|
759
|
+
return summarize();
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// "May hold a family member", from the receiver's static type and its
|
|
764
|
+
// allocation -- the same two questions `class-family-member-read.ts` asks.
|
|
765
|
+
const holdsFamily = (declaration) => family.has(declaration) || [...ancestorsOf.values()].some((ancestors) => ancestors.has(declaration));
|
|
766
|
+
// The checker's type, or -- only where the checker says nothing -- the
|
|
767
|
+
// settled census's. Three stores through receivers the checker types `any`
|
|
768
|
+
// (`currentRenderState.state.transmissionRenderTarget[ camera.id ] = new
|
|
769
|
+
// WebGLRenderTarget(...)`, `programs[ programCacheKey ] = program`); the
|
|
770
|
+
// census types the first `Record<string, WebGLRenderTarget | undefined>`,
|
|
771
|
+
// a carrier no class instance can be stored into, so the write cannot land
|
|
772
|
+
// on a family member. Those two writes alone held `WebGLUtils.convert`'s
|
|
773
|
+
// result -- and 409 results downstream of it -- dynamic on the three.js app.
|
|
774
|
+
const typeOf = (expression) => {
|
|
775
|
+
const own = checker.getTypeAtLocation(expression);
|
|
776
|
+
if (!census || (own.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0)
|
|
777
|
+
return own;
|
|
778
|
+
const settled = census.typeAt(expression);
|
|
779
|
+
return settled && (settled.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0 ? settled : own;
|
|
780
|
+
};
|
|
781
|
+
const typeMayHold = (type, depth = 0) => {
|
|
782
|
+
if (depth > 8 || (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)
|
|
783
|
+
return true;
|
|
784
|
+
if (type.isUnionOrIntersection())
|
|
785
|
+
return type.types.some((part) => typeMayHold(part, depth + 1));
|
|
786
|
+
if ((type.flags & ts.TypeFlags.TypeParameter) !== 0) {
|
|
787
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
788
|
+
return constraint === undefined || constraint === type || typeMayHold(constraint, depth + 1);
|
|
789
|
+
}
|
|
790
|
+
if ((type.flags & INSTANCE_PRIMITIVE_FLAGS) !== 0)
|
|
791
|
+
return false;
|
|
792
|
+
if ((type.flags & (ts.TypeFlags.Object | ts.TypeFlags.NonPrimitive)) === 0)
|
|
793
|
+
return true;
|
|
794
|
+
const declaration = sourceClassOfInstance(type);
|
|
795
|
+
if (declaration)
|
|
796
|
+
return holdsFamily(declaration);
|
|
797
|
+
return [...family.values()].some((instance) => checker.isTypeAssignableTo(instance, type));
|
|
798
|
+
};
|
|
799
|
+
// The shared source-slot proof accounts for replacements and receiver
|
|
800
|
+
// escapes; a descriptor inventory or a field annotation does not. Retain
|
|
801
|
+
// that proof context across this query's parameter and field continuations.
|
|
802
|
+
let origins;
|
|
803
|
+
const sourcesOf = (anchor) => (origins ??= closedValueOriginAuthorityOf(checker, flow, anchor));
|
|
804
|
+
const through = (values) => ({
|
|
805
|
+
admitted: true,
|
|
806
|
+
seed: false,
|
|
807
|
+
dependencies: values.map(unwrapValueExpression)
|
|
808
|
+
});
|
|
809
|
+
const excluded = (yes) => ({ admitted: yes, seed: yes, dependencies: [] });
|
|
810
|
+
// A recursive alias must be grounded in an actual disjoint allocation.
|
|
811
|
+
// Revisiting an active expression is not itself evidence of disjointness.
|
|
812
|
+
const nonFamilyOrigin = seededOriginSolver((value) => {
|
|
813
|
+
if (ts.isObjectLiteralExpression(value) ||
|
|
814
|
+
ts.isArrayLiteralExpression(value) ||
|
|
815
|
+
ts.isFunctionExpression(value) ||
|
|
816
|
+
ts.isArrowFunction(value) ||
|
|
817
|
+
ts.isRegularExpressionLiteral(value))
|
|
818
|
+
return excluded(true);
|
|
819
|
+
if (ts.isConditionalExpression(value))
|
|
820
|
+
return through([value.whenTrue, value.whenFalse]);
|
|
821
|
+
if (ts.isBinaryExpression(value) &&
|
|
822
|
+
[ts.SyntaxKind.BarBarToken, ts.SyntaxKind.AmpersandAmpersandToken, ts.SyntaxKind.QuestionQuestionToken].includes(value.operatorToken.kind))
|
|
823
|
+
return through([value.left, value.right]);
|
|
824
|
+
if (ts.isNewExpression(value)) {
|
|
825
|
+
const constructed = checker.getTypeAtLocation(value);
|
|
826
|
+
const declaration = sourceClassOfInstance(constructed);
|
|
827
|
+
if (declaration) {
|
|
828
|
+
const allocations = sourcesOf(value).classAllocationsOf(value);
|
|
829
|
+
return excluded(allocations !== null && [...allocations.classes].every((owner) => !holdsFamily(owner)));
|
|
830
|
+
}
|
|
831
|
+
const callee = checker.getSymbolAtLocation(value.expression)?.valueDeclaration;
|
|
832
|
+
return excluded(callee !== undefined && callee.getSourceFile().isDeclarationFile && !typeMayHold(constructed));
|
|
833
|
+
}
|
|
834
|
+
if (ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) {
|
|
835
|
+
const stored = sourcesOf(value).fieldValuesOf(value);
|
|
836
|
+
return stored !== null && stored.length > 0 ? through(stored) : excluded(false);
|
|
837
|
+
}
|
|
838
|
+
if (!ts.isIdentifier(value))
|
|
839
|
+
return excluded(!typeMayHold(typeOf(value)));
|
|
840
|
+
const declaration = flow.targetOf(value)?.declaration;
|
|
841
|
+
if (declaration && ts.isParameter(declaration)) {
|
|
842
|
+
const incoming = sourcesOf(value).parameterValuesOf(declaration);
|
|
843
|
+
return incoming !== null && incoming.length > 0 ? through(incoming) : excluded(false);
|
|
844
|
+
}
|
|
845
|
+
if (!declaration || !ts.isVariableDeclaration(declaration))
|
|
846
|
+
return excluded(false);
|
|
847
|
+
const values = sourcesOf(value).bindingValuesOf(declaration);
|
|
848
|
+
return values !== null && values.length > 0 ? through(values) : excluded(false);
|
|
849
|
+
});
|
|
850
|
+
const excludesFamily = (expression) => nonFamilyOrigin(unwrapValueExpression(expression)) === 'allocated';
|
|
851
|
+
const mayHold = (expression) => !expression || (typeMayHold(typeOf(expression)) && !excludesFamily(expression));
|
|
852
|
+
const keyMayNameNumeric = (expression) => expression === undefined || ts.isSpreadElement(expression) || domainMayNameNumeric(keys.of(expression));
|
|
853
|
+
/** Whether an `Object.assign` source or `defineProperties` map can carry a numeric key: only a spread-free literal says it cannot. */
|
|
854
|
+
const literalMayNameNumeric = (expression) => {
|
|
855
|
+
const literal = expression && unwrapValueExpression(expression);
|
|
856
|
+
if (!literal || !ts.isObjectLiteralExpression(literal))
|
|
857
|
+
return true;
|
|
858
|
+
return literal.properties.some((property) => {
|
|
859
|
+
if (ts.isSpreadAssignment(property))
|
|
860
|
+
return true;
|
|
861
|
+
const key = property.name;
|
|
862
|
+
if (!key)
|
|
863
|
+
return true;
|
|
864
|
+
if (ts.isComputedPropertyName(key))
|
|
865
|
+
return domainMayNameNumeric(keys.of(key.expression));
|
|
866
|
+
if (ts.isIdentifier(key) || ts.isStringLiteralLike(key) || ts.isNumericLiteral(key))
|
|
867
|
+
return isCanonicalNumericName(key.text);
|
|
868
|
+
return true;
|
|
869
|
+
});
|
|
870
|
+
};
|
|
871
|
+
// Named, keyed and prototype writes.
|
|
872
|
+
for (const write of flow.allWrites) {
|
|
873
|
+
if (write.slot === 'member' && write.member !== null) {
|
|
874
|
+
// A delete never creates a property; a literal's own member is not an
|
|
875
|
+
// expando on an object that already existed; a binding pattern's member
|
|
876
|
+
// edge is a READ of its source.
|
|
877
|
+
if (write.edge === 'delete')
|
|
878
|
+
continue;
|
|
879
|
+
if (ts.isPropertyAssignment(write.site) || ts.isShorthandPropertyAssignment(write.site))
|
|
880
|
+
continue;
|
|
881
|
+
if ((write.edge === 'destructuring' || write.edge === 'destructuring-default') && ts.isBindingElement(write.site))
|
|
882
|
+
continue;
|
|
883
|
+
if (write.member === '__proto__' && mayHold(write.naming)) {
|
|
884
|
+
refuse('prototype-write', write.site);
|
|
885
|
+
if (!enumerating)
|
|
886
|
+
return summarize();
|
|
887
|
+
}
|
|
888
|
+
if (isCanonicalNumericName(write.member) && mayHold(write.naming)) {
|
|
889
|
+
refuse('numeric-member-write', write.site);
|
|
890
|
+
if (!enumerating)
|
|
891
|
+
return summarize();
|
|
892
|
+
}
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (write.slot !== 'element' || !KEYED_WRITE_EDGES.has(write.edge))
|
|
896
|
+
continue;
|
|
897
|
+
const receiver = write.naming;
|
|
898
|
+
const access = receiver?.parent;
|
|
899
|
+
if (!receiver || !access || !ts.isElementAccessExpression(access) || access.expression !== receiver) {
|
|
900
|
+
if (mayHold(receiver)) {
|
|
901
|
+
refuse('keyed-write-unattributed', write.site);
|
|
902
|
+
if (!enumerating)
|
|
903
|
+
return summarize();
|
|
904
|
+
}
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
// This is the shape the debug arm was built for: the three.js app's `programs[
|
|
908
|
+
// programCacheKey ] = program` and `state.transmissionRenderTarget[
|
|
909
|
+
// camera.id ] = ...` are TWO independent keyed writes onto the same class
|
|
910
|
+
// family, and the old first-refusal-ends-the-proof behavior meant a run
|
|
911
|
+
// could only ever report one of them.
|
|
912
|
+
if (domainMayNameNumeric(keys.of(access.argumentExpression)) && mayHold(receiver)) {
|
|
913
|
+
if (numericAbsenceForce === undefined || (numericAbsenceForce !== '*' && numericAbsenceForce !== root.name?.text)) {
|
|
914
|
+
refuse('keyed-write', write.site);
|
|
915
|
+
if (!enumerating)
|
|
916
|
+
return summarize();
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
refuse('keyed-write(FORCED-PAST)', write.site);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
// Reflective definitions and prototype replacement, at every direct call.
|
|
923
|
+
for (const { call } of flow.calls) {
|
|
924
|
+
if (!ts.isCallExpression(call))
|
|
925
|
+
continue;
|
|
926
|
+
const callee = unwrapValueExpression(call.expression);
|
|
927
|
+
if (!ts.isPropertyAccessExpression(callee))
|
|
928
|
+
continue;
|
|
929
|
+
const method = callee.name.text;
|
|
930
|
+
const owner = callee.expression;
|
|
931
|
+
const onObject = OBJECT_PROPERTY_MUTATORS.includes(method) && isGlobalObjectConstructor(checker, owner, checker.getTypeAtLocation(owner));
|
|
932
|
+
const onReflect = !onObject && REFLECT_PROPERTY_MUTATORS.includes(method) && isStandardGlobalValue(checker, owner, 'Reflect');
|
|
933
|
+
if (!onObject && !onReflect)
|
|
934
|
+
continue;
|
|
935
|
+
const args = call.arguments;
|
|
936
|
+
// A spread hides which argument is the target and which the key, so the
|
|
937
|
+
// rest of this call's own checks below (which index into `args`
|
|
938
|
+
// positionally) cannot run meaningfully -- skip straight to the next call
|
|
939
|
+
// even while enumerating.
|
|
940
|
+
if (args.some(ts.isSpreadElement)) {
|
|
941
|
+
refuse('reflective-spread-arguments', call);
|
|
942
|
+
if (!enumerating)
|
|
943
|
+
return summarize();
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
const target = args[0];
|
|
947
|
+
if (method === 'setPrototypeOf') {
|
|
948
|
+
if (mayHold(target)) {
|
|
949
|
+
refuse('prototype-replaced', call);
|
|
950
|
+
if (!enumerating)
|
|
951
|
+
return summarize();
|
|
952
|
+
}
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (method === 'assign') {
|
|
956
|
+
if (mayHold(target) && args.slice(1).some(literalMayNameNumeric)) {
|
|
957
|
+
refuse('object-assign', call);
|
|
958
|
+
if (!enumerating)
|
|
959
|
+
return summarize();
|
|
960
|
+
}
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
if (method === 'defineProperties') {
|
|
964
|
+
if (mayHold(target) && literalMayNameNumeric(args[1])) {
|
|
965
|
+
refuse('define-properties', call);
|
|
966
|
+
if (!enumerating)
|
|
967
|
+
return summarize();
|
|
968
|
+
}
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
// `defineProperty` (either owner) and `Reflect.set`, whose optional fourth
|
|
972
|
+
// argument is the receiver a data property is created on.
|
|
973
|
+
const receivers = method === 'set' && args[3] ? [target, args[3]] : [target];
|
|
974
|
+
if (receivers.some(mayHold) && keyMayNameNumeric(args[1])) {
|
|
975
|
+
refuse(`reflective-${method}`, call);
|
|
976
|
+
if (!enumerating)
|
|
977
|
+
return summarize();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
// The intrinsics themselves, resolved from the standard library -- a program
|
|
981
|
+
// that shadows `Object` or `Reflect` at the root's own scope proves nothing.
|
|
982
|
+
const intrinsic = (name) => {
|
|
983
|
+
const symbol = checker.resolveName(name, root, ts.SymbolFlags.Value, false);
|
|
984
|
+
const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
|
|
985
|
+
return symbol && declaration?.getSourceFile().isDeclarationFile ? checker.getTypeOfSymbolAtLocation(symbol, root) : null;
|
|
986
|
+
};
|
|
987
|
+
const objectConstructor = intrinsic('Object');
|
|
988
|
+
const reflect = intrinsic('Reflect');
|
|
989
|
+
const objectPrototype = objectConstructor?.getProperty('prototype');
|
|
990
|
+
if (!objectConstructor || !reflect || !objectPrototype) {
|
|
991
|
+
refuse('intrinsics-unresolved');
|
|
992
|
+
return summarize();
|
|
993
|
+
}
|
|
994
|
+
const isDirectCallee = (reference) => {
|
|
995
|
+
const access = reference.parent;
|
|
996
|
+
return (ts.isPropertyAccessExpression(access) &&
|
|
997
|
+
access.name === reference &&
|
|
998
|
+
ts.isCallExpression(outermostValueWrapperOf(access).parent) &&
|
|
999
|
+
unwrapValueExpression(outermostValueWrapperOf(access).parent.expression) === access);
|
|
1000
|
+
};
|
|
1001
|
+
// `const define = Object.defineProperty; define( o, 0, d )` calls a mutator
|
|
1002
|
+
// the scan above never sees as one.
|
|
1003
|
+
const mutators = [
|
|
1004
|
+
...OBJECT_PROPERTY_MUTATORS.map((method) => objectConstructor.getProperty(method)),
|
|
1005
|
+
...REFLECT_PROPERTY_MUTATORS.map((method) => reflect.getProperty(method))
|
|
1006
|
+
];
|
|
1007
|
+
for (const symbol of mutators) {
|
|
1008
|
+
if (!symbol)
|
|
1009
|
+
continue;
|
|
1010
|
+
for (const reference of flow.memberReferencesToSymbol(symbol)) {
|
|
1011
|
+
if (!isDirectCallee(reference)) {
|
|
1012
|
+
refuse('reflective-mutator-escapes', reference);
|
|
1013
|
+
if (!enumerating)
|
|
1014
|
+
return summarize();
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
/** A value that may be `Object.prototype` is only ever read from or compared. */
|
|
1019
|
+
const onlyReadOrCompared = (value) => {
|
|
1020
|
+
const use = outermostValueWrapperOf(value);
|
|
1021
|
+
const parent = use.parent;
|
|
1022
|
+
return ((ts.isPropertyAccessExpression(parent) && parent.expression === use) ||
|
|
1023
|
+
(ts.isBinaryExpression(parent) && EQUALITY_OPERATORS.has(parent.operatorToken.kind)));
|
|
1024
|
+
};
|
|
1025
|
+
for (const reference of flow.memberReferencesToSymbol(objectPrototype)) {
|
|
1026
|
+
const access = reference.parent;
|
|
1027
|
+
if (!ts.isPropertyAccessExpression(access) || access.name !== reference || !onlyReadOrCompared(access)) {
|
|
1028
|
+
refuse('object-prototype-escapes', reference);
|
|
1029
|
+
if (!enumerating)
|
|
1030
|
+
return summarize();
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
for (const symbol of [objectConstructor.getProperty('getPrototypeOf'), reflect.getProperty('getPrototypeOf')]) {
|
|
1034
|
+
if (!symbol)
|
|
1035
|
+
continue;
|
|
1036
|
+
for (const reference of flow.memberReferencesToSymbol(symbol)) {
|
|
1037
|
+
if (!isDirectCallee(reference) || !onlyReadOrCompared(outermostValueWrapperOf(reference.parent).parent)) {
|
|
1038
|
+
refuse('prototype-read-escapes', reference);
|
|
1039
|
+
if (!enumerating)
|
|
1040
|
+
return summarize();
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (refused > 0)
|
|
1045
|
+
return summarize();
|
|
1046
|
+
return true;
|
|
1047
|
+
};
|
|
1048
|
+
const closedLiteralAbsenceProofs = new WeakMap();
|
|
1049
|
+
const closedLiteralAbsenceDebug = process.env['GEA_CLOSED_LITERAL_ABSENCE_DEBUG'];
|
|
1050
|
+
/** Same question `typeMayHold` (above, in `numericNamesAbsentFrom`) asks for a class family, restated for ONE structural record: object literals have no subclasses, so "may hold" is plain assignability. */
|
|
1051
|
+
const literalMayHoldType = (checker, candidate, record, depth = 0) => {
|
|
1052
|
+
if (depth > 8 || (candidate.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)
|
|
1053
|
+
return true;
|
|
1054
|
+
if (candidate.isUnionOrIntersection())
|
|
1055
|
+
return candidate.types.some((part) => literalMayHoldType(checker, part, record, depth + 1));
|
|
1056
|
+
if ((candidate.flags & ts.TypeFlags.TypeParameter) !== 0) {
|
|
1057
|
+
const constraint = checker.getBaseConstraintOfType(candidate);
|
|
1058
|
+
return constraint === undefined || constraint === candidate || literalMayHoldType(checker, constraint, record, depth + 1);
|
|
1059
|
+
}
|
|
1060
|
+
if ((candidate.flags & INSTANCE_PRIMITIVE_FLAGS) !== 0)
|
|
1061
|
+
return false;
|
|
1062
|
+
if ((candidate.flags & (ts.TypeFlags.Object | ts.TypeFlags.NonPrimitive)) === 0)
|
|
1063
|
+
return true;
|
|
1064
|
+
// Is ONE OF OUR OWN instances assignable to the write's receiver -- could the
|
|
1065
|
+
// receiver, per its own declared shape, actually be holding this record?
|
|
1066
|
+
return checker.isTypeAssignableTo(record, candidate);
|
|
1067
|
+
};
|
|
1068
|
+
/**
|
|
1069
|
+
* Whether NO write anywhere in the program can put `name` on `record` --
|
|
1070
|
+
* `numericNamesAbsentFrom`'s write inventory (named/keyed writes, `Object`/
|
|
1071
|
+
* `Reflect` mutators, prototype escapes), restated for one closed object
|
|
1072
|
+
* LITERAL instead of an owned class family. There is no inheritance chain to
|
|
1073
|
+
* close and no constructor-function computed-member case to refuse, because a
|
|
1074
|
+
* literal has neither; there is still every other way a program can hand a
|
|
1075
|
+
* plain object a new key.
|
|
1076
|
+
*/
|
|
1077
|
+
const closedLiteralMemberAbsent = (checker, flow, record, name, at) => {
|
|
1078
|
+
const refuse = (reason, site) => {
|
|
1079
|
+
if (closedLiteralAbsenceDebug !== undefined) {
|
|
1080
|
+
const file = site?.getSourceFile();
|
|
1081
|
+
const where = site && file
|
|
1082
|
+
? ` ${file.fileName.split('/').pop()}:${file.getLineAndCharacterOfPosition(site.getStart()).line + 1} ${site.getText().slice(0, 80)}`
|
|
1083
|
+
: '';
|
|
1084
|
+
process.stderr.write(`[CLOSED-LITERAL-ABSENCE] ${name} ${reason}${where}\n`);
|
|
1085
|
+
}
|
|
1086
|
+
return false;
|
|
1087
|
+
};
|
|
1088
|
+
// Only a class instance or a declared interface can grow members this
|
|
1089
|
+
// reading never sees; a literal's own declaration(s) are the whole story.
|
|
1090
|
+
const declarations = record.getSymbol()?.declarations;
|
|
1091
|
+
if (!declarations || declarations.length === 0 || !declarations.every(ts.isObjectLiteralExpression))
|
|
1092
|
+
return refuse('not-a-closed-literal');
|
|
1093
|
+
// An index signature already answers every named key through `indexedTypeOf`;
|
|
1094
|
+
// reaching here at all means none was stated, but a defensive check keeps
|
|
1095
|
+
// this proof correct even if that changes upstream.
|
|
1096
|
+
if (checker.getIndexInfosOfType(record).length > 0)
|
|
1097
|
+
return refuse('index-signature');
|
|
1098
|
+
const domains = createPropertyKeyDomains(checker, flow, () => true);
|
|
1099
|
+
const typeOf = (expression) => checker.getTypeAtLocation(expression);
|
|
1100
|
+
const mayHold = (expression) => !!expression && literalMayHoldType(checker, typeOf(expression), record);
|
|
1101
|
+
const keyMayName = (expression) => expression === undefined || ts.isSpreadElement(expression) || domains.mayName(domains.of(expression), name);
|
|
1102
|
+
/** Whether an `Object.assign` source or `defineProperties` map can carry `name`: only a spread-free literal says it cannot. */
|
|
1103
|
+
const literalMayName = (expression) => {
|
|
1104
|
+
const literal = expression && unwrapValueExpression(expression);
|
|
1105
|
+
if (!literal || !ts.isObjectLiteralExpression(literal))
|
|
1106
|
+
return true;
|
|
1107
|
+
return literal.properties.some((property) => {
|
|
1108
|
+
if (ts.isSpreadAssignment(property))
|
|
1109
|
+
return true;
|
|
1110
|
+
const key = property.name;
|
|
1111
|
+
if (!key)
|
|
1112
|
+
return true;
|
|
1113
|
+
if (ts.isComputedPropertyName(key))
|
|
1114
|
+
return domains.mayName(domains.of(key.expression), name);
|
|
1115
|
+
if (ts.isIdentifier(key) || ts.isStringLiteralLike(key) || ts.isNumericLiteral(key))
|
|
1116
|
+
return key.text === name;
|
|
1117
|
+
return true;
|
|
1118
|
+
});
|
|
1119
|
+
};
|
|
1120
|
+
// Named, keyed and prototype writes.
|
|
1121
|
+
for (const write of flow.allWrites) {
|
|
1122
|
+
if (write.slot === 'member' && write.member !== null) {
|
|
1123
|
+
// A delete never creates a property; a literal's own member is not an
|
|
1124
|
+
// expando on an object that already existed; a binding pattern's member
|
|
1125
|
+
// edge is a READ of its source.
|
|
1126
|
+
if (write.edge === 'delete')
|
|
1127
|
+
continue;
|
|
1128
|
+
if (ts.isPropertyAssignment(write.site) || ts.isShorthandPropertyAssignment(write.site))
|
|
1129
|
+
continue;
|
|
1130
|
+
if ((write.edge === 'destructuring' || write.edge === 'destructuring-default') && ts.isBindingElement(write.site))
|
|
1131
|
+
continue;
|
|
1132
|
+
if (write.member === '__proto__' && mayHold(write.naming))
|
|
1133
|
+
return refuse('prototype-write', write.site);
|
|
1134
|
+
if (write.member === name && mayHold(write.naming))
|
|
1135
|
+
return refuse('named-write', write.site);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
if (write.slot !== 'element' || !KEYED_WRITE_EDGES.has(write.edge))
|
|
1139
|
+
continue;
|
|
1140
|
+
const target = write.naming;
|
|
1141
|
+
const access = target?.parent;
|
|
1142
|
+
if (!target || !access || !ts.isElementAccessExpression(access) || access.expression !== target) {
|
|
1143
|
+
if (mayHold(target))
|
|
1144
|
+
return refuse('keyed-write-unattributed', write.site);
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
if (keyMayName(access.argumentExpression) && mayHold(target))
|
|
1148
|
+
return refuse('keyed-write', write.site);
|
|
1149
|
+
}
|
|
1150
|
+
// Reflective definitions and prototype replacement, at every direct call.
|
|
1151
|
+
for (const { call } of flow.calls) {
|
|
1152
|
+
if (!ts.isCallExpression(call))
|
|
1153
|
+
continue;
|
|
1154
|
+
const callee = unwrapValueExpression(call.expression);
|
|
1155
|
+
if (!ts.isPropertyAccessExpression(callee))
|
|
1156
|
+
continue;
|
|
1157
|
+
const method = callee.name.text;
|
|
1158
|
+
const owner = callee.expression;
|
|
1159
|
+
const onObject = OBJECT_PROPERTY_MUTATORS.includes(method) && isGlobalObjectConstructor(checker, owner, checker.getTypeAtLocation(owner));
|
|
1160
|
+
const onReflect = !onObject && REFLECT_PROPERTY_MUTATORS.includes(method) && isStandardGlobalValue(checker, owner, 'Reflect');
|
|
1161
|
+
if (!onObject && !onReflect)
|
|
1162
|
+
continue;
|
|
1163
|
+
const args = call.arguments;
|
|
1164
|
+
// A spread hides which argument is the target and which the key.
|
|
1165
|
+
if (args.some(ts.isSpreadElement))
|
|
1166
|
+
return refuse('reflective-spread-arguments', call);
|
|
1167
|
+
const target = args[0];
|
|
1168
|
+
if (method === 'setPrototypeOf') {
|
|
1169
|
+
if (mayHold(target))
|
|
1170
|
+
return refuse('prototype-replaced', call);
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
if (method === 'assign') {
|
|
1174
|
+
if (mayHold(target) && args.slice(1).some(literalMayName))
|
|
1175
|
+
return refuse('object-assign', call);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
if (method === 'defineProperties') {
|
|
1179
|
+
if (mayHold(target) && literalMayName(args[1]))
|
|
1180
|
+
return refuse('define-properties', call);
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
// `defineProperty` (either owner) and `Reflect.set`, whose optional fourth
|
|
1184
|
+
// argument is the receiver a data property is created on.
|
|
1185
|
+
const receivers = method === 'set' && args[3] ? [target, args[3]] : [target];
|
|
1186
|
+
if (receivers.some(mayHold) && keyMayName(args[1]))
|
|
1187
|
+
return refuse(`reflective-${method}`, call);
|
|
1188
|
+
}
|
|
1189
|
+
// The intrinsics themselves, resolved from the standard library -- a program
|
|
1190
|
+
// that shadows `Object` or `Reflect` at the root's own scope proves nothing.
|
|
1191
|
+
const intrinsic = (symbolName) => {
|
|
1192
|
+
const symbol = checker.resolveName(symbolName, at, ts.SymbolFlags.Value, false);
|
|
1193
|
+
const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
|
|
1194
|
+
return symbol && declaration?.getSourceFile().isDeclarationFile ? checker.getTypeOfSymbolAtLocation(symbol, at) : null;
|
|
1195
|
+
};
|
|
1196
|
+
const objectConstructor = intrinsic('Object');
|
|
1197
|
+
const reflect = intrinsic('Reflect');
|
|
1198
|
+
const objectPrototype = objectConstructor?.getProperty('prototype');
|
|
1199
|
+
if (!objectConstructor || !reflect || !objectPrototype)
|
|
1200
|
+
return refuse('intrinsics-unresolved');
|
|
1201
|
+
const isDirectCallee = (reference) => {
|
|
1202
|
+
const access = reference.parent;
|
|
1203
|
+
return (ts.isPropertyAccessExpression(access) &&
|
|
1204
|
+
access.name === reference &&
|
|
1205
|
+
ts.isCallExpression(outermostValueWrapperOf(access).parent) &&
|
|
1206
|
+
unwrapValueExpression(outermostValueWrapperOf(access).parent.expression) === access);
|
|
1207
|
+
};
|
|
1208
|
+
// `const define = Object.defineProperty; define( o, 0, d )` calls a mutator
|
|
1209
|
+
// the scan above never sees as one.
|
|
1210
|
+
const mutators = [
|
|
1211
|
+
...OBJECT_PROPERTY_MUTATORS.map((method) => objectConstructor.getProperty(method)),
|
|
1212
|
+
...REFLECT_PROPERTY_MUTATORS.map((method) => reflect.getProperty(method))
|
|
1213
|
+
];
|
|
1214
|
+
for (const symbol of mutators) {
|
|
1215
|
+
if (!symbol)
|
|
1216
|
+
continue;
|
|
1217
|
+
for (const reference of flow.memberReferencesToSymbol(symbol)) {
|
|
1218
|
+
if (!isDirectCallee(reference))
|
|
1219
|
+
return refuse('reflective-mutator-escapes', reference);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
/** A value that may be `Object.prototype` is only ever read from or compared. */
|
|
1223
|
+
const onlyReadOrCompared = (value) => {
|
|
1224
|
+
const use = outermostValueWrapperOf(value);
|
|
1225
|
+
const parent = use.parent;
|
|
1226
|
+
return ((ts.isPropertyAccessExpression(parent) && parent.expression === use) ||
|
|
1227
|
+
(ts.isBinaryExpression(parent) && EQUALITY_OPERATORS.has(parent.operatorToken.kind)));
|
|
1228
|
+
};
|
|
1229
|
+
for (const reference of flow.memberReferencesToSymbol(objectPrototype)) {
|
|
1230
|
+
const access = reference.parent;
|
|
1231
|
+
if (!ts.isPropertyAccessExpression(access) || access.name !== reference || !onlyReadOrCompared(access))
|
|
1232
|
+
return refuse('object-prototype-escapes', reference);
|
|
1233
|
+
}
|
|
1234
|
+
for (const symbol of [objectConstructor.getProperty('getPrototypeOf'), reflect.getProperty('getPrototypeOf')]) {
|
|
1235
|
+
if (!symbol)
|
|
1236
|
+
continue;
|
|
1237
|
+
for (const reference of flow.memberReferencesToSymbol(symbol)) {
|
|
1238
|
+
if (!isDirectCallee(reference) || !onlyReadOrCompared(outermostValueWrapperOf(reference.parent).parent))
|
|
1239
|
+
return refuse('prototype-read-escapes', reference);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return true;
|
|
1243
|
+
};
|
|
1244
|
+
const CLOSED_LITERAL_ABSENCE_LEDGER_SCOPE = 'closed-literal-member-absence';
|
|
1245
|
+
const publishedClosedLiteralAbsenceRequirements = new WeakMap();
|
|
1246
|
+
/**
|
|
1247
|
+
* Keep a surviving answer's intrinsic obligations -- `flow/class-family-member-read.ts`'s
|
|
1248
|
+
* own `publish` restated for this proof. `ledger.include` only reaches an
|
|
1249
|
+
* active `capture` frame, which a binding census's `propertyTypeOf` does not
|
|
1250
|
+
* open around this ask (unlike the producer-context hook, which does); without
|
|
1251
|
+
* a `capture`-independent path the obligation this proof raises would just be
|
|
1252
|
+
* dropped the first time a census asks outside one, and the answer it is
|
|
1253
|
+
* attached to would be unsound. `ledger.replace` writes straight into the
|
|
1254
|
+
* ledger's persistent scope map, so it works regardless.
|
|
1255
|
+
*/
|
|
1256
|
+
const publishClosedLiteralAbsenceRequirements = (ledger, requirements) => {
|
|
1257
|
+
if (requirements.length === 0)
|
|
1258
|
+
return;
|
|
1259
|
+
ledger.include(requirements);
|
|
1260
|
+
let held = publishedClosedLiteralAbsenceRequirements.get(ledger);
|
|
1261
|
+
if (!held)
|
|
1262
|
+
publishedClosedLiteralAbsenceRequirements.set(ledger, (held = { all: [], seen: new Map() }));
|
|
1263
|
+
let added = false;
|
|
1264
|
+
for (const requirement of requirements) {
|
|
1265
|
+
let kinds = held.seen.get(requirement.location);
|
|
1266
|
+
if (!kinds)
|
|
1267
|
+
held.seen.set(requirement.location, (kinds = new Set()));
|
|
1268
|
+
const kind = `${requirement.intrinsic}.${intrinsicProtocolRequirementKind(requirement) ?? ''}`;
|
|
1269
|
+
if (kinds.has(kind))
|
|
1270
|
+
continue;
|
|
1271
|
+
kinds.add(kind);
|
|
1272
|
+
held.all.push(requirement);
|
|
1273
|
+
added = true;
|
|
1274
|
+
}
|
|
1275
|
+
if (added)
|
|
1276
|
+
ledger.replace(CLOSED_LITERAL_ABSENCE_LEDGER_SCOPE, held.all);
|
|
1277
|
+
};
|
|
1278
|
+
/**
|
|
1279
|
+
* `undefined`, when a NAMED key is proven absent from a CLOSED object-literal
|
|
1280
|
+
* record and nothing in the program can add it -- the same fact
|
|
1281
|
+
* `flow/class-family-member-read.ts` states for an absent CLASS key, restated
|
|
1282
|
+
* for a record with no class behind it at all.
|
|
1283
|
+
*
|
|
1284
|
+
* three's `getProgramCacheKey( parameters )` pushes
|
|
1285
|
+
* `parameters.morphAttributeCount` into the program's cache-key array.
|
|
1286
|
+
* `parameters` is the 135-field literal `getParameters` returns, and
|
|
1287
|
+
* `morphAttributeCount` is not one of its fields -- not written anywhere in
|
|
1288
|
+
* the file, not anywhere in three's whole source (measured: the only mention
|
|
1289
|
+
* is this one read). JavaScript reads an absent own property as `undefined`,
|
|
1290
|
+
* falling through to `Object.prototype.morphAttributeCount`, which nothing
|
|
1291
|
+
* defines -- so the push is `undefined` on every call, while the checker's own
|
|
1292
|
+
* answer for the read (an unknown member of an object type with no index
|
|
1293
|
+
* signature) is `any`.
|
|
1294
|
+
*
|
|
1295
|
+
* `closedObjectLiteralIndexTypeOf` already states this fact for an
|
|
1296
|
+
* unresolved KEY read on a closed literal, from the literal's own declared
|
|
1297
|
+
* properties alone. A NAMED read commits to one key (`morphAttributeCount`)
|
|
1298
|
+
* up front, so unlike an index read it can be wrong about a SPECIFIC name if
|
|
1299
|
+
* the literal is handed to code that adds exactly that key through an alias
|
|
1300
|
+
* this read never sees syntactically -- which is why this proof also has to
|
|
1301
|
+
* survive every write in the program (`closedLiteralMemberAbsent`) rather
|
|
1302
|
+
* than stop at the literal's own declaration.
|
|
1303
|
+
*
|
|
1304
|
+
* The Object obligation is the one `flow/class-family-member-read.ts`
|
|
1305
|
+
* publishes for the same reason: the answer depends on `Object.prototype`
|
|
1306
|
+
* staying intact for this one key, recorded on the deferred ledger
|
|
1307
|
+
* (`requirePrototypeKeys('Object', { names: [name] }, at)`) and discharged
|
|
1308
|
+
* against the final host-mutation census, never assumed here.
|
|
1309
|
+
*/
|
|
1310
|
+
const absentClosedObjectLiteralMemberTypeOf = (checker, flow, receiver, name, at) => {
|
|
1311
|
+
const ledger = deferredIntrinsicProtocolLedgerOf(flow);
|
|
1312
|
+
if (!ledger) {
|
|
1313
|
+
if (closedLiteralAbsenceDebug !== undefined)
|
|
1314
|
+
process.stderr.write(`[CLOSED-LITERAL-ABSENCE] ${name} no-ledger\n`);
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
const arms = receiver.isUnion() ? receiver.types : [receiver];
|
|
1318
|
+
for (const arm of arms) {
|
|
1319
|
+
if (checker.getPropertyOfType(arm, name)) {
|
|
1320
|
+
if (closedLiteralAbsenceDebug !== undefined)
|
|
1321
|
+
process.stderr.write(`[CLOSED-LITERAL-ABSENCE] ${name} property-found-on-arm ${checker.typeToString(arm)}\n`);
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
let proofsByType = closedLiteralAbsenceProofs.get(flow);
|
|
1325
|
+
if (!proofsByType)
|
|
1326
|
+
closedLiteralAbsenceProofs.set(flow, (proofsByType = new WeakMap()));
|
|
1327
|
+
let proofs = proofsByType.get(arm);
|
|
1328
|
+
if (!proofs)
|
|
1329
|
+
proofsByType.set(arm, (proofs = new Map()));
|
|
1330
|
+
let proved = proofs.get(name);
|
|
1331
|
+
if (!proved) {
|
|
1332
|
+
proved = ledger.capture(() => {
|
|
1333
|
+
// The prototype obligation is recorded HERE, not inside the write
|
|
1334
|
+
// scan: the scan can refuse for reasons that have nothing to do with
|
|
1335
|
+
// `Object.prototype` (a keyed write it cannot rule out), and a
|
|
1336
|
+
// refused proof must not still publish an obligation nobody needs.
|
|
1337
|
+
if (ledger.requirePrototypeKeys('Object', { names: [name] }, at) !== true) {
|
|
1338
|
+
if (closedLiteralAbsenceDebug !== undefined)
|
|
1339
|
+
process.stderr.write(`[CLOSED-LITERAL-ABSENCE] ${name} require-prototype-keys-failed\n`);
|
|
1340
|
+
return false;
|
|
1341
|
+
}
|
|
1342
|
+
return closedLiteralMemberAbsent(checker, flow, arm, name, at);
|
|
1343
|
+
});
|
|
1344
|
+
proofs.set(name, proved);
|
|
1345
|
+
}
|
|
1346
|
+
if (!proved.value) {
|
|
1347
|
+
if (closedLiteralAbsenceDebug !== undefined)
|
|
1348
|
+
process.stderr.write(`[CLOSED-LITERAL-ABSENCE] ${name} proof-rejected\n`);
|
|
1349
|
+
return null;
|
|
1350
|
+
}
|
|
1351
|
+
// `ledger.include` only reaches an active `capture` frame; a caller that
|
|
1352
|
+
// asks this OUTSIDE one (every binding census's `propertyTypeOf`, unlike
|
|
1353
|
+
// the producer-context hook which wraps its own ask) would silently lose
|
|
1354
|
+
// the obligation right here. Publish it into a persistent scope instead --
|
|
1355
|
+
// the same `capture`-independent path `flow/class-family-member-read.ts`'s
|
|
1356
|
+
// own `publish` uses for the identical class-family obligation -- so the
|
|
1357
|
+
// requirement survives to the final sealed-census discharge regardless of
|
|
1358
|
+
// which caller asked first.
|
|
1359
|
+
publishClosedLiteralAbsenceRequirements(ledger, proved.requirements);
|
|
1360
|
+
}
|
|
1361
|
+
return checker.getUndefinedType();
|
|
1362
|
+
};
|
|
1363
|
+
/**
|
|
1364
|
+
* Whether a NAMED property read is proven to miss every instance of a closed
|
|
1365
|
+
* object-literal record -- `numericIndexAbsenceProven`'s counterpart for a
|
|
1366
|
+
* named key instead of a numeric one. Deliberately separate from
|
|
1367
|
+
* `memberTypeOf`: an `undefined` answer there can also come from an ordinary
|
|
1368
|
+
* declared member or an array index signature, neither of which licenses a
|
|
1369
|
+
* PRODUCER skipping the runtime property lookup and constant-folding the read
|
|
1370
|
+
* -- only THIS proof does, which is why `producers/properties.ts` asks it
|
|
1371
|
+
* directly rather than re-deriving "was it the absence arm" from
|
|
1372
|
+
* `memberTypeOf`'s result.
|
|
1373
|
+
*/
|
|
1374
|
+
export const closedLiteralMemberAbsenceProven = (checker, flow, receiver, name, at) => deferredIntrinsicProtocolLedgerOf(flow) !== null && absentClosedObjectLiteralMemberTypeOf(checker, flow, receiver, name, at) !== null;
|
|
1375
|
+
/**
|
|
1376
|
+
* The literal member NAME an element access spells, or `null` when its key is
|
|
1377
|
+
* not a literal. `x[ 'name' ]` and `x[ 0 ]` are named reads written with
|
|
1378
|
+
* brackets and resolve exactly as `x.name` does.
|
|
1379
|
+
*/
|
|
1380
|
+
export const literalMemberNameOf = (node) => {
|
|
1381
|
+
const argument = node.argumentExpression;
|
|
1382
|
+
if (!argument)
|
|
1383
|
+
return null;
|
|
1384
|
+
if (ts.isStringLiteralLike(argument))
|
|
1385
|
+
return argument.text;
|
|
1386
|
+
if (ts.isNumericLiteral(argument))
|
|
1387
|
+
return argument.text;
|
|
1388
|
+
return null;
|
|
1389
|
+
};
|
|
1390
|
+
/**
|
|
1391
|
+
* The one overload of `resolved` whose declared parameter count admits this
|
|
1392
|
+
* call's argument count, or `null` when none or several do.
|
|
1393
|
+
*
|
|
1394
|
+
* Asked only when the checker resolved the call to a signature with NO
|
|
1395
|
+
* declaration -- its fabricated any-call, which is what a call through an
|
|
1396
|
+
* `any` receiver gets. The RECEIVER here is census-typed (`memberTypeOf` was
|
|
1397
|
+
* handed it), so the member's overload set is real and only the checker's own
|
|
1398
|
+
* choice among its members is missing. Counting arguments is the checker's own
|
|
1399
|
+
* first step (`chooseOverload` discards every candidate the count rules out
|
|
1400
|
+
* before comparing one type), so it is the step that can be repeated without
|
|
1401
|
+
* the receiver the checker refused to type; a tie keeps the set whole. Three's
|
|
1402
|
+
* `LOD.addLevel` is the measured case: `levels` is a descriptor-defined field
|
|
1403
|
+
* the checker declares nothing for, and `levels.splice( l, 0, level )` chose
|
|
1404
|
+
* the two-parameter overload by joining, so the item reached the emitter
|
|
1405
|
+
* unpacked -- `Array.prototype.splice`'s renderer refused it by name.
|
|
1406
|
+
*/
|
|
1407
|
+
export const arityAdmittedSignature = (resolved, call) => {
|
|
1408
|
+
if (call.arguments.some((argument) => ts.isSpreadElement(argument)))
|
|
1409
|
+
return null;
|
|
1410
|
+
const count = call.arguments.length;
|
|
1411
|
+
const admitted = resolved.getCallSignatures().filter((signature) => {
|
|
1412
|
+
const declaration = signature.getDeclaration();
|
|
1413
|
+
if (!declaration)
|
|
1414
|
+
return false;
|
|
1415
|
+
const parameters = declaration.parameters;
|
|
1416
|
+
const last = parameters[parameters.length - 1];
|
|
1417
|
+
const rest = last?.dotDotDotToken !== undefined;
|
|
1418
|
+
const required = parameters.filter((parameter) => !parameter.questionToken && !parameter.initializer && !parameter.dotDotDotToken).length;
|
|
1419
|
+
return count >= required && (rest || count <= parameters.length);
|
|
1420
|
+
});
|
|
1421
|
+
const only = admitted[0];
|
|
1422
|
+
return admitted.length === 1 && only ? only : null;
|
|
1423
|
+
};
|
|
1424
|
+
const arityAdmittedDeclaration = (resolved, call) => arityAdmittedSignature(resolved, call)?.getDeclaration() ?? null;
|
|
1425
|
+
export const singleConventionAt = (checker, resolved, node) => {
|
|
1426
|
+
const overloaded = resolved.getCallSignatures().length > 1 || resolved.getConstructSignatures().length > 1;
|
|
1427
|
+
if (!overloaded)
|
|
1428
|
+
return resolved;
|
|
1429
|
+
const call = node.parent;
|
|
1430
|
+
if (!call || !ts.isCallExpression(call) || call.expression !== node)
|
|
1431
|
+
return null;
|
|
1432
|
+
const declaration = checker.getResolvedSignature(call)?.declaration ?? arityAdmittedDeclaration(resolved, call);
|
|
1433
|
+
if (!declaration)
|
|
1434
|
+
return null;
|
|
1435
|
+
const single = checker.getTypeAtLocation(declaration);
|
|
1436
|
+
if ((single.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)
|
|
1437
|
+
return null;
|
|
1438
|
+
return single.getCallSignatures().length === 1 ? single : null;
|
|
1439
|
+
};
|
|
1440
|
+
/** The result of an authenticated `.call`/`.apply`, read from its already-resolved underlying callable. */
|
|
1441
|
+
export const explicitThisCallReturnType = (fallback, underlying) => {
|
|
1442
|
+
if (!underlying)
|
|
1443
|
+
return fallback.getReturnType();
|
|
1444
|
+
const signatures = underlying.getCallSignatures();
|
|
1445
|
+
const signature = signatures[0];
|
|
1446
|
+
if (signatures.length !== 1 || !signature || signature.typeParameters?.length)
|
|
1447
|
+
return fallback.getReturnType();
|
|
1448
|
+
return signature.getReturnType();
|
|
1449
|
+
};
|
|
1450
|
+
/**
|
|
1451
|
+
* An overload-independent result is knowable even when its physical call
|
|
1452
|
+
* convention is not. Read every declared return through the same recovered
|
|
1453
|
+
* receiver type; no overload is selected and no callable carrier is changed.
|
|
1454
|
+
* Generic signatures remain the checker's instantiation question.
|
|
1455
|
+
*/
|
|
1456
|
+
export const overloadInvariantReturnTypeAt = (checker, call, read) => {
|
|
1457
|
+
const expression = call.expression;
|
|
1458
|
+
let callee;
|
|
1459
|
+
if (ts.isPropertyAccessExpression(expression) || ts.isElementAccessExpression(expression)) {
|
|
1460
|
+
const receiver = read(expression.expression);
|
|
1461
|
+
const name = ts.isPropertyAccessExpression(expression) ? expression.name.text : literalMemberNameOf(expression);
|
|
1462
|
+
if (!receiver || name === null)
|
|
1463
|
+
return null;
|
|
1464
|
+
// memberTypeOf also asks for a single convention. This query only asks
|
|
1465
|
+
// what all conventions return, so retain the complete member type.
|
|
1466
|
+
const member = checker.getPropertyOfType(checker.getNonNullableType(receiver), name);
|
|
1467
|
+
callee = member ? checker.getTypeOfSymbolAtLocation(member, expression) : null;
|
|
1468
|
+
}
|
|
1469
|
+
else {
|
|
1470
|
+
callee = read(expression);
|
|
1471
|
+
}
|
|
1472
|
+
if (!callee)
|
|
1473
|
+
return null;
|
|
1474
|
+
const signatures = ts.isNewExpression(call) ? callee.getConstructSignatures() : callee.getCallSignatures();
|
|
1475
|
+
if (signatures.length < 2 || signatures.some((signature) => signature.typeParameters?.length))
|
|
1476
|
+
return null;
|
|
1477
|
+
const returned = signatures[0]?.getReturnType();
|
|
1478
|
+
if (!returned || isUnusableEvidence(returned))
|
|
1479
|
+
return null;
|
|
1480
|
+
return signatures.every((signature) => signature.getReturnType() === returned) ? returned : null;
|
|
1481
|
+
};
|
|
1482
|
+
/** Whether `node` (through any parentheses) is the expression a call or `new` invokes. */
|
|
1483
|
+
const calleePosition = (node) => {
|
|
1484
|
+
let current = node;
|
|
1485
|
+
while (current.parent && ts.isParenthesizedExpression(current.parent))
|
|
1486
|
+
current = current.parent;
|
|
1487
|
+
const parent = current.parent;
|
|
1488
|
+
return parent !== undefined && (ts.isCallExpression(parent) || ts.isNewExpression(parent)) && parent.expression === current;
|
|
1489
|
+
};
|
|
1490
|
+
export const memberTypeOf = (checker, receiver, name, at, flow) => {
|
|
1491
|
+
const nonNullReceiver = checker.getNonNullableType(receiver);
|
|
1492
|
+
const property = checker.getPropertyOfType(nonNullReceiver, name);
|
|
1493
|
+
if (!property) {
|
|
1494
|
+
// Array index signatures also govern literal keys. An array has no own
|
|
1495
|
+
// declared "0" member, unlike a tuple; treating that absence as silence
|
|
1496
|
+
// loses an element already recovered from the receiver's storage.
|
|
1497
|
+
if (checker.isArrayLikeType(nonNullReceiver) && String(Number(name)) === name) {
|
|
1498
|
+
const element = checker.getIndexTypeOfType(nonNullReceiver, ts.IndexKind.Number);
|
|
1499
|
+
return element && !isUnusableEvidence(element) ? checker.getNullableType(element, ts.TypeFlags.Undefined) : null;
|
|
1500
|
+
}
|
|
1501
|
+
// JavaScript reads an absent own property as `undefined`. When the
|
|
1502
|
+
// receiver is proven a CLOSED object-literal record that nothing in the
|
|
1503
|
+
// program can add `name` to, that really is the read's value -- see
|
|
1504
|
+
// `absentClosedObjectLiteralMemberTypeOf`'s own header for why this is
|
|
1505
|
+
// answered here (the one shared authority every binding census's
|
|
1506
|
+
// `propertyTypeOf` already routes through) rather than once per census.
|
|
1507
|
+
//
|
|
1508
|
+
// Never for a CALLEE. `undefined` is the read's value; calling it is a
|
|
1509
|
+
// guaranteed TypeError, and a callee slot typed `undefined` is not a
|
|
1510
|
+
// refusal, it is a program the lowering has to invent a meaning for.
|
|
1511
|
+
// `test/runtime/borrowed-array-method-unmatched-receiver.runtime.js`
|
|
1512
|
+
// is the measured case: `borrowed-builtin-call-bind-source-transform.ts`
|
|
1513
|
+
// rewrites `Array.prototype.join.call( o, ',' )` into `o.join( ',' )`, a
|
|
1514
|
+
// read the ORIGINAL program never performs, on a literal that proves
|
|
1515
|
+
// `join` absent. Answered as absent, the site was refused only by the
|
|
1516
|
+
// host-mutation census happening to read the same call as a possible
|
|
1517
|
+
// `Object.prototype.join` mutator -- a circular refusal that names the
|
|
1518
|
+
// wrong thing. Left unanswered, the callee falls through to the record
|
|
1519
|
+
// member get, which refuses by name (`"Array.prototype.join" has no
|
|
1520
|
+
// rendering off a "record(...)"`), exactly as before absence existed.
|
|
1521
|
+
return flow && !calleePosition(at) ? absentClosedObjectLiteralMemberTypeOf(checker, flow, nonNullReceiver, name, at) : null;
|
|
1522
|
+
}
|
|
1523
|
+
const type = checker.getTypeOfSymbolAtLocation(property, at);
|
|
1524
|
+
if (isUnusableEvidence(type))
|
|
1525
|
+
return null;
|
|
1526
|
+
return singleConventionAt(checker, type, at);
|
|
1527
|
+
};
|
|
1528
|
+
/**
|
|
1529
|
+
* Whether a JSDoc tag resolved to nothing AT ALL -- `any` or `unknown`.
|
|
1530
|
+
*
|
|
1531
|
+
* Not the whole question a cell has; see `jsDocTypeStatesNothing`, which is
|
|
1532
|
+
* what every cell should ask. This is only the arm that is specific to a TAG:
|
|
1533
|
+
* a tag naming a type this module cannot import degrades to `any`. A tag
|
|
1534
|
+
* that SPELLS `unknown` (`{unknown}`, `{?}`) is not that degradation -- it is
|
|
1535
|
+
* the author's own statement, exactly as a real `unknown` annotation is
|
|
1536
|
+
* (`annotationStatesNothing`), and is answered `false` below so the two
|
|
1537
|
+
* spellings of one statement get one census answer.
|
|
1538
|
+
*/
|
|
1539
|
+
export const jsDocTypeIsUninformative = (checker, typeNode) => {
|
|
1540
|
+
// A SPELLED `{unknown}` (or its JSDoc form `{?}`) is the author writing
|
|
1541
|
+
// the statement a TS `: unknown` annotation makes, and stays one: a name
|
|
1542
|
+
// that fails to resolve degrades to `any`, never to `unknown`, so this
|
|
1543
|
+
// spelling cannot be the degradation this exists to admit. Measured the
|
|
1544
|
+
// other way on test262's own harness compiled as JavaScript: `@param
|
|
1545
|
+
// {unknown} actual` on `assert.sameValue` was admitted, the call sites
|
|
1546
|
+
// bound the slot `number`, and the body -- laid out from the checker's
|
|
1547
|
+
// `unknown` -- declared the same parameter dynamic, so the ABI and the
|
|
1548
|
+
// frame disagreed and every case using the shim refused. `{any}`/`{*}`
|
|
1549
|
+
// stay admitted: three.js writes `{any}` 31 times on parameters the three.js app
|
|
1550
|
+
// measures as call-site-bindable, and that tag IS indistinguishable in
|
|
1551
|
+
// resolved type from a degraded name.
|
|
1552
|
+
if (typeNode.kind === ts.SyntaxKind.UnknownKeyword || typeNode.kind === ts.SyntaxKind.JSDocUnknownType)
|
|
1553
|
+
return false;
|
|
1554
|
+
const resolved = checker.getTypeFromTypeNode(typeNode);
|
|
1555
|
+
return (resolved.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
|
|
1556
|
+
};
|
|
1557
|
+
/**
|
|
1558
|
+
* Whether a JSDoc tag states nothing about storage -- the ONE test every cell
|
|
1559
|
+
* asks of a tag, tag-specific degradation plus the identical vacuity rule a
|
|
1560
|
+
* real annotation gets.
|
|
1561
|
+
*
|
|
1562
|
+
* Four cells asked this and each asked it differently:
|
|
1563
|
+
* `parameter-bindings.ts` had a private copy that added `isEmptyObjectType`,
|
|
1564
|
+
* while `return-bindings.ts`, `field-bindings.ts` and `local-bindings.ts`
|
|
1565
|
+
* asked `jsDocTypeIsUninformative` alone -- so a `@param {{}}` tag was
|
|
1566
|
+
* non-evidence at one cell and a stated type at the other three. Ordinary
|
|
1567
|
+
* drift, of the shape this compiler keeps finding.
|
|
1568
|
+
*
|
|
1569
|
+
* The measured hole all four shared is `{Object}`. Three separate comments in
|
|
1570
|
+
* this file asserted that TypeScript's JSDoc parser maps a capitalized
|
|
1571
|
+
* `{Object}` tag to `any`, so `jsDocTypeIsUninformative` would catch it and
|
|
1572
|
+
* `isGlobalObjectInterface` was documented as "not reachable from JSDoc".
|
|
1573
|
+
* That is false, measured on three's `UniformsUtils.js`:
|
|
1574
|
+
*
|
|
1575
|
+
* @return {Object} The cloned uniforms.
|
|
1576
|
+
* export function cloneUniforms( src ) { const dst = {}; ... return dst }
|
|
1577
|
+
*
|
|
1578
|
+
* `getReturnTypeOfSignature` answers the REAL `lib.es5.d.ts` `Object`
|
|
1579
|
+
* interface -- 7 properties, `TypeFlags.Object`, not `any` and not empty. So
|
|
1580
|
+
* it passed every vacuity test, became the return cell's stated answer, and
|
|
1581
|
+
* the honest `dictionary` the body builds had to convert to a named record
|
|
1582
|
+
* with zero members (every one of `Object`'s members is an ambient method
|
|
1583
|
+
* `structural.ts` drops). No backend can render that pair, and the whole
|
|
1584
|
+
* uniforms chain downstream of it -- `parameters.uniforms`,
|
|
1585
|
+
* `materialProperties.uniforms`, and the 338 boxed carriers of
|
|
1586
|
+
* `WebGLMaterials.js`'s `refreshUniforms*( uniforms, material )` -- was
|
|
1587
|
+
* dynamic because of one tag.
|
|
1588
|
+
*
|
|
1589
|
+
* Recording that premise as false is the finding; making the tag path REFUSE
|
|
1590
|
+
* an `{Object}` is not the cure, and the inline comment below carries the
|
|
1591
|
+
* measurement that rules it out.
|
|
1592
|
+
*/
|
|
1593
|
+
export const jsDocTypeStatesNothing = (checker, typeNode) =>
|
|
1594
|
+
// The parameter census's own private copy of this test, made shared: three
|
|
1595
|
+
// cells asked only `jsDocTypeIsUninformative`, one asked that OR
|
|
1596
|
+
// `isEmptyObjectType`, and a tag is one question, so they answer with one
|
|
1597
|
+
// authority now.
|
|
1598
|
+
//
|
|
1599
|
+
// Deliberately NOT `annotationStatesNothing`: that also refuses a tag
|
|
1600
|
+
// resolving to the global `Object` interface. Adding that arm was measured on
|
|
1601
|
+
// the three.js app and is a DEAD LEVER -- boxedTop stayed at exactly 9431, boxed rose
|
|
1602
|
+
// 15899 -> 15906, and `withheld` went 8 -> 39, taking 134 operations with it
|
|
1603
|
+
// (the 31 new ones sit on `mergeUniforms`/`cloneUniforms` results cited by
|
|
1604
|
+
// object literals: ShaderLib 20, WebGLBackground 8, plus ShaderMaterial,
|
|
1605
|
+
// Object3D and WebGLEnvironments). The carrier does not change because refusing
|
|
1606
|
+
// the tag only makes the census decline, after which the CHECKER hands the
|
|
1607
|
+
// very same named-empty `Object` record to the signature -- so the two
|
|
1608
|
+
// authorities on one call's result stop agreeing while the boxing stays put.
|
|
1609
|
+
// Do not retry it here. `@return {Object}` DOES reach a cell and resolve to
|
|
1610
|
+
// the real 7-member `lib.es5` interface (three comments in this file once
|
|
1611
|
+
// claimed the JSDoc parser maps it to `any` -- it does not, they are
|
|
1612
|
+
// corrected), but the fix has to change what that interface DERIVES to, not
|
|
1613
|
+
// whether a census reads it.
|
|
1614
|
+
jsDocTypeIsUninformative(checker, typeNode) || isEmptyObjectType(checker.getTypeFromTypeNode(typeNode));
|
|
1615
|
+
/**
|
|
1616
|
+
* Whether a resolved type is a structural object type with ZERO evidence in
|
|
1617
|
+
* it: no properties, no call signature, no construct signature, no index
|
|
1618
|
+
* signature.
|
|
1619
|
+
*
|
|
1620
|
+
* Lived in `parameter-bindings.ts` until `return-bindings.ts` needed the same
|
|
1621
|
+
* question; moved here -- the module both already share for
|
|
1622
|
+
* `jsDocTypeIsUninformative` and `widestOf` -- rather than copied, so the two
|
|
1623
|
+
* cannot drift.
|
|
1624
|
+
*
|
|
1625
|
+
* `jsDocTypeIsUninformative` above catches a tag that resolves to
|
|
1626
|
+
* `any`/`unknown` -- an unimported cross-module name, or the JSDoc-special
|
|
1627
|
+
* `Object`/`object`. It does NOT catch this: three writes `@param {Object}
|
|
1628
|
+
* data` for `InterleavedBuffer.clone( data )`, and
|
|
1629
|
+
* `declaration-overlay-transform.ts`'s `replaceVagueParamTags` correctly
|
|
1630
|
+
* prefers the SHIPPED declaration's answer over that vague tag -- but the
|
|
1631
|
+
* shipped declaration (`@types/three`) states `clone(data: {}): ...`, and
|
|
1632
|
+
* `{}` is itself a type with nothing IN it. The overlay's replacement is a
|
|
1633
|
+
* real improvement in the general case (`Material.fromJSON`'s `json` gets a
|
|
1634
|
+
* real `MaterialJSON`); here it swaps one uninformative spelling for another,
|
|
1635
|
+
* and without this check the resolved `{}` reads as "the program stated a
|
|
1636
|
+
* real type" and stays excluded from the call-site census forever, even
|
|
1637
|
+
* though it states precisely as much as `any` does about what the parameter
|
|
1638
|
+
* holds.
|
|
1639
|
+
*
|
|
1640
|
+
* The same non-evidence `parameter-bindings.ts`'s `isBareFunctionType`
|
|
1641
|
+
* already is for a type with zero CALLING convention, generalized to zero
|
|
1642
|
+
* MEMBER convention.
|
|
1643
|
+
*/
|
|
1644
|
+
export const isEmptyObjectType = (type) => {
|
|
1645
|
+
// The bare `object` KEYWORD (lowercase, `TypeFlags.NonPrimitive`) is a
|
|
1646
|
+
// structural type distinct from `ts.TypeFlags.Object` -- it is TypeScript's
|
|
1647
|
+
// own "some non-primitive value, shape unstated" wildcard, so it declares
|
|
1648
|
+
// no properties/signatures by construction and states nothing about
|
|
1649
|
+
// storage for the identical reason an empty `{}` type literal does. Caught
|
|
1650
|
+
// here rather than folded into `jsDocTypeIsUninformative`: lowercase
|
|
1651
|
+
// `object` resolves to this REAL, non-`any`/`unknown` type (and so, it
|
|
1652
|
+
// turns out, does capitalized `Object` -- see `isGlobalObjectInterface`,
|
|
1653
|
+
// which handles that one) -- `WebGLProgram`'s own
|
|
1654
|
+
// `@param {object} parameters` (matching what `@types/three` itself
|
|
1655
|
+
// declares: `constructor(..., parameters: object)`) reads as "the program
|
|
1656
|
+
// stated a real type" without this check, even though `object` carries
|
|
1657
|
+
// exactly as little information as `any` does for what can be read off it.
|
|
1658
|
+
if ((type.flags & ts.TypeFlags.NonPrimitive) !== 0)
|
|
1659
|
+
return true;
|
|
1660
|
+
if ((type.flags & ts.TypeFlags.Object) === 0)
|
|
1661
|
+
return false;
|
|
1662
|
+
if (type.getProperties().length > 0)
|
|
1663
|
+
return false;
|
|
1664
|
+
if (type.getCallSignatures().length > 0)
|
|
1665
|
+
return false;
|
|
1666
|
+
if (type.getConstructSignatures().length > 0)
|
|
1667
|
+
return false;
|
|
1668
|
+
return !type.getStringIndexType() && !type.getNumberIndexType();
|
|
1669
|
+
};
|
|
1670
|
+
/**
|
|
1671
|
+
* Whether a type has no NAME of its own -- the `object` keyword, or an
|
|
1672
|
+
* anonymous `{}` type literal, as opposed to a declared interface that
|
|
1673
|
+
* happens to have no members.
|
|
1674
|
+
*
|
|
1675
|
+
* This distinction does not arise on the JSDoc path and is what makes
|
|
1676
|
+
* `annotationStatesNothing` below narrower than `isEmptyObjectType`. Measured
|
|
1677
|
+
* over the three.js app's whole program: 168 of the 197 parameter annotations that
|
|
1678
|
+
* `isEmptyObjectType` alone calls empty are NAMED, and they are `lib.dom`'s
|
|
1679
|
+
* opaque handle interfaces -- `interface WebGLProgram {}`, `WebGLBuffer`,
|
|
1680
|
+
* `WebGLShader`, `PeriodicWave`, `FragmentDirective`. Those are declared
|
|
1681
|
+
* empty on purpose: emptiness IS the statement, a nominal handle nobody may
|
|
1682
|
+
* read a member off. Re-deriving one of them from its call sites would
|
|
1683
|
+
* replace a nominal identity the host owns with whatever this compiler's own
|
|
1684
|
+
* join happened to pick, so a named type keeps outranking the census here
|
|
1685
|
+
* even when it is structurally empty.
|
|
1686
|
+
*/
|
|
1687
|
+
const isAnonymousType = (type) => {
|
|
1688
|
+
if ((type.flags & ts.TypeFlags.NonPrimitive) !== 0)
|
|
1689
|
+
return true;
|
|
1690
|
+
const name = type.getSymbol()?.getName();
|
|
1691
|
+
return name === undefined || name === '__type' || name === '__object';
|
|
1692
|
+
};
|
|
1693
|
+
/**
|
|
1694
|
+
* The checker's own answer for a standard-library INTERFACE name, resolved at
|
|
1695
|
+
* `anchor` and memoized per checker -- so a name a large program's walk asks
|
|
1696
|
+
* about at every write site (`Object`, `Map`, ...) is resolved once rather
|
|
1697
|
+
* than once per site.
|
|
1698
|
+
*
|
|
1699
|
+
* `checker.resolveName` is the public API for exactly "what does this name
|
|
1700
|
+
* mean here", independent of any use site -- the mechanism `host-protocols.ts`
|
|
1701
|
+
* already uses to name `Promise`/`Date`/`String`/the four keyed collections.
|
|
1702
|
+
* This shares that same first step. Where it deliberately does NOT follow
|
|
1703
|
+
* `host-protocols.ts` is the second: those functions take
|
|
1704
|
+
* `identities.symbolDeclarationId(...)` and key a `Map<DeclarationId, ...>`
|
|
1705
|
+
* with it, because they publish an answer for OTHER layers to read back
|
|
1706
|
+
* across specialization copies. Every caller here asks a one-off "is this
|
|
1707
|
+
* type THAT ambient interface" and keys nothing -- and an ambient library
|
|
1708
|
+
* interface has exactly one `ts.Symbol` per checker for every INSTANTIATION
|
|
1709
|
+
* of that same generic declaration (`Map<string, number>`'s own `getSymbol()`
|
|
1710
|
+
* answers the same object bare `Map` does), so comparing that symbol
|
|
1711
|
+
* directly states the identical fact `symbolDeclarationId` equality would
|
|
1712
|
+
* for that question, without needing an `IdentityTable` this layer does not
|
|
1713
|
+
* otherwise hold.
|
|
1714
|
+
*
|
|
1715
|
+
* What raw symbol equality does NOT state, and `symbolDeclarationId`
|
|
1716
|
+
* equality would not either: whether some OTHER type derives from the
|
|
1717
|
+
* standard interface rather than being it directly. `class Registry extends
|
|
1718
|
+
* Map<string, number> {}` has its own `Registry` symbol, distinct from
|
|
1719
|
+
* `Map`'s; `<T extends Map<string, number>>` has its own per-declaration
|
|
1720
|
+
* type-parameter symbol. `isStandardInterfaceType(checker, anchor, 'Map',
|
|
1721
|
+
* receiverType)` answers `false` for both, correctly by its own question
|
|
1722
|
+
* ("IS this type the standard interface") but not by the question a
|
|
1723
|
+
* receiver-identity check usually means to ask ("is this a Map"). A caller
|
|
1724
|
+
* that means the second question -- `flow/value-flow.ts`'s
|
|
1725
|
+
* `matchesStandardInterfaceTransitively` is the one that does -- must take
|
|
1726
|
+
* `checker.getApparentType` (to resolve a type parameter to its constraint)
|
|
1727
|
+
* and walk `checker.getBaseTypes` transitively (to resolve a subclass to
|
|
1728
|
+
* what it extends) itself; this function is one ingredient of that
|
|
1729
|
+
* question, not the whole answer to it.
|
|
1730
|
+
*/
|
|
1731
|
+
const standardInterfaceSymbolCache = new WeakMap();
|
|
1732
|
+
const standardInterfaceSymbol = (checker, anchor, name) => {
|
|
1733
|
+
let cache = standardInterfaceSymbolCache.get(checker);
|
|
1734
|
+
if (!cache) {
|
|
1735
|
+
cache = new Map();
|
|
1736
|
+
standardInterfaceSymbolCache.set(checker, cache);
|
|
1737
|
+
}
|
|
1738
|
+
const cached = cache.get(name);
|
|
1739
|
+
if (cached !== undefined)
|
|
1740
|
+
return cached;
|
|
1741
|
+
const resolved = checker.resolveName(name, anchor, ts.SymbolFlags.Interface, false) ?? null;
|
|
1742
|
+
cache.set(name, resolved);
|
|
1743
|
+
return resolved;
|
|
1744
|
+
};
|
|
1745
|
+
/**
|
|
1746
|
+
* Whether `type` IS the standard library's `name` interface -- by declaration
|
|
1747
|
+
* identity, never by the symbol's own spelling. See `standardInterfaceSymbol`.
|
|
1748
|
+
*/
|
|
1749
|
+
export const isStandardInterfaceType = (checker, anchor, name, type) => {
|
|
1750
|
+
const symbol = type.getSymbol();
|
|
1751
|
+
return symbol !== undefined && symbol === standardInterfaceSymbol(checker, anchor, name);
|
|
1752
|
+
};
|
|
1753
|
+
/**
|
|
1754
|
+
* Whether a type is the global `Object` INTERFACE -- `x: Object`, spelled as a
|
|
1755
|
+
* real TypeScript annotation.
|
|
1756
|
+
*
|
|
1757
|
+
* The exact argument `parameter-bindings.ts`'s `isBareFunctionType` makes for
|
|
1758
|
+
* bare `Function`, one level up: `Object` declares only what every value
|
|
1759
|
+
* already has (`toString`, `valueOf`, `hasOwnProperty`), every non-null value
|
|
1760
|
+
* is assignable to it, and it therefore states nothing about what this
|
|
1761
|
+
* particular value holds. Resolved by DECLARATION IDENTITY (`isStandardInterfaceType`),
|
|
1762
|
+
* so a user interface that merely declares those members -- and would once
|
|
1763
|
+
* have matched on the bare spelling `'Object'` -- is not swept in beside it;
|
|
1764
|
+
* only the library's own `Object` is.
|
|
1765
|
+
*
|
|
1766
|
+
* Reachable from JSDoc TOO, contrary to what this comment said for a long
|
|
1767
|
+
* time. A capitalized `{Object}` tag does NOT degrade to `any`: measured on
|
|
1768
|
+
* three's `@return {Object}`, the checker answers this very interface. See
|
|
1769
|
+
* `jsDocTypeStatesNothing`, which is what makes the tag path ask.
|
|
1770
|
+
*
|
|
1771
|
+
* `normalize/structural.ts` interns exactly THIS interface as `any`, and the
|
|
1772
|
+
* two obvious companions to it are measured DEAD LEVERS, recorded here so the
|
|
1773
|
+
* next session does not re-derive them:
|
|
1774
|
+
*
|
|
1775
|
+
* - an anonymous `{}` (empty, no signatures, no index, excluding fresh object
|
|
1776
|
+
* literals, which share the identical type and are real values whose shape
|
|
1777
|
+
* the object-bag and collection censuses discover): the three.js app's unmet
|
|
1778
|
+
* obligations went 33 UP to 34 and `boxed` 17190 -> 17568. `{}` is the
|
|
1779
|
+
* checker's answer for a great many things that are not "unstated" at all --
|
|
1780
|
+
* an intersection reduced to nothing, a mapped type over no keys, a bag
|
|
1781
|
+
* before its census runs -- and boxing all of them costs more pairings than
|
|
1782
|
+
* it closes.
|
|
1783
|
+
* - the lowercase `object` KEYWORD (`TypeFlags.NonPrimitive`), which really is
|
|
1784
|
+
* one unambiguous spelling and really does state nothing: exactly NEUTRAL on
|
|
1785
|
+
* unmet obligations (33 -> 33) at `boxed` +103. A cost with no return.
|
|
1786
|
+
*
|
|
1787
|
+
* Both remain correct as statements about what those types SAY. Neither is
|
|
1788
|
+
* worth what it costs as a carrier decision.
|
|
1789
|
+
*/
|
|
1790
|
+
export const isGlobalObjectInterface = (checker, anchor, type) => {
|
|
1791
|
+
if ((type.flags & ts.TypeFlags.Object) === 0)
|
|
1792
|
+
return false;
|
|
1793
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0)
|
|
1794
|
+
return false;
|
|
1795
|
+
return isStandardInterfaceType(checker, anchor, 'Object', type);
|
|
1796
|
+
};
|
|
1797
|
+
/**
|
|
1798
|
+
* Whether `type` is the standard library's `ObjectConstructor` interface --
|
|
1799
|
+
* the type the global `Object` VALUE has, as opposed to `isGlobalObjectInterface`'s
|
|
1800
|
+
* `Object` INSTANCE interface immediately above.
|
|
1801
|
+
*
|
|
1802
|
+
* Exported so the two call sites that ask "is this call really `Object.assign`
|
|
1803
|
+
* on the real global" (`flow/value-flow.ts`'s `isGlobalObjectAssign`,
|
|
1804
|
+
* `object-bag-bindings.ts`'s `isGlobalObjectAssignCall`) ask one shared
|
|
1805
|
+
* question instead of two byte-for-byte-identical copies of it, each
|
|
1806
|
+
* comparing `checker.getTypeAtLocation(callee.expression).getSymbol()?.getName()`
|
|
1807
|
+
* against the literal string `'ObjectConstructor'`.
|
|
1808
|
+
*/
|
|
1809
|
+
export const isGlobalObjectConstructor = (checker, anchor, type) => isStandardInterfaceType(checker, anchor, 'ObjectConstructor', type);
|
|
1810
|
+
/** The standard global Function constructor value, by ambient declaration identity. */
|
|
1811
|
+
export const isGlobalFunctionConstructor = (checker, anchor, type) => isStandardInterfaceType(checker, anchor, 'FunctionConstructor', type);
|
|
1812
|
+
/** The standard global Array constructor value, by ambient declaration identity. */
|
|
1813
|
+
export const isGlobalArrayConstructor = (checker, anchor, type) => isStandardInterfaceType(checker, anchor, 'ArrayConstructor', type);
|
|
1814
|
+
/** A standard global namespace value, resolved by symbol identity rather than spelling. */
|
|
1815
|
+
export const isStandardGlobalValue = (checker, expression, name) => {
|
|
1816
|
+
let actual = checker.getSymbolAtLocation(expression);
|
|
1817
|
+
if (actual && (actual.flags & ts.SymbolFlags.Alias) !== 0)
|
|
1818
|
+
actual = checker.getAliasedSymbol(actual);
|
|
1819
|
+
let expected = checker.resolveName(name, expression, ts.SymbolFlags.Value | ts.SymbolFlags.Namespace, false);
|
|
1820
|
+
if (expected && (expected.flags & ts.SymbolFlags.Alias) !== 0)
|
|
1821
|
+
expected = checker.getAliasedSymbol(expected);
|
|
1822
|
+
return actual !== undefined && expected !== undefined && actual === expected;
|
|
1823
|
+
};
|
|
1824
|
+
/**
|
|
1825
|
+
* The target type of an authenticated `Object.assign` call with at least one source.
|
|
1826
|
+
*
|
|
1827
|
+
* The ambient signature's `T & U` result describes which properties source
|
|
1828
|
+
* code may read after the copy. The runtime value is still `T`: ECMA-262
|
|
1829
|
+
* mutates and returns the first argument. Every binding census has to read
|
|
1830
|
+
* that identity the same way, or one census publishes the intersection while
|
|
1831
|
+
* the invocation producer publishes the target and creates a conversion
|
|
1832
|
+
* between two carriers for one object. The extra keys live in the native
|
|
1833
|
+
* target's dynamic-property sidecar; changing the value's carrier is neither
|
|
1834
|
+
* necessary nor correct.
|
|
1835
|
+
*
|
|
1836
|
+
* A primitive target is excluded because Object.assign first boxes it, and
|
|
1837
|
+
* this backend does not implement wrapper-object carriers.
|
|
1838
|
+
*/
|
|
1839
|
+
export const objectAssignTargetType = (checker, node) => {
|
|
1840
|
+
if (!isAuthenticatedObjectAssign(checker, node))
|
|
1841
|
+
return null;
|
|
1842
|
+
const target = node.arguments[0];
|
|
1843
|
+
if (!target)
|
|
1844
|
+
return null;
|
|
1845
|
+
const fresh = ts.isObjectLiteralExpression(target) ? objectAssignFreshTargetType(checker, target) : null;
|
|
1846
|
+
if (fresh !== null)
|
|
1847
|
+
return fresh;
|
|
1848
|
+
const type = checker.getTypeAtLocation(target);
|
|
1849
|
+
return (type.flags & ts.TypeFlags.Object) !== 0 ? type : null;
|
|
1850
|
+
};
|
|
1851
|
+
/** `Object.assign(...)` on the real global with at least one source, resolved by declaration identity. */
|
|
1852
|
+
const isAuthenticatedObjectAssign = (checker, node) => {
|
|
1853
|
+
if (!ts.isCallExpression(node) || node.arguments.length < 2)
|
|
1854
|
+
return false;
|
|
1855
|
+
const callee = node.expression;
|
|
1856
|
+
if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== 'assign')
|
|
1857
|
+
return false;
|
|
1858
|
+
return isGlobalObjectConstructor(checker, callee.expression, checker.getTypeAtLocation(callee.expression));
|
|
1859
|
+
};
|
|
1860
|
+
/**
|
|
1861
|
+
* The object `Object.assign( {}, ...sources )` builds, when `literal` is that
|
|
1862
|
+
* bare `{}`.
|
|
1863
|
+
*
|
|
1864
|
+
* `objectAssignTargetType`'s identity argument holds for a target that exists
|
|
1865
|
+
* before the call: the value returned IS that object, so its carrier is that
|
|
1866
|
+
* object's own, extra keys in the sidecar. A bare empty literal written in the
|
|
1867
|
+
* target position has no carrier of its own to keep. It is allocated for this
|
|
1868
|
+
* call, observed nowhere else, and the one statement anywhere of what it holds
|
|
1869
|
+
* afterwards is the call's own `T & U` -- which the checker has already
|
|
1870
|
+
* reduced, dropping the empty member (`{} & Record<string, number[]>` reports
|
|
1871
|
+
* as `Record<string, number[]>`). Publishing the literal's own `{}` in its
|
|
1872
|
+
* place put an empty record on one side of the copy and a dictionary on the
|
|
1873
|
+
* other: three's `Object.assign( {}, source.defines )` then refused for want of
|
|
1874
|
+
* a record<-dictionary arm, and no such arm can exist, since an empty struct
|
|
1875
|
+
* has nowhere to put a key.
|
|
1876
|
+
*
|
|
1877
|
+
* Read by BOTH the call's result (`objectAssignTargetType`) and the literal's
|
|
1878
|
+
* own layout (`structural-layout-type.ts`), so the target and the value it
|
|
1879
|
+
* becomes are one carrier by construction. A literal with properties keeps its
|
|
1880
|
+
* own shape -- it states one -- and so does a literal under an assertion.
|
|
1881
|
+
*/
|
|
1882
|
+
export const objectAssignFreshTargetType = (checker, literal) => {
|
|
1883
|
+
if (literal.properties.length !== 0)
|
|
1884
|
+
return null;
|
|
1885
|
+
const call = literal.parent;
|
|
1886
|
+
if (!isAuthenticatedObjectAssign(checker, call) || call.arguments[0] !== literal)
|
|
1887
|
+
return null;
|
|
1888
|
+
const type = checker.getTypeAtLocation(call);
|
|
1889
|
+
return (type.flags & ts.TypeFlags.Object) !== 0 ? type : null;
|
|
1890
|
+
};
|
|
1891
|
+
/**
|
|
1892
|
+
* Whether a REAL TypeScript type ANNOTATION states nothing -- the same
|
|
1893
|
+
* non-statement `jsDocTypeStatesNothing` already recognises in a JSDoc tag,
|
|
1894
|
+
* asked of a type the program wrote out in TypeScript.
|
|
1895
|
+
*
|
|
1896
|
+
* `getExtension( name: string ): object | null`, `f( options: {} )`, `g( x:
|
|
1897
|
+
* Object )`: each is exactly as informative about storage as the JSDoc
|
|
1898
|
+
* `@param {Object}` tag that commit `f6c90e4b4` stopped trusting, and for the
|
|
1899
|
+
* same reason -- a stated type that resolves to something which says nothing
|
|
1900
|
+
* is WORSE than no type at all, because it becomes the answer and outranks a
|
|
1901
|
+
* census that could have derived a real one.
|
|
1902
|
+
*
|
|
1903
|
+
* Deliberately NOT widened past that:
|
|
1904
|
+
*
|
|
1905
|
+
* - `unknown` is a statement. An author who writes `unknown` is saying "do not
|
|
1906
|
+
* assume", and that must keep outranking anything this compiler infers.
|
|
1907
|
+
* - a NAMED empty type is a statement -- see `isAnonymousType`.
|
|
1908
|
+
* - a non-empty interface is a statement, however loose.
|
|
1909
|
+
* - `any` written out as a real annotation is left exactly where it is. It is
|
|
1910
|
+
* a separate lever with a separate blast radius, and this module's
|
|
1911
|
+
* `resolvedReturnTypeOf` already documents honouring `: any` at face value.
|
|
1912
|
+
*
|
|
1913
|
+
* A union is seen through only when its non-absent half is itself a
|
|
1914
|
+
* non-statement: `object | null` states as little as `object` does, since
|
|
1915
|
+
* `null` is not a shape. Any other union member is real evidence and stops
|
|
1916
|
+
* this.
|
|
1917
|
+
*
|
|
1918
|
+
* `checker`/`anchor` exist only for `isGlobalObjectInterface`'s declaration-identity
|
|
1919
|
+
* resolution (`isStandardInterfaceType`) -- `anchor` is any node reachable
|
|
1920
|
+
* from the site this type came from; a REAL `Object` is a single ambient
|
|
1921
|
+
* global, so which node names it does not change the answer.
|
|
1922
|
+
*/
|
|
1923
|
+
/**
|
|
1924
|
+
* The one test for "this PART of a type says nothing about storage" -- bare
|
|
1925
|
+
* `{}` (anonymous and empty) or the global `Object` interface.
|
|
1926
|
+
*
|
|
1927
|
+
* Shared so `annotationStatesNothing` (is the WHOLE thing a non-statement?)
|
|
1928
|
+
* and `withoutVacuousMembers` (which PARTS of it are) cannot drift apart:
|
|
1929
|
+
* they are the same question asked of a type and of its members, and a join
|
|
1930
|
+
* that trusts one while the other disagrees is exactly the bug both exist to
|
|
1931
|
+
* prevent.
|
|
1932
|
+
*/
|
|
1933
|
+
const statesNothingPart = (checker, anchor, part) => (isEmptyObjectType(part) && isAnonymousType(part)) || (anchor !== null && isGlobalObjectInterface(checker, anchor, part));
|
|
1934
|
+
/**
|
|
1935
|
+
* `statesNothingPart` asked THROUGH a constraint, for a part that is not
|
|
1936
|
+
* itself a type at all but a stand-in for one.
|
|
1937
|
+
*
|
|
1938
|
+
* `E['Bindings']`, where hono declares `type Bindings = object` and
|
|
1939
|
+
* `E extends Env`, is a deferred indexed access: not an object type, so
|
|
1940
|
+
* neither arm of `statesNothingPart` sees it, and yet the checker relates
|
|
1941
|
+
* every object type to it exactly as it does to `object` -- through that
|
|
1942
|
+
* constraint. It carries as little as `object` does (this compiler derives it
|
|
1943
|
+
* to `dynamic`), and a join that treats it as a real statement lets it cover
|
|
1944
|
+
* observations it says nothing about, which is the whole defect the veto
|
|
1945
|
+
* exists to stop.
|
|
1946
|
+
*
|
|
1947
|
+
* Kept OUT of `annotationStatesNothing`, deliberately: that function decides
|
|
1948
|
+
* whether a stated type is evidence at all, and a constraint-following
|
|
1949
|
+
* version of it would demote every `T extends object` annotation in every
|
|
1950
|
+
* census at once. This one is read only by `withoutVacuousMembers`, whose
|
|
1951
|
+
* single caller uses it to RE-ASK a coverage question it has already been
|
|
1952
|
+
* given an answer to -- so a wrong answer here can only refuse a join, never
|
|
1953
|
+
* narrow a slot.
|
|
1954
|
+
*
|
|
1955
|
+
* A constraint that is a union is vacuous only when its every non-absent
|
|
1956
|
+
* member is -- the same shape, and the same reason, as the union arm of
|
|
1957
|
+
* `annotationStatesNothing` itself.
|
|
1958
|
+
*/
|
|
1959
|
+
const statesNothingThroughConstraint = (checker, anchor, part) => {
|
|
1960
|
+
if (statesNothingPart(checker, anchor, part))
|
|
1961
|
+
return true;
|
|
1962
|
+
const constraint = checker.getBaseConstraintOfType(part);
|
|
1963
|
+
if (constraint === undefined || constraint === part)
|
|
1964
|
+
return false;
|
|
1965
|
+
const absent = ts.TypeFlags.Null | ts.TypeFlags.Undefined;
|
|
1966
|
+
let sawStatement = false;
|
|
1967
|
+
for (const member of constraint.isUnion() ? constraint.types : [constraint]) {
|
|
1968
|
+
if ((member.flags & absent) !== 0)
|
|
1969
|
+
continue;
|
|
1970
|
+
if (!statesNothingPart(checker, anchor, member))
|
|
1971
|
+
return false;
|
|
1972
|
+
sawStatement = true;
|
|
1973
|
+
}
|
|
1974
|
+
return sawStatement;
|
|
1975
|
+
};
|
|
1976
|
+
/**
|
|
1977
|
+
* `type` with every vacuous member dropped, or `null` when nothing is left.
|
|
1978
|
+
*
|
|
1979
|
+
* `annotationStatesNothing` answers "is this type ENTIRELY a non-statement",
|
|
1980
|
+
* which is the right question for evidence: a type that says nothing must not
|
|
1981
|
+
* become the answer. It is the wrong question for a JOIN with a vacuous
|
|
1982
|
+
* member ALONGSIDE a real one -- `{} | E['Bindings'] | undefined`, the first
|
|
1983
|
+
* element of the tuple TypeScript gives hono's `fetch(request, ...rest)`.
|
|
1984
|
+
* That type is not entirely vacuous (the indexed access is a statement), yet
|
|
1985
|
+
* the `{}` inside it still makes EVERY object type assignable to the whole,
|
|
1986
|
+
* so a `widestOf` join hands it back as "the" type and the other elements --
|
|
1987
|
+
* `ExecutionContext` -- vanish without a trace.
|
|
1988
|
+
*
|
|
1989
|
+
* The caller's use is a re-test, never an answer: strip the vacuous parts and
|
|
1990
|
+
* ask whether the coverage survives. If it does, the join was real; if it
|
|
1991
|
+
* does not, the coverage was `{}`'s alone and the join has to state the union
|
|
1992
|
+
* instead. Nothing published to representation is ever the stripped type --
|
|
1993
|
+
* that would be the opposite error, narrowing a slot below what the program
|
|
1994
|
+
* annotated.
|
|
1995
|
+
*/
|
|
1996
|
+
export const withoutVacuousMembers = (checker, anchor, type) => {
|
|
1997
|
+
const parts = type.isUnion() ? type.types : [type];
|
|
1998
|
+
// `isGlobalObjectInterface` resolves the NAME `Object` somewhere in the
|
|
1999
|
+
// program to compare declaration identity, and any node in it answers that
|
|
2000
|
+
// the same way (a real `Object` is a single ambient global). A caller with
|
|
2001
|
+
// no node in hand -- `widestOf`, which is handed types alone -- gets one
|
|
2002
|
+
// from the type itself rather than losing that arm of the test.
|
|
2003
|
+
const at = anchor ?? parts.flatMap((part) => part.getSymbol()?.getDeclarations() ?? [])[0] ?? null;
|
|
2004
|
+
const kept = parts.filter((part) => !statesNothingThroughConstraint(checker, at, part));
|
|
2005
|
+
if (kept.length === parts.length)
|
|
2006
|
+
return type;
|
|
2007
|
+
if (kept.length === 0)
|
|
2008
|
+
return null;
|
|
2009
|
+
if (kept.length === 1)
|
|
2010
|
+
return kept[0];
|
|
2011
|
+
const constructing = checker;
|
|
2012
|
+
return typeof constructing.getUnionType === 'function' ? constructing.getUnionType(kept) : null;
|
|
2013
|
+
};
|
|
2014
|
+
export const annotationStatesNothing = (checker, anchor, type) => {
|
|
2015
|
+
const bare = (part) => statesNothingPart(checker, anchor, part);
|
|
2016
|
+
if (bare(type))
|
|
2017
|
+
return true;
|
|
2018
|
+
if (!type.isUnion())
|
|
2019
|
+
return false;
|
|
2020
|
+
const absent = ts.TypeFlags.Null | ts.TypeFlags.Undefined;
|
|
2021
|
+
let sawNonStatement = false;
|
|
2022
|
+
for (const part of type.types) {
|
|
2023
|
+
if ((part.flags & absent) !== 0)
|
|
2024
|
+
continue;
|
|
2025
|
+
if (!bare(part))
|
|
2026
|
+
return false;
|
|
2027
|
+
sawNonStatement = true;
|
|
2028
|
+
}
|
|
2029
|
+
return sawNonStatement;
|
|
2030
|
+
};
|
|
2031
|
+
/**
|
|
2032
|
+
* The exact type of an EMPTY object literal the program wrote as a value --
|
|
2033
|
+
* `{}` in `const { fn = function () {} } = {}` -- or `null` for any other node.
|
|
2034
|
+
*
|
|
2035
|
+
* `annotationStatesNothing` above is right to treat the vacuous type `{}` as
|
|
2036
|
+
* a non-statement when it arrives as an ANNOTATION or as a join member: every
|
|
2037
|
+
* type is assignable to it, so it dominates. The same type on a literal the
|
|
2038
|
+
* program EVALUATES is the opposite: a fresh object with exactly zero members,
|
|
2039
|
+
* the most precise statement there is, and the one a destructuring pattern
|
|
2040
|
+
* reads out of. Filtering it out there left every pattern over `{}` with no
|
|
2041
|
+
* holder at all, so each leaf fell back to the checker's own `any` (the JS
|
|
2042
|
+
* checker's answer for a key the literal lacks) and boxed the default it was
|
|
2043
|
+
* meant to bind. A literal with spreads or properties is not this case: the
|
|
2044
|
+
* ordinary literal type is already non-vacuous and needs no exception.
|
|
2045
|
+
*/
|
|
2046
|
+
export const exactEmptyObjectLiteralType = (checker, node) => ts.isObjectLiteralExpression(node) && node.properties.length === 0 ? checker.getTypeAtLocation(node) : null;
|
|
2047
|
+
/**
|
|
2048
|
+
* A position the DECLARATION states nothing about.
|
|
2049
|
+
*
|
|
2050
|
+
* `annotationStatesNothing` above answers this for a WHOLE annotation -- `x:
|
|
2051
|
+
* {}`, `x: object`. This is the same question asked of one position INSIDE
|
|
2052
|
+
* one: `any` and `unknown` are the checker's two spellings of "no statement
|
|
2053
|
+
* about this value", and bare `Function` is the third (see
|
|
2054
|
+
* `parameter-bindings.ts`'s `isBareFunctionType` -- zero call signatures,
|
|
2055
|
+
* zero construct signatures, so no arity and no result either).
|
|
2056
|
+
*/
|
|
2057
|
+
const positionStatesNothing = (checker, anchor, declared) => {
|
|
2058
|
+
if ((declared.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)
|
|
2059
|
+
return true;
|
|
2060
|
+
if (annotationStatesNothing(checker, anchor, declared))
|
|
2061
|
+
return true;
|
|
2062
|
+
if (declared.getCallSignatures().length > 0 || declared.getConstructSignatures().length > 0)
|
|
2063
|
+
return false;
|
|
2064
|
+
return declared.getSymbol()?.getName() === 'Function';
|
|
2065
|
+
};
|
|
2066
|
+
/**
|
|
2067
|
+
* Whether a stated type has an unstated position ANYWHERE inside it, and so
|
|
2068
|
+
* could be an upper bound rather than the last word.
|
|
2069
|
+
*
|
|
2070
|
+
* A pre-filter, not a judgement: `narrowsOnlyUnstatedPositions` below decides
|
|
2071
|
+
* whether a particular value may narrow a particular statement. This only
|
|
2072
|
+
* keeps a census from carrying every annotated declaration in the program as
|
|
2073
|
+
* a candidate when almost none of them state nothing anywhere.
|
|
2074
|
+
*/
|
|
2075
|
+
export const containsUnstatedPosition = (checker, anchor, declared, depth = 0) => {
|
|
2076
|
+
if (depth > 8)
|
|
2077
|
+
return false;
|
|
2078
|
+
if (positionStatesNothing(checker, anchor, declared))
|
|
2079
|
+
return true;
|
|
2080
|
+
// `Iterable<T>` states the values iteration yields and deliberately does
|
|
2081
|
+
// not state what physical object implements the protocol. A concrete Array,
|
|
2082
|
+
// Set, generator or user iterator returned through that annotation must keep
|
|
2083
|
+
// its own carrier; flattening the interface into a record invents an object
|
|
2084
|
+
// layout for a protocol view. Treat the implementation position as unstated
|
|
2085
|
+
// while `narrowsOnlyUnstatedPositions` below still requires the concrete
|
|
2086
|
+
// type to satisfy this exact standard-library declaration, including `T`.
|
|
2087
|
+
if (isStandardInterfaceType(checker, anchor, 'Iterable', declared))
|
|
2088
|
+
return true;
|
|
2089
|
+
if (declared.isUnion() || declared.isIntersection()) {
|
|
2090
|
+
return declared.types.some((part) => containsUnstatedPosition(checker, anchor, part, depth + 1));
|
|
2091
|
+
}
|
|
2092
|
+
if ((declared.flags & ts.TypeFlags.Object) === 0)
|
|
2093
|
+
return false;
|
|
2094
|
+
const reference = declared;
|
|
2095
|
+
if (reference.target === undefined)
|
|
2096
|
+
return false;
|
|
2097
|
+
return checker.getTypeArguments(reference).some((part) => containsUnstatedPosition(checker, anchor, part, depth + 1));
|
|
2098
|
+
};
|
|
2099
|
+
/**
|
|
2100
|
+
* WHETHER A STATED TYPE AND THE TYPE THAT ACTUALLY FLOWS INTO IT DIFFER ONLY
|
|
2101
|
+
* WHERE THE STATEMENT SAID NOTHING.
|
|
2102
|
+
*
|
|
2103
|
+
* `annotationStatesNothing` is all-or-nothing about one annotation, which is
|
|
2104
|
+
* the right question for a parameter written `: {}` and the wrong one for
|
|
2105
|
+
* hono's `matchResult: Result<[unknown, RouterRoute]>`. That annotation states
|
|
2106
|
+
* plenty -- a two-armed union of tuples of arrays of tuples -- and states
|
|
2107
|
+
* NOTHING at exactly one leaf, the `unknown` where the handler goes. The
|
|
2108
|
+
* program's only writer hands it `Result<[H, RouterRoute]>`, identical
|
|
2109
|
+
* everywhere the annotation spoke and concrete at the one place it did not.
|
|
2110
|
+
*
|
|
2111
|
+
* Reading the annotation as the last word there is what forces a value that is
|
|
2112
|
+
* already exactly right to be rebuilt into a boxed copy of itself -- through
|
|
2113
|
+
* two arrays and three records, which is not a conversion any backend can
|
|
2114
|
+
* render soundly (an array rebuild is a COPY, and a copy is not the array the
|
|
2115
|
+
* caller passed). Reading it as an upper bound instead, satisfied at every
|
|
2116
|
+
* position it constrains, is what lets the value stay itself.
|
|
2117
|
+
*
|
|
2118
|
+
* This is deliberately NOT "the actual type is assignable to the declared
|
|
2119
|
+
* one". Assignability admits a narrowing at a position the program DID state
|
|
2120
|
+
* -- `x: string | number` fed only strings -- and honoring the statement is
|
|
2121
|
+
* this compiler's rule everywhere else. `checker.isTypeAssignableTo` is still
|
|
2122
|
+
* required (the caller asks it), but it is the floor, not the test.
|
|
2123
|
+
*/
|
|
2124
|
+
export const narrowsOnlyUnstatedPositions = (checker, anchor, declared, actual, depth = 0) => {
|
|
2125
|
+
if (declared === actual)
|
|
2126
|
+
return true;
|
|
2127
|
+
if (depth > 8)
|
|
2128
|
+
return false;
|
|
2129
|
+
// A statement of nothing accepts anything CONCRETE. `any` on the actual side
|
|
2130
|
+
// is the absence these censuses exist to fill, never an answer to propagate.
|
|
2131
|
+
if (positionStatesNothing(checker, anchor, declared))
|
|
2132
|
+
return (actual.flags & ts.TypeFlags.Any) === 0;
|
|
2133
|
+
// The implementation half of the `Iterable<T>` upper bound described in
|
|
2134
|
+
// `containsUnstatedPosition`: assignability is the checker's proof that the
|
|
2135
|
+
// actual object implements this precise protocol and yields the declared
|
|
2136
|
+
// element. The concrete carrier is therefore strictly more informative at
|
|
2137
|
+
// the one position the annotation leaves open.
|
|
2138
|
+
if (isStandardInterfaceType(checker, anchor, 'Iterable', declared)) {
|
|
2139
|
+
return (actual.flags & ts.TypeFlags.Any) === 0 && checker.isTypeAssignableTo(actual, declared);
|
|
2140
|
+
}
|
|
2141
|
+
// Two spellings of one type -- an alias and its expansion, `Params` and
|
|
2142
|
+
// `Record<string, string>` -- state the identical thing at every position.
|
|
2143
|
+
if (checker.isTypeAssignableTo(actual, declared) && checker.isTypeAssignableTo(declared, actual))
|
|
2144
|
+
return true;
|
|
2145
|
+
const declaredReference = declared;
|
|
2146
|
+
const actualReference = actual;
|
|
2147
|
+
if ((declared.flags & ts.TypeFlags.Object) !== 0 &&
|
|
2148
|
+
(actual.flags & ts.TypeFlags.Object) !== 0 &&
|
|
2149
|
+
declaredReference.target !== undefined &&
|
|
2150
|
+
declaredReference.target === actualReference.target) {
|
|
2151
|
+
const left = checker.getTypeArguments(declaredReference);
|
|
2152
|
+
const right = checker.getTypeArguments(actualReference);
|
|
2153
|
+
if (left.length === 0 || left.length !== right.length)
|
|
2154
|
+
return false;
|
|
2155
|
+
return left.every((part, index) => {
|
|
2156
|
+
const other = right[index];
|
|
2157
|
+
return other !== undefined && narrowsOnlyUnstatedPositions(checker, anchor, part, other, depth + 1);
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
// A union member that states its whole carrier is not an open position.
|
|
2161
|
+
// Preserve every such member before allowing call-site evidence to refine
|
|
2162
|
+
// the members that do contain `any`/`unknown`. Otherwise one open arm makes
|
|
2163
|
+
// the whole union look open: `string | ArrayBuffer | ReadableStream<unknown>`
|
|
2164
|
+
// observed at an incomplete indirect call as only `ReadableStream` dropped
|
|
2165
|
+
// the two fully stated body arms, and generated Response construction then
|
|
2166
|
+
// read a string as the stream arm. Hono's `Result<[unknown, Route]>` remains
|
|
2167
|
+
// refinable because each member's differing position is itself unstated.
|
|
2168
|
+
if (declared.isUnion()) {
|
|
2169
|
+
const actualMembers = actual.isUnion() ? actual.types : [actual];
|
|
2170
|
+
for (const member of declared.types) {
|
|
2171
|
+
if (containsUnstatedPosition(checker, anchor, member, depth + 1))
|
|
2172
|
+
continue;
|
|
2173
|
+
const preserved = actualMembers.some((candidate) => checker.isTypeAssignableTo(candidate, member) && checker.isTypeAssignableTo(member, candidate));
|
|
2174
|
+
if (!preserved)
|
|
2175
|
+
return false;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
// A union is matched member-to-member, and NOT as a bijection: `A | B` both
|
|
2179
|
+
// narrowing the same declared member is still a narrowing only at unstated
|
|
2180
|
+
// positions, which is the whole claim. What is refused is an actual member
|
|
2181
|
+
// no declared member covers -- that one is a widening the statement forbids.
|
|
2182
|
+
if (declared.isUnion() && actual.isUnion()) {
|
|
2183
|
+
return actual.types.every((member) => declared.types.some((candidate) => narrowsOnlyUnstatedPositions(checker, anchor, candidate, member, depth + 1)));
|
|
2184
|
+
}
|
|
2185
|
+
if (declared.isUnion()) {
|
|
2186
|
+
return declared.types.some((candidate) => narrowsOnlyUnstatedPositions(checker, anchor, candidate, actual, depth + 1));
|
|
2187
|
+
}
|
|
2188
|
+
return false;
|
|
2189
|
+
};
|
|
2190
|
+
/**
|
|
2191
|
+
* `T | undefined` -> `T`, and ONLY the `undefined` member: a `null` member is
|
|
2192
|
+
* a different absence value and stays.
|
|
2193
|
+
*
|
|
2194
|
+
* Needed where a cell's contents and a READ of that cell disagree about an
|
|
2195
|
+
* absence the language already resolved -- a DEFAULTED parameter, whose slot
|
|
2196
|
+
* carries `undefined` (that is what the default exists to answer) and whose
|
|
2197
|
+
* body binding cannot: `producers/bindings.ts`'s `contributeDefaultedParameter`
|
|
2198
|
+
* strips it there from the compiler's own type table, and this is the same
|
|
2199
|
+
* strip asked of a `ts.Type`, for the censuses that answer a body read.
|
|
2200
|
+
*
|
|
2201
|
+
* `getNonNullableType` is the wrong instrument -- it strips `null` too, and
|
|
2202
|
+
* `p: T | null = null` is ordinary TypeScript whose body really does observe
|
|
2203
|
+
* the `null`.
|
|
2204
|
+
*/
|
|
2205
|
+
export const withoutUndefinedMember = (checker, type) => {
|
|
2206
|
+
if (!type.isUnion())
|
|
2207
|
+
return type;
|
|
2208
|
+
const kept = type.types.filter((member) => (member.flags & ts.TypeFlags.Undefined) === 0);
|
|
2209
|
+
if (kept.length === type.types.length || kept.length === 0)
|
|
2210
|
+
return type;
|
|
2211
|
+
const sole = kept.length === 1 ? kept[0] : undefined;
|
|
2212
|
+
if (sole)
|
|
2213
|
+
return sole;
|
|
2214
|
+
const constructing = checker;
|
|
2215
|
+
return typeof constructing.getUnionType === 'function' ? constructing.getUnionType(kept) : type;
|
|
2216
|
+
};
|
|
2217
|
+
/** The class a constructor-object type (`typeof C`) belongs to, or `null` for any other type. */
|
|
2218
|
+
const classOfConstructorType = (type) => type.symbol !== undefined && (type.symbol.flags & ts.SymbolFlags.Class) !== 0 && type.getConstructSignatures().length > 0
|
|
2219
|
+
? type.symbol
|
|
2220
|
+
: null;
|
|
2221
|
+
/** Whether `a` and `b` are the constructor objects of two different classes -- nominally distinct, whatever their shapes. */
|
|
2222
|
+
export const isDistinctClassConstructorPair = (a, b) => {
|
|
2223
|
+
const first = classOfConstructorType(a);
|
|
2224
|
+
const second = classOfConstructorType(b);
|
|
2225
|
+
return first !== null && second !== null && first !== second;
|
|
2226
|
+
};
|
|
2227
|
+
/** Shape subtyping cannot discard the identity of a class constructor selected at runtime. */
|
|
2228
|
+
export const nominalConstructorChoiceTypeAt = (checker, node, read) => {
|
|
2229
|
+
if (ts.isParenthesizedExpression(node))
|
|
2230
|
+
return nominalConstructorChoiceTypeAt(checker, node.expression, read);
|
|
2231
|
+
if (!ts.isConditionalExpression(node))
|
|
2232
|
+
return null;
|
|
2233
|
+
const arms = [read(node.whenTrue), read(node.whenFalse)].flatMap((type) => (type.isUnion() ? type.types : [type]));
|
|
2234
|
+
if (arms.some((arm) => classOfConstructorType(arm) === null))
|
|
2235
|
+
return null;
|
|
2236
|
+
return disjointUnionTypeOf(checker, arms);
|
|
2237
|
+
};
|
|
2238
|
+
export const widestOf = (checker, types) => {
|
|
2239
|
+
const agree = (other, candidate) => {
|
|
2240
|
+
const candidateArity = soleCallArity(candidate);
|
|
2241
|
+
const otherArity = soleCallArity(other);
|
|
2242
|
+
if (candidateArity !== null && otherArity !== null && candidateArity !== otherArity)
|
|
2243
|
+
return false;
|
|
2244
|
+
// Two DIFFERENT classes' constructor objects never agree, however alike
|
|
2245
|
+
// their shapes: `class {}` beside `class X {}` is structurally assignable
|
|
2246
|
+
// both ways to the checker, but this compiler's `constructor-family`
|
|
2247
|
+
// carrier is nominal (one class per carrier), and a join that picked one
|
|
2248
|
+
// as "the" type made the other's default convert into it --
|
|
2249
|
+
// `[cls = class {}, xCls = class X {}]` read `xCls.name` as `"cls"`.
|
|
2250
|
+
if (isDistinctClassConstructorPair(other, candidate))
|
|
2251
|
+
return false;
|
|
2252
|
+
if (!checker.isTypeAssignableTo(other, candidate))
|
|
2253
|
+
return false;
|
|
2254
|
+
// Assignability is not carriage, the same reason the nominal veto above
|
|
2255
|
+
// exists. A union with a VACUOUS member alongside real ones -- hono's
|
|
2256
|
+
// `Env?: E['Bindings'] | {}` -- absorbs every object type there is, so a
|
|
2257
|
+
// candidate carrying one "covers" observations it says nothing about:
|
|
2258
|
+
// `widestOf([E['Bindings'] | {} | undefined, ExecutionContext |
|
|
2259
|
+
// undefined])` answered the first, and the array built from it
|
|
2260
|
+
// (`parameter-slot.ts`'s rest element, reached through `joinOfWrites`'s
|
|
2261
|
+
// call-site tail join) named no `ExecutionContext` at all -- so
|
|
2262
|
+
// `app.request(path, init, Env, executionCtx)` forwarding a real one had
|
|
2263
|
+
// no conversion into it.
|
|
2264
|
+
//
|
|
2265
|
+
// Re-asked with the candidate's vacuous members removed: coverage that
|
|
2266
|
+
// survives was real and stands, coverage that does not was `{}`'s alone
|
|
2267
|
+
// and this is a disagreement, which every caller already handles (a union
|
|
2268
|
+
// of the observations, or the checker's own answer left standing).
|
|
2269
|
+
//
|
|
2270
|
+
// Only for a candidate that is a UNION with BOTH vacuous and non-vacuous
|
|
2271
|
+
// members. A wholly vacuous candidate is deliberately left exactly as it
|
|
2272
|
+
// was: `{}` is also the type of an EMPTY OBJECT LITERAL the program
|
|
2273
|
+
// evaluated, where it is the most precise statement there is rather than
|
|
2274
|
+
// a non-statement (see `exactEmptyObjectLiteralType`), and the censuses
|
|
2275
|
+
// that must not admit a vacuous ANNOTATION already filter it before they
|
|
2276
|
+
// ever get here (`field-bindings.ts`'s `known`).
|
|
2277
|
+
if (!candidate.isUnion())
|
|
2278
|
+
return true;
|
|
2279
|
+
const stated = withoutVacuousMembers(checker, null, candidate);
|
|
2280
|
+
if (stated === null || stated === candidate)
|
|
2281
|
+
return true;
|
|
2282
|
+
return checker.isTypeAssignableTo(other, stated);
|
|
2283
|
+
};
|
|
2284
|
+
for (const candidate of types) {
|
|
2285
|
+
if (types.every((other) => other === candidate || agree(other, candidate)))
|
|
2286
|
+
return candidate;
|
|
2287
|
+
}
|
|
2288
|
+
return null;
|
|
2289
|
+
};
|
|
2290
|
+
/**
|
|
2291
|
+
* Whether `type` states one value of a wider primitive. `isLiteral()` covers
|
|
2292
|
+
* string/number/bigint literals but, per the checker's own design, never a
|
|
2293
|
+
* boolean literal -- `true`/`false` are distinct intrinsic types, not
|
|
2294
|
+
* `LiteralType`s -- even though `getBaseTypeOfLiteralType` still widens them
|
|
2295
|
+
* to `boolean` correctly when asked. Shared so both call sites below agree.
|
|
2296
|
+
*/
|
|
2297
|
+
const hasLiteralForm = (type) => type.isLiteral() || (type.flags & ts.TypeFlags.BooleanLiteral) !== 0;
|
|
2298
|
+
/** `type`, widened off its literal form when it has one -- `"a" + "b"` is `string`, not the literal `"ab"` nobody wrote. */
|
|
2299
|
+
const widenLiteral = (checker, type) => hasLiteralForm(type) ? checker.getBaseTypeOfLiteralType(type) : type;
|
|
2300
|
+
const ARITHMETIC_ONLY_OPERATORS = new Set([
|
|
2301
|
+
ts.SyntaxKind.MinusToken,
|
|
2302
|
+
ts.SyntaxKind.AsteriskToken,
|
|
2303
|
+
ts.SyntaxKind.SlashToken,
|
|
2304
|
+
ts.SyntaxKind.PercentToken,
|
|
2305
|
+
ts.SyntaxKind.AsteriskAsteriskToken,
|
|
2306
|
+
ts.SyntaxKind.AmpersandToken,
|
|
2307
|
+
ts.SyntaxKind.BarToken,
|
|
2308
|
+
ts.SyntaxKind.CaretToken,
|
|
2309
|
+
ts.SyntaxKind.LessThanLessThanToken,
|
|
2310
|
+
ts.SyntaxKind.GreaterThanGreaterThanToken,
|
|
2311
|
+
ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken
|
|
2312
|
+
]);
|
|
2313
|
+
const LOGICAL_OPERATORS = new Set([
|
|
2314
|
+
ts.SyntaxKind.AmpersandAmpersandToken,
|
|
2315
|
+
ts.SyntaxKind.BarBarToken,
|
|
2316
|
+
ts.SyntaxKind.QuestionQuestionToken
|
|
2317
|
+
]);
|
|
2318
|
+
const binaryExpressionType = (checker, node, read) => {
|
|
2319
|
+
const op = node.operatorToken.kind;
|
|
2320
|
+
if (op === ts.SyntaxKind.PlusToken || op === ts.SyntaxKind.PlusEqualsToken) {
|
|
2321
|
+
const left = read(node.left);
|
|
2322
|
+
const right = read(node.right);
|
|
2323
|
+
if (!left || !right)
|
|
2324
|
+
return null;
|
|
2325
|
+
// ECMA-262 12.15.5: EITHER operand being a string makes the whole
|
|
2326
|
+
// expression string concatenation.
|
|
2327
|
+
if (stringDomain(left))
|
|
2328
|
+
return widenLiteral(checker, left);
|
|
2329
|
+
if (stringDomain(right))
|
|
2330
|
+
return widenLiteral(checker, right);
|
|
2331
|
+
if (numberDomain(left) && numberDomain(right))
|
|
2332
|
+
return widenLiteral(checker, left);
|
|
2333
|
+
return null;
|
|
2334
|
+
}
|
|
2335
|
+
if (ARITHMETIC_ONLY_OPERATORS.has(op)) {
|
|
2336
|
+
const left = read(node.left);
|
|
2337
|
+
const right = read(node.right);
|
|
2338
|
+
if (!left || !right || !numberDomain(left) || !numberDomain(right))
|
|
2339
|
+
return null;
|
|
2340
|
+
return widenLiteral(checker, left);
|
|
2341
|
+
}
|
|
2342
|
+
if (LOGICAL_OPERATORS.has(op)) {
|
|
2343
|
+
const left = read(node.left);
|
|
2344
|
+
const right = read(node.right);
|
|
2345
|
+
if (!left || !right)
|
|
2346
|
+
return null;
|
|
2347
|
+
const operator = op === ts.SyntaxKind.AmpersandAmpersandToken ? '&&' : op === ts.SyntaxKind.BarBarToken ? '||' : '??';
|
|
2348
|
+
return logicalResultTypeOf(checker, operator, left, right);
|
|
2349
|
+
}
|
|
2350
|
+
return null;
|
|
2351
|
+
};
|
|
2352
|
+
/**
|
|
2353
|
+
* Whether two expressions are the SAME pure read of the same storage, with no
|
|
2354
|
+
* evaluation hidden in either of them.
|
|
2355
|
+
*
|
|
2356
|
+
* Two shapes qualify, and deliberately no others. Property reads can invoke
|
|
2357
|
+
* accessors and arbitrary computed keys can call user code, so this is much
|
|
2358
|
+
* smaller than syntactic equality:
|
|
2359
|
+
*
|
|
2360
|
+
* - two IDENTIFIERS resolving to one symbol. A bare identifier read has no
|
|
2361
|
+
* receiver, no key and no user code behind it at all -- it is strictly
|
|
2362
|
+
* purer than the element access below, not a relaxation of it. Comparing
|
|
2363
|
+
* SYMBOLS rather than source spelling keeps shadowed bindings distinct.
|
|
2364
|
+
* - two element accesses reading one slot of one checker-proven Array
|
|
2365
|
+
* through an identifier-bound receiver and an identifier/literal index --
|
|
2366
|
+
* exactly the carrier this compiler lowers through
|
|
2367
|
+
* `ArrayObject::elementAt`.
|
|
2368
|
+
*
|
|
2369
|
+
* Used only across the authenticated, pure `Array.isArray` call immediately
|
|
2370
|
+
* below. There is therefore no operation between the two reads that can
|
|
2371
|
+
* mutate the storage -- which is why a `let` binding is admitted alongside a
|
|
2372
|
+
* `const` one: nothing can run between a conditional's condition and the arm
|
|
2373
|
+
* it selects.
|
|
2374
|
+
*/
|
|
2375
|
+
const sameStablePureRead = (checker, left, right) => {
|
|
2376
|
+
while (ts.isParenthesizedExpression(left) || ts.isNonNullExpression(left))
|
|
2377
|
+
left = left.expression;
|
|
2378
|
+
while (ts.isParenthesizedExpression(right) || ts.isNonNullExpression(right))
|
|
2379
|
+
right = right.expression;
|
|
2380
|
+
if (ts.isIdentifier(left) && ts.isIdentifier(right)) {
|
|
2381
|
+
const symbol = checker.getSymbolAtLocation(left);
|
|
2382
|
+
return symbol !== undefined && symbol === checker.getSymbolAtLocation(right);
|
|
2383
|
+
}
|
|
2384
|
+
if (!ts.isElementAccessExpression(left) || !ts.isElementAccessExpression(right))
|
|
2385
|
+
return false;
|
|
2386
|
+
if (!ts.isIdentifier(left.expression) || !ts.isIdentifier(right.expression))
|
|
2387
|
+
return false;
|
|
2388
|
+
const leftReceiver = checker.getSymbolAtLocation(left.expression);
|
|
2389
|
+
const rightReceiver = checker.getSymbolAtLocation(right.expression);
|
|
2390
|
+
if (!leftReceiver || leftReceiver !== rightReceiver)
|
|
2391
|
+
return false;
|
|
2392
|
+
const receiverType = checker.getNonNullableType(checker.getTypeAtLocation(left.expression));
|
|
2393
|
+
if (!checker.isArrayType(receiverType))
|
|
2394
|
+
return false;
|
|
2395
|
+
const leftKey = left.argumentExpression;
|
|
2396
|
+
const rightKey = right.argumentExpression;
|
|
2397
|
+
if (!leftKey || !rightKey)
|
|
2398
|
+
return false;
|
|
2399
|
+
if (ts.isIdentifier(leftKey) && ts.isIdentifier(rightKey)) {
|
|
2400
|
+
const leftSymbol = checker.getSymbolAtLocation(leftKey);
|
|
2401
|
+
return leftSymbol !== undefined && leftSymbol === checker.getSymbolAtLocation(rightKey);
|
|
2402
|
+
}
|
|
2403
|
+
if (ts.isNumericLiteral(leftKey) && ts.isNumericLiteral(rightKey))
|
|
2404
|
+
return leftKey.text === rightKey.text;
|
|
2405
|
+
if (ts.isStringLiteralLike(leftKey) && ts.isStringLiteralLike(rightKey))
|
|
2406
|
+
return leftKey.text === rightKey.text;
|
|
2407
|
+
return false;
|
|
2408
|
+
};
|
|
2409
|
+
/**
|
|
2410
|
+
* The array carrier selected by the normalizing idiom
|
|
2411
|
+
*
|
|
2412
|
+
* Array.isArray(values[i]) ? values[i] : [values[i]]
|
|
2413
|
+
* Array.isArray(data) ? data : [data]
|
|
2414
|
+
*
|
|
2415
|
+
* or `null` when the expression does not prove that one carrier.
|
|
2416
|
+
*
|
|
2417
|
+
* A binding census can know more about `values[i]` than TypeScript's checker
|
|
2418
|
+
* does. Three's `UniformsGroup.copy` is the measured case: the census has
|
|
2419
|
+
* `Uniform | Uniform[]`, while the JSDoc-backed array literal still has
|
|
2420
|
+
* `Uniform[]`. The generic conditional join sees `Uniform | Uniform[]` and
|
|
2421
|
+
* republishes that union, losing the fact the condition just established;
|
|
2422
|
+
* the subsequent numeric read then correctly refuses a union whose scalar arm
|
|
2423
|
+
* is not indexable.
|
|
2424
|
+
*
|
|
2425
|
+
* The rule is tied to the real global `Array.isArray`, to a repeated stable
|
|
2426
|
+
* PURE READ -- a bare identifier, or one slot of one proven native Array --
|
|
2427
|
+
* and to a one-element alternate containing that same read. The IDENTIFIER
|
|
2428
|
+
* form is what a one-or-many parameter is normally written over -- hono's
|
|
2429
|
+
* `node-server` websocket bridge is `const datas = Array.isArray(data) ?
|
|
2430
|
+
* data : [data]` over `data: WebSocketData` -- and without it the join
|
|
2431
|
+
* republished the whole union, so the `for (const data of datas)` that
|
|
2432
|
+
* follows read an element off a carrier whose scalar arms are not indexable
|
|
2433
|
+
* at all. Every array arm already discovered for the slot must fit the
|
|
2434
|
+
* alternate's array type. Thus the answer is the carrier both reachable arms
|
|
2435
|
+
* share; this does not turn a general predicate or a mutable/accessor read
|
|
2436
|
+
* into a narrowing.
|
|
2437
|
+
*/
|
|
2438
|
+
export const normalizedArrayConditionalType = (checker, node, read) => {
|
|
2439
|
+
const condition = node.condition;
|
|
2440
|
+
if (!ts.isCallExpression(condition) || condition.arguments.length !== 1)
|
|
2441
|
+
return null;
|
|
2442
|
+
const callee = condition.expression;
|
|
2443
|
+
if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== 'isArray')
|
|
2444
|
+
return null;
|
|
2445
|
+
const receiverType = checker.getTypeAtLocation(callee.expression);
|
|
2446
|
+
if (!isStandardInterfaceType(checker, callee.expression, 'ArrayConstructor', receiverType))
|
|
2447
|
+
return null;
|
|
2448
|
+
const tested = condition.arguments[0];
|
|
2449
|
+
if (!tested || !sameStablePureRead(checker, tested, node.whenTrue))
|
|
2450
|
+
return null;
|
|
2451
|
+
if (!ts.isArrayLiteralExpression(node.whenFalse) || node.whenFalse.elements.length !== 1)
|
|
2452
|
+
return null;
|
|
2453
|
+
const singleton = node.whenFalse.elements[0];
|
|
2454
|
+
if (!singleton || ts.isSpreadElement(singleton) || !sameStablePureRead(checker, tested, singleton))
|
|
2455
|
+
return null;
|
|
2456
|
+
const alternate = read(node.whenFalse);
|
|
2457
|
+
const source = read(tested);
|
|
2458
|
+
if (!alternate || !source || !checker.isArrayType(alternate))
|
|
2459
|
+
return null;
|
|
2460
|
+
const sourceMembers = source.isUnion() ? source.types : [source];
|
|
2461
|
+
// A `readonly T[]` member is NOT admitted here, although `readonly` has no
|
|
2462
|
+
// physical reading in this compiler (`structural.ts` publishes every array
|
|
2463
|
+
// as `readonly: false`) and node-server's
|
|
2464
|
+
// `WebSocketData = string | ArrayBuffer | Uint8Array | readonly Uint8Array[]`
|
|
2465
|
+
// is refused for exactly that spelling. Admitting it was measured: the
|
|
2466
|
+
// arm's element is NARROWER than the alternate's (`Uint8Array` against the
|
|
2467
|
+
// whole union), so the answer widens an array's element -- and the binding
|
|
2468
|
+
// conversion this function feeds then selected an arm that produced an
|
|
2469
|
+
// EMPTY array at runtime instead of refusing. A plain
|
|
2470
|
+
// `array-object(record) -> array-object(tagged-union)` is refused outright
|
|
2471
|
+
// (`conversion:array-object(...)->array-object(...)`: no runtime conversion
|
|
2472
|
+
// installed), which is the correct fail-closed answer; reaching the same
|
|
2473
|
+
// widening through this idiom's arm selection turned that refusal into a
|
|
2474
|
+
// wrong answer, so the arm selection is what has to be fixed before the
|
|
2475
|
+
// spelling can be admitted.
|
|
2476
|
+
const arrayMembers = sourceMembers.filter((member) => checker.isArrayType(checker.getNonNullableType(member)));
|
|
2477
|
+
if (!arrayMembers.every((member) => checker.isTypeAssignableTo(member, alternate)))
|
|
2478
|
+
return null;
|
|
2479
|
+
// With no array arm the authenticated predicate is statically false only
|
|
2480
|
+
// for a concrete class instance. An interface/object/type parameter can
|
|
2481
|
+
// still describe an Array value structurally, and `any`/`unknown` state no
|
|
2482
|
+
// carrier at all, so none of those license the same conclusion.
|
|
2483
|
+
if (arrayMembers.length === 0) {
|
|
2484
|
+
const symbol = checker.getNonNullableType(source).getSymbol();
|
|
2485
|
+
if (!symbol || (symbol.flags & ts.SymbolFlags.Class) === 0)
|
|
2486
|
+
return null;
|
|
2487
|
+
}
|
|
2488
|
+
return alternate;
|
|
2489
|
+
};
|
|
2490
|
+
/**
|
|
2491
|
+
* The type an `arg is any[]` predicate REALLY establishes, when the checker
|
|
2492
|
+
* answered with the intersection it falls back to.
|
|
2493
|
+
*
|
|
2494
|
+
* `Array.isArray`'s `lib.es5.d.ts` signature is `isArray(arg: any): arg is
|
|
2495
|
+
* any[]`, and TypeScript narrows a union by a predicate by KEEPING the
|
|
2496
|
+
* constituents assignable to the predicate's type. Where none is, it does not
|
|
2497
|
+
* answer `never` -- it answers the INTERSECTION `T & any[]`, distributed over
|
|
2498
|
+
* `T`'s arms, and every read through it is typed `any`:
|
|
2499
|
+
*
|
|
2500
|
+
* Array.isArray(value) ? value[0] : value // value: string
|
|
2501
|
+
* Array.isArray(pattern) ? pattern[0] : p // readonly [...] | '*'
|
|
2502
|
+
*
|
|
2503
|
+
* (`@hono/node-server`'s `createUpgradeRequest` and hono's trie router; a
|
|
2504
|
+
* `readonly` tuple is not assignable to the mutable `any[]` either, so the
|
|
2505
|
+
* fallback fires for a type that IS an array.) That `any` is an artifact of a
|
|
2506
|
+
* failed narrowing, not a boundary the program declared: the value is a
|
|
2507
|
+
* `std::string` or a fixed tuple and the read has a stated answer either way.
|
|
2508
|
+
* Carried as `dynamic` it reached the backend as a result with no absence
|
|
2509
|
+
* materialization, and boxing the read to make it compile is the shortcut this
|
|
2510
|
+
* compiler does not take.
|
|
2511
|
+
*
|
|
2512
|
+
* `any[]` states no carrier at all (`isUnusableEvidence`), so intersecting
|
|
2513
|
+
* with it adds nothing physical; what it adds is the assertion that the value
|
|
2514
|
+
* is an array HERE. So the answer is the array/tuple constituents of the rest
|
|
2515
|
+
* -- and with none of those the predicate is statically false for a value that
|
|
2516
|
+
* has a carrier, the arm is dead, and the honest type is the one the value
|
|
2517
|
+
* already had. Only a SINGLE surviving subject is answered: two array arms
|
|
2518
|
+
* would need a join this is not the place to take.
|
|
2519
|
+
*/
|
|
2520
|
+
export const arrayPredicateNarrowedTypeOf = (checker, type) => {
|
|
2521
|
+
const isPredicateArray = (part) => checker.isArrayType(part) && isUnusableEvidence(checker.getTypeArguments(part)[0] ?? part);
|
|
2522
|
+
const subjects = [];
|
|
2523
|
+
for (const member of type.isUnion() ? type.types : [type]) {
|
|
2524
|
+
if (!member.isIntersection())
|
|
2525
|
+
return null;
|
|
2526
|
+
const rest = member.types.filter((part) => !isPredicateArray(part));
|
|
2527
|
+
const subject = rest.length === 1 ? rest[0] : undefined;
|
|
2528
|
+
if (subject === undefined || rest.length === member.types.length)
|
|
2529
|
+
return null;
|
|
2530
|
+
subjects.push(subject);
|
|
2531
|
+
}
|
|
2532
|
+
const arrays = subjects.filter((subject) => checker.isArrayType(subject) || checker.isTupleType(subject));
|
|
2533
|
+
if (arrays.length === 1)
|
|
2534
|
+
return arrays[0] ?? null;
|
|
2535
|
+
return arrays.length === 0 && subjects.length === 1 ? (subjects[0] ?? null) : null;
|
|
2536
|
+
};
|
|
2537
|
+
/**
|
|
2538
|
+
* The element an `x[k]` read produces when the checker typed it `any` only
|
|
2539
|
+
* because `x` is standing in an `arg is any[]` narrowing's intersection.
|
|
2540
|
+
*
|
|
2541
|
+
* The key is answered off the narrowed subject exactly as any other read is:
|
|
2542
|
+
* a literal key names a member (a tuple's `[0]` is its first element type,
|
|
2543
|
+
* not the join of all of them), and anything else takes the receiver's own
|
|
2544
|
+
* index signature -- `String`'s is `readonly [index: number]: string`, which
|
|
2545
|
+
* is the carrier `stringIndexText`'s `charAt` already renders.
|
|
2546
|
+
*/
|
|
2547
|
+
export const arrayPredicateNarrowedElementTypeOf = (checker, node) => {
|
|
2548
|
+
if (!isUnusableEvidence(checker.getTypeAtLocation(node)))
|
|
2549
|
+
return null;
|
|
2550
|
+
const narrowed = arrayPredicateNarrowedTypeOf(checker, checker.getTypeAtLocation(node.expression));
|
|
2551
|
+
if (narrowed === null)
|
|
2552
|
+
return null;
|
|
2553
|
+
const name = literalMemberNameOf(node);
|
|
2554
|
+
const property = name === null ? undefined : checker.getPropertyOfType(narrowed, name);
|
|
2555
|
+
if (property) {
|
|
2556
|
+
const member = checker.getTypeOfSymbolAtLocation(property, node);
|
|
2557
|
+
return isUnusableEvidence(member) ? null : member;
|
|
2558
|
+
}
|
|
2559
|
+
const key = node.argumentExpression;
|
|
2560
|
+
return key ? indexedTypeOf(checker, narrowed, checker.getTypeAtLocation(key), node) : null;
|
|
2561
|
+
};
|
|
2562
|
+
/**
|
|
2563
|
+
* The outcome of `x === undefined`, `x !== null`, `x == null` (either side
|
|
2564
|
+
* literal) when `read` types `x` as nothing but the nullish value(s) it is
|
|
2565
|
+
* compared with or against -- `null` when the operands do not decide it.
|
|
2566
|
+
*
|
|
2567
|
+
* `( gl[ p ] !== undefined ) ? gl[ p ] : null` (three's `WebGLUtils.convert`)
|
|
2568
|
+
* is the shape: once the element read is proved `undefined`, the arms are
|
|
2569
|
+
* `undefined` and `null`, which `joinOfWrites` rightly refuses to join -- but
|
|
2570
|
+
* the untaken arm contributes no value, so the conditional is its other arm.
|
|
2571
|
+
* The literal side is recognised by syntax and the standard `undefined`
|
|
2572
|
+
* binding's identity, never read, so a census's read of it cannot attribute a
|
|
2573
|
+
* refusal of its own.
|
|
2574
|
+
*/
|
|
2575
|
+
const decidedNullishEqualityOf = (checker, condition, read) => {
|
|
2576
|
+
let test = condition;
|
|
2577
|
+
while (ts.isParenthesizedExpression(test))
|
|
2578
|
+
test = test.expression;
|
|
2579
|
+
if (!ts.isBinaryExpression(test))
|
|
2580
|
+
return null;
|
|
2581
|
+
const operator = test.operatorToken.kind;
|
|
2582
|
+
const strict = operator === ts.SyntaxKind.EqualsEqualsEqualsToken || operator === ts.SyntaxKind.ExclamationEqualsEqualsToken;
|
|
2583
|
+
const loose = operator === ts.SyntaxKind.EqualsEqualsToken || operator === ts.SyntaxKind.ExclamationEqualsToken;
|
|
2584
|
+
if (!strict && !loose)
|
|
2585
|
+
return null;
|
|
2586
|
+
const literalNullish = (side) => {
|
|
2587
|
+
let value = side;
|
|
2588
|
+
while (ts.isParenthesizedExpression(value))
|
|
2589
|
+
value = value.expression;
|
|
2590
|
+
if (value.kind === ts.SyntaxKind.NullKeyword)
|
|
2591
|
+
return ts.TypeFlags.Null;
|
|
2592
|
+
if (ts.isVoidExpression(value))
|
|
2593
|
+
return ts.TypeFlags.Undefined;
|
|
2594
|
+
return ts.isIdentifier(value) && value.text === 'undefined' && isStandardGlobalValue(checker, value, 'undefined')
|
|
2595
|
+
? ts.TypeFlags.Undefined
|
|
2596
|
+
: null;
|
|
2597
|
+
};
|
|
2598
|
+
const left = literalNullish(test.left);
|
|
2599
|
+
const right = literalNullish(test.right);
|
|
2600
|
+
const literal = left ?? right;
|
|
2601
|
+
if (literal === null || (left !== null && right !== null))
|
|
2602
|
+
return null;
|
|
2603
|
+
const operand = read(left === null ? test.left : test.right);
|
|
2604
|
+
if (!operand)
|
|
2605
|
+
return null;
|
|
2606
|
+
const flags = operand.flags & NULLISH_FLAGS;
|
|
2607
|
+
if (flags === 0 || (operand.flags & ~NULLISH_FLAGS) !== 0)
|
|
2608
|
+
return null;
|
|
2609
|
+
// `==` treats `null` and `undefined` as equal; `===` only a match of each.
|
|
2610
|
+
const equal = loose ? true : flags === literal ? true : (flags & literal) === 0 ? false : null;
|
|
2611
|
+
if (equal === null)
|
|
2612
|
+
return null;
|
|
2613
|
+
const negated = operator === ts.SyntaxKind.ExclamationEqualsEqualsToken || operator === ts.SyntaxKind.ExclamationEqualsToken;
|
|
2614
|
+
return negated ? !equal : equal;
|
|
2615
|
+
};
|
|
2616
|
+
/**
|
|
2617
|
+
* `node`'s own type, derived from its immediate operands via `read`.
|
|
2618
|
+
*
|
|
2619
|
+
* Handles exactly the composite shapes described in this file's header
|
|
2620
|
+
* comment, and nothing else: this is a LEAF function, not a walker -- it
|
|
2621
|
+
* asks `read` for each operand's type and combines the answers by the
|
|
2622
|
+
* operator's own fixed rule, doing no traversal of its own beyond that one
|
|
2623
|
+
* level. Every caller supplies its own `read` (its `known ?? resolve`), so
|
|
2624
|
+
* this function's own recursion depth is always exactly one.
|
|
2625
|
+
*/
|
|
2626
|
+
export const derivedExpressionType = (checker, node, read) => {
|
|
2627
|
+
if (ts.isConditionalExpression(node)) {
|
|
2628
|
+
const whenTrue = read(node.whenTrue);
|
|
2629
|
+
const whenFalse = read(node.whenFalse);
|
|
2630
|
+
// A ternary's two arms are a two-element WRITE SET to one storage
|
|
2631
|
+
// location -- the value the expression evaluates to. `cond ? x : null`
|
|
2632
|
+
// is exactly the "sometimes empty, otherwise T" shape `joinOfWrites`
|
|
2633
|
+
// exists for (see its own header comment), and `cond ? 5126 : 5131`
|
|
2634
|
+
// is exactly the same literal-form-agreement shape. `widestOf` alone
|
|
2635
|
+
// answers FIRST and unchanged; only a pair it already refused reaches
|
|
2636
|
+
// `joinOfWrites`'s nullish-partition and literal-widening retries. Kept
|
|
2637
|
+
// to the ONE join this compiler owns rather than a second copy of its
|
|
2638
|
+
// reasoning.
|
|
2639
|
+
const joined = whenTrue && whenFalse ? joinOfWrites(checker, [whenTrue, whenFalse]) : null;
|
|
2640
|
+
if (joined)
|
|
2641
|
+
return joined;
|
|
2642
|
+
// Only a pair the join refused asks the condition: a census that attributes
|
|
2643
|
+
// a refusal to every unresolved operand must not see one more read here
|
|
2644
|
+
// than it did before on any conditional it already answered.
|
|
2645
|
+
const decided = decidedNullishEqualityOf(checker, node.condition, read);
|
|
2646
|
+
return decided === null ? null : decided ? whenTrue : whenFalse;
|
|
2647
|
+
}
|
|
2648
|
+
if (ts.isBinaryExpression(node))
|
|
2649
|
+
return binaryExpressionType(checker, node, read);
|
|
2650
|
+
if (ts.isTemplateExpression(node)) {
|
|
2651
|
+
// Defensive only -- see header comment. `ToString` is total, so the
|
|
2652
|
+
// checker already reports `string` here without this module's help in
|
|
2653
|
+
// every case observed; this exists so a genuine surprise refuses
|
|
2654
|
+
// instead of throwing.
|
|
2655
|
+
const own = checker.getTypeAtLocation(node);
|
|
2656
|
+
return isUnusableEvidence(own) ? null : own;
|
|
2657
|
+
}
|
|
2658
|
+
return null;
|
|
2659
|
+
};
|
|
2660
|
+
/** `null`/`undefined` and nothing else: a write stating the storage is EMPTY rather than naming a type for it. */
|
|
2661
|
+
const NULLISH_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined;
|
|
2662
|
+
export const isNullishType = (type) => (type.flags & NULLISH_FLAGS) !== 0 && (type.flags & ~NULLISH_FLAGS) === 0;
|
|
2663
|
+
/** `type` off its literal form, the same rule `widenLiteral` above states. */
|
|
2664
|
+
const widenLiteralForm = (checker, type) => hasLiteralForm(type) ? checker.getBaseTypeOfLiteralType(type) : type;
|
|
2665
|
+
/**
|
|
2666
|
+
* The join of a WRITE SET -- every value the program stores into one storage
|
|
2667
|
+
* location -- once the two things `widestOf` reads as a disagreement but
|
|
2668
|
+
* which are not one have been accounted for; or `null` when the writes
|
|
2669
|
+
* genuinely do not agree.
|
|
2670
|
+
*
|
|
2671
|
+
* `widestOf` answers FIRST and unchanged, so every location bound today binds
|
|
2672
|
+
* to exactly the same type; only a set it has already refused reaches the
|
|
2673
|
+
* rest of this.
|
|
2674
|
+
*
|
|
2675
|
+
* ## A nullish write states ABSENCE, not a rival type
|
|
2676
|
+
*
|
|
2677
|
+
* `widestOf` asks whether one OBSERVED type covers every other, and under
|
|
2678
|
+
* `strictNullChecks` no object type covers `null`. So the single commonest
|
|
2679
|
+
* shape a write-set census exists for -- `let currentProgram = null;` filled
|
|
2680
|
+
* in later with a real value and set back to `null` on teardown, or
|
|
2681
|
+
* `this.view = null` in a constructor and a real object in a setter --
|
|
2682
|
+
* refuses, attributed `writes-disagree`: the join read the location's own
|
|
2683
|
+
* emptiness as a type competing for the slot. It is not one. Storage that is
|
|
2684
|
+
* sometimes empty and otherwise holds `T` is exactly `T | null` -- the
|
|
2685
|
+
* `optional(T, null)` carrier this compiler already has, not a union of its
|
|
2686
|
+
* own making. Every constituent is still a type the program itself wrote.
|
|
2687
|
+
*
|
|
2688
|
+
* `getNullableType` is the checker's own constructor for that union, the
|
|
2689
|
+
* same one `return-bindings.ts` uses for a `Map.get` result and
|
|
2690
|
+
* `parameter-slot.ts` for a defaulted parameter, and it flattens -- so a
|
|
2691
|
+
* write already typed `T | null` re-joins to `T | null` rather than nesting.
|
|
2692
|
+
*
|
|
2693
|
+
* The observed nullish FLAGS are carried through rather than fixed: writes
|
|
2694
|
+
* holding `null` yield `T | null`, `undefined` yields `T | undefined`, both
|
|
2695
|
+
* yield both. Answering `null` where the program only ever wrote `undefined`
|
|
2696
|
+
* (or the reverse) would be inventing an inhabitant no census here may
|
|
2697
|
+
* invent. And a set that is ENTIRELY nullish is left to `widestOf`, which
|
|
2698
|
+
* already answers it on its own.
|
|
2699
|
+
*
|
|
2700
|
+
* ## A literal FORM is not a disagreement either
|
|
2701
|
+
*
|
|
2702
|
+
* The other shape is storage whose every write is a different LITERAL of one
|
|
2703
|
+
* primitive: `let type;` in three's `WebGLAttributes.js` is written `5126`,
|
|
2704
|
+
* `5131`, `5123`, `5122`, `5125`, `5124`, `5120`, `5121` -- eight writes,
|
|
2705
|
+
* eight literal types, none covering another, refused. The storage question
|
|
2706
|
+
* has an answer nobody has to invent: `number`. It is TypeScript's own
|
|
2707
|
+
* answer too -- a `let` initialized with `5126` is widened to `number` by
|
|
2708
|
+
* the language's mutable-binding rule, and the literal type survives here
|
|
2709
|
+
* only because these censuses read the RIGHT-HAND SIDE expression rather
|
|
2710
|
+
* than the declaration. `widenLiteral` above states the same rule for the
|
|
2711
|
+
* same reason (`"a" + "b"` is `string`, not the literal `"ab"` nobody
|
|
2712
|
+
* wrote). Tried only after the unwidened join has refused, so storage whose
|
|
2713
|
+
* writes DO agree on a literal keeps it.
|
|
2714
|
+
*
|
|
2715
|
+
* ## Why this is shared rather than copied
|
|
2716
|
+
*
|
|
2717
|
+
* `local-bindings.ts` (a `let`/`var` cell) and `field-bindings.ts` (a class
|
|
2718
|
+
* field) ask the identical question of two different declaration kinds, and
|
|
2719
|
+
* a field written `null` in the constructor and filled in later is the same
|
|
2720
|
+
* shape as the cell this landed for. Two copies of a join rule is the
|
|
2721
|
+
* "two authorities" defect this compiler keeps paying for -- the same
|
|
2722
|
+
* reasoning that made `annotationStatesNothing` shared. One rule, one file,
|
|
2723
|
+
* asked by both.
|
|
2724
|
+
*/
|
|
2725
|
+
export const joinOfWrites = (checker, types) => {
|
|
2726
|
+
const direct = widestOf(checker, types);
|
|
2727
|
+
if (direct)
|
|
2728
|
+
return direct;
|
|
2729
|
+
let observed = 0;
|
|
2730
|
+
const present = [];
|
|
2731
|
+
for (const type of types) {
|
|
2732
|
+
if (isNullishType(type))
|
|
2733
|
+
observed |= type.flags & NULLISH_FLAGS;
|
|
2734
|
+
else
|
|
2735
|
+
present.push(type);
|
|
2736
|
+
}
|
|
2737
|
+
if (present.length === 0)
|
|
2738
|
+
return null;
|
|
2739
|
+
const joined = (observed === 0 ? null : widestOf(checker, present)) ??
|
|
2740
|
+
widestOf(checker, present.map((part) => widenLiteralForm(checker, part)));
|
|
2741
|
+
if (!joined)
|
|
2742
|
+
return null;
|
|
2743
|
+
return observed === 0 ? joined : checker.getNullableType(joined, observed);
|
|
2744
|
+
};
|
|
2745
|
+
/**
|
|
2746
|
+
* A member with no evidence at all -- vetoes the whole set (see
|
|
2747
|
+
* `disjointArmsOf`). Deliberately wider than `isUnusableEvidence` above:
|
|
2748
|
+
* `unknown` is a genuine dynamic boundary here too, not merely a case that
|
|
2749
|
+
* other join rules leave alone.
|
|
2750
|
+
*
|
|
2751
|
+
* Exported for `parameter-bindings.ts`'s `restElementTypeAt`: a rest
|
|
2752
|
+
* parameter's own binding can be REASSIGNED in the body (`params =
|
|
2753
|
+
* enhanceLogMessage(params)`), and that write is evidence about the SAME
|
|
2754
|
+
* cell the call-site tail is joined into -- an `Array<any>`-returning
|
|
2755
|
+
* reassignment is exactly the "genuine dynamic boundary" this test already
|
|
2756
|
+
* names, not a second question needing its own copy of the flag set.
|
|
2757
|
+
*/
|
|
2758
|
+
export const carriesNoEvidence = (type) => (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Void | ts.TypeFlags.Never)) !== 0;
|
|
2759
|
+
/**
|
|
2760
|
+
* The DISTINCT, pairwise-disjoint members of `types` -- every one a real
|
|
2761
|
+
* type, none of them subsuming another -- or `null` when the set cannot
|
|
2762
|
+
* safely become one union.
|
|
2763
|
+
*
|
|
2764
|
+
* `joinOfWrites` above answers "is there ONE type every write agrees on".
|
|
2765
|
+
* This is the question that only gets asked once that has already failed:
|
|
2766
|
+
* is the DISAGREEMENT ITSELF the answer? `A | B | C`, where `A`, `B`, `C` are
|
|
2767
|
+
* exactly the types the program's own write sites used, is not a guess when
|
|
2768
|
+
* every one of them is real and none of them overlaps another -- it is
|
|
2769
|
+
* precisely what the program does. This compiler's `tagged-union`
|
|
2770
|
+
* representation (`representation/model.ts`) exists for it.
|
|
2771
|
+
*
|
|
2772
|
+
* Two members overlap when either is assignable to the other -- the SAME
|
|
2773
|
+
* `checker.isTypeAssignableTo` `widestOf`'s own `agree` reads a single
|
|
2774
|
+
* covering candidate from above, applied PAIRWISE instead of one-to-all.
|
|
2775
|
+
* Assignable in one direction only is a subsumption (a literal `5` inside
|
|
2776
|
+
* `number`, or one record type's fields a strict subset of another's) --
|
|
2777
|
+
* accepting it would let a value that is honestly a `B` also match arm `A`,
|
|
2778
|
+
* which is a mis-selection, not a conservative one, so the WHOLE set
|
|
2779
|
+
* refuses rather than dropping the overlapping member. Assignable both ways
|
|
2780
|
+
* is the same member spelled twice (two structurally-identical types, or
|
|
2781
|
+
* two references to one declaration) and collapses to one arm silently,
|
|
2782
|
+
* the same reduction `representation/union.ts`'s own `canonicalMembersOf`
|
|
2783
|
+
* performs one layer down. Neither direction is exactly ECMAScript's own
|
|
2784
|
+
* "no runtime value can satisfy both", which is the fact a tagged union's
|
|
2785
|
+
* arms need to be told apart at all -- see that file's own module comment.
|
|
2786
|
+
*
|
|
2787
|
+
* A member that states no evidence at all (`any`/`unknown`/`void`/`never`)
|
|
2788
|
+
* vetoes the whole set outright: unioning a real type with "nothing was
|
|
2789
|
+
* declared" would launder the missing statement as if it were one more arm,
|
|
2790
|
+
* and `void`/`never` are refusals about the STORAGE, not values competing
|
|
2791
|
+
* for the slot.
|
|
2792
|
+
*
|
|
2793
|
+
* Cardinality is not missing evidence. Preserve every admitted member of a
|
|
2794
|
+
* finite write set; representation planning owns its storage and dispatch
|
|
2795
|
+
* costs. An earlier four-arm cutoff erased complete five-way inputs and
|
|
2796
|
+
* made downstream forwarding indistinguishable from an unknown caller.
|
|
2797
|
+
*/
|
|
2798
|
+
export const disjointArmsOf = (checker, types) => {
|
|
2799
|
+
const arms = [];
|
|
2800
|
+
for (const type of types) {
|
|
2801
|
+
if (carriesNoEvidence(type))
|
|
2802
|
+
return null;
|
|
2803
|
+
let duplicate = false;
|
|
2804
|
+
for (const existing of arms) {
|
|
2805
|
+
// Two different classes' constructor objects are disjoint by identity,
|
|
2806
|
+
// whatever the checker says of their shapes -- `widestOf`'s rule.
|
|
2807
|
+
if (isDistinctClassConstructorPair(type, existing))
|
|
2808
|
+
continue;
|
|
2809
|
+
const forward = checker.isTypeAssignableTo(type, existing);
|
|
2810
|
+
const backward = checker.isTypeAssignableTo(existing, type);
|
|
2811
|
+
if (forward && backward) {
|
|
2812
|
+
duplicate = true;
|
|
2813
|
+
break;
|
|
2814
|
+
}
|
|
2815
|
+
if (forward || backward)
|
|
2816
|
+
return null; // one member subsumes the other: not disjoint
|
|
2817
|
+
}
|
|
2818
|
+
if (!duplicate)
|
|
2819
|
+
arms.push(type);
|
|
2820
|
+
}
|
|
2821
|
+
return arms.length >= 2 ? arms : null;
|
|
2822
|
+
};
|
|
2823
|
+
/**
|
|
2824
|
+
* The full member list for a SYNTHESIZED union carrier, once `joinOfWrites`
|
|
2825
|
+
* has already refused -- every present (non-nullish) write partitioned the
|
|
2826
|
+
* identical way `joinOfWrites` partitions them, checked for disjointness via
|
|
2827
|
+
* `disjointArmsOf`, with the observed absence values (`null`/`undefined`)
|
|
2828
|
+
* carried through UNCHANGED as members rather than folded into a flag here.
|
|
2829
|
+
*
|
|
2830
|
+
* That last point is deliberate, not an oversight: this function answers a
|
|
2831
|
+
* STRUCTURAL question ("what are this union's members"), not a `ts.Type`
|
|
2832
|
+
* one -- `ts.TypeChecker.getUnionType` is not on the public surface (see
|
|
2833
|
+
* `absent-globals.ts` and `producers/bindings.ts`'s own comments on the same
|
|
2834
|
+
* wall), so unlike `joinOfWrites` this cannot hand back one `ts.Type` a
|
|
2835
|
+
* caller re-derives structurally. It hands back the member LIST instead, for
|
|
2836
|
+
* a caller that is about to `table.intern({ kind: 'union', members:
|
|
2837
|
+
* list.map(typeOf) })` directly -- and `representation/union.ts`'s own
|
|
2838
|
+
* `deriveUnion` already partitions absent members from present ones and
|
|
2839
|
+
* wraps the result in `optional` exactly as `joinOfWrites` would have, so
|
|
2840
|
+
* passing the nullish members through as ordinary list entries here, rather
|
|
2841
|
+
* than pre-collapsing them, is asking that ONE authority to do it once
|
|
2842
|
+
* instead of a second copy of the same partition living here too.
|
|
2843
|
+
*/
|
|
2844
|
+
export const disjointUnionMembersOf = (checker, types) => {
|
|
2845
|
+
const present = [];
|
|
2846
|
+
const nullish = [];
|
|
2847
|
+
const atomsOf = (type) => (type.isUnion() ? type.types.flatMap(atomsOf) : [type]);
|
|
2848
|
+
for (const type of types)
|
|
2849
|
+
for (const atom of atomsOf(type)) {
|
|
2850
|
+
const widened = widenLiteralForm(checker, atom);
|
|
2851
|
+
if (isNullishType(widened)) {
|
|
2852
|
+
if (!nullish.some((seen) => seen.flags === widened.flags))
|
|
2853
|
+
nullish.push(widened);
|
|
2854
|
+
}
|
|
2855
|
+
else
|
|
2856
|
+
present.push(widened);
|
|
2857
|
+
}
|
|
2858
|
+
const arms = disjointArmsOf(checker, present);
|
|
2859
|
+
return arms ? [...arms, ...nullish] : null;
|
|
2860
|
+
};
|
|
2861
|
+
/**
|
|
2862
|
+
* `disjointUnionMembersOf`, answered as ONE `ts.Type` -- for a census whose
|
|
2863
|
+
* contract is a `ts.Type` and whose every consumer reads one.
|
|
2864
|
+
*
|
|
2865
|
+
* `collection-bindings.ts`'s array-element census is that census: every
|
|
2866
|
+
* sibling write-set census (a `let` cell, a class field, a parameter, a
|
|
2867
|
+
* return) already answers a disagreeing write set as a disjoint tagged union
|
|
2868
|
+
* through its own `unionArmsAt` channel, while the array census asked
|
|
2869
|
+
* `joinOfWrites` alone and REFUSED the same disagreement
|
|
2870
|
+
* (`array:elements-disagree`). `var xs = []; xs.push(1); xs.push('a')` is the
|
|
2871
|
+
* shape: the array's storage fell to the box, and every `push` boxed its
|
|
2872
|
+
* argument on the way in, for a program that declared nothing dynamic.
|
|
2873
|
+
*
|
|
2874
|
+
* Two things make the `ts.Type` form the right one here rather than a fourth
|
|
2875
|
+
* arms channel. The census's array answers travel through `arrayElementAt`/
|
|
2876
|
+
* `arrayElementForRead`/`arrayElementForOwner` into three other censuses that
|
|
2877
|
+
* read a `ts.Type` (`local-bindings.ts`, `return-bindings.ts`,
|
|
2878
|
+
* `field-bindings.ts`), so a list would leave every one of them answering the
|
|
2879
|
+
* checker's `any` for a union the storage already carries. And the checker's
|
|
2880
|
+
* OWN evolving-array machinery settles the SAME array to the SAME union at
|
|
2881
|
+
* every reference it can finalize (`xs.join(...)` after the pushes is `(string
|
|
2882
|
+
* | number)[]` there): TypeScript widens each pushed value off its literal form
|
|
2883
|
+
* (`addEvolvingArrayElementType`, `getBaseTypeOfLiteralType`) and unions the
|
|
2884
|
+
* results. Building the union through the checker from the identically
|
|
2885
|
+
* widened members yields the IDENTICAL `ts.Type` object (verified: `built ===
|
|
2886
|
+
* settled` for `[number, string, boolean]`), so the storage the census types,
|
|
2887
|
+
* the reads the checker settled, and the `push` slots
|
|
2888
|
+
* (`structural.ts`'s `evolvingArrayMemberTypeAt`) intern to one structural
|
|
2889
|
+
* type with no conversion between them. An arms list interned separately
|
|
2890
|
+
* could not promise that.
|
|
2891
|
+
*
|
|
2892
|
+
* Widening is therefore part of the rule, not a convenience: unwidened, `1`
|
|
2893
|
+
* and `"a"` are two disjoint LITERAL arms, and a storage of `1 | "a"` would be
|
|
2894
|
+
* a second authority beside the checker's `string | number` at every settled
|
|
2895
|
+
* read. `disjointUnionMembersOf`'s own soundness test (no member subsumes
|
|
2896
|
+
* another) is applied unchanged to the
|
|
2897
|
+
* widened members, so a set the cell census would refuse is refused here too.
|
|
2898
|
+
*
|
|
2899
|
+
* Built through the checker's internal `getUnionType`, the same guarded reach
|
|
2900
|
+
* `withoutUndefinedMember` above and `field-bindings.ts`'s `withNullish` make;
|
|
2901
|
+
* a checker without it answers `null`, which is the refusal the caller already
|
|
2902
|
+
* handles.
|
|
2903
|
+
*/
|
|
2904
|
+
export const disjointUnionTypeOf = (checker, types) => {
|
|
2905
|
+
const members = disjointUnionMembersOf(checker, types.map((type) => widenLiteralForm(checker, type)));
|
|
2906
|
+
if (!members)
|
|
2907
|
+
return null;
|
|
2908
|
+
const constructing = checker;
|
|
2909
|
+
return typeof constructing.getUnionType === 'function' ? constructing.getUnionType(members) : null;
|
|
2910
|
+
};
|
|
2911
|
+
// -----------------------------------------------------------------------
|
|
2912
|
+
// CALLABLE VALUE FLOW -- moved here from a would-be sibling module because
|
|
2913
|
+
// `normalize/` sits at the architecture gate's directory-file cap. This
|
|
2914
|
+
// section answers `parameter-bindings.ts`'s one question about a reference
|
|
2915
|
+
// to a named function -- "what value does it stand for" -- plus the two
|
|
2916
|
+
// symbol-anchored ALIAS evidence indexes (`indexNamedCallables`,
|
|
2917
|
+
// `indexAliasEvidence`) that let a call through a resolved member or a
|
|
2918
|
+
// selector's result count as a real call site of the function it names.
|
|
2919
|
+
// -----------------------------------------------------------------------
|
|
2920
|
+
/**
|
|
2921
|
+
* The parts of `parameter-bindings.ts` that answer one question --
|
|
2922
|
+
* "what value does this reference of a named function's identifier stand
|
|
2923
|
+
* for" -- without touching that module's own fixpoint state. Moved here so
|
|
2924
|
+
* `parameter-bindings.ts` has room, under the architecture gate, for the
|
|
2925
|
+
* value-flow evidence this module also builds: `indexNamedCallables` and
|
|
2926
|
+
* `indexAliasEvidence`, below.
|
|
2927
|
+
*/
|
|
2928
|
+
/** Whether this declaration is one of the five function-like shapes the parameter-binding census tracks. */
|
|
2929
|
+
export const isTrackedCallable = (node) => ts.isFunctionDeclaration(node) ||
|
|
2930
|
+
ts.isFunctionExpression(node) ||
|
|
2931
|
+
ts.isMethodDeclaration(node) ||
|
|
2932
|
+
ts.isArrowFunction(node) ||
|
|
2933
|
+
ts.isConstructorDeclaration(node);
|
|
2934
|
+
/**
|
|
2935
|
+
* The real callee and the arguments that actually reach it, once an explicit-
|
|
2936
|
+
* `this` wrapper -- `.call( thisArg, ...args )` or `.apply( thisArg, [ ...
|
|
2937
|
+
* args ] )` -- is unwrapped, or `null` when this call is neither.
|
|
2938
|
+
*
|
|
2939
|
+
* `EventDispatcher.dispatchEvent` calls every registered listener as
|
|
2940
|
+
* `array[ i ].call( this, event )`: a call to `array[ i ]`, with ONE argument,
|
|
2941
|
+
* not a call to `Function.prototype.call` with two. Left wrapped, `checker.
|
|
2942
|
+
* getResolvedSignature` resolves `.call`'s own (unrelated) ambient signature
|
|
2943
|
+
* successfully -- it is not `any`, so it never fell through to the fallback
|
|
2944
|
+
* that would have resolved the real callee -- and the call vanished from
|
|
2945
|
+
* every candidate's evidence without a trace: not refused, simply never
|
|
2946
|
+
* counted. `collect` skips recording checker attribution for these and
|
|
2947
|
+
* `attributeCalls`/`agreedArgumentType` read this map instead of the raw
|
|
2948
|
+
* node, so the SAME machinery that already resolves an ordinary call site's
|
|
2949
|
+
* callee and arguments does the resolving here too, just against `callee`
|
|
2950
|
+
* and `args` rather than `call.expression` and `call.arguments`.
|
|
2951
|
+
*
|
|
2952
|
+
* `.apply` is unwrapped only when its argument list is a literal array with
|
|
2953
|
+
* no spread (`.apply( this, [ a, b ] )`) -- anything else (a spread, a
|
|
2954
|
+
* variable holding the array) stays wrapped rather than guessed at, exactly
|
|
2955
|
+
* the same "refuse rather than invent" rule this module uses everywhere
|
|
2956
|
+
* else.
|
|
2957
|
+
*
|
|
2958
|
+
* The RECEIVER -- `call.expression.expression`, `array[ i ]` above -- must
|
|
2959
|
+
* itself be a FUNCTION VALUE before any of this fires. `.call`/`.apply` are
|
|
2960
|
+
* ordinary member names, and a program is free to declare its own object with
|
|
2961
|
+
* a `.call( ... )` or `.apply( ... )` method (a Command/Strategy pattern, a
|
|
2962
|
+
* Callable-shaped API): unwrapping THAT receiver would silently replace its
|
|
2963
|
+
* real callee and arguments with wrong ones, from a name match with no
|
|
2964
|
+
* relationship to `Function.prototype.call`/`.apply` at all. The receiver's
|
|
2965
|
+
* own static type having at least one CALL SIGNATURE is what makes it
|
|
2966
|
+
* actually a function value -- the same fact `parameter-bindings.ts`'s
|
|
2967
|
+
* `isUnannotated` reads off a `Function`-typed parameter, asked here of the
|
|
2968
|
+
* `.call`/`.apply` receiver instead.
|
|
2969
|
+
*/
|
|
2970
|
+
export const unwrapExplicitThisCall = (checker, call) => {
|
|
2971
|
+
if (!ts.isPropertyAccessExpression(call.expression))
|
|
2972
|
+
return null;
|
|
2973
|
+
const name = call.expression.name.text;
|
|
2974
|
+
if (name !== 'call' && name !== 'apply')
|
|
2975
|
+
return null;
|
|
2976
|
+
const member = checker.getSymbolAtLocation(call.expression.name);
|
|
2977
|
+
if (!member?.declarations?.length || !member.declarations.every((declaration) => declaration.getSourceFile().hasNoDefaultLib))
|
|
2978
|
+
return null;
|
|
2979
|
+
const receiver = call.expression.expression;
|
|
2980
|
+
if (checker.getSignaturesOfType(checker.getTypeAtLocation(receiver), ts.SignatureKind.Call).length === 0)
|
|
2981
|
+
return null;
|
|
2982
|
+
if (name === 'call')
|
|
2983
|
+
return { callee: receiver, receiver: call.arguments[0] ?? null, args: call.arguments.slice(1) };
|
|
2984
|
+
if (call.arguments.length === 2) {
|
|
2985
|
+
const argumentsArray = call.arguments[1];
|
|
2986
|
+
if (argumentsArray &&
|
|
2987
|
+
ts.isArrayLiteralExpression(argumentsArray) &&
|
|
2988
|
+
argumentsArray.elements.every((element) => !ts.isSpreadElement(element))) {
|
|
2989
|
+
return { callee: receiver, receiver: call.arguments[0] ?? null, args: argumentsArray.elements };
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
return null;
|
|
2993
|
+
};
|
|
2994
|
+
const staticInvocationKeyOf = (expression) => {
|
|
2995
|
+
if (ts.isPropertyAccessExpression(expression))
|
|
2996
|
+
return expression.name.text;
|
|
2997
|
+
if (!ts.isElementAccessExpression(expression) || !expression.argumentExpression)
|
|
2998
|
+
return null;
|
|
2999
|
+
const argument = unwrapErasedExpression(expression.argumentExpression);
|
|
3000
|
+
if (ts.isStringLiteralLike(argument) || ts.isNoSubstitutionTemplateLiteral(argument))
|
|
3001
|
+
return argument.text;
|
|
3002
|
+
if (ts.isNumericLiteral(argument)) {
|
|
3003
|
+
const key = String(Number(argument.text));
|
|
3004
|
+
return key === argument.text ? key : null;
|
|
3005
|
+
}
|
|
3006
|
+
return null;
|
|
3007
|
+
};
|
|
3008
|
+
const isWithin = (node, root) => {
|
|
3009
|
+
for (let current = node; current; current = current.parent)
|
|
3010
|
+
if (current === root)
|
|
3011
|
+
return true;
|
|
3012
|
+
return false;
|
|
3013
|
+
};
|
|
3014
|
+
const staticHomeObjectOf = (member) => (ts.getCombinedModifierFlags(member) & ts.ModifierFlags.Static) !== 0;
|
|
3015
|
+
const isInsideCallableExecution = (expression, callable) => isWithin(expression, callable.body) || callable.parameters.some((parameter) => isWithin(expression, parameter));
|
|
3016
|
+
const lexicalSuperHomeOf = (expression) => {
|
|
3017
|
+
for (let current = expression.parent; current; current = current.parent) {
|
|
3018
|
+
if (ts.isArrowFunction(current))
|
|
3019
|
+
continue;
|
|
3020
|
+
if (ts.isMethodDeclaration(current) || ts.isGetAccessorDeclaration(current) || ts.isSetAccessorDeclaration(current)) {
|
|
3021
|
+
const owner = current.parent;
|
|
3022
|
+
if ((ts.isClassDeclaration(owner) || ts.isClassExpression(owner)) && isInsideCallableExecution(expression, current))
|
|
3023
|
+
return { home: owner, static: staticHomeObjectOf(current) };
|
|
3024
|
+
if (ts.isObjectLiteralExpression(owner) && isInsideCallableExecution(expression, current))
|
|
3025
|
+
return { home: owner, static: false };
|
|
3026
|
+
// Computed names are evaluated in the enclosing context, not with this
|
|
3027
|
+
// member's home object. Keep walking so an enclosing method can own it.
|
|
3028
|
+
continue;
|
|
3029
|
+
}
|
|
3030
|
+
if (ts.isConstructorDeclaration(current)) {
|
|
3031
|
+
const owner = current.parent;
|
|
3032
|
+
return ts.isClassDeclaration(owner) || ts.isClassExpression(owner) ? { home: owner, static: false } : { home: null, static: false };
|
|
3033
|
+
}
|
|
3034
|
+
if (ts.isPropertyDeclaration(current)) {
|
|
3035
|
+
const owner = current.parent;
|
|
3036
|
+
if ((ts.isClassDeclaration(owner) || ts.isClassExpression(owner)) && isWithin(expression, current.initializer))
|
|
3037
|
+
return { home: owner, static: staticHomeObjectOf(current) };
|
|
3038
|
+
continue;
|
|
3039
|
+
}
|
|
3040
|
+
if (ts.isClassStaticBlockDeclaration(current)) {
|
|
3041
|
+
const owner = current.parent;
|
|
3042
|
+
return ts.isClassDeclaration(owner) || ts.isClassExpression(owner) ? { home: owner, static: true } : { home: null, static: false };
|
|
3043
|
+
}
|
|
3044
|
+
if (ts.isFunctionLike(current))
|
|
3045
|
+
return { home: null, static: false };
|
|
3046
|
+
}
|
|
3047
|
+
return { home: null, static: false };
|
|
3048
|
+
};
|
|
3049
|
+
const superConstructorHomeOf = (call) => {
|
|
3050
|
+
for (let current = call.parent; current; current = current.parent) {
|
|
3051
|
+
if (ts.isArrowFunction(current))
|
|
3052
|
+
continue;
|
|
3053
|
+
if (ts.isConstructorDeclaration(current)) {
|
|
3054
|
+
const home = current.parent;
|
|
3055
|
+
return isWithin(call, current.body) && (ts.isClassDeclaration(home) || ts.isClassExpression(home)) ? home : null;
|
|
3056
|
+
}
|
|
3057
|
+
if (ts.isFunctionLike(current))
|
|
3058
|
+
return null;
|
|
3059
|
+
}
|
|
3060
|
+
return null;
|
|
3061
|
+
};
|
|
3062
|
+
/** The actual operand frame of a source call, with authenticated `.call`/`.apply` wrappers erased.
|
|
3063
|
+
*
|
|
3064
|
+
* `pendingExplicitThisAt` is the round-over-round counterpart of
|
|
3065
|
+
* `unwrapExplicitThisCall`'s own static gate: that gate needs the receiver's
|
|
3066
|
+
* CHECKER type to already carry a call signature, which an untyped JS array
|
|
3067
|
+
* element never does -- `EventDispatcher.dispatchEvent`'s `array[ i ].call(
|
|
3068
|
+
* this, event )` types `array[ i ]` as `any` even with a `@param {Function}`
|
|
3069
|
+
* JSDoc tag on the pushing `addEventListener`, because the field the array
|
|
3070
|
+
* lives on (`this._listeners`) itself carries no type. Unwrapping THAT
|
|
3071
|
+
* receiver unconditionally would be unsound (a program's own `.call`-shaped
|
|
3072
|
+
* Command/Strategy object would be silently misread as `Function.prototype.
|
|
3073
|
+
* call`), so the census this resolver comes from proves the array's whole
|
|
3074
|
+
* write history is CLOSED to real function values first (`callableArrayTargetsOf`
|
|
3075
|
+
* in `parameter-bindings.ts`, the identical flow proof `callable-reach.ts`'s
|
|
3076
|
+
* own target resolution trusts) and supplies a reading only once that holds --
|
|
3077
|
+
* never guessed, and absent (round one, and every caller with no census yet)
|
|
3078
|
+
* this stays byte-identical to the always-unresolved answer it replaced. */
|
|
3079
|
+
export const invocationOperandsOf = (checker, call, pendingExplicitThisAt) => {
|
|
3080
|
+
const explicitThis = ts.isCallExpression(call) ? (unwrapExplicitThisCall(checker, call) ?? pendingExplicitThisAt?.(call) ?? null) : null;
|
|
3081
|
+
const callee = unwrapErasedExpression(explicitThis?.callee ?? call.expression);
|
|
3082
|
+
const kind = ts.isNewExpression(call)
|
|
3083
|
+
? 'construct'
|
|
3084
|
+
: callee.kind === ts.SyntaxKind.SuperKeyword
|
|
3085
|
+
? 'super'
|
|
3086
|
+
: 'call';
|
|
3087
|
+
const receiver = explicitThis
|
|
3088
|
+
? explicitThis.receiver
|
|
3089
|
+
: kind === 'call' && (ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee))
|
|
3090
|
+
? callee.expression
|
|
3091
|
+
: null;
|
|
3092
|
+
let dispatch = { kind: 'direct' };
|
|
3093
|
+
if (kind === 'super') {
|
|
3094
|
+
dispatch = { kind: 'super-constructor', home: superConstructorHomeOf(call) };
|
|
3095
|
+
}
|
|
3096
|
+
else if ((ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) &&
|
|
3097
|
+
callee.expression.kind === ts.SyntaxKind.SuperKeyword) {
|
|
3098
|
+
dispatch = { kind: 'lexical-super', ...lexicalSuperHomeOf(callee), key: staticInvocationKeyOf(callee) };
|
|
3099
|
+
}
|
|
3100
|
+
else if (ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) {
|
|
3101
|
+
dispatch = { kind: 'member', lookup: callee.expression, key: staticInvocationKeyOf(callee) };
|
|
3102
|
+
}
|
|
3103
|
+
return {
|
|
3104
|
+
kind,
|
|
3105
|
+
dispatch,
|
|
3106
|
+
explicitThis: explicitThis !== null,
|
|
3107
|
+
callee,
|
|
3108
|
+
receiver,
|
|
3109
|
+
args: explicitThis?.args ?? call.arguments ?? []
|
|
3110
|
+
};
|
|
3111
|
+
};
|
|
3112
|
+
/** Whether an identifier stands in the callee position of this call. */
|
|
3113
|
+
export const isCalleeOf = (node, call) => (ts.isCallExpression(call) || ts.isNewExpression(call)) && call.expression === node;
|
|
3114
|
+
/**
|
|
3115
|
+
* The symbol that names this function's value, or `null` when nothing does.
|
|
3116
|
+
*
|
|
3117
|
+
* A function is enumerable exactly when the program refers to it by a name,
|
|
3118
|
+
* because a name is what a reference can be counted against. Three forms of
|
|
3119
|
+
* name exist and all three are ordinary: a declaration's own (`function f(){}`),
|
|
3120
|
+
* the variable a function expression initializes (`const f = function(){}`),
|
|
3121
|
+
* and the member a method declares (`{ f() {} }`, `class { f() {} }`). A
|
|
3122
|
+
* function expression written directly as an argument has no name at all, and
|
|
3123
|
+
* refusing it is right: its only caller is the function it was passed to, which
|
|
3124
|
+
* is precisely the caller this census cannot enumerate.
|
|
3125
|
+
*/
|
|
3126
|
+
export const nameOfCallable = (declaration) => {
|
|
3127
|
+
if (ts.isFunctionDeclaration(declaration) || ts.isFunctionExpression(declaration)) {
|
|
3128
|
+
if (declaration.name)
|
|
3129
|
+
return declaration.name;
|
|
3130
|
+
}
|
|
3131
|
+
// A private name is a name: `static #m(x) {}` is reached through `this.#m`
|
|
3132
|
+
// and nothing else, and the checker resolves that reference to the same
|
|
3133
|
+
// symbol an identifier's would. Left out, every private method was
|
|
3134
|
+
// `unnamed`, and every one of its parameters dynamic.
|
|
3135
|
+
if (ts.isMethodDeclaration(declaration) && (ts.isIdentifier(declaration.name) || ts.isPrivateIdentifier(declaration.name))) {
|
|
3136
|
+
return declaration.name;
|
|
3137
|
+
}
|
|
3138
|
+
// A constructor is named by its class: `new WebGLRenderer( ... )` reaches it
|
|
3139
|
+
// through that name and no other.
|
|
3140
|
+
if (ts.isConstructorDeclaration(declaration)) {
|
|
3141
|
+
const owner = declaration.parent;
|
|
3142
|
+
if (ts.isClassDeclaration(owner) && owner.name)
|
|
3143
|
+
return owner.name;
|
|
3144
|
+
}
|
|
3145
|
+
const parent = declaration.parent;
|
|
3146
|
+
if (parent && ts.isVariableDeclaration(parent) && parent.initializer === declaration && ts.isIdentifier(parent.name)) {
|
|
3147
|
+
return parent.name;
|
|
3148
|
+
}
|
|
3149
|
+
if (parent && ts.isPropertyAssignment(parent) && parent.initializer === declaration && ts.isIdentifier(parent.name)) {
|
|
3150
|
+
return parent.name;
|
|
3151
|
+
}
|
|
3152
|
+
// `this.render = function ( scene, camera ) { ... }` and `_this.shadowMap =
|
|
3153
|
+
// shadowMap` are how `WebGLRenderer` declares half its surface. TypeScript's
|
|
3154
|
+
// JavaScript inference does not read that idiom as a declaration, but the
|
|
3155
|
+
// member being assigned is still a NAME -- the same kind of name a method
|
|
3156
|
+
// declaration is -- and a name is all this needs to count references against.
|
|
3157
|
+
if (parent && ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && parent.right === declaration) {
|
|
3158
|
+
if (ts.isPropertyAccessExpression(parent.left) && ts.isIdentifier(parent.left.name))
|
|
3159
|
+
return parent.left.name;
|
|
3160
|
+
if (ts.isIdentifier(parent.left))
|
|
3161
|
+
return parent.left;
|
|
3162
|
+
}
|
|
3163
|
+
return null;
|
|
3164
|
+
};
|
|
3165
|
+
/**
|
|
3166
|
+
* Whether a reference to a function's name is one that keeps it enumerable.
|
|
3167
|
+
*
|
|
3168
|
+
* An import or export specifier re-binds the name in another module's scope
|
|
3169
|
+
* without letting anything hold the function as a value, and a declaration's
|
|
3170
|
+
* own name is not a reference to it. A shorthand property in a returned object
|
|
3171
|
+
* (`return { has, init, get }`) is the idiom three's factories are built on:
|
|
3172
|
+
* the function is reachable only as a member of that record, so every call to
|
|
3173
|
+
* it is a member call this census resolves once the record has a type -- which
|
|
3174
|
+
* is what the fixpoint is for.
|
|
3175
|
+
*
|
|
3176
|
+
* Two more shapes are binding-only for the identical reason, one level of
|
|
3177
|
+
* indirection removed -- see `indexAliasEvidence` for the evidence that makes
|
|
3178
|
+
* each of them SOUND rather than merely silent:
|
|
3179
|
+
*
|
|
3180
|
+
* - a bare `return f;`: the SELECTOR-RETURN idiom (`getSingularSetter(type)`
|
|
3181
|
+
* returning one of several named setters from a switch). The function is
|
|
3182
|
+
* reachable only by calling whatever the selector returns, and
|
|
3183
|
+
* `indexAliasEvidence.returnedFrom` records exactly which functions a given
|
|
3184
|
+
* selector declaration can return, so a call through its result becomes
|
|
3185
|
+
* real evidence for every one of them.
|
|
3186
|
+
* - `<propertyAccess> = f`: the MEMBER-PUBLICATION idiom (`this.setValue =
|
|
3187
|
+
* setValueV3f`), the same idea `nameOfCallable` already handles for a
|
|
3188
|
+
* function declared directly in that position, extended to a function
|
|
3189
|
+
* merely REFERENCED there. `indexAliasEvidence.publishedUnderMember` keys
|
|
3190
|
+
* on the checker's own symbol for the member, never its spelling, so a
|
|
3191
|
+
* later call through an unrelated member that happens to share a name
|
|
3192
|
+
* never matches.
|
|
3193
|
+
*
|
|
3194
|
+
* A fourth shape needs no alias evidence at all, because it carries no value
|
|
3195
|
+
* flow to begin with: a class named in a HERITAGE CLAUSE (`class Sub extends
|
|
3196
|
+
* Base`, `class Sub implements Base`). `ExpressionWithTypeArguments` is the
|
|
3197
|
+
* node TypeScript uses for exactly these two positions and nowhere else, so
|
|
3198
|
+
* a reference whose direct parent is one names the class being extended or
|
|
3199
|
+
* implemented -- it does not hand the class's constructor to anything. The
|
|
3200
|
+
* two ways a heritage mention could matter for a constructor's own
|
|
3201
|
+
* enumerability are both already real `NewExpression`/`CallExpression` nodes
|
|
3202
|
+
* this census's own `attributeCalls` visits and attributes through
|
|
3203
|
+
* `checker.getResolvedSignature`, independent of the heritage reference
|
|
3204
|
+
* itself: a `super(...)` call in a declared derived constructor resolves
|
|
3205
|
+
* straight to the base constructor's declaration, and `new Derived(...)`
|
|
3206
|
+
* where `Derived` declares no constructor of its own resolves to that same
|
|
3207
|
+
* base declaration too (TypeScript gives the derived class the base's own
|
|
3208
|
+
* construct signature when it has none -- measured directly against this
|
|
3209
|
+
* compiler's own checker: `new Derived(...)`'s resolved signature IS the
|
|
3210
|
+
* base constructor node, and a declared `super(...)` resolves to it too).
|
|
3211
|
+
* Counting the heritage mention as a further escape does not add evidence
|
|
3212
|
+
* the walk lacks -- it double-refuses a constructor whose real callers are
|
|
3213
|
+
* already enumerated elsewhere.
|
|
3214
|
+
*
|
|
3215
|
+
* Everything else -- an argument, a bare variable initializer, a return of
|
|
3216
|
+
* the bare name wrapped in a way that isn't a plain `ReturnStatement` -- hands
|
|
3217
|
+
* the function to a caller this census cannot see, and stays an escape.
|
|
3218
|
+
*/
|
|
3219
|
+
export const isBindingOnlyReference = (node) => {
|
|
3220
|
+
const parent = node.parent;
|
|
3221
|
+
if (!parent)
|
|
3222
|
+
return false;
|
|
3223
|
+
if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent) || ts.isImportClause(parent))
|
|
3224
|
+
return true;
|
|
3225
|
+
if (ts.isShorthandPropertyAssignment(parent) || ts.isPropertyAssignment(parent))
|
|
3226
|
+
return true;
|
|
3227
|
+
if (ts.isVariableDeclaration(parent) && parent.name === node)
|
|
3228
|
+
return true;
|
|
3229
|
+
if (ts.isMethodDeclaration(parent) && parent.name === node)
|
|
3230
|
+
return true;
|
|
3231
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === node)
|
|
3232
|
+
return true;
|
|
3233
|
+
if (ts.isReturnStatement(parent) && parent.expression === node)
|
|
3234
|
+
return true;
|
|
3235
|
+
if (ts.isExpressionWithTypeArguments(parent) && parent.expression === node)
|
|
3236
|
+
return true;
|
|
3237
|
+
if (ts.isBinaryExpression(parent) &&
|
|
3238
|
+
parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
3239
|
+
parent.right === node &&
|
|
3240
|
+
ts.isPropertyAccessExpression(parent.left)) {
|
|
3241
|
+
return true;
|
|
3242
|
+
}
|
|
3243
|
+
return ts.isFunctionDeclaration(parent) || ts.isFunctionExpression(parent) || ts.isClassDeclaration(parent) ? parent.name === node : false;
|
|
3244
|
+
};
|
|
3245
|
+
/**
|
|
3246
|
+
* Every tracked callable declaration in the program, keyed by the symbol
|
|
3247
|
+
* `nameOfCallable` resolves for it -- built once, over EVERY declaration
|
|
3248
|
+
* (not only the census's own unannotated-parameter candidates), because a
|
|
3249
|
+
* selector can return, or a constructor can publish, a callable whose own
|
|
3250
|
+
* parameters are fully typed and irrelevant to this census except as the
|
|
3251
|
+
* far end of an alias chain leading to one that is not.
|
|
3252
|
+
*/
|
|
3253
|
+
export const indexNamedCallables = (checker, files, reachable) => {
|
|
3254
|
+
const bySymbol = new Map();
|
|
3255
|
+
const visit = (node) => {
|
|
3256
|
+
if (isTrackedCallable(node)) {
|
|
3257
|
+
const name = nameOfCallable(node);
|
|
3258
|
+
const symbol = name && checker.getSymbolAtLocation(name);
|
|
3259
|
+
if (symbol && !bySymbol.has(symbol))
|
|
3260
|
+
bySymbol.set(symbol, node);
|
|
3261
|
+
}
|
|
3262
|
+
ts.forEachChild(node, visit);
|
|
3263
|
+
};
|
|
3264
|
+
for (const file of files)
|
|
3265
|
+
forEachReachableStatement(reachable, file, visit);
|
|
3266
|
+
return bySymbol;
|
|
3267
|
+
};
|
|
3268
|
+
export const emptyAliasEvidence = { publishedUnderMember: new Map(), returnedFrom: new Map() };
|
|
3269
|
+
const EMPTY_CALLABLES = new Set();
|
|
3270
|
+
export const indexAliasEvidence = (checker, files, reachable, namedCallables) => {
|
|
3271
|
+
const publishedUnderMember = new Map();
|
|
3272
|
+
const returnedFrom = new Map();
|
|
3273
|
+
const record = (map, key, values) => {
|
|
3274
|
+
if (values.size === 0)
|
|
3275
|
+
return;
|
|
3276
|
+
const existing = map.get(key);
|
|
3277
|
+
if (existing)
|
|
3278
|
+
for (const value of values)
|
|
3279
|
+
existing.add(value);
|
|
3280
|
+
else
|
|
3281
|
+
map.set(key, new Set(values));
|
|
3282
|
+
};
|
|
3283
|
+
/** The function a bare identifier names, via the checker's own symbol -- never its spelling. */
|
|
3284
|
+
const namedCallableOf = (expression) => {
|
|
3285
|
+
if (!ts.isIdentifier(expression))
|
|
3286
|
+
return null;
|
|
3287
|
+
const symbol = checker.getSymbolAtLocation(expression);
|
|
3288
|
+
const direct = symbol ? namedCallables.get(symbol) : undefined;
|
|
3289
|
+
if (direct)
|
|
3290
|
+
return direct;
|
|
3291
|
+
// A shorthand property has a property symbol at its name, while the
|
|
3292
|
+
// callable value is the local symbol behind that property. Ask the
|
|
3293
|
+
// checker for that value symbol so `{ method }` records the same callable
|
|
3294
|
+
// publication as `{ method: method }`.
|
|
3295
|
+
const parent = expression.parent;
|
|
3296
|
+
if (ts.isShorthandPropertyAssignment(parent)) {
|
|
3297
|
+
const valueSymbol = checker.getShorthandAssignmentValueSymbol(parent);
|
|
3298
|
+
return valueSymbol ? (namedCallables.get(valueSymbol) ?? null) : null;
|
|
3299
|
+
}
|
|
3300
|
+
return null;
|
|
3301
|
+
};
|
|
3302
|
+
/**
|
|
3303
|
+
* Every function `expression` could evaluate to: a written callable,
|
|
3304
|
+
* a bare name, or a call straight
|
|
3305
|
+
* through a known selector -- `getSingularSetter( type )`, resolved by
|
|
3306
|
+
* the CHECKER's own attribution, never this module's. `this.setValue =
|
|
3307
|
+
* getSingularSetter( activeInfo.type )` is the real idiom this exists
|
|
3308
|
+
* for: the member is published not with a single named function but with
|
|
3309
|
+
* whichever one the selector call itself picks, and the two shapes
|
|
3310
|
+
* compose -- a member publication whose right side is a selector call
|
|
3311
|
+
* publishes the WHOLE returned set under that member, one pass after
|
|
3312
|
+
* `returnedFrom` has settled it for the selector.
|
|
3313
|
+
*/
|
|
3314
|
+
const callablesOf = (expression) => {
|
|
3315
|
+
// A callable written directly into a member is its exact allocation
|
|
3316
|
+
// declaration, just as a named callable assigned there is. Omitting
|
|
3317
|
+
// these values loses every later member-call argument from their
|
|
3318
|
+
// parameter census despite knowing the member's publication identity.
|
|
3319
|
+
if (ts.isFunctionExpression(expression) || ts.isArrowFunction(expression))
|
|
3320
|
+
return new Set([expression]);
|
|
3321
|
+
const named = namedCallableOf(expression);
|
|
3322
|
+
if (named)
|
|
3323
|
+
return new Set([named]);
|
|
3324
|
+
if (ts.isCallExpression(expression)) {
|
|
3325
|
+
const selector = checker.getResolvedSignature(expression)?.declaration;
|
|
3326
|
+
const returned = selector && returnedFrom.get(selector);
|
|
3327
|
+
if (returned)
|
|
3328
|
+
return returned;
|
|
3329
|
+
}
|
|
3330
|
+
return EMPTY_CALLABLES;
|
|
3331
|
+
};
|
|
3332
|
+
// Pass 1: every selector's returned set, so pass 2 can already consult it
|
|
3333
|
+
// for a member published from a selector CALL rather than a bare name.
|
|
3334
|
+
const visitReturns = (node) => {
|
|
3335
|
+
if (ts.isReturnStatement(node) && node.expression) {
|
|
3336
|
+
const callable = namedCallableOf(node.expression);
|
|
3337
|
+
// A `return` belongs to its nearest enclosing function-like ancestor by
|
|
3338
|
+
// construction -- syntax cannot let it skip over a nested one -- so
|
|
3339
|
+
// this is exactly the declaration the reference above is binding-only
|
|
3340
|
+
// FOR, no separate containment walk needed.
|
|
3341
|
+
const enclosing = ts.findAncestor(node, isTrackedCallable);
|
|
3342
|
+
if (callable && enclosing)
|
|
3343
|
+
record(returnedFrom, enclosing, new Set([callable]));
|
|
3344
|
+
}
|
|
3345
|
+
ts.forEachChild(node, visitReturns);
|
|
3346
|
+
};
|
|
3347
|
+
for (const file of files)
|
|
3348
|
+
forEachReachableStatement(reachable, file, visitReturns);
|
|
3349
|
+
// Pass 2: every member publication, now free to resolve a selector call
|
|
3350
|
+
// on the right side through the completed `returnedFrom`. Object
|
|
3351
|
+
// literals need the same treatment as `receiver.member = callable`:
|
|
3352
|
+
// factories such as WebGLState return a record whose properties are the
|
|
3353
|
+
// local forwarding functions. The property symbol is the checker-owned
|
|
3354
|
+
// identity that later member calls expose, so recording it here keeps the
|
|
3355
|
+
// alias path generic and does not depend on the property's spelling.
|
|
3356
|
+
const visitPublications = (node) => {
|
|
3357
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isPropertyAccessExpression(node.left)) {
|
|
3358
|
+
const callables = callablesOf(node.right);
|
|
3359
|
+
if (callables.size > 0) {
|
|
3360
|
+
const memberSymbol = checker.getSymbolAtLocation(node.left.name);
|
|
3361
|
+
if (memberSymbol)
|
|
3362
|
+
record(publishedUnderMember, memberSymbol, callables);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
if (ts.isPropertyAssignment(node)) {
|
|
3366
|
+
const name = node.name;
|
|
3367
|
+
if (ts.isIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name)) {
|
|
3368
|
+
const callables = callablesOf(node.initializer);
|
|
3369
|
+
// The symbol at an object-literal key is the declaration-local
|
|
3370
|
+
// property symbol. Reads use the property symbol from the inferred
|
|
3371
|
+
// object type, so use that same stable identity for both sides.
|
|
3372
|
+
const memberSymbol = checker.getTypeAtLocation(node.parent).getProperty(name.text);
|
|
3373
|
+
if (memberSymbol && callables.size > 0)
|
|
3374
|
+
record(publishedUnderMember, memberSymbol, callables);
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
else if (ts.isShorthandPropertyAssignment(node)) {
|
|
3378
|
+
const callables = callablesOf(node.name);
|
|
3379
|
+
// The shorthand value and the inferred object property have distinct
|
|
3380
|
+
// symbol objects. Ask the object literal's apparent type for the
|
|
3381
|
+
// PROPERTY symbol that later member calls resolve to.
|
|
3382
|
+
const memberSymbol = checker.getTypeAtLocation(node.parent).getProperty(node.name.text);
|
|
3383
|
+
if (memberSymbol && callables.size > 0)
|
|
3384
|
+
record(publishedUnderMember, memberSymbol, callables);
|
|
3385
|
+
}
|
|
3386
|
+
ts.forEachChild(node, visitPublications);
|
|
3387
|
+
};
|
|
3388
|
+
for (const file of files)
|
|
3389
|
+
forEachReachableStatement(reachable, file, visitPublications);
|
|
3390
|
+
return { publishedUnderMember, returnedFrom };
|
|
3391
|
+
};
|
|
3392
|
+
/**
|
|
3393
|
+
* The type a node holds according to every authority in the program, checker
|
|
3394
|
+
* included.
|
|
3395
|
+
*
|
|
3396
|
+
* The binding census answers `null` for "nothing here improves on the
|
|
3397
|
+
* checker's answer", which is the safe default for the census but a trap for
|
|
3398
|
+
* its readers: it makes consulting the census OPTIONAL. A consumer that simply
|
|
3399
|
+
* calls `checker.getTypeAtLocation` gets a plausible type, no error, and no
|
|
3400
|
+
* indication that a better answer already existed one call away. That is not a
|
|
3401
|
+
* hypothetical -- it is the single most common defect this compiler has had.
|
|
3402
|
+
* `signatureOf` asked the census for a signature's parameters and not its
|
|
3403
|
+
* result; `memberOf` had the census threaded into its own module and never
|
|
3404
|
+
* called it; the array-element consumer asked only about `never` and not
|
|
3405
|
+
* `any`. Each was a few lines, each cost hundreds of boxed carriers, and each
|
|
3406
|
+
* looked locally correct.
|
|
3407
|
+
*
|
|
3408
|
+
* So the rule is: a consumer asks THIS, not the checker. The checker is an
|
|
3409
|
+
* input to the census, not an alternative to it, and the fallback belongs in
|
|
3410
|
+
* one place rather than being rewritten -- or forgotten -- at every call site.
|
|
3411
|
+
*
|
|
3412
|
+
* A site that genuinely wants the checker's own answer wants a different
|
|
3413
|
+
* question than this one ("what did the program STATE here", for a stated
|
|
3414
|
+
* annotation, versus "what does this hold"), and should say so in its own
|
|
3415
|
+
* words at the call rather than reaching past this by habit.
|
|
3416
|
+
*/
|
|
3417
|
+
export const censusedTypeAt = (checker, census, node) => census.typeAt(node) ?? checker.getTypeAtLocation(node);
|
|
3418
|
+
/**
|
|
3419
|
+
* The synthesized-union arms a write-set census resolved for a cell, answered
|
|
3420
|
+
* at the DECLARATION and at every READ of it alike.
|
|
3421
|
+
*
|
|
3422
|
+
* A cell whose writes disagree DISJOINTLY resolves to a union rather than a
|
|
3423
|
+
* refusal (see `disjointUnionMembersOf`), and that answer has to travel as an
|
|
3424
|
+
* arm LIST because `ts.TypeChecker.getUnionType` is not public -- so it cannot
|
|
3425
|
+
* ride in a census's `typeAt`, and each census publishes it through a second
|
|
3426
|
+
* channel, `unionArmsAt`, for `structural.ts` to intern.
|
|
3427
|
+
*
|
|
3428
|
+
* Every one of those channels was written as `ts.isParameter(node) ? arms.get(
|
|
3429
|
+
* node) : null` (and the variable/field equivalents), which answers for the
|
|
3430
|
+
* declaration NODE and for nothing else. That is the two-authorities defect
|
|
3431
|
+
* this compiler keeps rediscovering, in its quietest possible form: the
|
|
3432
|
+
* declaration got the union, and every ordinary reference to the cell -- an
|
|
3433
|
+
* identifier one line down -- fell through to the checker's own `any` and
|
|
3434
|
+
* boxed. Nothing reported a disagreement, because the second authority
|
|
3435
|
+
* answered `null`, and `null` is exactly how a census says "the checker's
|
|
3436
|
+
* answer stands".
|
|
3437
|
+
*
|
|
3438
|
+
* Measured on the three.js app: **176 top-level `dynamic` carriers sat on identifiers
|
|
3439
|
+
* reading a parameter the census had already resolved** -- 104 + 57 + 15,
|
|
3440
|
+
* the three synthesized-union parameters in the renderer's object walks, to
|
|
3441
|
+
* the unit. Answering here instead swapped exactly 171 `dynamic` carriers for
|
|
3442
|
+
* 171 `tagged-union` ones (a carrier kind the same program already selected
|
|
3443
|
+
* 2040 times), with `ops` unchanged and 92 FEWER unmet obligations.
|
|
3444
|
+
*
|
|
3445
|
+
* No flow narrowing is discarded by answering at a read, which is the one
|
|
3446
|
+
* thing that could make this unsound and the reason a census's `typeAt` needs
|
|
3447
|
+
* an equality guard for its ordinary bindings: a synthesized union arises only
|
|
3448
|
+
* where the declaration's own checker type is `any`, and `any` carries no
|
|
3449
|
+
* narrowing for a reference to report differently.
|
|
3450
|
+
*
|
|
3451
|
+
* `declarations.length === 1` is the same soleness test every resolver in this
|
|
3452
|
+
* module applies -- a name with two declarations is two cells, and answering
|
|
3453
|
+
* for either would be picking one.
|
|
3454
|
+
*/
|
|
3455
|
+
export const synthesizedUnionArmsAt = (checker, node, arms, owns = ts.isParameter) => {
|
|
3456
|
+
if (owns(node))
|
|
3457
|
+
return arms.get(node) ?? null;
|
|
3458
|
+
if (!ts.isIdentifier(node))
|
|
3459
|
+
return null;
|
|
3460
|
+
const declarations = checker.getSymbolAtLocation(node)?.declarations;
|
|
3461
|
+
const declaration = declarations && declarations.length === 1 ? declarations[0] : undefined;
|
|
3462
|
+
return declaration && owns(declaration) ? (arms.get(declaration) ?? null) : null;
|
|
3463
|
+
};
|