@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,3339 @@
|
|
|
1
|
+
import { isRealCallableDeclaration, isModuleExportedDeclaration, isTypePositionReference, runtimeParametersOf } from './flow/targets.js';
|
|
2
|
+
import { inProgramImportReferencesOf } from './flow/export-importers.js';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { implicitArgumentsSlotOf } from './implicit-arguments.js';
|
|
5
|
+
import { implicitArgumentsReadTypeAt, inferImplicitArgumentsTuple } from './implicit-arguments-tuple.js';
|
|
6
|
+
import { censusArgumentsObjects } from './arguments-objects.js';
|
|
7
|
+
import { censusRefusal } from './census-refusal.js';
|
|
8
|
+
import { classFamilyMemberReadTypeOf } from './flow/class-family-member-read.js';
|
|
9
|
+
import { omissionStatedTypeOf, statedParameterWithOmission } from './omitted-stated-parameter.js';
|
|
10
|
+
import { indexValueFlow } from './flow/value-flow.js';
|
|
11
|
+
import { closedArrayCalleeAuthorityOf, hasClosedMemberCallableUses } from './flow/callable-reach.js';
|
|
12
|
+
import { deferredIntrinsicProtocolLedgerOf } from './deferred-intrinsic-protocols.js';
|
|
13
|
+
import { callbackContractParameterType, callbackParameterContractsFor } from './callback-parameter-contracts.js';
|
|
14
|
+
import { declaredClosedTupleRestElementsOf, impliedPatternElementOfArms, impliedPatternPositionPresentEverywhere } from './parameter-slot.js';
|
|
15
|
+
import { iteratorYieldTypesOf } from './producers/iteration-yield.js';
|
|
16
|
+
import { annotationStatesNothing, exactEmptyObjectLiteralType, isNullishType, containsUnstatedPosition, derivedExpressionType, indexAliasEvidence, indexedTypeOf, explicitThisCallReturnType, overloadInvariantReturnTypeAt, indexNamedCallables, isStandardInterfaceType, isBindingOnlyReference, isCalleeOf, isTrackedCallable, isUnusableEvidence as statesNoStorage, carriesNoEvidence, disjointUnionMembersOf, disjointUnionTypeOf, joinOfWrites, jsDocTypeStatesNothing, literalMemberNameOf, narrowsOnlyUnstatedPositions, withoutUndefinedMember, memberTypeOf, objectAssignTargetType, nameOfCallable, synthesizedUnionArmsAt, widestOf, impliedPatternElementRootOf, impliedPatternParameterOf } from './derived-expression-type.js';
|
|
17
|
+
import { isUnreducedTypeForm } from './unreduced-type-form.js';
|
|
18
|
+
import { forEachReachableStatement } from './reachability.js';
|
|
19
|
+
/** A census that binds nothing, for callers that state no program. */
|
|
20
|
+
export const emptyParameterBindingCensus = {
|
|
21
|
+
typeAt: () => null,
|
|
22
|
+
statedTypeAt: () => null,
|
|
23
|
+
unionArmsAt: () => null,
|
|
24
|
+
boundCount: 0,
|
|
25
|
+
refusals: [],
|
|
26
|
+
refusalOf: () => null
|
|
27
|
+
};
|
|
28
|
+
const isAnyType = (type) => (type.flags & ts.TypeFlags.Any) !== 0;
|
|
29
|
+
/** A stable empty set, so alias attribution can return "nothing" without allocating one each time. */
|
|
30
|
+
const EMPTY_DECLARATIONS = new Set();
|
|
31
|
+
/**
|
|
32
|
+
* Whether a type is bare `Function` -- a type with NO calling convention at
|
|
33
|
+
* all. TypeScript's checker lets any callable be assigned to `Function` as an
|
|
34
|
+
* error-tolerance rule, but the type itself declares zero call signatures and
|
|
35
|
+
* zero construct signatures, so it says nothing about arity or return either.
|
|
36
|
+
* Signature count is the real question and is checked first; the symbol name
|
|
37
|
+
* is a second, narrower gate so a user type that happens to declare no
|
|
38
|
+
* signatures of its own (an empty interface, `Record<string, never>`) is not
|
|
39
|
+
* swept in beside it -- only the library's own `Function` is.
|
|
40
|
+
*/
|
|
41
|
+
const isBareFunctionType = (type) => {
|
|
42
|
+
if (type.getCallSignatures().length > 0)
|
|
43
|
+
return false;
|
|
44
|
+
if (type.getConstructSignatures().length > 0)
|
|
45
|
+
return false;
|
|
46
|
+
return type.getSymbol()?.getName() === 'Function';
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Whether a type says nothing about STORAGE, and so is not evidence.
|
|
50
|
+
*
|
|
51
|
+
* Four of the checker's answers are not carriers, and a census that admits
|
|
52
|
+
* them binds a parameter to a fact about something other than the value it
|
|
53
|
+
* holds:
|
|
54
|
+
*
|
|
55
|
+
* - `any` is the absence this module exists to fill, and propagating it is what
|
|
56
|
+
* it exists to stop.
|
|
57
|
+
* - `void` is a statement about a RESULT nobody may read. It and `undefined`
|
|
58
|
+
* are one runtime value and two facts, so a call passing the result of a void
|
|
59
|
+
* function binds `void` into a cell whose ABI correctly says `undefined` --
|
|
60
|
+
* measured, as an ABI disagreement in `PolyhedronGeometry`.
|
|
61
|
+
* - `never` is REACHABILITY, not storage. `const v = [];` before any write is
|
|
62
|
+
* `never[]`, so `v[ i ][ k + 1 ]` reads `never` -- and `pushVertex( v[ i ][ k
|
|
63
|
+
* + 1 ] )` is a call that plainly does happen. Binding its parameter to
|
|
64
|
+
* "no value ever arrives" describes the empty literal, not the argument.
|
|
65
|
+
* - bare `Function` is `isUnannotated`'s own exception admitted as a
|
|
66
|
+
* CANDIDATE, and it must stay non-evidence everywhere else in this module
|
|
67
|
+
* for the same reason: `Object3D.traverse( callback ) { callback( this );
|
|
68
|
+
* children[i].traverse( callback ); }` passes `callback` to a recursive
|
|
69
|
+
* call of `traverse` itself, and if the declared `Function` type were
|
|
70
|
+
* usable evidence, `known()` would read it straight off the checker at that
|
|
71
|
+
* self-reference (before this round's own binding exists) and hand it to
|
|
72
|
+
* `agreedArgumentType` as one of the call sites' passed types. Bare
|
|
73
|
+
* `Function` is assignable-to by nearly every real function type, so
|
|
74
|
+
* `widestOf` would then pick it as the "widest" answer over every genuine
|
|
75
|
+
* callback type the OTHER call sites pass -- collapsing a real function
|
|
76
|
+
* value back down to the one type that carries no calling convention,
|
|
77
|
+
* silently. Measured: admitting it produced exactly that, surfacing as a
|
|
78
|
+
* `native-record-ref` binding a `function-value-dispatch` ABI disagreed
|
|
79
|
+
* with. Excluding it here instead means a self-reference like this one is
|
|
80
|
+
* refused (`argument-states-no-storage`) rather than answered wrong, and
|
|
81
|
+
* the parameter binds only once every OTHER call site's real evidence
|
|
82
|
+
* agrees.
|
|
83
|
+
*
|
|
84
|
+
* Refused rather than rewritten: every type this module produces comes out of
|
|
85
|
+
* the checker, and substituting `undefined` for a `void` here would be the
|
|
86
|
+
* compiler stating a type instead of reading one. A parameter whose only
|
|
87
|
+
* evidence is one of these keeps the checker's own answer, which is the
|
|
88
|
+
* behaviour before this module existed.
|
|
89
|
+
*/
|
|
90
|
+
const isUnusableEvidence = (type) => statesNoStorage(type) || isBareFunctionType(type) || isGenericCallableType(type);
|
|
91
|
+
/**
|
|
92
|
+
* Whether a type is a GENERIC callable -- `typeof emit` for `function emit<T
|
|
93
|
+
* extends Node>(node: T)`, passed uninstantiated to a parameter typed by a
|
|
94
|
+
* generic function type (`emitNodeList(emit, ...)` over `EmitFunction`).
|
|
95
|
+
*
|
|
96
|
+
* Not evidence for the same reason `Function` is not: the spelling says which
|
|
97
|
+
* function, not which frame. The frame the cell holds is the copy the
|
|
98
|
+
* specialization census mints for that very reference (`fromValueUse`), over
|
|
99
|
+
* the instantiation the PARAMETER's own type has in this program -- and the
|
|
100
|
+
* declared parameter type resolves to exactly that, while this argument type
|
|
101
|
+
* resolved in the callee's copy would read the function's own `T` with
|
|
102
|
+
* nothing binding it. Measured: `emitFn(child)` refused
|
|
103
|
+
* `function-value-dispatch((record(Node)) -> void) ->
|
|
104
|
+
* function-value-dispatch((native-record-ref(Stmt)) -> void)`, the cell
|
|
105
|
+
* typed from the argument and the read from the declaration.
|
|
106
|
+
*/
|
|
107
|
+
const isGenericCallableType = (type) => [...type.getCallSignatures(), ...type.getConstructSignatures()].some((signature) => (signature.getTypeParameters()?.length ?? 0) > 0);
|
|
108
|
+
/**
|
|
109
|
+
* Whether this parameter is one the program never typed -- for the one
|
|
110
|
+
* question this census answers: what can a callable value here be called
|
|
111
|
+
* with.
|
|
112
|
+
*
|
|
113
|
+
* A type annotation, a JSDoc `@param`/`@type` tag, and a default initializer
|
|
114
|
+
* are all the program stating the type; only the total absence of all three
|
|
115
|
+
* (or a bare `Function` annotation, immediately below) leaves the checker
|
|
116
|
+
* with nothing to read. The `any` test comes last and is what makes this
|
|
117
|
+
* honest for a parameter TypeScript typed contextually -- a callback's
|
|
118
|
+
* parameter in `arr.map(x => x)` has no annotation and is not `any`, and
|
|
119
|
+
* rebinding it from a call site would overwrite a real inference.
|
|
120
|
+
*
|
|
121
|
+
* Bare `Function` -- `@param {Function} callback`, or `: Function` -- is the
|
|
122
|
+
* one exception to "an annotation settles it". Calling a `Function`-typed
|
|
123
|
+
* value is permitted only by the checker's own error tolerance; the type
|
|
124
|
+
* declares no call signature and no construct signature, so for THIS
|
|
125
|
+
* question -- what does a call here actually pass -- it is exactly as
|
|
126
|
+
* uninformative as no annotation at all. `Object3D.traverse`'s `callback`
|
|
127
|
+
* parameter is declared this way and the body calls `callback( this )`: one
|
|
128
|
+
* argument, where the annotation states none. A type that DOES declare a call
|
|
129
|
+
* signature -- `(x: T) => U`, a JSDoc `@callback` typedef -- is real evidence
|
|
130
|
+
* and keeps stopping the census exactly as before; only the signature-free
|
|
131
|
+
* case is let through.
|
|
132
|
+
*
|
|
133
|
+
* A real annotation that RESOLVES TO A NON-STATEMENT -- `options: {}`, `x:
|
|
134
|
+
* object`, `v: Object` -- is the second exception, and it is the JSDoc rule
|
|
135
|
+
* three lines of this function already apply, asked of the other spelling.
|
|
136
|
+
* `jsDocTypeStatesNothing` stopped a vague `@param {Object}` tag from
|
|
137
|
+
* outranking the census; a parameter written `: object` in TypeScript states
|
|
138
|
+
* exactly as little, and there is no reason the census should defer to one
|
|
139
|
+
* spelling of "nothing" and not the other. See `annotationStatesNothing`
|
|
140
|
+
* (`derived-expression-type.js`) for what is deliberately NOT in it --
|
|
141
|
+
* `unknown`, a named empty type, an explicit `: any`.
|
|
142
|
+
*/
|
|
143
|
+
const isUnannotated = (checker, parameter) => {
|
|
144
|
+
if (parameter.dotDotDotToken)
|
|
145
|
+
return false;
|
|
146
|
+
if (!ts.isIdentifier(parameter.name)) {
|
|
147
|
+
// A DESTRUCTURING PATTERN as the parameter's name -- `function f([x, y])`,
|
|
148
|
+
// `function g({ a, b })` -- with no annotation. TypeScript synthesizes the
|
|
149
|
+
// parameter's type from the pattern's own SHAPE (`[any, any]`, `{ a: any;
|
|
150
|
+
// b: any }`): a statement about the syntax of the binding, not about any
|
|
151
|
+
// value a caller passes, and the `[any, any]` tuple is actively wrong for
|
|
152
|
+
// the language, which binds the pattern off ANY iterable of any length
|
|
153
|
+
// (`f([])` is legal and binds both names to `undefined`). So the site
|
|
154
|
+
// type is no evidence at all, exactly as a bare `any` parameter's is, and
|
|
155
|
+
// the census binds the slot from the call sites; the pattern's leaves then
|
|
156
|
+
// read out of THAT bound value (`resolve`'s `BindingElement` branch).
|
|
157
|
+
// A JSDoc tag or a real annotation on the pattern is a statement and
|
|
158
|
+
// keeps the parameter out, as it does for a named one.
|
|
159
|
+
return impliedPatternParameterOf(checker, parameter) !== null;
|
|
160
|
+
}
|
|
161
|
+
if (parameter.type) {
|
|
162
|
+
// Read at the TYPE NODE, never at the parameter. `checker.getTypeAtLocation`
|
|
163
|
+
// on an OPTIONAL parameter answers for the site -- `object | undefined`,
|
|
164
|
+
// `{} | undefined` -- and the trailing `isAnyType` test at the bottom of
|
|
165
|
+
// this function is written for a site type that really is bare `any`. That
|
|
166
|
+
// exact confusion is what silently re-excluded the empty-object JSDoc tags
|
|
167
|
+
// this rule's own sibling had just admitted (see the comment below), so
|
|
168
|
+
// this branch asks the annotation itself and RETURNS immediately either
|
|
169
|
+
// way rather than falling through to a test that answers a different
|
|
170
|
+
// question.
|
|
171
|
+
return annotationStatesNothing(checker, parameter.type, checker.getTypeFromTypeNode(parameter.type));
|
|
172
|
+
}
|
|
173
|
+
const jsDocParamTags = ts.getJSDocParameterTags(parameter);
|
|
174
|
+
const jsDocType = ts.getJSDocType(parameter) ?? jsDocParamTags[0]?.typeExpression?.type;
|
|
175
|
+
if (jsDocType || jsDocParamTags.length > 0) {
|
|
176
|
+
// HELD, not landed: candidacy itself is sound (see the doc above), but
|
|
177
|
+
// admitting it before `.call`/`.apply` attribution exists (see
|
|
178
|
+
// `unwrapExplicitThisCall`) measurably regressed `EventDispatcher.
|
|
179
|
+
// addEventListener`'s `listener` -- `callsByDeclaration` only ever saw
|
|
180
|
+
// the 2 direct-call sites in the whole corpus, both coincidentally
|
|
181
|
+
// zero-arg, so the join correctly found no disagreement among evidence
|
|
182
|
+
// that was itself an incomplete sample and bound `() => void`, while
|
|
183
|
+
// `dispatchEvent` really calls every listener with one argument via
|
|
184
|
+
// `array[i].call( this, event )`. Re-enable
|
|
185
|
+
// (`isBareFunctionType(checker.getTypeAtLocation(parameter))`) once that
|
|
186
|
+
// attribution gap is closed and re-measured.
|
|
187
|
+
//
|
|
188
|
+
// NARROWED, not repealed: the exclusion above still stands whenever the
|
|
189
|
+
// JSDoc type resolves to something the checker can actually read -- that
|
|
190
|
+
// is real evidence, exactly as informative as a TS annotation, and stays
|
|
191
|
+
// out of this census for the same reason a typed parameter always has.
|
|
192
|
+
// It is lifted for unusable JSDoc evidence or the same broad object bound
|
|
193
|
+
// `annotationStatesNothing` admits for a TypeScript parameter. This also
|
|
194
|
+
// covers the real lib Object interface, whose inherited methods state no
|
|
195
|
+
// configuration fields. Parameter refinement does not discard that tag on
|
|
196
|
+
// return cells. At that point the program has stated nothing this
|
|
197
|
+
// compiler can read, so refusing the parameter is refusing it for a fact
|
|
198
|
+
// that isn't there. Measured on the three.js corpus: 31/38 obligations (82%)
|
|
199
|
+
// and 20/24 distinct parameters (83%) in this bucket resolve to nothing
|
|
200
|
+
// and are exactly the unimported cross-module-name pattern documented on
|
|
201
|
+
// `jsDocTypeIsUninformative`; the remaining 17-18% resolve to a real type
|
|
202
|
+
// (`number | Vector3`, ...) and keep being excluded here, same as before
|
|
203
|
+
// this change.
|
|
204
|
+
if (!jsDocType ||
|
|
205
|
+
!(jsDocTypeStatesNothing(checker, jsDocType) || annotationStatesNothing(checker, jsDocType, checker.getTypeFromTypeNode(jsDocType))))
|
|
206
|
+
return false;
|
|
207
|
+
// Admit it HERE, rather than falling through to the `isAnyType(checker.
|
|
208
|
+
// getTypeAtLocation(parameter))` test below -- that test is written for
|
|
209
|
+
// the OTHER path into this function, the parameter with no annotation of
|
|
210
|
+
// any kind, where the checker's site type really is bare `any`. A tagged
|
|
211
|
+
// parameter's site type is not: it is the checker's answer for the TAG,
|
|
212
|
+
// and a `[name]`-bracketed (optional) tag makes that a real UNION with
|
|
213
|
+
// `undefined` for any resolved type except `any` itself -- TypeScript's
|
|
214
|
+
// own union normalization absorbs `any | undefined` back down to `any`,
|
|
215
|
+
// which is the ONLY reason the `any`/`unknown` half of `jsDocTypeStates
|
|
216
|
+
// Nothing` ever reached this point undetected before. `{} | undefined`
|
|
217
|
+
// does not absorb the same way, so an empty-object tag marked optional
|
|
218
|
+
// -- `InterleavedBuffer.clone( [data] )` via `@types/three`'s own `data:
|
|
219
|
+
// {}` -- hit the `isAnyType` test below, found a `Union`, and was
|
|
220
|
+
// silently re-excluded even though the line above had already judged the
|
|
221
|
+
// tag to state nothing. Measured: this was the entire reason widening
|
|
222
|
+
// `jsDocTypeStatesNothing` alone moved zero boxes on the three.js app.
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
// A DEFAULT VALUE is not a type. `constructor( parameters = {} )` states what
|
|
226
|
+
// the parameter holds when a caller omits it, and TypeScript widens that into
|
|
227
|
+
// `{}` -- a type with no members, which every real call site contradicts.
|
|
228
|
+
// `WebGLRenderer`'s whole configuration arrives through this parameter, so
|
|
229
|
+
// `{}` is the wall the entire renderer's typing stands behind.
|
|
230
|
+
//
|
|
231
|
+
// Binding it is sound only when the default is UNREACHABLE, and that is a
|
|
232
|
+
// condition this census already tests for its own reasons: every call site
|
|
233
|
+
// must pass an argument at this position (`call-passes-no-argument`) and the
|
|
234
|
+
// function must not escape to a caller it cannot see. A default that can
|
|
235
|
+
// still run is a second value the agreed type does not describe.
|
|
236
|
+
if (parameter.initializer)
|
|
237
|
+
return true;
|
|
238
|
+
return isAnyType(checker.getTypeAtLocation(parameter));
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* Whether a type still carries a piece of an UNSUBSTITUTED generic -- a bare
|
|
242
|
+
* type parameter, an indexed access whose object type is one (`E['Bindings']`),
|
|
243
|
+
* a conditional or a substitution.
|
|
244
|
+
*
|
|
245
|
+
* Such a type is not a carrier and never becomes one: the representation
|
|
246
|
+
* deriver has nothing to resolve `E['Bindings']` against and publishes
|
|
247
|
+
* `unresolved(...)`, which fails preflight as an unmet obligation rather than
|
|
248
|
+
* as a refusal anyone can read. hono's `Hono` is generic in `E` and every
|
|
249
|
+
* copy of it shares one parameter node, so a census answer taken from a call
|
|
250
|
+
* site INSIDE the generic carries `E` out with it -- 10 unmet obligations
|
|
251
|
+
* reading "an indexed access whose object type is still a type parameter has
|
|
252
|
+
* no member set to resolve", measured the first time this admission ran
|
|
253
|
+
* without the guard.
|
|
254
|
+
*/
|
|
255
|
+
export const carriesUnsubstitutedGeneric = (checker, type, depth = 0) => {
|
|
256
|
+
if (depth > 8)
|
|
257
|
+
return false;
|
|
258
|
+
const open = ts.TypeFlags.TypeParameter | ts.TypeFlags.IndexedAccess | ts.TypeFlags.Conditional | ts.TypeFlags.Substitution;
|
|
259
|
+
if ((type.flags & open) !== 0)
|
|
260
|
+
return true;
|
|
261
|
+
if (type.isUnion() || type.isIntersection()) {
|
|
262
|
+
return type.types.some((part) => carriesUnsubstitutedGeneric(checker, part, depth + 1));
|
|
263
|
+
}
|
|
264
|
+
if ((type.flags & ts.TypeFlags.Object) === 0)
|
|
265
|
+
return false;
|
|
266
|
+
const reference = type;
|
|
267
|
+
if (reference.target === undefined)
|
|
268
|
+
return false;
|
|
269
|
+
return checker.getTypeArguments(reference).some((part) => carriesUnsubstitutedGeneric(checker, part, depth + 1));
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* A parameter the program DID type, whose statement is still only an UPPER
|
|
273
|
+
* BOUND -- one with an `any`/`unknown`/bare-`Function` position somewhere
|
|
274
|
+
* inside it. Returns that stated type, for `narrowsOnlyUnstatedPositions` to
|
|
275
|
+
* test the agreed argument type against; `null` for every parameter whose
|
|
276
|
+
* annotation constrains every position it mentions, which is nearly all of
|
|
277
|
+
* them and which this census must not touch.
|
|
278
|
+
*
|
|
279
|
+
* hono's `HonoRequest( ..., matchResult: Result<[unknown, RouterRoute]> )` and
|
|
280
|
+
* `compose( middleware: [[Function, unknown], unknown][] | [[Function]][] )`
|
|
281
|
+
* are the two measured cases: both state a full structure and leave exactly
|
|
282
|
+
* the handler slot unstated, and both are handed a fully concrete value by the
|
|
283
|
+
* program's only caller. See `narrowsOnlyUnstatedPositions` for why reading
|
|
284
|
+
* such an annotation as the last word forces an unrenderable aggregate rebuild.
|
|
285
|
+
*
|
|
286
|
+
* The same three structural exclusions `isUnannotated` applies hold here --
|
|
287
|
+
* a rest parameter, a destructured one, and a type that is itself no evidence.
|
|
288
|
+
*/
|
|
289
|
+
const statedUpperBound = (checker, parameter) => {
|
|
290
|
+
if (parameter.dotDotDotToken)
|
|
291
|
+
return null;
|
|
292
|
+
if (!ts.isIdentifier(parameter.name))
|
|
293
|
+
return null;
|
|
294
|
+
if (!parameter.type)
|
|
295
|
+
return null;
|
|
296
|
+
const declared = checker.getTypeFromTypeNode(parameter.type);
|
|
297
|
+
// Already `isUnannotated`'s own business, and admitted there.
|
|
298
|
+
if (annotationStatesNothing(checker, parameter.type, declared))
|
|
299
|
+
return null;
|
|
300
|
+
if (isUnusableEvidence(declared))
|
|
301
|
+
return null;
|
|
302
|
+
if (!containsUnstatedPosition(checker, parameter.type, declared))
|
|
303
|
+
return null;
|
|
304
|
+
// A DEFAULTED or OPTIONAL parameter's statement is `T | undefined` to its
|
|
305
|
+
// callers, whatever its annotation spells: the absence is what the default
|
|
306
|
+
// exists to answer. Testing the agreed argument type against the bare `T`
|
|
307
|
+
// refuses every such parameter whose caller passes a possibly-absent value
|
|
308
|
+
// -- hono's `new HonoRequest( ..., this.#matchResult )` passing a `Result<
|
|
309
|
+
// [H, RouterRoute]> | undefined` into `matchResult: Result<[unknown,
|
|
310
|
+
// RouterRoute]> = [[]]` is the measured case, and it is ordinary
|
|
311
|
+
// TypeScript. `contributeDefaultedParameter` (`producers/bindings.ts`)
|
|
312
|
+
// already splits this answer back into the raw slot (with the absence) and
|
|
313
|
+
// the body's own binding (without it), so handing it the union is what
|
|
314
|
+
// makes both halves agree.
|
|
315
|
+
const absent = parameter.initializer !== undefined || parameter.questionToken !== undefined;
|
|
316
|
+
return absent ? checker.getNullableType(declared, ts.TypeFlags.Undefined) : declared;
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* A pure open object dictionary whose values the program explicitly leaves
|
|
320
|
+
* dynamic.
|
|
321
|
+
*
|
|
322
|
+
* BSON's `Document` is exactly `{ [key: string]: any }`. TypeScript permits a
|
|
323
|
+
* value with that annotation to be flow-narrowed to Array, Map or a named
|
|
324
|
+
* record by `Array.isArray`, `instanceof` and user predicates. Those narrowed
|
|
325
|
+
* values do not become dictionaries at runtime; the annotation is an upper
|
|
326
|
+
* bound on property reads, while the flow facts still state their physical
|
|
327
|
+
* identities. This predicate is deliberately limited to a memberless string
|
|
328
|
+
* index with an explicit `any` value so an ordinary typed record is never
|
|
329
|
+
* widened by the mechanism below.
|
|
330
|
+
*/
|
|
331
|
+
const openDynamicObjectUpperBound = (checker, parameter) => {
|
|
332
|
+
if (!parameter.type || !ts.isIdentifier(parameter.name) || parameter.dotDotDotToken)
|
|
333
|
+
return null;
|
|
334
|
+
const declared = checker.getTypeFromTypeNode(parameter.type);
|
|
335
|
+
if (checker.getPropertiesOfType(declared).length !== 0)
|
|
336
|
+
return null;
|
|
337
|
+
const dynamicStringIndex = checker
|
|
338
|
+
.getIndexInfosOfType(declared)
|
|
339
|
+
.find((index) => (index.keyType.flags & ts.TypeFlags.String) !== 0 && (index.type.flags & ts.TypeFlags.Any) !== 0);
|
|
340
|
+
return dynamicStringIndex ? declared : null;
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* A flow narrowing that changes the value's physical container identity.
|
|
344
|
+
*
|
|
345
|
+
* An open `[string]: any` annotation admits both exotic containers and named
|
|
346
|
+
* structural records. Only the former need a distinct storage arm: Array and
|
|
347
|
+
* the keyed collections have layouts a dictionary cannot impersonate. A user
|
|
348
|
+
* predicate narrowing the same value to a record interface merely gives typed
|
|
349
|
+
* names to dictionary properties; treating that view as a second allocation
|
|
350
|
+
* kind loses negative narrowing when control leaves the predicate branch.
|
|
351
|
+
*/
|
|
352
|
+
const isFlowContainerType = (checker, anchor, type) => checker.isArrayType(type) || ['Map', 'Set', 'WeakMap', 'WeakSet'].some((name) => isStandardInterfaceType(checker, anchor, name, type));
|
|
353
|
+
export const indexParameterBindingProgram = (checker, files, reachable, valueFlow = indexValueFlow(checker, files, reachable), argumentsObjects = censusArgumentsObjects(checker, files)) => {
|
|
354
|
+
// `GEA_INDEX_TIMING=1` prints what one build of this index costs. It is the
|
|
355
|
+
// only honest way to price the hoist on a shared machine: across two runs
|
|
356
|
+
// the load swings by a factor of two and `dist/` is rebuilt underneath
|
|
357
|
+
// them, but WITHIN one run the count of builds is the whole difference --
|
|
358
|
+
// five without the hoist, one with it.
|
|
359
|
+
const startedAt = process.env['GEA_INDEX_TIMING'] ? performance.now() : 0;
|
|
360
|
+
const candidates = [];
|
|
361
|
+
const restParameterCandidates = [];
|
|
362
|
+
const omissionSites = [];
|
|
363
|
+
const assigned = new Set();
|
|
364
|
+
// An update, logical assignment, destructuring assignment, or loop binding
|
|
365
|
+
// also replaces a parameter. Sharing the write inventory prevents the
|
|
366
|
+
// argument census from retaining an argument-only carrier after such a write.
|
|
367
|
+
const assignedEvidence = new Map();
|
|
368
|
+
for (const write of valueFlow.allWrites) {
|
|
369
|
+
if (write.slot !== 'whole' || !write.target.symbol)
|
|
370
|
+
continue;
|
|
371
|
+
const declaration = write.target.declaration;
|
|
372
|
+
if (declaration &&
|
|
373
|
+
ts.isParameter(declaration) &&
|
|
374
|
+
write.edge !== 'call-argument' &&
|
|
375
|
+
write.edge !== 'super-argument' &&
|
|
376
|
+
write.edge !== 'default-parameter') {
|
|
377
|
+
assigned.add(write.target.symbol);
|
|
378
|
+
// A compound assignment or update names no sub-expression holding the
|
|
379
|
+
// written value, so the flow layer records none. The checker can state
|
|
380
|
+
// its result at the whole expression; operand-sensitive cases may need
|
|
381
|
+
// the complete incoming frame before that result can be recovered. Every other write hands over its value expression, and
|
|
382
|
+
// a write that hands over neither leaves this parameter with a gap no
|
|
383
|
+
// join can close.
|
|
384
|
+
const evidence = write.edge === 'compound-assignment' &&
|
|
385
|
+
(ts.isBinaryExpression(write.site) || ts.isPostfixUnaryExpression(write.site) || ts.isPrefixUnaryExpression(write.site))
|
|
386
|
+
? { expression: write.site, operatorTyped: true }
|
|
387
|
+
: write.value !== null
|
|
388
|
+
? { expression: write.value, operatorTyped: false }
|
|
389
|
+
: null;
|
|
390
|
+
const seen = assignedEvidence.get(write.target.symbol);
|
|
391
|
+
if (seen === null)
|
|
392
|
+
continue;
|
|
393
|
+
if (evidence === null)
|
|
394
|
+
assignedEvidence.set(write.target.symbol, null);
|
|
395
|
+
else if (seen)
|
|
396
|
+
seen.push(evidence);
|
|
397
|
+
else
|
|
398
|
+
assignedEvidence.set(write.target.symbol, [evidence]);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const allCalls = valueFlow.calls.map((site) => site.call);
|
|
402
|
+
const checkerAttribution = new Map(valueFlow.calls.map((site) => [site.call, site.checkerDeclaration]));
|
|
403
|
+
const invocationOperands = new Map(valueFlow.calls.map((site) => [site.call, site.operands]));
|
|
404
|
+
for (const file of files) {
|
|
405
|
+
// The same boundary `flow/value-flow.ts` has always walked, and the fourth
|
|
406
|
+
// census to need it (`local-bindings.ts` and `collection-bindings.ts` were
|
|
407
|
+
// the other two corrected). `forEachReachableStatement` filters TOP-LEVEL
|
|
408
|
+
// statements only; a method inside a reachable class that whole-program DCE
|
|
409
|
+
// has pruned is still descended into by the bare `ts.forEachChild` below.
|
|
410
|
+
// Its parameters were therefore gathered as candidates, `agreedArgumentType`
|
|
411
|
+
// asked `calls` a question about a body that never runs, and the correct
|
|
412
|
+
// answer -- no call sites, no counted references -- was published as a
|
|
413
|
+
// refusal. The three.js app's `Quaternion.setFromUnitVectors`,
|
|
414
|
+
// `Vector3.project`/`unproject` and the whole pruned `BufferGeometry`
|
|
415
|
+
// helper set are refusals of exactly this kind: evidence that was never
|
|
416
|
+
// going to exist for code that is never emitted.
|
|
417
|
+
const visit = (node) => {
|
|
418
|
+
if (reachable.memberIsPruned(node))
|
|
419
|
+
return;
|
|
420
|
+
if (isTrackedCallable(node)) {
|
|
421
|
+
runtimeParametersOf(node).forEach((parameter, index) => {
|
|
422
|
+
// An UNANNOTATED rest parameter is a candidate for its OWN
|
|
423
|
+
// element-wise join (`restElementTypeAt`), never for the ordinary
|
|
424
|
+
// fixed-position sweep below -- `isUnannotated` returns `false` for
|
|
425
|
+
// every rest parameter precisely because a rest parameter's slot is
|
|
426
|
+
// not one argument's type. A real annotation (`...args: string[]`,
|
|
427
|
+
// even the widening `...args: any`) is the program stating a real
|
|
428
|
+
// element already, and is left to the checker exactly as before.
|
|
429
|
+
if (parameter.dotDotDotToken !== undefined && parameter.type === undefined) {
|
|
430
|
+
restParameterCandidates.push({ declaration: node, parameter, index });
|
|
431
|
+
}
|
|
432
|
+
if (isUnannotated(checker, parameter))
|
|
433
|
+
candidates.push({ declaration: node, parameter, index, stated: null, flowCarrierUpperBound: null });
|
|
434
|
+
else {
|
|
435
|
+
const stated = statedUpperBound(checker, parameter);
|
|
436
|
+
const flowCarrierUpperBound = openDynamicObjectUpperBound(checker, parameter);
|
|
437
|
+
if (stated || flowCarrierUpperBound)
|
|
438
|
+
candidates.push({
|
|
439
|
+
declaration: node,
|
|
440
|
+
parameter,
|
|
441
|
+
index,
|
|
442
|
+
stated: stated ?? flowCarrierUpperBound,
|
|
443
|
+
flowCarrierUpperBound
|
|
444
|
+
});
|
|
445
|
+
else {
|
|
446
|
+
// Not inferred -- but a caller may still leave it out.
|
|
447
|
+
const omissionStated = omissionStatedTypeOf(checker, parameter);
|
|
448
|
+
if (omissionStated)
|
|
449
|
+
omissionSites.push({ declaration: node, parameter, index, stated: omissionStated });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
ts.forEachChild(node, visit);
|
|
455
|
+
};
|
|
456
|
+
forEachReachableStatement(reachable, file, visit);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* VALUE-FLOW evidence for the two alias shapes `isBindingOnlyReference`
|
|
460
|
+
* treats as non-escapes: which functions are published under a member
|
|
461
|
+
* symbol, and which are bare-returned from a selector. Built once --
|
|
462
|
+
* see `AliasEvidence`'s own doc for why this does not need the round loop.
|
|
463
|
+
*/
|
|
464
|
+
const aliasEvidence = indexAliasEvidence(checker, files, reachable, indexNamedCallables(checker, files, reachable));
|
|
465
|
+
// A reassignment makes a parameter's value differ from the argument's -- but
|
|
466
|
+
// not, on its own, its TYPE. `function f(r, a) { r *= a; }` called as
|
|
467
|
+
// `f(1, 2)` holds a number on entry and a number after the write, and
|
|
468
|
+
// refusing it outright bound neither: three's
|
|
469
|
+
// `setClear( r, g, b, a, premultipliedAlpha )` lost all five parameters
|
|
470
|
+
// because three of them are scaled in the body. An assignment is a WRITE to
|
|
471
|
+
// the parameter's cell, exactly as a `let`'s later assignment is a write to
|
|
472
|
+
// its cell, and `writeSetTypeOf` already answers that shape by joining every
|
|
473
|
+
// write. So the assigned values join the call-site arguments and face the
|
|
474
|
+
// same agreement test -- `x = 'text'` against `f(1)` still refuses, as a
|
|
475
|
+
// disagreement rather than as a category.
|
|
476
|
+
//
|
|
477
|
+
// What survives as a category is a reassignment whose written value this
|
|
478
|
+
// layer never produced (a destructuring or iteration binding it does not
|
|
479
|
+
// open). There the cell holds something unstated, and no join can close it.
|
|
480
|
+
// That test is a fact about the program, asked here once rather than once
|
|
481
|
+
// per round, and the refusal keeps naming the parameter.
|
|
482
|
+
const notReassigned = [];
|
|
483
|
+
const reassigned = [];
|
|
484
|
+
for (const candidate of candidates) {
|
|
485
|
+
const symbol = checker.getSymbolAtLocation(candidate.parameter.name);
|
|
486
|
+
if (symbol && assigned.has(symbol) && assignedEvidence.get(symbol) == null)
|
|
487
|
+
reassigned.push(candidate);
|
|
488
|
+
else
|
|
489
|
+
notReassigned.push(candidate);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Every method declaration that OVERRIDES a given base-class method, keyed
|
|
493
|
+
* by the base's own declaration.
|
|
494
|
+
*
|
|
495
|
+
* A call is written against the declaration the checker resolves it to,
|
|
496
|
+
* which for `this.interpolate_( i1, t0, t, t1 )` inside `Interpolant` is
|
|
497
|
+
* `Interpolant`'s own declaration -- never `LinearInterpolant`'s override,
|
|
498
|
+
* even though that override is what actually runs. The override therefore
|
|
499
|
+
* looks callerless (`no-call-site`) and its parameters stay `any`, which is
|
|
500
|
+
* how three's whole interpolant, loader and curve hierarchy stays dynamic:
|
|
501
|
+
* `interpolate_`, `load`, `getTangentAt` are each defined once with real
|
|
502
|
+
* callers on the base and re-declared with none on every subclass.
|
|
503
|
+
*
|
|
504
|
+
* Inheriting the base's calls is sound because it states LESS than the
|
|
505
|
+
* program does, not more: a virtual call may dispatch into ANY override, so
|
|
506
|
+
* every override's parameter storage must already accept what that call
|
|
507
|
+
* passes. Evidence is only ever ADDED -- the override's own direct calls are
|
|
508
|
+
* still collected, both sets feed the same `widestOf` join, and a genuine
|
|
509
|
+
* disagreement between them refuses with `call-sites-disagree` rather than
|
|
510
|
+
* picking a side. An override with more parameters than the base is passed
|
|
511
|
+
* fewer arguments than it declares, which `call-passes-no-argument` already
|
|
512
|
+
* refuses; that is a refusal replacing a refusal, never a binding.
|
|
513
|
+
*
|
|
514
|
+
* Built once, outside the fixpoint: class inheritance is a fact about the
|
|
515
|
+
* program's syntax, so unlike call attribution it cannot improve as
|
|
516
|
+
* bindings accumulate.
|
|
517
|
+
*/
|
|
518
|
+
const overridesOfBaseMethod = (() => {
|
|
519
|
+
const overrides = new Map();
|
|
520
|
+
const record = (base, override) => {
|
|
521
|
+
const existing = overrides.get(base);
|
|
522
|
+
if (existing)
|
|
523
|
+
existing.push(override);
|
|
524
|
+
else
|
|
525
|
+
overrides.set(base, [override]);
|
|
526
|
+
};
|
|
527
|
+
const link = (method) => {
|
|
528
|
+
const owner = method.parent;
|
|
529
|
+
if (!ts.isClassDeclaration(owner) && !ts.isClassExpression(owner))
|
|
530
|
+
return;
|
|
531
|
+
if (!ts.isIdentifier(method.name))
|
|
532
|
+
return;
|
|
533
|
+
const name = method.name.text;
|
|
534
|
+
const seen = new Set();
|
|
535
|
+
// `getBaseTypes` READS `resolvedBaseTypes` and is defined only for a
|
|
536
|
+
// class-or-interface type. Handing it any other object type -- or the
|
|
537
|
+
// STATIC side of a class, which `getTypeAtLocation(classDeclaration)`
|
|
538
|
+
// returns -- throws rather than answering, so both are tested here.
|
|
539
|
+
const classOrInterface = (type) => type !== undefined &&
|
|
540
|
+
(type.flags & ts.TypeFlags.Object) !== 0 &&
|
|
541
|
+
(type.objectFlags & ts.ObjectFlags.ClassOrInterface) !== 0;
|
|
542
|
+
const climb = (type, depth) => {
|
|
543
|
+
if (depth > 8)
|
|
544
|
+
return;
|
|
545
|
+
for (const base of checker.getBaseTypes(type) ?? []) {
|
|
546
|
+
if (!base || seen.has(base))
|
|
547
|
+
continue;
|
|
548
|
+
seen.add(base);
|
|
549
|
+
for (const declaration of checker.getPropertyOfType(base, name)?.declarations ?? []) {
|
|
550
|
+
if (ts.isMethodDeclaration(declaration) && declaration !== method)
|
|
551
|
+
record(declaration, method);
|
|
552
|
+
}
|
|
553
|
+
if (classOrInterface(base))
|
|
554
|
+
climb(base, depth + 1);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
const ownSymbol = owner.name ? checker.getSymbolAtLocation(owner.name) : checker.getTypeAtLocation(owner).getSymbol();
|
|
558
|
+
const ownType = ownSymbol ? checker.getDeclaredTypeOfSymbol(ownSymbol) : undefined;
|
|
559
|
+
if (classOrInterface(ownType))
|
|
560
|
+
climb(ownType, 0);
|
|
561
|
+
};
|
|
562
|
+
const walk = (node) => {
|
|
563
|
+
if (ts.isMethodDeclaration(node))
|
|
564
|
+
link(node);
|
|
565
|
+
ts.forEachChild(node, walk);
|
|
566
|
+
};
|
|
567
|
+
for (const file of files)
|
|
568
|
+
forEachReachableStatement(reachable, file, walk);
|
|
569
|
+
return overrides;
|
|
570
|
+
})();
|
|
571
|
+
/**
|
|
572
|
+
* `overridesOfBaseMethod` links a SUBCLASS method to the base it overrides
|
|
573
|
+
* through `extends`. This links the OTHER way one symbol gets more than one
|
|
574
|
+
* body: a class's own declared shape of a key alongside a `receiver.<name>
|
|
575
|
+
* = function ( ... ) { ... }` write that overrides it on ONE instance,
|
|
576
|
+
* never through inheritance. Three's `mesh.onBeforeRender = function (
|
|
577
|
+
* renderer, object ) { ... }` next to `Object3D`'s declared (empty)
|
|
578
|
+
* `onBeforeRender(){}` is exactly this: `object.onBeforeRender( this,
|
|
579
|
+
* object )` resolves, through the checker, to the STATED stub -- TypeScript
|
|
580
|
+
* has no flow model of "this one instance was later given its own property"
|
|
581
|
+
* -- so every call through an `Object3D`-typed receiver is attributed there
|
|
582
|
+
* and never to the override that actually runs and reads its parameters.
|
|
583
|
+
* `nameOfCallable` already draws the member name out of both shapes;
|
|
584
|
+
* grouping by the SYMBOL the checker resolves that name to (never by
|
|
585
|
+
* spelling) is what keeps this sound against an unrelated look-alike: an
|
|
586
|
+
* object literal's own `onBeforeRender(){}` gets its OWN anonymous-type
|
|
587
|
+
* property symbol, never `Object3D`'s, so it is never grouped with the
|
|
588
|
+
* class's declaration and never receives its calls.
|
|
589
|
+
*
|
|
590
|
+
* No separate "is this slot closed" gate is needed here: attributing a call
|
|
591
|
+
* between siblings only ever ADDS evidence, exactly as `overridesOfBaseMethod`
|
|
592
|
+
* argues above, and `escapeReason`'s own member-closure obligation --
|
|
593
|
+
* asked of the SAME symbol, over EVERY write to it -- already refuses
|
|
594
|
+
* whenever some other write into the slot cannot be named. This can only
|
|
595
|
+
* widen a join or trigger that existing refusal; it cannot narrow one.
|
|
596
|
+
*/
|
|
597
|
+
const siblingMemberDeclarations = (() => {
|
|
598
|
+
const bySymbol = new Map();
|
|
599
|
+
const record = (declaration) => {
|
|
600
|
+
const name = nameOfCallable(declaration);
|
|
601
|
+
const symbol = name && checker.getSymbolAtLocation(name);
|
|
602
|
+
if (!symbol)
|
|
603
|
+
return;
|
|
604
|
+
const existing = bySymbol.get(symbol);
|
|
605
|
+
if (existing)
|
|
606
|
+
existing.push(declaration);
|
|
607
|
+
else
|
|
608
|
+
bySymbol.set(symbol, [declaration]);
|
|
609
|
+
};
|
|
610
|
+
const walk = (node) => {
|
|
611
|
+
if ((ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node)) &&
|
|
612
|
+
'body' in node &&
|
|
613
|
+
node.body !== undefined)
|
|
614
|
+
record(node);
|
|
615
|
+
ts.forEachChild(node, walk);
|
|
616
|
+
};
|
|
617
|
+
for (const file of files)
|
|
618
|
+
forEachReachableStatement(reachable, file, walk);
|
|
619
|
+
const result = new Map();
|
|
620
|
+
for (const declarations of bySymbol.values()) {
|
|
621
|
+
if (declarations.length < 2)
|
|
622
|
+
continue;
|
|
623
|
+
for (const declaration of declarations)
|
|
624
|
+
result.set(declaration, declarations.filter((other) => other !== declaration));
|
|
625
|
+
}
|
|
626
|
+
return result;
|
|
627
|
+
})();
|
|
628
|
+
if (process.env['GEA_INDEX_TIMING']) {
|
|
629
|
+
process.stderr.write(`[INDEX] parameter-binding program index built in ${(performance.now() - startedAt).toFixed(0)}ms\n`);
|
|
630
|
+
}
|
|
631
|
+
return Object.freeze({
|
|
632
|
+
implicitArgumentsUses: argumentsObjects.usesByOwner,
|
|
633
|
+
valueFlow,
|
|
634
|
+
candidates,
|
|
635
|
+
restParameterCandidates,
|
|
636
|
+
notReassigned,
|
|
637
|
+
reassigned,
|
|
638
|
+
omissionSites,
|
|
639
|
+
assigned,
|
|
640
|
+
assignedEvidence,
|
|
641
|
+
allCalls,
|
|
642
|
+
checkerAttribution,
|
|
643
|
+
invocationOperands,
|
|
644
|
+
aliasEvidence,
|
|
645
|
+
overridesOfBaseMethod,
|
|
646
|
+
siblingMemberDeclarations
|
|
647
|
+
});
|
|
648
|
+
};
|
|
649
|
+
/**
|
|
650
|
+
* The declared tuple element a call's LAST argument, a spread of a closed-tuple
|
|
651
|
+
* rest parameter, supplies at argument position `index`, or `null` when the
|
|
652
|
+
* call has no such spread or `index` lies before it or past its arity. Only a
|
|
653
|
+
* final spread: a written argument after a spread would land at a position
|
|
654
|
+
* only the spread's runtime length knows.
|
|
655
|
+
*/
|
|
656
|
+
const declaredTupleSpreadPositionAt = (checker, args, index) => {
|
|
657
|
+
const spreadAt = args.findIndex(ts.isSpreadElement);
|
|
658
|
+
if (spreadAt === -1 || spreadAt !== args.length - 1 || index < spreadAt)
|
|
659
|
+
return null;
|
|
660
|
+
const spread = args[spreadAt];
|
|
661
|
+
if (!spread || !ts.isSpreadElement(spread) || !ts.isIdentifier(spread.expression))
|
|
662
|
+
return null;
|
|
663
|
+
const declaration = checker.getSymbolAtLocation(spread.expression)?.valueDeclaration;
|
|
664
|
+
if (!declaration || !ts.isParameter(declaration))
|
|
665
|
+
return null;
|
|
666
|
+
return declaredClosedTupleRestElementsOf(checker, declaration)?.[index - spreadAt] ?? null;
|
|
667
|
+
};
|
|
668
|
+
export const censusParameterBindings = (checker, files, reachable,
|
|
669
|
+
/**
|
|
670
|
+
* A previously composed census this round may read from, the same way
|
|
671
|
+
* `withReturnBindings` and `withLocalBindings` already take one.
|
|
672
|
+
*
|
|
673
|
+
* `censusParameterBindings` used to be the one census with no upstream at
|
|
674
|
+
* all: `return-bindings.ts` and `local-bindings.ts` both compose OVER its
|
|
675
|
+
* output, but nothing ever composed back INTO it, so it answered every
|
|
676
|
+
* question -- including "what does this call site's argument hold" -- with
|
|
677
|
+
* only the checker's own evidence and its own internal fixpoint. That is
|
|
678
|
+
* why `texture`, `attribute` and `geometry` stayed `any` in the three.js app even
|
|
679
|
+
* after the other two censuses learned their receivers: those parameters'
|
|
680
|
+
* call sites pass expressions -- `state.buffers`, `const me = m.elements`
|
|
681
|
+
* one step removed -- whose types only exist once a LATER census has run,
|
|
682
|
+
* and by the time it runs this one has already finished.
|
|
683
|
+
*
|
|
684
|
+
* Feeding a prior round's composed census in here as `upstream` closes that
|
|
685
|
+
* gap for a second round: `known()` below consults it exactly where it
|
|
686
|
+
* already falls back from "the checker has no answer" to "keep looking",
|
|
687
|
+
* ahead of this census's own machinery reconstructing one from scratch.
|
|
688
|
+
* `emptyParameterBindingCensus` (round one, and every other caller) makes
|
|
689
|
+
* this identical to the unparameterized behaviour before -- `?? null` never
|
|
690
|
+
* changes an answer this module already had.
|
|
691
|
+
*
|
|
692
|
+
* ⛔ This is also the reason `binding-fixpoint.ts` carrying only
|
|
693
|
+
* `.parameters` across the round boundary (never `.facts.valueFlow`) does
|
|
694
|
+
* NOT stop a finished round's `ValueFlowIndex` from staying reachable.
|
|
695
|
+
* `known()` and `computeStatedTypeAt()` below close over BOTH `valueFlow`
|
|
696
|
+
* (this round's own) and `upstream` (this round's WHOLE argument, not a
|
|
697
|
+
* snapshot of it) so they can answer a node neither has seen yet. The
|
|
698
|
+
* object this function returns is exactly `upstream` for the NEXT round --
|
|
699
|
+
* so round N+1's `typeAt` closes over round N's `typeAt`, which closes over
|
|
700
|
+
* round N-1's, back to round one. `sourceValueSessionOf`
|
|
701
|
+
* (`flow/source-value-session.ts`) memoizes a ~1 GB dependency-solver
|
|
702
|
+
* session in a WeakMap keyed on the `ValueFlowIndex` object identity, so
|
|
703
|
+
* every round on this chain that was ever asked a call-target/receiver
|
|
704
|
+
* question keeps its whole session alive for as long as `settled.parameters`
|
|
705
|
+
* (`frontend.ts`) is reachable -- which is the rest of the compile. Measured
|
|
706
|
+
* on the three.js app: 4 rounds, 4 live `[SOLVER]` sessions at exit, not 1.
|
|
707
|
+
*
|
|
708
|
+
* Not cut here. Forcing `known()`/`computeStatedTypeAt()` to resolve eagerly
|
|
709
|
+
* instead of lazily falling through to `upstream` would answer a node
|
|
710
|
+
* before the round that can actually answer it has run -- the exact
|
|
711
|
+
* pending-read-as-decided collapse `SEMANTIC-AUTHORITY.md` §2(b) names.
|
|
712
|
+
* Truncating the chain (answering from this round's own evidence only,
|
|
713
|
+
* dropping `upstream` once this round stops being the fixpoint's active
|
|
714
|
+
* `previousCensus`) is answer-preserving only if no node reachable after
|
|
715
|
+
* that point was going to need a fact this round's own machinery never
|
|
716
|
+
* independently derived -- true for the RECORDED node set the convergence
|
|
717
|
+
* check in `binding-fixpoint.ts` verifies, unproven for the unbounded set of
|
|
718
|
+
* nodes `frontend.ts` and every downstream producer query afterward, and
|
|
719
|
+
* unverifiable here since the emitted-set gate is off-limits mid-measurement.
|
|
720
|
+
* A real fix has `typeAt`/`statedTypeAt`/`patternReadTypeAt` take the
|
|
721
|
+
* requesting round's own `ValueFlowIndex` as a call-time argument instead of
|
|
722
|
+
* a captured one, which is a migration of the shared `ParameterBindingCensus`
|
|
723
|
+
* surface (`return-bindings.ts`, `local-bindings.ts`, `field-bindings.ts`,
|
|
724
|
+
* `structural.ts`, every producer that calls `typeAt`), not a change
|
|
725
|
+
* confined to this file.
|
|
726
|
+
*/
|
|
727
|
+
upstream = emptyParameterBindingCensus,
|
|
728
|
+
/**
|
|
729
|
+
* The program index every round shares. Defaulted so a caller that runs the
|
|
730
|
+
* census once needs no change; `frontend.ts`, which runs it up to eight
|
|
731
|
+
* times, builds it once and passes it.
|
|
732
|
+
*/
|
|
733
|
+
index = indexParameterBindingProgram(checker, files, reachable),
|
|
734
|
+
/**
|
|
735
|
+
* The current round's value-flow facts. The reusable program index owns
|
|
736
|
+
* syntax discovery, not the evolving call attribution and collection edges.
|
|
737
|
+
* A one-shot census can use its index's flow; a composed census must receive
|
|
738
|
+
* the same round snapshot as the field, local, return and collection censuses.
|
|
739
|
+
*/
|
|
740
|
+
valueFlow = index.valueFlow) => {
|
|
741
|
+
/** Every call this program makes, grouped by the declaration its signature resolved to. */
|
|
742
|
+
const callsByDeclaration = new Map();
|
|
743
|
+
const callTargets = new Map();
|
|
744
|
+
const resolvedCallDeclarations = new Map();
|
|
745
|
+
/**
|
|
746
|
+
* A `.call`/`.apply` wrapper whose receiver's checker type gave
|
|
747
|
+
* `unwrapExplicitThisCall` no call signature to unwrap statically (an
|
|
748
|
+
* untyped JS array element, `array[ i ].call( this, event )`), authenticated
|
|
749
|
+
* here instead by the SAME closed-array proof `aliasDeclarationsFor` already
|
|
750
|
+
* asks for TYPE attribution just below -- see its own comment. Feeds
|
|
751
|
+
* `indexValueFlow`'s `censusExplicitThisAt` on the NEXT round, so the
|
|
752
|
+
* operand frame this round proved sound becomes the one the whole flow
|
|
753
|
+
* index (argument-to-parameter binding included, not just target
|
|
754
|
+
* resolution) is built from, rather than staying a second reading nobody
|
|
755
|
+
* downstream of `site.operands` would ever see.
|
|
756
|
+
*/
|
|
757
|
+
const pendingExplicitThisReadings = new Map();
|
|
758
|
+
/**
|
|
759
|
+
* Built once by `indexParameterBindingProgram` and shared by every round --
|
|
760
|
+
* see its doc. `callsByDeclaration` above is the one index that is NOT
|
|
761
|
+
* shared: `attributeCalls` rewrites it as bindings improve.
|
|
762
|
+
*/
|
|
763
|
+
const { allCalls, checkerAttribution, invocationOperands, aliasEvidence, overridesOfBaseMethod, siblingMemberDeclarations, assignedEvidence } = index;
|
|
764
|
+
const protocolLedger = deferredIntrinsicProtocolLedgerOf(valueFlow);
|
|
765
|
+
const protocolRequirements = new Map();
|
|
766
|
+
const upstreamProtocolRequirements = protocolLedger?.requirements() ?? [];
|
|
767
|
+
/** What each bound parameter declaration now holds. */
|
|
768
|
+
const bindings = new Map();
|
|
769
|
+
/** Synthesized union arms for a disjointly-disagreeing parameter -- see `agreedArgumentType`. */
|
|
770
|
+
const unionArms = new Map();
|
|
771
|
+
// A synthesized parameter union is also a value read by sibling parameter
|
|
772
|
+
// inference. Publishing only unionArmsAt lets structural layout see it while
|
|
773
|
+
// a forwarding call sees no type at all. Materialize the same admitted arms
|
|
774
|
+
// through the shared checker union helper, without adding new alternatives.
|
|
775
|
+
const unionTypes = new Map();
|
|
776
|
+
const parameterTypeOf = (parameter) => {
|
|
777
|
+
const bound = bindings.get(parameter);
|
|
778
|
+
if (bound)
|
|
779
|
+
return bound;
|
|
780
|
+
const arms = unionArms.get(parameter);
|
|
781
|
+
if (!arms)
|
|
782
|
+
return null;
|
|
783
|
+
if (!unionTypes.has(parameter))
|
|
784
|
+
unionTypes.set(parameter, disjointUnionTypeOf(checker, arms));
|
|
785
|
+
return unionTypes.get(parameter) ?? null;
|
|
786
|
+
};
|
|
787
|
+
/** What each array-pattern element reads before its default -- see `patternReadTypeAt`. */
|
|
788
|
+
const patternReadTypes = new Map();
|
|
789
|
+
/** Physical object arms proven by flow narrowing of an open dynamic-object upper bound. */
|
|
790
|
+
const flowCarrierArms = new Map();
|
|
791
|
+
const flowCarrierBounds = new Map();
|
|
792
|
+
/**
|
|
793
|
+
* Whether every reference to this function's name is a call this census
|
|
794
|
+
* counted. A function nothing holds as a value has exactly the callers this
|
|
795
|
+
* enumeration found, which is what makes binding from them sound.
|
|
796
|
+
*/
|
|
797
|
+
/**
|
|
798
|
+
* A returned record is a real callable flow edge, not an unknown escape,
|
|
799
|
+
* when its property declaration and complete value-flow inventory show only
|
|
800
|
+
* attributed calls. This is the same negative proof as the direct-name
|
|
801
|
+
* check below, applied one level farther out: accepting the object literal
|
|
802
|
+
* merely because it *looks* like a return would let an unobserved
|
|
803
|
+
* `record[key]()` call narrow an implicit-arguments frame from a partial
|
|
804
|
+
* sample.
|
|
805
|
+
*/
|
|
806
|
+
/**
|
|
807
|
+
* Whether every value a cell can hold was ALLOCATED here -- following a
|
|
808
|
+
* write that merely FORWARDS another cell's value into it.
|
|
809
|
+
*
|
|
810
|
+
* A factory result reaches its reader by being PASSED. Three builds `state`
|
|
811
|
+
* once in `WebGLRenderer` and hands it to `WebGLTextures`, so the cell this
|
|
812
|
+
* proof actually examines is `WebGLTextures`'s own `state` parameter, whose
|
|
813
|
+
* only write is the argument edge naming `state` -- a bare identifier.
|
|
814
|
+
* Refusing that outright refused the ordinary way a record travels, which
|
|
815
|
+
* is the very case the caller's own comment describes, and it proved
|
|
816
|
+
* nothing: an identifier names a cell whose writes this same index holds,
|
|
817
|
+
* so the question is answerable one hop out rather than unanswerable. All
|
|
818
|
+
* ten of `WebGLState`'s `arguments` shims died here.
|
|
819
|
+
*
|
|
820
|
+
* A forward is followed only to a cell whose OWN writes satisfy the same
|
|
821
|
+
* test, so nothing is admitted that an allocation does not ultimately back.
|
|
822
|
+
* A cycle answers `true` because some other write in it must still be an
|
|
823
|
+
* allocation for the cell to hold anything at all; every other value kind
|
|
824
|
+
* stays the refusal it was.
|
|
825
|
+
*/
|
|
826
|
+
const cellHoldsOnlyAllocations = (symbol, seen) => {
|
|
827
|
+
if (seen.has(symbol))
|
|
828
|
+
return true;
|
|
829
|
+
seen.add(symbol);
|
|
830
|
+
// A PARAMETER's write inventory is its argument edges, so it is complete
|
|
831
|
+
// only while every call site is counted -- the same obligation the
|
|
832
|
+
// factory itself is already held to a few lines above.
|
|
833
|
+
const declaration = symbol.valueDeclaration;
|
|
834
|
+
if (declaration && ts.isParameter(declaration)) {
|
|
835
|
+
const owner = ts.findAncestor(declaration, isTrackedCallable);
|
|
836
|
+
if (!owner || escapeReason(owner, true) !== null)
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
for (const write of valueFlow.writesToSymbol(symbol)) {
|
|
840
|
+
if (write.slot !== 'whole' || write.value === null)
|
|
841
|
+
continue;
|
|
842
|
+
const value = write.value;
|
|
843
|
+
if (ts.isCallExpression(value) || ts.isNewExpression(value) || ts.isObjectLiteralExpression(value))
|
|
844
|
+
continue;
|
|
845
|
+
if (ts.isIdentifier(value)) {
|
|
846
|
+
const forwarded = valueFlow.targetOf(value)?.symbol;
|
|
847
|
+
if (forwarded && cellHoldsOnlyAllocations(forwarded, seen))
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
return true;
|
|
853
|
+
};
|
|
854
|
+
/** Why the published-member proof gave up, for `GEA_ESCAPE_DEBUG`; always `false`, so it reads as the refusal it replaces. */
|
|
855
|
+
const escapeTrace = (member, reason) => {
|
|
856
|
+
if (process.env['GEA_ESCAPE_DEBUG'])
|
|
857
|
+
console.error(`[ESCAPE-PUBLISHED] ${member.name} ${reason}`);
|
|
858
|
+
return false;
|
|
859
|
+
};
|
|
860
|
+
const publishedMemberUsesAreCounted = (member, counted) => {
|
|
861
|
+
// Property symbols are late-bound by the checker: two lookups of the
|
|
862
|
+
// same inferred member commonly produce distinct Symbol objects. The
|
|
863
|
+
// declaration node is the stable identity shared by value-flow and the
|
|
864
|
+
// alias index, so use it for the completeness proof.
|
|
865
|
+
const declarations = member.declarations ?? [];
|
|
866
|
+
const accesses = declarations.flatMap((declaration) => valueFlow.referencesToDeclaration(declaration));
|
|
867
|
+
// An empty set is not a proof. It means this property's value-flow index
|
|
868
|
+
// could not connect the publication to a use, so retaining the dynamic
|
|
869
|
+
// checker frame is safer than inferring from no calls.
|
|
870
|
+
if (accesses.length === 0)
|
|
871
|
+
return escapeTrace(member, 'no-accesses');
|
|
872
|
+
const publication = declarations[0];
|
|
873
|
+
const factory = publication && ts.findAncestor(publication, isTrackedCallable);
|
|
874
|
+
const objectLiteral = publication?.parent;
|
|
875
|
+
const directReturn = objectLiteral &&
|
|
876
|
+
ts.isObjectLiteralExpression(objectLiteral) &&
|
|
877
|
+
ts.isReturnStatement(objectLiteral.parent) &&
|
|
878
|
+
objectLiteral.parent.expression === objectLiteral;
|
|
879
|
+
if (!directReturn)
|
|
880
|
+
return escapeTrace(member, 'not-a-direct-return');
|
|
881
|
+
// Every factory result is an ownership instance. If the factory has more
|
|
882
|
+
// than one call site, a result that never exposes this member can still
|
|
883
|
+
// escape through an unknown API. A single attributed call is the smallest
|
|
884
|
+
// closed publication set this proof can establish without inventing a
|
|
885
|
+
// result-to-receiver correspondence.
|
|
886
|
+
if (!factory)
|
|
887
|
+
return escapeTrace(member, 'no-factory');
|
|
888
|
+
if ((callsByDeclaration.get(factory)?.length ?? 0) !== 1)
|
|
889
|
+
return escapeTrace(member, `factory-call-sites=${callsByDeclaration.get(factory)?.length ?? 0}`);
|
|
890
|
+
const factoryEscape = escapeReason(factory, true);
|
|
891
|
+
if (factoryEscape !== null)
|
|
892
|
+
return escapeTrace(member, `factory-escapes:${factoryEscape}`);
|
|
893
|
+
const receivers = new Set();
|
|
894
|
+
for (const reference of accesses) {
|
|
895
|
+
if (ts.isElementAccessExpression(reference))
|
|
896
|
+
return false;
|
|
897
|
+
if (ts.isPropertyAccessExpression(reference)) {
|
|
898
|
+
if (!counted.has(reference))
|
|
899
|
+
return false;
|
|
900
|
+
// A receiver with no named cell (`factory().method()` or
|
|
901
|
+
// `{ api: factory() }.api.method()`) has no complete use inventory.
|
|
902
|
+
// Refuse it rather than treating the one visible call as the whole
|
|
903
|
+
// factory-result flow.
|
|
904
|
+
if (!ts.isIdentifier(reference.expression))
|
|
905
|
+
return escapeTrace(member, 'receiver-not-a-named-cell');
|
|
906
|
+
const receiver = valueFlow.targetOf(reference.expression);
|
|
907
|
+
if (receiver?.symbol)
|
|
908
|
+
receivers.add(receiver.symbol);
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (ts.isIdentifier(reference)) {
|
|
912
|
+
const parent = reference.parent;
|
|
913
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === reference && counted.has(parent))
|
|
914
|
+
continue;
|
|
915
|
+
if (ts.isPropertyAssignment(parent) && (parent.name === reference || parent.initializer === reference))
|
|
916
|
+
continue;
|
|
917
|
+
if (ts.isShorthandPropertyAssignment(parent) && parent.name === reference)
|
|
918
|
+
continue;
|
|
919
|
+
return escapeTrace(member, `identifier-use:${ts.SyntaxKind[parent.kind]}`);
|
|
920
|
+
}
|
|
921
|
+
return escapeTrace(member, `reference-kind:${ts.SyntaxKind[reference.kind]}`);
|
|
922
|
+
}
|
|
923
|
+
// A factory result can be safely kept as a record while its members are
|
|
924
|
+
// read, but passing the record itself through another cell/API is an
|
|
925
|
+
// escape the member census cannot enumerate. Check the shared reference
|
|
926
|
+
// inventory for every named receiver discovered above; a property read
|
|
927
|
+
// is a closed use, every other use is a leak (including object spread,
|
|
928
|
+
// assignment, and an unknown call argument).
|
|
929
|
+
if (receivers.size === 0)
|
|
930
|
+
return escapeTrace(member, 'no-named-receivers');
|
|
931
|
+
for (const receiver of receivers) {
|
|
932
|
+
if (!cellHoldsOnlyAllocations(receiver, new Set()))
|
|
933
|
+
return escapeTrace(member, 'receiver-holds-more-than-allocations');
|
|
934
|
+
const references = valueFlow.referencesToSymbol(receiver);
|
|
935
|
+
if (references.length === 0)
|
|
936
|
+
return escapeTrace(member, 'receiver-has-no-references');
|
|
937
|
+
for (const reference of references) {
|
|
938
|
+
if (!ts.isIdentifier(reference))
|
|
939
|
+
continue;
|
|
940
|
+
const type = checker.getTypeAtLocation(reference);
|
|
941
|
+
if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)
|
|
942
|
+
return escapeTrace(member, `receiver-${receiver.name}-is-any`);
|
|
943
|
+
const parent = reference.parent;
|
|
944
|
+
if (ts.isVariableDeclaration(parent) && parent.name === reference)
|
|
945
|
+
continue;
|
|
946
|
+
if (ts.isBindingElement(parent) && parent.name === reference)
|
|
947
|
+
continue;
|
|
948
|
+
// A parameter's own name declares the cell; it is no more a use of the
|
|
949
|
+
// record than a variable declaration's name is. Omitting it refused
|
|
950
|
+
// every receiver that arrives as a parameter, which is every receiver
|
|
951
|
+
// a factory result is passed to.
|
|
952
|
+
if (ts.isParameter(parent) && parent.name === reference)
|
|
953
|
+
continue;
|
|
954
|
+
if (ts.isPropertyAccessExpression(parent) && parent.expression === reference)
|
|
955
|
+
continue;
|
|
956
|
+
return escapeTrace(member, `receiver-use:${ts.SyntaxKind[parent.kind]}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return true;
|
|
960
|
+
};
|
|
961
|
+
/** The member symbol published by an object-literal property value. */
|
|
962
|
+
const publishedMemberOf = (reference) => {
|
|
963
|
+
const parent = reference.parent;
|
|
964
|
+
if (ts.isPropertyAssignment(parent) && parent.initializer === reference) {
|
|
965
|
+
const name = parent.name;
|
|
966
|
+
if (!ts.isIdentifier(name) && !ts.isStringLiteralLike(name) && !ts.isNumericLiteral(name))
|
|
967
|
+
return null;
|
|
968
|
+
return checker.getTypeAtLocation(parent.parent).getProperty(name.text) ?? null;
|
|
969
|
+
}
|
|
970
|
+
if (ts.isShorthandPropertyAssignment(parent) && parent.name === reference) {
|
|
971
|
+
return checker.getTypeAtLocation(parent.parent).getProperty(parent.name.text) ?? null;
|
|
972
|
+
}
|
|
973
|
+
return null;
|
|
974
|
+
};
|
|
975
|
+
const isDirectReturnedPublication = (reference) => {
|
|
976
|
+
const parent = reference.parent;
|
|
977
|
+
if (!ts.isPropertyAssignment(parent) && !ts.isShorthandPropertyAssignment(parent))
|
|
978
|
+
return false;
|
|
979
|
+
const objectLiteral = parent.parent;
|
|
980
|
+
return (ts.isObjectLiteralExpression(objectLiteral) &&
|
|
981
|
+
ts.isReturnStatement(objectLiteral.parent) &&
|
|
982
|
+
objectLiteral.parent.expression === objectLiteral);
|
|
983
|
+
};
|
|
984
|
+
/** Match an inferred member through its stable declaration, not Symbol identity. */
|
|
985
|
+
const publishedDeclarationsFor = (member) => {
|
|
986
|
+
const direct = aliasEvidence.publishedUnderMember.get(member);
|
|
987
|
+
if (direct)
|
|
988
|
+
return direct;
|
|
989
|
+
const declarations = member.declarations ?? [];
|
|
990
|
+
if (declarations.length === 0)
|
|
991
|
+
return undefined;
|
|
992
|
+
let result;
|
|
993
|
+
for (const [candidate, values] of aliasEvidence.publishedUnderMember) {
|
|
994
|
+
if (!(candidate.declarations ?? []).some((declaration) => declarations.includes(declaration)))
|
|
995
|
+
continue;
|
|
996
|
+
if (!result)
|
|
997
|
+
result = new Set();
|
|
998
|
+
for (const value of values)
|
|
999
|
+
result.add(value);
|
|
1000
|
+
}
|
|
1001
|
+
return result;
|
|
1002
|
+
};
|
|
1003
|
+
const callbackContracts = new Map();
|
|
1004
|
+
const memberOpenUses = new Map();
|
|
1005
|
+
const contractsFor = (declaration) => {
|
|
1006
|
+
const cached = callbackContracts.get(declaration);
|
|
1007
|
+
if (cached !== undefined)
|
|
1008
|
+
return cached;
|
|
1009
|
+
const result = callbackParameterContractsFor(checker, valueFlow, declaration, new Set(callsByDeclaration.get(declaration) ?? []));
|
|
1010
|
+
callbackContracts.set(declaration, result);
|
|
1011
|
+
return result;
|
|
1012
|
+
};
|
|
1013
|
+
const escapeReason = (declaration, requireCountedReferences = false) => {
|
|
1014
|
+
memberOpenUses.delete(declaration);
|
|
1015
|
+
const name = nameOfCallable(declaration);
|
|
1016
|
+
if (!name)
|
|
1017
|
+
return `function-escapes:unnamed:${declaration.parent ? ts.SyntaxKind[declaration.parent.kind] : 'root'}`;
|
|
1018
|
+
const symbol = checker.getSymbolAtLocation(name);
|
|
1019
|
+
if (!symbol)
|
|
1020
|
+
return 'function-escapes:no-symbol';
|
|
1021
|
+
const calls = callsByDeclaration.get(declaration) ?? [];
|
|
1022
|
+
// The receiving formal publishes a complete callback input contract only
|
|
1023
|
+
// when every use of this function is accounted for by the shared flow.
|
|
1024
|
+
// This is parameter evidence, not the stricter single-publication proof
|
|
1025
|
+
// used by factory/receiver analyses elsewhere in this census.
|
|
1026
|
+
const memberClosed = (member) => hasClosedMemberCallableUses(checker, valueFlow, member, new Set(calls), memberClosureReceiverTypeAt, implicitArgumentsUsesAt, (reference, kind) => {
|
|
1027
|
+
const path = memberOpenUses.get(declaration) ?? [];
|
|
1028
|
+
if (!path.some((entry) => entry.reference === reference && entry.kind === kind))
|
|
1029
|
+
path.push({ reference, kind });
|
|
1030
|
+
memberOpenUses.set(declaration, path);
|
|
1031
|
+
});
|
|
1032
|
+
// A method is carried by every instance of its class family, and one that
|
|
1033
|
+
// never names `.m` still hands unknown code a way to call it: after
|
|
1034
|
+
// `globalThis.unknownConsumer( new A() )` that code can run `m` with any
|
|
1035
|
+
// argument, or replace it on the shared prototype. The member-name
|
|
1036
|
+
// inventory below sees only the mentions; the member proof also walks
|
|
1037
|
+
// every construction of the family -- the obligation a function-valued
|
|
1038
|
+
// slot already owes just below.
|
|
1039
|
+
// ⛔ UNSOUND MEASUREMENT ARM (`GEA_MEMBER_ESCAPE_FORCE=<member>[,<member>]|*`):
|
|
1040
|
+
// prices what the member-closure obligation alone is holding open, for one
|
|
1041
|
+
// named method or for all of them. Never set it for a build whose output is
|
|
1042
|
+
// kept -- it admits a method the program can replace, or call with
|
|
1043
|
+
// arguments no site here counted.
|
|
1044
|
+
const escapeForce = process.env['GEA_MEMBER_ESCAPE_FORCE'];
|
|
1045
|
+
const forced = escapeForce !== undefined && (escapeForce === '*' || escapeForce.split(',').includes(symbol.getName()));
|
|
1046
|
+
if (ts.isMethodDeclaration(declaration) && !forced && !memberClosed(symbol))
|
|
1047
|
+
return 'function-escapes:uncounted-member-reference';
|
|
1048
|
+
// An export is a mention no expression spells. Code outside the stated
|
|
1049
|
+
// module set can call it -- `inProgramImportReferencesOf` answers null
|
|
1050
|
+
// then -- and every in-program importer's mention must be a counted call,
|
|
1051
|
+
// exactly as the declaring module's own mentions are below.
|
|
1052
|
+
if (ts.isFunctionDeclaration(declaration) && isModuleExportedDeclaration(checker, declaration, symbol)) {
|
|
1053
|
+
const imported = inProgramImportReferencesOf(checker, valueFlow, declaration);
|
|
1054
|
+
if (imported === null)
|
|
1055
|
+
return 'function-escapes:exported';
|
|
1056
|
+
const callees = new Set(calls.map((call) => (ts.isCallExpression(call) || ts.isNewExpression(call) ? call.expression : call)));
|
|
1057
|
+
if (!imported.every((mention) => callees.has(mention)))
|
|
1058
|
+
return 'function-escapes:uncounted-import';
|
|
1059
|
+
}
|
|
1060
|
+
if (!requireCountedReferences && contractsFor(declaration) !== null)
|
|
1061
|
+
return null;
|
|
1062
|
+
const publication = declaration.parent;
|
|
1063
|
+
const inlineMemberPublication = (ts.isPropertyAssignment(publication) && publication.initializer === declaration) ||
|
|
1064
|
+
(ts.isBinaryExpression(publication) &&
|
|
1065
|
+
publication.right === declaration &&
|
|
1066
|
+
publication.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
1067
|
+
(ts.isPropertyAccessExpression(publication.left) || ts.isElementAccessExpression(publication.left)));
|
|
1068
|
+
const publishedMember = ts.isPropertyAssignment(publication)
|
|
1069
|
+
? checker.getSymbolAtLocation(publication.name)
|
|
1070
|
+
: ts.isBinaryExpression(publication) && ts.isPropertyAccessExpression(publication.left)
|
|
1071
|
+
? checker.getSymbolAtLocation(publication.left.name)
|
|
1072
|
+
: ts.isBinaryExpression(publication) && ts.isElementAccessExpression(publication.left)
|
|
1073
|
+
? checker.getSymbolAtLocation(publication.left)
|
|
1074
|
+
: undefined;
|
|
1075
|
+
if ((ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration)) && inlineMemberPublication && !publishedMember)
|
|
1076
|
+
return 'function-escapes:unresolved-published-member';
|
|
1077
|
+
if ((ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration)) &&
|
|
1078
|
+
inlineMemberPublication &&
|
|
1079
|
+
(!publishedMember || !memberClosed(publishedMember)))
|
|
1080
|
+
return 'function-escapes:uncounted-member-reference';
|
|
1081
|
+
const counted = new Set(calls.map((call) => (ts.isCallExpression(call) || ts.isNewExpression(call) ? call.expression : call)));
|
|
1082
|
+
// Ordinary inference keeps its member-name inventory. The strict path
|
|
1083
|
+
// needs full declaration-keyed expression references to prove publication
|
|
1084
|
+
// closure, including shorthand values and receiver aliases.
|
|
1085
|
+
const references = requireCountedReferences
|
|
1086
|
+
? (symbol.declarations ?? []).flatMap((declaration) => valueFlow.referencesToDeclaration(declaration))
|
|
1087
|
+
: valueFlow.memberReferencesToSymbol(symbol);
|
|
1088
|
+
for (const reference of references) {
|
|
1089
|
+
if (counted.has(reference))
|
|
1090
|
+
continue;
|
|
1091
|
+
if (requireCountedReferences) {
|
|
1092
|
+
if (reference === name || isTypePositionReference(reference))
|
|
1093
|
+
continue;
|
|
1094
|
+
const access = reference.parent;
|
|
1095
|
+
// `export { f }`, `export { f as g } from`, `import { f }`: the binding,
|
|
1096
|
+
// not a use. It is closed when nothing outside the stated module set
|
|
1097
|
+
// can reach it and every importer mention it leads to is counted.
|
|
1098
|
+
if (ts.isExportSpecifier(access) || ts.isImportSpecifier(access) || ts.isImportClause(access) || ts.isNamespaceImport(access)) {
|
|
1099
|
+
const exposure = inProgramImportReferencesOf(checker, valueFlow, ts.isExportSpecifier(access) ? access : declaration);
|
|
1100
|
+
if (exposure !== null && exposure.every((mention) => counted.has(mention)))
|
|
1101
|
+
continue;
|
|
1102
|
+
return 'function-escapes:uncounted-import';
|
|
1103
|
+
}
|
|
1104
|
+
if ((ts.isCallExpression(access) || ts.isNewExpression(access)) && isCalleeOf(reference, access) && counted.has(access.expression))
|
|
1105
|
+
continue;
|
|
1106
|
+
if (ts.isPropertyAccessExpression(access) && access.name === reference && counted.has(access))
|
|
1107
|
+
continue;
|
|
1108
|
+
const publishedMember = publishedMemberOf(reference);
|
|
1109
|
+
const publicationOwner = publishedMember?.declarations?.[0] && ts.findAncestor(publishedMember.declarations[0], isTrackedCallable);
|
|
1110
|
+
if (publishedMember && publicationOwner === declaration && isDirectReturnedPublication(reference))
|
|
1111
|
+
continue;
|
|
1112
|
+
// The record's uses reach past one cell -- three's `WebGLState` is
|
|
1113
|
+
// handed to `WebGLTextures` and kept on `renderer.state` -- and the
|
|
1114
|
+
// shared member proof is the one that walks call arguments and
|
|
1115
|
+
// fields: every mention of the slot a counted call, every receiver of
|
|
1116
|
+
// the record closed.
|
|
1117
|
+
if (publishedMember &&
|
|
1118
|
+
publishedDeclarationsFor(publishedMember)?.has(declaration) === true &&
|
|
1119
|
+
(publishedMemberUsesAreCounted(publishedMember, counted) || memberClosed(publishedMember)))
|
|
1120
|
+
continue;
|
|
1121
|
+
// WHICH half of the published-member proof failed is the only thing a
|
|
1122
|
+
// reader needs here, and the refusal string cannot carry it: all four
|
|
1123
|
+
// ways to miss spell the same word. `WebGLState`'s ten `arguments`
|
|
1124
|
+
// shims are refused here and nothing said which.
|
|
1125
|
+
if (process.env['GEA_ESCAPE_DEBUG']) {
|
|
1126
|
+
const site = reference.getSourceFile();
|
|
1127
|
+
console.error(`[ESCAPE] ${nameOfCallable(declaration)?.getText() ?? '<anonymous>'} uncounted at ` +
|
|
1128
|
+
`${site.fileName.split('/').pop()}:${site.getLineAndCharacterOfPosition(reference.getStart()).line + 1} ` +
|
|
1129
|
+
`"${reference.parent?.getText().replace(/\s+/g, ' ').slice(0, 60) ?? ''}" ` +
|
|
1130
|
+
`published=${publishedMember ? publishedMember.name : 'none'} ` +
|
|
1131
|
+
`owns=${publishedMember ? String(publishedDeclarationsFor(publishedMember)?.has(declaration) === true) : '-'} ` +
|
|
1132
|
+
`usesCounted=${publishedMember ? String(publishedMemberUsesAreCounted(publishedMember, counted)) : '-'} ` +
|
|
1133
|
+
`memberClosed=${publishedMember ? String(memberClosed(publishedMember)) : '-'}`);
|
|
1134
|
+
}
|
|
1135
|
+
return 'function-escapes:uncounted-reference';
|
|
1136
|
+
}
|
|
1137
|
+
if ((ts.isIdentifier(reference) || ts.isPrivateIdentifier(reference)) && isBindingOnlyReference(reference))
|
|
1138
|
+
continue;
|
|
1139
|
+
if (isTypePositionReference(reference))
|
|
1140
|
+
continue;
|
|
1141
|
+
const parent = reference.parent;
|
|
1142
|
+
if (parent && (ts.isCallExpression(parent) || ts.isNewExpression(parent)) && isCalleeOf(reference, parent))
|
|
1143
|
+
continue;
|
|
1144
|
+
return `function-escapes:${parent ? ts.SyntaxKind[parent.kind] : 'root'}`;
|
|
1145
|
+
}
|
|
1146
|
+
return null;
|
|
1147
|
+
};
|
|
1148
|
+
/**
|
|
1149
|
+
* A resolver, in one of the two strengths this census needs.
|
|
1150
|
+
*
|
|
1151
|
+
* `lateAssignment` is the difference, and it is a soundness boundary rather
|
|
1152
|
+
* than a tuning knob. `let extensions;` filled later by `extensions = new
|
|
1153
|
+
* WebGLExtensions( _gl )` tells you exactly what the CALL passes -- the call
|
|
1154
|
+
* is downstream of the assignment -- so binding a parameter from it is right.
|
|
1155
|
+
* It does not tell you what the variable's own CELL holds, because that cell
|
|
1156
|
+
* begins empty. Publishing the assignment's type for the cell asks the
|
|
1157
|
+
* compiler to convert `null` into a `class-ref`, which is what preflight
|
|
1158
|
+
* reported the moment this was tried both ways: twelve new
|
|
1159
|
+
* `binding-read-conversion:null->...` obligations, one per late-filled
|
|
1160
|
+
* binding in three's renderer.
|
|
1161
|
+
*
|
|
1162
|
+
* So the strong resolver propagates and the weak one publishes. A variable
|
|
1163
|
+
* the weak resolver will not answer for stays exactly as it is today --
|
|
1164
|
+
* boxed -- and the unboxing happens at the call boundary, where the value
|
|
1165
|
+
* really has arrived.
|
|
1166
|
+
*/
|
|
1167
|
+
/**
|
|
1168
|
+
* `this`, read at its own keyword inside an ordinary (non-static) class
|
|
1169
|
+
* method, checks as the POLYMORPHIC self type -- `typeToString` spells it
|
|
1170
|
+
* literally `this` -- because the checker is answering for every possible
|
|
1171
|
+
* subclass, not this one. That is the right answer for the METHOD's own
|
|
1172
|
+
* signature, but wrong the moment `this` is READ as a plain value and
|
|
1173
|
+
* handed somewhere else: `object.onBeforeRender( this, object )` passes
|
|
1174
|
+
* the calling `Renderer`, not an unspellable self-type, and a parameter
|
|
1175
|
+
* bound from it must carry the concrete class the same way any other
|
|
1176
|
+
* argument's checker type does.
|
|
1177
|
+
*
|
|
1178
|
+
* Mirrors `structural-receiver.ts`'s `implicitReceiverOf`, which resolves
|
|
1179
|
+
* the identical instance case to `checker.getDeclaredTypeOfSymbol` on the
|
|
1180
|
+
* owning class for the ABI's receiver frame; this is the same fact, read
|
|
1181
|
+
* for a value use rather than a calling convention. Deliberately narrow:
|
|
1182
|
+
* only the plain instance-method shape is resolved here, and everything
|
|
1183
|
+
* else -- static methods, object-literal methods, a `this` the checker
|
|
1184
|
+
* cannot place at all -- falls through to the checker's own answer
|
|
1185
|
+
* unchanged.
|
|
1186
|
+
*/
|
|
1187
|
+
// Memoized because it is a pure function of the node and this census's own
|
|
1188
|
+
// checker, and `known` -- which asks it for every `this` it meets -- runs
|
|
1189
|
+
// thousands of times per sweep. Each miss walks the AST to the enclosing
|
|
1190
|
+
// non-arrow function, which for a `this` deep inside a method body is the
|
|
1191
|
+
// whole chain of enclosing nodes, re-walked per mention.
|
|
1192
|
+
const concreteThisTypes = new WeakMap();
|
|
1193
|
+
const concreteThisTypeOf = (keyword) => {
|
|
1194
|
+
const known = concreteThisTypes.get(keyword);
|
|
1195
|
+
if (known !== undefined)
|
|
1196
|
+
return known;
|
|
1197
|
+
if (concreteThisTypes.has(keyword))
|
|
1198
|
+
return null;
|
|
1199
|
+
const answer = concreteThisTypeUncached(keyword);
|
|
1200
|
+
concreteThisTypes.set(keyword, answer);
|
|
1201
|
+
return answer;
|
|
1202
|
+
};
|
|
1203
|
+
const concreteThisTypeUncached = (keyword) => {
|
|
1204
|
+
const frame = ts.findAncestor(keyword, (node) => ts.isFunctionLike(node) && !ts.isArrowFunction(node));
|
|
1205
|
+
if (frame === undefined || !ts.isFunctionLike(frame))
|
|
1206
|
+
return null;
|
|
1207
|
+
if ((ts.getCombinedModifierFlags(frame) & ts.ModifierFlags.Static) !== 0)
|
|
1208
|
+
return null;
|
|
1209
|
+
const holder = frame.parent;
|
|
1210
|
+
if (!ts.isClassDeclaration(holder) && !ts.isClassExpression(holder))
|
|
1211
|
+
return null;
|
|
1212
|
+
if (!holder.name)
|
|
1213
|
+
return null;
|
|
1214
|
+
const symbol = checker.getSymbolAtLocation(holder.name);
|
|
1215
|
+
return symbol ? checker.getDeclaredTypeOfSymbol(symbol) : null;
|
|
1216
|
+
};
|
|
1217
|
+
/** `GEA_KNOWN_DEBUG=<identifier>` reports, for every mention of that name,
|
|
1218
|
+
* what the checker answers, whether the vacuous-evidence guard fires, and
|
|
1219
|
+
* what the upstream round already knew -- which is the one place a fact that
|
|
1220
|
+
* exists in the settled census but not in a proof shows itself. Read once:
|
|
1221
|
+
* `known` runs thousands of times per sweep. */
|
|
1222
|
+
const watchedBinding = process.env['GEA_KNOWN_DEBUG'];
|
|
1223
|
+
const createResolver = (lateAssignment) => {
|
|
1224
|
+
// Memoized per round: a binding learned this round changes what a later
|
|
1225
|
+
// question answers, so a memo that outlived the round would answer with the
|
|
1226
|
+
// previous round's smaller view.
|
|
1227
|
+
let memo = new Map();
|
|
1228
|
+
const resolving = new Set();
|
|
1229
|
+
/**
|
|
1230
|
+
* The type this node holds, following a bound parameter through the
|
|
1231
|
+
* expressions computed from it.
|
|
1232
|
+
*
|
|
1233
|
+
* Every step delegates to the checker: a member is `getTypeOfPropertyOfType`,
|
|
1234
|
+
* a call is the return type of a signature the checker resolved. The walk
|
|
1235
|
+
* bottoms out ONLY at a bound parameter -- a node whose chain does not reach
|
|
1236
|
+
* one answers `null` and keeps the checker's own answer -- which is what
|
|
1237
|
+
* keeps a genuinely dynamic value dynamic.
|
|
1238
|
+
*/
|
|
1239
|
+
const resolve = (node) => {
|
|
1240
|
+
const cached = memo.get(node);
|
|
1241
|
+
if (cached !== undefined)
|
|
1242
|
+
return cached;
|
|
1243
|
+
if (resolving.has(node))
|
|
1244
|
+
return null;
|
|
1245
|
+
resolving.add(node);
|
|
1246
|
+
const answer = compute(node);
|
|
1247
|
+
resolving.delete(node);
|
|
1248
|
+
memo.set(node, answer);
|
|
1249
|
+
return answer;
|
|
1250
|
+
};
|
|
1251
|
+
/**
|
|
1252
|
+
* The checker's answer when it is a real one, so a resolved chain stops as
|
|
1253
|
+
* soon as a type is known -- or, when the checker has nothing, the
|
|
1254
|
+
* upstream census's answer for this exact node. `upstream` is a settled
|
|
1255
|
+
* prior round (or `emptyParameterBindingCensus`, which always answers
|
|
1256
|
+
* `null` and changes nothing): consulting it here, ahead of this
|
|
1257
|
+
* resolver's own machinery, is what lets a second round see a fact only
|
|
1258
|
+
* `return-bindings.ts`/`local-bindings.ts` derived -- without this
|
|
1259
|
+
* resolver having to reconstruct that fact itself.
|
|
1260
|
+
*/
|
|
1261
|
+
const known = (node) => {
|
|
1262
|
+
if (watchedBinding !== undefined && ts.isIdentifier(node) && node.text === watchedBinding) {
|
|
1263
|
+
const held = checker.getTypeAtLocation(node);
|
|
1264
|
+
const file = node.getSourceFile();
|
|
1265
|
+
process.stderr.write(`[KNOWN] ${file.fileName.split('/').pop()}#${file.getLineAndCharacterOfPosition(node.getStart()).line + 1} ` +
|
|
1266
|
+
`checker=${checker.typeToString(held).slice(0, 30)} unusable=${isUnusableEvidence(held)} ` +
|
|
1267
|
+
`vacuous=${annotationStatesNothing(checker, node, held)} upstream=${(() => {
|
|
1268
|
+
const answer = upstream.typeAt(node);
|
|
1269
|
+
return answer ? checker.typeToString(answer).slice(0, 40) : 'null';
|
|
1270
|
+
})()}\n`);
|
|
1271
|
+
}
|
|
1272
|
+
const argument = implicitArgumentsReadTypeAt(checker, node, (owner) => upstream.implicitArgumentsTupleAt?.(owner) ?? null);
|
|
1273
|
+
if (argument)
|
|
1274
|
+
return argument;
|
|
1275
|
+
const type = (node.kind === ts.SyntaxKind.ThisKeyword ? concreteThisTypeOf(node) : null) ??
|
|
1276
|
+
objectAssignTargetType(checker, node) ??
|
|
1277
|
+
checker.getTypeAtLocation(node);
|
|
1278
|
+
// `annotationStatesNothing` beside `isUnusableEvidence`, both halves of
|
|
1279
|
+
// the one shared rule: a vacuous type (`Object`, `{}`, bare `object`)
|
|
1280
|
+
// is not merely non-evidence -- handed to a `widestOf` join it
|
|
1281
|
+
// DOMINATES, because every type is assignable to it, so one vacuous
|
|
1282
|
+
// call site out-votes every real one. The layout resolver and the two
|
|
1283
|
+
// write-set censuses already ask both halves; this asking one was
|
|
1284
|
+
// drift. See `field-bindings.ts`'s `known` for the measured case.
|
|
1285
|
+
if (isUnusableEvidence(type) || annotationStatesNothing(checker, node, type))
|
|
1286
|
+
return upstream.typeAt(node);
|
|
1287
|
+
// A bound parameter's DECLARED type can be one TypeScript widened from a
|
|
1288
|
+
// default value -- `constructor( parameters = {} )` declares `{}`, a type
|
|
1289
|
+
// with no members, so every `parameters.canvas` reading through it finds
|
|
1290
|
+
// nothing. When the checker is reporting that declared type UNCHANGED at
|
|
1291
|
+
// this site, the census knows better and should answer.
|
|
1292
|
+
//
|
|
1293
|
+
// The equality test is what keeps flow narrowing intact: a site where the
|
|
1294
|
+
// checker narrowed the value reports a DIFFERENT type than the
|
|
1295
|
+
// declaration, and there the checker is right and this defers to it. A
|
|
1296
|
+
// census that always won would silently discard every narrowing in the
|
|
1297
|
+
// program.
|
|
1298
|
+
if (ts.isIdentifier(node)) {
|
|
1299
|
+
const declaration = declarationOf(node);
|
|
1300
|
+
if (declaration && ts.isParameter(declaration) && bindings.has(declaration)) {
|
|
1301
|
+
if (checker.getTypeAtLocation(declaration) === type)
|
|
1302
|
+
return null;
|
|
1303
|
+
}
|
|
1304
|
+
// A name bound by a parameter's implied pattern is typed by the
|
|
1305
|
+
// checker from the pattern's SHAPE (`any`, `any[]` for a rest
|
|
1306
|
+
// element), which is the silhouette `impliedPatternParameterOf`
|
|
1307
|
+
// exists to see through; the pattern's own read is the answer.
|
|
1308
|
+
if (declaration && ts.isBindingElement(declaration) && impliedPatternElementRootOf(checker, declaration) !== null)
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1311
|
+
return type;
|
|
1312
|
+
};
|
|
1313
|
+
const declarationOf = (node) => {
|
|
1314
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
1315
|
+
const declarations = symbol?.declarations;
|
|
1316
|
+
return declarations && declarations.length === 1 ? (declarations[0] ?? null) : null;
|
|
1317
|
+
};
|
|
1318
|
+
/**
|
|
1319
|
+
* The type of one member of a resolved receiver.
|
|
1320
|
+
*
|
|
1321
|
+
* `getPropertyOfType` answers with the symbol and `getTypeOfSymbolAtLocation`
|
|
1322
|
+
* types it at the site that reads it, which is the pair the checker exposes
|
|
1323
|
+
* for exactly this question -- the same answer it computed to check the
|
|
1324
|
+
* access, read back rather than recomputed.
|
|
1325
|
+
*/
|
|
1326
|
+
/**
|
|
1327
|
+
* The expression a literal member was written with -- `{ color: cb }`, or
|
|
1328
|
+
* shorthand `{ cb }` -- or `null` for any other declaration form.
|
|
1329
|
+
* `field-bindings.ts` states the same shape for the same reason.
|
|
1330
|
+
*/
|
|
1331
|
+
const literalMemberInitializerOf = (member) => {
|
|
1332
|
+
const declaration = member?.valueDeclaration;
|
|
1333
|
+
if (!declaration)
|
|
1334
|
+
return null;
|
|
1335
|
+
if (ts.isPropertyAssignment(declaration))
|
|
1336
|
+
return declaration.initializer;
|
|
1337
|
+
if (ts.isShorthandPropertyAssignment(declaration))
|
|
1338
|
+
return declaration.name;
|
|
1339
|
+
return null;
|
|
1340
|
+
};
|
|
1341
|
+
const resolvingLiteralMembers = new Set();
|
|
1342
|
+
/**
|
|
1343
|
+
* A member read, answered by the checker -- and, where the checker has no
|
|
1344
|
+
* answer, by what the literal actually PUT there.
|
|
1345
|
+
*
|
|
1346
|
+
* The checker widens an object literal's member to `any` as soon as its
|
|
1347
|
+
* initializer is untyped, and in JavaScript a factory's product is exactly
|
|
1348
|
+
* that: `function ColorBuffer() { return { setClear: function (...) {} } }`
|
|
1349
|
+
* called with `new` has no construct signature, so `cb` is `any` to the
|
|
1350
|
+
* checker and so is `{ color: cb }.color`. This census has ALREADY resolved
|
|
1351
|
+
* `cb` to the literal it holds -- that is what it exists to do -- and
|
|
1352
|
+
* throwing that away at the property assignment is what made three's
|
|
1353
|
+
* `state.buffers.color.setClear(...)` unattributable, leaving every method
|
|
1354
|
+
* on all three WebGLState buffer literals with no call sites and all their
|
|
1355
|
+
* parameters dynamic.
|
|
1356
|
+
*
|
|
1357
|
+
* Asked only where the checker's own answer is unusable, so nothing that
|
|
1358
|
+
* already had a type changes; the recursion guard is for a literal whose
|
|
1359
|
+
* member initializer reads back through the same member.
|
|
1360
|
+
*/
|
|
1361
|
+
const propertyTypeOf = (receiver, name, at) => {
|
|
1362
|
+
const answer = memberTypeOf(checker, receiver, name, at, valueFlow);
|
|
1363
|
+
if (answer !== null && !isUnusableEvidence(answer))
|
|
1364
|
+
return answer;
|
|
1365
|
+
// A member only some classes of the receiver's closed family declare:
|
|
1366
|
+
// three's `material.glslVersion` through a `Material`. See
|
|
1367
|
+
// `flow/class-family-member-read.ts`.
|
|
1368
|
+
const family = answer === null ? classFamilyMemberReadTypeOf(checker, valueFlow, receiver, name, upstream) : null;
|
|
1369
|
+
if (family !== null)
|
|
1370
|
+
return family;
|
|
1371
|
+
const written = literalMemberInitializerOf(checker.getPropertyOfType(checker.getApparentType(receiver), name));
|
|
1372
|
+
if (written === null || resolvingLiteralMembers.has(written))
|
|
1373
|
+
return answer;
|
|
1374
|
+
resolvingLiteralMembers.add(written);
|
|
1375
|
+
try {
|
|
1376
|
+
const resolved = known(written) ?? resolve(written);
|
|
1377
|
+
return resolved !== null && !isUnusableEvidence(resolved) ? resolved : answer;
|
|
1378
|
+
}
|
|
1379
|
+
finally {
|
|
1380
|
+
resolvingLiteralMembers.delete(written);
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
/**
|
|
1384
|
+
|
|
1385
|
+
* What a function actually returns, when its declared return type is `any`.
|
|
1386
|
+
|
|
1387
|
+
*
|
|
1388
|
+
|
|
1389
|
+
* Only the `return` statements of THIS function: a nested function's returns
|
|
1390
|
+
|
|
1391
|
+
* belong to it, and walking into one would attribute its answer to the wrong
|
|
1392
|
+
|
|
1393
|
+
* frame. Agreement is required for the same reason it is required of a
|
|
1394
|
+
|
|
1395
|
+
* parameter's call sites -- two different types are two answers, and picking
|
|
1396
|
+
|
|
1397
|
+
* one is guessing. A bare `return;` among them is a refusal rather than an
|
|
1398
|
+
|
|
1399
|
+
* agreement: it means the function also returns `undefined`, which the
|
|
1400
|
+
|
|
1401
|
+
* agreed type does not describe.
|
|
1402
|
+
|
|
1403
|
+
*/
|
|
1404
|
+
const resolvedReturnTypeOf = (declaration) => {
|
|
1405
|
+
const body = 'body' in declaration ? declaration.body : undefined;
|
|
1406
|
+
if (!body)
|
|
1407
|
+
return null;
|
|
1408
|
+
if (!ts.isBlock(body))
|
|
1409
|
+
return known(body) ?? resolve(body);
|
|
1410
|
+
const returnedTypes = [];
|
|
1411
|
+
let sawReturn = false;
|
|
1412
|
+
let refused = false;
|
|
1413
|
+
const walk = (node) => {
|
|
1414
|
+
if (refused)
|
|
1415
|
+
return;
|
|
1416
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node))
|
|
1417
|
+
return;
|
|
1418
|
+
if (ts.isReturnStatement(node)) {
|
|
1419
|
+
sawReturn = true;
|
|
1420
|
+
if (!node.expression) {
|
|
1421
|
+
refused = true;
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
const returned = known(node.expression) ?? resolve(node.expression);
|
|
1425
|
+
if (!returned || isUnusableEvidence(returned))
|
|
1426
|
+
refused = true;
|
|
1427
|
+
else
|
|
1428
|
+
returnedTypes.push(returned);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
ts.forEachChild(node, walk);
|
|
1432
|
+
};
|
|
1433
|
+
ts.forEachChild(body, walk);
|
|
1434
|
+
return refused || !sawReturn ? null : widestOf(checker, returnedTypes);
|
|
1435
|
+
};
|
|
1436
|
+
/**
|
|
1437
|
+
* What a `var`/`let`/`const` cell holds, read from EVERY write to it.
|
|
1438
|
+
*
|
|
1439
|
+
* Its initializer is one write; so is every later assignment. `let _gl =
|
|
1440
|
+
* context; ... _gl = getContext( contextName, contextAttributes );` in
|
|
1441
|
+
* `WebGLRenderer` writes twice, and reading only the initializer would
|
|
1442
|
+
* describe a value the program does not have -- the second write is how a
|
|
1443
|
+
* context that arrived as `null` gets created, and the cell has to hold
|
|
1444
|
+
* both. The writes settling on one carrier is the answer (`widestOf`);
|
|
1445
|
+
* anything else is refused.
|
|
1446
|
+
*
|
|
1447
|
+
* Both the declaration node and every identifier that reads the binding
|
|
1448
|
+
* route through here, because they are one cell and one carrier. Answering
|
|
1449
|
+
* the declaration from its initializer while answering references from the
|
|
1450
|
+
* whole write set is two authorities on one storage location, which is how
|
|
1451
|
+
* a cell gets a non-optional carrier and a `null` written into it.
|
|
1452
|
+
*
|
|
1453
|
+
* `let extensions, capabilities, state, info;` filled later inside
|
|
1454
|
+
* `initGLContext()` is the same rule with no initializer among the writes,
|
|
1455
|
+
* and it is the hop every chain into three's sub-modules passes through --
|
|
1456
|
+
* which is exactly why only the propagating resolver may use it. See
|
|
1457
|
+
* `createResolver`.
|
|
1458
|
+
*/
|
|
1459
|
+
const writeSetTypeOf = (declaration) => {
|
|
1460
|
+
if (declaration.type)
|
|
1461
|
+
return null;
|
|
1462
|
+
if (!declaration.initializer && !lateAssignment)
|
|
1463
|
+
return null;
|
|
1464
|
+
const edges = valueFlow.writesToDeclaration(declaration).filter((write) => write.slot === 'whole');
|
|
1465
|
+
if (edges.some((write) => write.value === null))
|
|
1466
|
+
return null;
|
|
1467
|
+
const writes = edges.flatMap((write) => (write.value === null ? [] : [write.value]));
|
|
1468
|
+
if (writes.length === 0)
|
|
1469
|
+
return null;
|
|
1470
|
+
const writtenTypes = [];
|
|
1471
|
+
for (const write of writes) {
|
|
1472
|
+
const written = known(write) ?? resolve(write);
|
|
1473
|
+
if (!written || isUnusableEvidence(written))
|
|
1474
|
+
return null;
|
|
1475
|
+
writtenTypes.push(written);
|
|
1476
|
+
}
|
|
1477
|
+
return widestOf(checker, writtenTypes);
|
|
1478
|
+
};
|
|
1479
|
+
/**
|
|
1480
|
+
* The value a whole binding pattern reads out of: a parameter's own bound
|
|
1481
|
+
* slot, an outer element's resolved type for a nested pattern, or a
|
|
1482
|
+
* declaration's initializer. The pattern node itself resolves to this so
|
|
1483
|
+
* a producer asking `typeAt(pattern)` for the base of its steps gets the
|
|
1484
|
+
* census's answer rather than the checker's implied-shape type.
|
|
1485
|
+
*/
|
|
1486
|
+
const patternHoldersOf = (pattern) => {
|
|
1487
|
+
const root = pattern.parent;
|
|
1488
|
+
if (ts.isParameter(root)) {
|
|
1489
|
+
// A disagreement the census settled as SYNTHESIZED arms has no single
|
|
1490
|
+
// `ts.Type` (`unionArms`), so the pattern reads out of every arm and
|
|
1491
|
+
// joins what it finds -- `f([1, 2])` next to `f([])` is that shape.
|
|
1492
|
+
const bound = bindings.get(root);
|
|
1493
|
+
return bound ? [bound] : (unionArms.get(root) ?? null);
|
|
1494
|
+
}
|
|
1495
|
+
const holder = ts.isBindingElement(root)
|
|
1496
|
+
? (known(root) ?? resolve(root))
|
|
1497
|
+
: ts.isVariableDeclaration(root) && root.initializer
|
|
1498
|
+
? (exactEmptyObjectLiteralType(checker, root.initializer) ?? known(root.initializer) ?? resolve(root.initializer))
|
|
1499
|
+
: null;
|
|
1500
|
+
return holder ? [holder] : null;
|
|
1501
|
+
};
|
|
1502
|
+
const patternHolderOf = (pattern) => {
|
|
1503
|
+
const holders = patternHoldersOf(pattern);
|
|
1504
|
+
return holders && holders.length === 1 ? (holders[0] ?? null) : null;
|
|
1505
|
+
};
|
|
1506
|
+
/**
|
|
1507
|
+
* An object pattern's NUMERIC key over a plain array -- `[...{ 0: v, 3: y,
|
|
1508
|
+
* length: z }] = [7, 8, 9]` -- reads the array's element WITH `undefined`:
|
|
1509
|
+
* position 3 of a three-element array is `undefined` to the language, and
|
|
1510
|
+
* a read typed bare aborted at runtime ("read of array hole or out-of-range
|
|
1511
|
+
* index"). A tuple holder states its positions, so a stated one answers
|
|
1512
|
+
* bare and an unstated one `undefined`; every other key (`length`) is an
|
|
1513
|
+
* ordinary member read. Published as the element's READ (`patternReadTypes`)
|
|
1514
|
+
* so the layout's outranking rule can lift the checker's bare binding.
|
|
1515
|
+
*/
|
|
1516
|
+
const arrayIndexPatternReadOf = (holder, key, element) => {
|
|
1517
|
+
if (!/^(0|[1-9][0-9]*)$/.test(key))
|
|
1518
|
+
return null;
|
|
1519
|
+
const nonNull = checker.getNonNullableType(holder);
|
|
1520
|
+
let read = null;
|
|
1521
|
+
if (checker.isTupleType(nonNull)) {
|
|
1522
|
+
const stated = checker.getTypeArguments(nonNull)[Number(key)];
|
|
1523
|
+
read = stated === undefined ? checker.getUndefinedType() : checker.getBaseTypeOfLiteralType(stated);
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
const indexed = checker.getIndexTypeOfType(nonNull, ts.IndexKind.Number);
|
|
1527
|
+
if (!indexed || isUnusableEvidence(indexed) || checker.getIndexTypeOfType(nonNull, ts.IndexKind.String))
|
|
1528
|
+
return null;
|
|
1529
|
+
read = checker.getNullableType(indexed, ts.TypeFlags.Undefined);
|
|
1530
|
+
}
|
|
1531
|
+
patternReadTypes.set(element, read);
|
|
1532
|
+
if (!element.initializer)
|
|
1533
|
+
return read;
|
|
1534
|
+
const fallback = defaultWriteOf(element.initializer);
|
|
1535
|
+
return fallback ? (joinOfWrites(checker, [withoutUndefinedMember(checker, read), fallback]) ?? read) : read;
|
|
1536
|
+
};
|
|
1537
|
+
/**
|
|
1538
|
+
* A key the holder's closed object type declares no member for reads
|
|
1539
|
+
* `undefined` -- ECMA-262 `KeyedBindingInitialization` reads through
|
|
1540
|
+
* `GetV` and finds nothing, so `const { fn = function () {} } = {}` binds
|
|
1541
|
+
* the default. The checker binds the name `any` in a JavaScript file (an
|
|
1542
|
+
* error in TypeScript), which was enough to box the default's own
|
|
1543
|
+
* function type and every `.name`/call on it. Only a single object type
|
|
1544
|
+
* with no member of that name and no index signature of either kind
|
|
1545
|
+
* answers; a union, a primitive, or an open dictionary keeps the ordinary
|
|
1546
|
+
* member read. `getPropertyOfType` sees the apparent members too, so a
|
|
1547
|
+
* `{ toString }` pattern over `{}` still reads `Object.prototype`'s.
|
|
1548
|
+
*/
|
|
1549
|
+
const absentKeyPatternReadOf = (holder, key, element) => {
|
|
1550
|
+
const nonNull = checker.getNonNullableType(holder);
|
|
1551
|
+
if (isUnusableEvidence(nonNull) || (nonNull.flags & ts.TypeFlags.Object) === 0)
|
|
1552
|
+
return null;
|
|
1553
|
+
if (checker.getPropertyOfType(nonNull, key) ||
|
|
1554
|
+
checker.getIndexTypeOfType(nonNull, ts.IndexKind.String) ||
|
|
1555
|
+
checker.getIndexTypeOfType(nonNull, ts.IndexKind.Number))
|
|
1556
|
+
return null;
|
|
1557
|
+
const read = checker.getUndefinedType();
|
|
1558
|
+
patternReadTypes.set(element, read);
|
|
1559
|
+
if (!element.initializer)
|
|
1560
|
+
return read;
|
|
1561
|
+
return defaultWriteOf(element.initializer) ?? read;
|
|
1562
|
+
};
|
|
1563
|
+
/**
|
|
1564
|
+
* One array-pattern element's own type, read out of `holder` the way the
|
|
1565
|
+
* language reads it (ECMA-262 IteratorBindingInitialization): a tuple
|
|
1566
|
+
* source answers by position; any other array answers its element type
|
|
1567
|
+
* WITH `undefined`, because the pattern may run past the array's length
|
|
1568
|
+
* and the language binds the name to `undefined` there rather than
|
|
1569
|
+
* faulting. A default resolves that absence to the default's own type
|
|
1570
|
+
* (`joinOfWrites`, the same nullish-is-absence join a defaulted object
|
|
1571
|
+
* element uses in `local-bindings.ts`). A rest element takes the holder's
|
|
1572
|
+
* remaining elements, which for a plain array is the same array type.
|
|
1573
|
+
*/
|
|
1574
|
+
const arrayPatternElementTypeOf = (holders, element, pattern) => {
|
|
1575
|
+
const position = pattern.elements.indexOf(element);
|
|
1576
|
+
// A parameter's implied pattern reads out of the ARRAY the value is
|
|
1577
|
+
// stored as (`impliedPatternElementOfArms`, the same answer the slot
|
|
1578
|
+
// takes), not out of the tuple one call site happened to write: the
|
|
1579
|
+
// position holds the stored element or nothing, whatever the arms said.
|
|
1580
|
+
if (ts.isParameter(pattern.parent) && impliedPatternParameterOf(checker, pattern.parent) !== null && !element.dotDotDotToken) {
|
|
1581
|
+
const stored = impliedPatternElementOfArms(checker, holders, pattern);
|
|
1582
|
+
if (stored && !isUnusableEvidence(stored)) {
|
|
1583
|
+
const own = impliedPatternPositionPresentEverywhere(checker, holders, position)
|
|
1584
|
+
? stored
|
|
1585
|
+
: checker.getNullableType(stored, ts.TypeFlags.Undefined);
|
|
1586
|
+
patternReadTypes.set(element, own);
|
|
1587
|
+
if (!element.initializer)
|
|
1588
|
+
return own;
|
|
1589
|
+
// The NAME holds the read with its absence replaced by the default
|
|
1590
|
+
// (`bodyBindingOf`'s rule for a parameter, one level down): the
|
|
1591
|
+
// read is `own`, published above for the pattern step to test,
|
|
1592
|
+
// and only a default that itself evaluates to `undefined` can put
|
|
1593
|
+
// the absence back. `[[,] = g()]` binding `Generator | undefined`
|
|
1594
|
+
// sent the nested pattern reading an optional cursor.
|
|
1595
|
+
const fallback = defaultWriteOf(element.initializer);
|
|
1596
|
+
return fallback ? (joinOfWrites(checker, [withoutUndefinedMember(checker, own), fallback]) ?? own) : own;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
const ownOf = (holder) => {
|
|
1600
|
+
const nonNull = checker.getNonNullableType(holder);
|
|
1601
|
+
if (checker.isTupleType(nonNull)) {
|
|
1602
|
+
if (element.dotDotDotToken)
|
|
1603
|
+
return null;
|
|
1604
|
+
const stated = checker.getTypeArguments(nonNull)[position];
|
|
1605
|
+
return stated === undefined ? checker.getUndefinedType() : checker.getBaseTypeOfLiteralType(stated);
|
|
1606
|
+
}
|
|
1607
|
+
const indexed = checker.getIndexTypeOfType(nonNull, ts.IndexKind.Number);
|
|
1608
|
+
if (!indexed) {
|
|
1609
|
+
// A non-array iterable holder (a generator, a Set, a Map) has no
|
|
1610
|
+
// numeric index; a position reads what the holder YIELDS, with
|
|
1611
|
+
// `undefined` for a cursor exhausted before it -- the checker binds
|
|
1612
|
+
// the name bare, and `var [a, b] = g()` over a one-yield generator
|
|
1613
|
+
// then read `0` for `b`. A rest element would need an array of the
|
|
1614
|
+
// yield, which no checker API mints, so it stays unanswered.
|
|
1615
|
+
if (element.dotDotDotToken)
|
|
1616
|
+
return null;
|
|
1617
|
+
const yielded = iteratorYieldTypesOf(checker, nonNull, pattern);
|
|
1618
|
+
const only = yielded?.length === 1 ? yielded[0] : undefined;
|
|
1619
|
+
return only && !isUnusableEvidence(only) ? checker.getNullableType(only, ts.TypeFlags.Undefined) : null;
|
|
1620
|
+
}
|
|
1621
|
+
if (isUnusableEvidence(indexed))
|
|
1622
|
+
return null;
|
|
1623
|
+
if (element.dotDotDotToken)
|
|
1624
|
+
return nonNull;
|
|
1625
|
+
return checker.getNullableType(indexed, ts.TypeFlags.Undefined);
|
|
1626
|
+
};
|
|
1627
|
+
const owns = holders.map(ownOf);
|
|
1628
|
+
if (owns.some((type) => type === null))
|
|
1629
|
+
return null;
|
|
1630
|
+
// Every arm's position, joined: a position one arm states and another
|
|
1631
|
+
// runs past is `T | undefined`, which is what `joinOfWrites` makes of
|
|
1632
|
+
// `[T, undefined]` -- the same nullish-is-absence join a default uses.
|
|
1633
|
+
const own = owns.length === 1 ? owns[0] : joinOfWrites(checker, owns);
|
|
1634
|
+
if (!own)
|
|
1635
|
+
return null;
|
|
1636
|
+
patternReadTypes.set(element, own);
|
|
1637
|
+
if (!element.initializer)
|
|
1638
|
+
return own;
|
|
1639
|
+
const fallback = defaultWriteOf(element.initializer);
|
|
1640
|
+
return fallback ? (joinOfWrites(checker, [own, fallback]) ?? own) : own;
|
|
1641
|
+
};
|
|
1642
|
+
/** What a default initializer WRITES: a `void` call's value is `undefined`, the language's own answer for it. */
|
|
1643
|
+
const defaultWriteOf = (initializer) => {
|
|
1644
|
+
if ((checker.getTypeAtLocation(initializer).flags & ts.TypeFlags.Void) !== 0)
|
|
1645
|
+
return checker.getUndefinedType();
|
|
1646
|
+
return known(initializer) ?? resolve(initializer);
|
|
1647
|
+
};
|
|
1648
|
+
const compute = (node) => {
|
|
1649
|
+
if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isNonNullExpression(node)) {
|
|
1650
|
+
return known(node.expression) ?? resolve(node.expression);
|
|
1651
|
+
}
|
|
1652
|
+
if (ts.isIdentifier(node)) {
|
|
1653
|
+
const declaration = declarationOf(node);
|
|
1654
|
+
if (!declaration)
|
|
1655
|
+
return null;
|
|
1656
|
+
if (ts.isParameter(declaration))
|
|
1657
|
+
return parameterTypeOf(declaration);
|
|
1658
|
+
// A binding holds what is written into it, and EVERY write counts. Its
|
|
1659
|
+
// initializer is one; so is every later assignment -- `let _gl = context;
|
|
1660
|
+
// ... _gl = getContext( contextName, contextAttributes );` in
|
|
1661
|
+
// `WebGLRenderer` writes twice, and reading only the initializer would
|
|
1662
|
+
// describe a value the program does not have. All the writes agreeing is
|
|
1663
|
+
// one answer; anything else is refused rather than merged, because the
|
|
1664
|
+
// union that would describe two is not a type this can build without
|
|
1665
|
+
// inventing one, and the honest answer to "which of these two" is neither.
|
|
1666
|
+
//
|
|
1667
|
+
// `let extensions, capabilities, state, info;` filled later inside
|
|
1668
|
+
// `initGLContext()` is the same rule with no initializer among the
|
|
1669
|
+
// writes, and it is the hop every chain into three's sub-modules passes
|
|
1670
|
+
// through -- which is exactly why only the propagating resolver may use
|
|
1671
|
+
// it. See `createResolver`.
|
|
1672
|
+
// A reference to a destructured name is a read of the SAME storage the
|
|
1673
|
+
// pattern bound, so it answers with the pattern's own answer. Without
|
|
1674
|
+
// this hop the chain stops one step short of everything it was built
|
|
1675
|
+
// for: `const { canvas, context } = parameters` binds both names, and
|
|
1676
|
+
// then `canvas.getContext( ... )` -- an ordinary reference, three lines
|
|
1677
|
+
// down -- resolves to nothing. A later assignment to the name is the
|
|
1678
|
+
// same disqualification it is for a `let`: the binding then holds
|
|
1679
|
+
// something the pattern's member type does not describe.
|
|
1680
|
+
if (ts.isBindingElement(declaration)) {
|
|
1681
|
+
const symbol = checker.getSymbolAtLocation(declaration.name);
|
|
1682
|
+
// The pattern's own binding and its own default (`[w = c()]`) are
|
|
1683
|
+
// recorded at the element's site; the pattern's answer already
|
|
1684
|
+
// joins them. Only a write from somewhere ELSE disqualifies.
|
|
1685
|
+
if (symbol && valueFlow.writesToSymbol(symbol).some((write) => write.slot === 'whole' && write.site !== declaration))
|
|
1686
|
+
return null;
|
|
1687
|
+
return resolve(declaration);
|
|
1688
|
+
}
|
|
1689
|
+
if (ts.isVariableDeclaration(declaration))
|
|
1690
|
+
return writeSetTypeOf(declaration);
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
if (ts.isObjectBindingPattern(node) || ts.isArrayBindingPattern(node))
|
|
1694
|
+
return patternHolderOf(node);
|
|
1695
|
+
if (ts.isBindingElement(node)) {
|
|
1696
|
+
// `const { canvas, context, antialias } = parameters` binds three names to
|
|
1697
|
+
// three members of one value. The member is the answer, and the value is
|
|
1698
|
+
// whatever this resolver can make of the pattern's own root.
|
|
1699
|
+
const pattern = node.parent;
|
|
1700
|
+
if (ts.isObjectBindingPattern(pattern)) {
|
|
1701
|
+
const holder = patternHolderOf(pattern);
|
|
1702
|
+
const key = node.propertyName ?? node.name;
|
|
1703
|
+
if (!holder || !(ts.isIdentifier(key) || ts.isStringLiteral(key) || ts.isNumericLiteral(key)))
|
|
1704
|
+
return null;
|
|
1705
|
+
const indexedRead = arrayIndexPatternReadOf(holder, key.text, node);
|
|
1706
|
+
if (indexedRead)
|
|
1707
|
+
return indexedRead;
|
|
1708
|
+
const absentRead = absentKeyPatternReadOf(holder, key.text, node);
|
|
1709
|
+
if (absentRead)
|
|
1710
|
+
return absentRead;
|
|
1711
|
+
return propertyTypeOf(holder, key.text, node);
|
|
1712
|
+
}
|
|
1713
|
+
const holders = patternHoldersOf(pattern);
|
|
1714
|
+
return holders ? arrayPatternElementTypeOf(holders, node, pattern) : null;
|
|
1715
|
+
}
|
|
1716
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
1717
|
+
const receiver = known(node.expression) ?? resolve(node.expression);
|
|
1718
|
+
if (!receiver)
|
|
1719
|
+
return null;
|
|
1720
|
+
return propertyTypeOf(receiver, node.name.text, node);
|
|
1721
|
+
}
|
|
1722
|
+
if (ts.isElementAccessExpression(node) && node.argumentExpression) {
|
|
1723
|
+
const receiver = known(node.expression) ?? resolve(node.expression);
|
|
1724
|
+
if (!receiver)
|
|
1725
|
+
return null;
|
|
1726
|
+
// A literal key is a named member spelled with brackets; any other key
|
|
1727
|
+
// is answered by the receiver's index signature, and by nothing else.
|
|
1728
|
+
const name = literalMemberNameOf(node);
|
|
1729
|
+
if (name !== null)
|
|
1730
|
+
return propertyTypeOf(receiver, name, node);
|
|
1731
|
+
const key = known(node.argumentExpression) ?? resolve(node.argumentExpression);
|
|
1732
|
+
return key ? indexedTypeOf(checker, receiver, key, node, valueFlow, upstream) : null;
|
|
1733
|
+
}
|
|
1734
|
+
if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
1735
|
+
const invariant = overloadInvariantReturnTypeAt(checker, node, (operand) => known(operand) ?? resolve(operand));
|
|
1736
|
+
// An OPEN invariant is not one: every overload agreeing on the same
|
|
1737
|
+
// unreduced type EXPRESSION says nothing about the type. Falls through
|
|
1738
|
+
// rather than returning null, so the ordinary path below still gets its
|
|
1739
|
+
// turn -- and refuses the same form for the same reason.
|
|
1740
|
+
if (invariant && !isUnreducedTypeForm(invariant))
|
|
1741
|
+
return invariant;
|
|
1742
|
+
const callee = known(node.expression) ?? resolve(node.expression);
|
|
1743
|
+
if (!callee)
|
|
1744
|
+
return null;
|
|
1745
|
+
// A JavaScript factory called with `new` that returns an object is typed
|
|
1746
|
+
// by what it returns, which is why the call signature is consulted for a
|
|
1747
|
+
// `new` whose callee declares no construct signature -- `new
|
|
1748
|
+
// WebGLExtensions( _gl )` is `function WebGLExtensions( gl ) { ...;
|
|
1749
|
+
// return { has, init, get }; }`, and the record it returns is the whole
|
|
1750
|
+
// of what the expression holds.
|
|
1751
|
+
const constructed = ts.isNewExpression(node) ? callee.getConstructSignatures() : [];
|
|
1752
|
+
const signatures = constructed.length > 0 ? constructed : callee.getCallSignatures();
|
|
1753
|
+
if (signatures.length !== 1)
|
|
1754
|
+
return null;
|
|
1755
|
+
const signature = signatures[0];
|
|
1756
|
+
const operands = ts.isCallExpression(node) ? invocationOperands.get(node) : undefined;
|
|
1757
|
+
if (ts.isCallExpression(node) && !operands)
|
|
1758
|
+
return null;
|
|
1759
|
+
const returned = explicitThisCallReturnType(signature, operands?.explicitThis ? (known(operands.callee) ?? resolve(operands.callee)) : null);
|
|
1760
|
+
// `void` and `never` are refused here for the reason `isUnusableEvidence`
|
|
1761
|
+
// states, and `any` falls through to the return-expression walk below
|
|
1762
|
+
// rather than being refused -- the three answers are not one answer.
|
|
1763
|
+
if ((returned.flags & (ts.TypeFlags.Void | ts.TypeFlags.Never)) !== 0)
|
|
1764
|
+
return null;
|
|
1765
|
+
// A GENERIC signature's declared return is an OPEN FORM, and reading it
|
|
1766
|
+
// off the signature publishes the type expression rather than the type.
|
|
1767
|
+
// `Reflect.get(pattern, 'route')` is the case: one call signature,
|
|
1768
|
+
// `P extends keyof T ? T[P] : any`, and nothing on this walk binds `T`
|
|
1769
|
+
// or `P` -- so the census answered a conditional that no later stage
|
|
1770
|
+
// can reduce, `structural.ts` refused it as "an anonymous conditional
|
|
1771
|
+
// type is still gated on a type parameter", and a program whose call
|
|
1772
|
+
// the CHECKER had already reduced to `any` lost its certificate.
|
|
1773
|
+
//
|
|
1774
|
+
// Refused rather than instantiated: instantiating is what the checker
|
|
1775
|
+
// did at the call site, and its answer is the one `layoutTypeAt` falls
|
|
1776
|
+
// back to the moment this census declines. Doing it a second time here
|
|
1777
|
+
// would be a second authority on the same reduction.
|
|
1778
|
+
//
|
|
1779
|
+
// Asked of the ANSWER, not of the signature. The signature reached here
|
|
1780
|
+
// came off a census-derived callee type, and its `getTypeParameters()`
|
|
1781
|
+
// is empty even where its return type is still open -- so gating on the
|
|
1782
|
+
// signature being generic left this exact case through.
|
|
1783
|
+
if (isUnreducedTypeForm(returned))
|
|
1784
|
+
return null;
|
|
1785
|
+
// A stated return type that says NOTHING falls through with `any`, for
|
|
1786
|
+
// the same reason and by the same rule. `function makesBag(): object {
|
|
1787
|
+
// return { a: 1, b: 2 } }` answered `object` here, and because this
|
|
1788
|
+
// census's answer is the FIRST half of the composed view
|
|
1789
|
+
// (`composeReturnBindings`), that shadowed the return census's own,
|
|
1790
|
+
// already-derived record for the same call -- two authorities on one
|
|
1791
|
+
// invocation, which `model/selected-signature.ts` catches fail-closed
|
|
1792
|
+
// and pays for by withholding the call AND every binding it feeds.
|
|
1793
|
+
if (!isAnyType(returned))
|
|
1794
|
+
return returned;
|
|
1795
|
+
// The return type was not written down either. A function's `return`
|
|
1796
|
+
// expressions are the same kind of evidence a call's arguments are,
|
|
1797
|
+
// and asking them is the same question one frame down: `getContext`
|
|
1798
|
+
// returns `canvas.getContext( ... )`, and once `canvas` is known so is
|
|
1799
|
+
// this.
|
|
1800
|
+
const declared = signature.declaration;
|
|
1801
|
+
return declared && ts.isFunctionLike(declared) ? resolvedReturnTypeOf(declared) : null;
|
|
1802
|
+
}
|
|
1803
|
+
if (ts.isVariableDeclaration(node))
|
|
1804
|
+
return writeSetTypeOf(node);
|
|
1805
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
1806
|
+
return known(node.right) ?? resolve(node.right);
|
|
1807
|
+
}
|
|
1808
|
+
// Last, and through the SHARED resolver rather than a local reading of
|
|
1809
|
+
// these node kinds. `return-bindings.ts` asks the identical question of
|
|
1810
|
+
// the identical expressions, and two censuses answering it two ways is
|
|
1811
|
+
// the defect class this compiler keeps rediscovering -- so the rule lives
|
|
1812
|
+
// in one module and both callers hand it their own operand resolver.
|
|
1813
|
+
// Deliberately after the assignment arm above: a `BinaryExpression` whose
|
|
1814
|
+
// operator is `=` is a write, answered by its right-hand side, and only
|
|
1815
|
+
// the operators that COMPUTE a value reach here.
|
|
1816
|
+
if (ts.isConditionalExpression(node) || ts.isBinaryExpression(node) || ts.isTemplateExpression(node)) {
|
|
1817
|
+
return derivedExpressionType(checker, node, (operand) => known(operand) ?? resolve(operand));
|
|
1818
|
+
}
|
|
1819
|
+
return null;
|
|
1820
|
+
};
|
|
1821
|
+
return {
|
|
1822
|
+
resolve,
|
|
1823
|
+
known,
|
|
1824
|
+
reset: () => {
|
|
1825
|
+
memo = new Map();
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
};
|
|
1829
|
+
const propagating = createResolver(true);
|
|
1830
|
+
// One pair of key functions per index: the member-closure memo keys on
|
|
1831
|
+
// their identity, and a closure minted per proof launch defeats it.
|
|
1832
|
+
const propagatingTypeAt = (expression) => propagating.known(expression) ?? propagating.resolve(expression);
|
|
1833
|
+
const implicitArgumentsUsesAt = (owner) => index.implicitArgumentsUses.get(owner);
|
|
1834
|
+
// `hasClosedMemberCallableUses`'s memo is keyed in part on THIS function's
|
|
1835
|
+
// identity, which is exactly right for a single whole-program authority --
|
|
1836
|
+
// but `bindingSweep` below re-derives `propagatingTypeAt`'s answers every
|
|
1837
|
+
// round from a GROWING `bindings` map behind the SAME stable reference, so
|
|
1838
|
+
// a member whose closure needs a later round's binding got refused once
|
|
1839
|
+
// and served that stale refusal forever after, with no further proof
|
|
1840
|
+
// activity to show for it. Reassigned once per round (not per proof
|
|
1841
|
+
// launch, which would defeat in-round reuse the comment above protects) so
|
|
1842
|
+
// each round's improved evidence gets its own cache generation.
|
|
1843
|
+
let memberClosureReceiverTypeAt = propagatingTypeAt;
|
|
1844
|
+
const publishing = createResolver(false);
|
|
1845
|
+
/**
|
|
1846
|
+
* Whether `call` is a recursive call: attributed to `declaration` (true of
|
|
1847
|
+
* every call in `callsByDeclaration.get(declaration)` by construction) AND
|
|
1848
|
+
* lexically inside `declaration`'s OWN body -- so this is `declaration`
|
|
1849
|
+
* calling itself, directly or through a different receiver of the same
|
|
1850
|
+
* method (`children[i].traverse( callback )` inside `Object3D.traverse`'s
|
|
1851
|
+
* own body is the same declaration, called again, from within itself).
|
|
1852
|
+
*
|
|
1853
|
+
* Direct self-reference only, deliberately: mutual recursion (`a` calling
|
|
1854
|
+
* `b` calling `a`) needs walking the call graph rather than one
|
|
1855
|
+
* containment check, and is worth the extra machinery only once this
|
|
1856
|
+
* narrower, measured, cheaply-checked case has proven the shape is worth
|
|
1857
|
+
* having at all.
|
|
1858
|
+
*/
|
|
1859
|
+
const isRecursiveCallWithin = (call, declaration) => {
|
|
1860
|
+
const body = 'body' in declaration ? declaration.body : undefined;
|
|
1861
|
+
if (!body)
|
|
1862
|
+
return false;
|
|
1863
|
+
for (let current = call.parent; current; current = current.parent) {
|
|
1864
|
+
if (current === body)
|
|
1865
|
+
return true;
|
|
1866
|
+
}
|
|
1867
|
+
return false;
|
|
1868
|
+
};
|
|
1869
|
+
/** Whether `expression` (parens aside) is a bare reference to exactly `parameter`'s own declaration. */
|
|
1870
|
+
const referencesParameter = (expression, parameter) => {
|
|
1871
|
+
let current = expression;
|
|
1872
|
+
while (ts.isParenthesizedExpression(current))
|
|
1873
|
+
current = current.expression;
|
|
1874
|
+
if (!ts.isIdentifier(current))
|
|
1875
|
+
return false;
|
|
1876
|
+
const symbol = checker.getSymbolAtLocation(current);
|
|
1877
|
+
const declarations = symbol?.declarations;
|
|
1878
|
+
return declarations !== undefined && declarations.length === 1 && declarations[0] === parameter;
|
|
1879
|
+
};
|
|
1880
|
+
/**
|
|
1881
|
+
* Whether a recursive argument's type depends on `parameter`, through only
|
|
1882
|
+
* reference-preserving reads this census can replay after the parameter has
|
|
1883
|
+
* a provisional binding. Unlike a bare self-reference, a member/element
|
|
1884
|
+
* read is not automatically the same type as its receiver: it may name a
|
|
1885
|
+
* different field entirely. This predicate therefore only chooses what to
|
|
1886
|
+
* DEFER; `validateDeferredRecursiveArguments` below re-resolves the full
|
|
1887
|
+
* expression and proves its resulting type fits before publication.
|
|
1888
|
+
*/
|
|
1889
|
+
const isDerivedFromParameter = (expression, parameter) => {
|
|
1890
|
+
const seen = new Set();
|
|
1891
|
+
const visit = (node) => {
|
|
1892
|
+
let current = node;
|
|
1893
|
+
while (ts.isParenthesizedExpression(current) ||
|
|
1894
|
+
ts.isAsExpression(current) ||
|
|
1895
|
+
ts.isTypeAssertionExpression(current) ||
|
|
1896
|
+
ts.isNonNullExpression(current) ||
|
|
1897
|
+
ts.isSatisfiesExpression(current))
|
|
1898
|
+
current = current.expression;
|
|
1899
|
+
if (seen.has(current))
|
|
1900
|
+
return false;
|
|
1901
|
+
seen.add(current);
|
|
1902
|
+
if (referencesParameter(current, parameter))
|
|
1903
|
+
return true;
|
|
1904
|
+
if (ts.isPropertyAccessExpression(current) || ts.isElementAccessExpression(current))
|
|
1905
|
+
return visit(current.expression);
|
|
1906
|
+
if (!ts.isIdentifier(current))
|
|
1907
|
+
return false;
|
|
1908
|
+
const declarations = checker.getSymbolAtLocation(current)?.declarations;
|
|
1909
|
+
if (!declarations || declarations.length !== 1)
|
|
1910
|
+
return false;
|
|
1911
|
+
const declaration = declarations[0];
|
|
1912
|
+
if (!declaration || (!ts.isVariableDeclaration(declaration) && !ts.isBindingElement(declaration)))
|
|
1913
|
+
return false;
|
|
1914
|
+
return valueFlow
|
|
1915
|
+
.writesToDeclaration(declaration)
|
|
1916
|
+
.some((write) => write.slot === 'whole' && write.value !== null && visit(write.value));
|
|
1917
|
+
};
|
|
1918
|
+
return visit(expression) && !referencesParameter(expression, parameter);
|
|
1919
|
+
};
|
|
1920
|
+
/**
|
|
1921
|
+
* One pass over every call attributed to `declaration`, resolving the
|
|
1922
|
+
* argument at `index`. `excludeBackEdges` chooses which of the two
|
|
1923
|
+
* readings `agreedArgumentType` (below) is asking for: skipping a back
|
|
1924
|
+
* edge -- see the module comment above `isRecursiveCallWithin` -- is the
|
|
1925
|
+
* sound, information-ADDING reading, and resolving it like any other
|
|
1926
|
+
* argument is the fallback for when a back edge is all the evidence
|
|
1927
|
+
* there is. `sawBackEdge` tells the caller whether skipping happened at
|
|
1928
|
+
* all, so it can tell "every call was a back edge" (this comes back with
|
|
1929
|
+
* an empty `passed` AND `sawBackEdge`) apart from "there were simply no
|
|
1930
|
+
* calls" (empty `passed`, no back edge) without re-walking `calls`.
|
|
1931
|
+
* `skipSilentSites` is the second such choice; see `EVIDENCE EXHAUSTED`.
|
|
1932
|
+
*/
|
|
1933
|
+
const argumentsByParameter = new Map();
|
|
1934
|
+
/**
|
|
1935
|
+
* A silent site is one the census has no evidence for YET. A site whose
|
|
1936
|
+
* argument is a runtime-keyed read (`obj[name]`) or an ambient member the
|
|
1937
|
+
* library declares `any` (`desc.value` on lib's PropertyDescriptor) has no
|
|
1938
|
+
* evidence to come: the value is dynamic in every authority. Skipping it
|
|
1939
|
+
* and binding the parameter from the other sites would unbox the value at
|
|
1940
|
+
* this call -- test262's `isSameValue(desc.value, obj[name])` bound `b` to
|
|
1941
|
+
* `string` from `newValue` and aborted on the first numeric `length`.
|
|
1942
|
+
*/
|
|
1943
|
+
const isSuppliedDynamic = (argument) => {
|
|
1944
|
+
let expression = argument;
|
|
1945
|
+
while (ts.isParenthesizedExpression(expression))
|
|
1946
|
+
expression = expression.expression;
|
|
1947
|
+
const type = checker.getTypeAtLocation(expression);
|
|
1948
|
+
if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0)
|
|
1949
|
+
return false;
|
|
1950
|
+
if (ts.isElementAccessExpression(expression)) {
|
|
1951
|
+
return !ts.isStringLiteralLike(expression.argumentExpression) && !ts.isNumericLiteral(expression.argumentExpression);
|
|
1952
|
+
}
|
|
1953
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
1954
|
+
const declaration = checker.getSymbolAtLocation(expression.name)?.valueDeclaration;
|
|
1955
|
+
return declaration !== undefined && declaration.getSourceFile().isDeclarationFile;
|
|
1956
|
+
}
|
|
1957
|
+
return false;
|
|
1958
|
+
};
|
|
1959
|
+
const collectPassedArguments = (calls, declaration, parameter, index, excludeBackEdges, skipSilentSites) => {
|
|
1960
|
+
const passed = [];
|
|
1961
|
+
const deferredRecursiveArguments = [];
|
|
1962
|
+
let sawBackEdge = false;
|
|
1963
|
+
let sawOmitted = false;
|
|
1964
|
+
let sawSilentSite = false;
|
|
1965
|
+
const record = (argument) => {
|
|
1966
|
+
const known = argumentsByParameter.get(parameter);
|
|
1967
|
+
if (known === undefined)
|
|
1968
|
+
argumentsByParameter.set(parameter, new Set([argument]));
|
|
1969
|
+
else
|
|
1970
|
+
known.add(argument);
|
|
1971
|
+
};
|
|
1972
|
+
for (const call of calls) {
|
|
1973
|
+
const effectiveArguments = invocationOperands.get(call).args;
|
|
1974
|
+
// `describe( ...args )` where `args` is a rest parameter DECLARED as a
|
|
1975
|
+
// closed tuple hands this position exactly the tuple's element there.
|
|
1976
|
+
// `producers/tuple-spread.ts` expands that spread into constant-index
|
|
1977
|
+
// reads typed by the same declaration; read the same fact here, or the
|
|
1978
|
+
// operands say `number` while this formal's storage is refused
|
|
1979
|
+
// `argument-unresolved` on the spread node and boxed -- which is how
|
|
1980
|
+
// `x * y` in a forwarding callee became a dynamic multiplication with
|
|
1981
|
+
// no C++ spelling (test/runtime/tuple-typed-rest-spread.runtime.js).
|
|
1982
|
+
// A position past the tuple's arity is a genuinely omitted argument,
|
|
1983
|
+
// and falls through to the omission rule below like any other.
|
|
1984
|
+
const forwarded = declaredTupleSpreadPositionAt(checker, effectiveArguments, index);
|
|
1985
|
+
if (forwarded !== null) {
|
|
1986
|
+
passed.push(forwarded.isLiteral() ? checker.getBaseTypeOfLiteralType(forwarded) : forwarded);
|
|
1987
|
+
continue;
|
|
1988
|
+
}
|
|
1989
|
+
const argument = effectiveArguments[index];
|
|
1990
|
+
// The checker also authenticates JSDoc's `[name]` as optional. Looking
|
|
1991
|
+
// only for a question token rejected legal JS constructor omissions and
|
|
1992
|
+
// discarded all their supplied configuration evidence. Absence is a
|
|
1993
|
+
// real incoming value, including when every caller omits the argument;
|
|
1994
|
+
// only a default replaces it before the body observes the binding.
|
|
1995
|
+
if (!argument && checker.isOptionalParameter(parameter)) {
|
|
1996
|
+
sawOmitted = true;
|
|
1997
|
+
if (parameter.initializer === undefined)
|
|
1998
|
+
passed.push(checker.getUndefinedType());
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
if (!argument)
|
|
2002
|
+
return { refused: 'call-passes-no-argument' };
|
|
2003
|
+
// A BACK EDGE, not a second opinion. `projectObject( children[ i ],
|
|
2004
|
+
// camera, groupOrder, sortObjects )`, called from inside `projectObject`
|
|
2005
|
+
// itself, hands `groupOrder` its OWN value right back unchanged --
|
|
2006
|
+
// carrying no information about what the parameter holds until the
|
|
2007
|
+
// parameter is already bound. Joining it as if it were a disagreeing
|
|
2008
|
+
// call site is a category error: it is the recursion's own back edge.
|
|
2009
|
+
//
|
|
2010
|
+
// Excluding it is sound because it is VERIFIED, not assumed: the
|
|
2011
|
+
// structural test below (`referencesParameter`) is the same test that
|
|
2012
|
+
// finds it, and it proves the excluded argument literally IS
|
|
2013
|
+
// `parameter` -- so whatever `parameter` ends up bound to from the
|
|
2014
|
+
// OTHER call sites, this call site passes exactly that value BY
|
|
2015
|
+
// CONSTRUCTION. There is no type this argument could independently
|
|
2016
|
+
// hold that would disagree; a bare re-read of the same storage cannot
|
|
2017
|
+
// diverge from what that storage is bound to. That is a stronger
|
|
2018
|
+
// guarantee than re-resolving the argument after binding would give,
|
|
2019
|
+
// and it is available now, in the same round, without reopening the
|
|
2020
|
+
// fixpoint.
|
|
2021
|
+
const recursiveWithin = isRecursiveCallWithin(call, declaration);
|
|
2022
|
+
const isIdentityBackEdge = recursiveWithin && referencesParameter(argument, parameter);
|
|
2023
|
+
const isDerivedBackEdge = recursiveWithin && !isIdentityBackEdge && isDerivedFromParameter(argument, parameter);
|
|
2024
|
+
const isBackEdge = isIdentityBackEdge || isDerivedBackEdge;
|
|
2025
|
+
if (isBackEdge)
|
|
2026
|
+
sawBackEdge = true;
|
|
2027
|
+
if (isDerivedBackEdge)
|
|
2028
|
+
deferredRecursiveArguments.push(argument);
|
|
2029
|
+
if (excludeBackEdges && isBackEdge)
|
|
2030
|
+
continue;
|
|
2031
|
+
const resolved = propagating.known(argument) ?? propagating.resolve(argument);
|
|
2032
|
+
if (!resolved || isUnusableEvidence(resolved)) {
|
|
2033
|
+
// A default is not an annotation. If this read still has the checker's
|
|
2034
|
+
// dynamic type, it is a possible supplied value, not an omitted call.
|
|
2035
|
+
// Publish that actual type beside the initializer so falling back to
|
|
2036
|
+
// the checker's default-only parameter type cannot erase the input.
|
|
2037
|
+
const supplied = checker.getTypeAtLocation(argument);
|
|
2038
|
+
if (parameter.initializer && (supplied.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {
|
|
2039
|
+
passed.push(supplied);
|
|
2040
|
+
record(argument);
|
|
2041
|
+
continue;
|
|
2042
|
+
}
|
|
2043
|
+
if (skipSilentSites && !resolved && isSuppliedDynamic(argument)) {
|
|
2044
|
+
passed.push(supplied);
|
|
2045
|
+
record(argument);
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
// An explicit unresolved argument is not an omitted argument. A
|
|
2049
|
+
// default describes only omission/undefined, so it cannot prove a
|
|
2050
|
+
// carrier for an unknown supplied value. Likewise void/never is a
|
|
2051
|
+
// real refusal, never evidence that the call did not happen.
|
|
2052
|
+
if (resolved || !skipSilentSites || parameter.initializer !== undefined) {
|
|
2053
|
+
return { refused: resolved ? 'argument-states-no-storage' : 'argument-unresolved' };
|
|
2054
|
+
}
|
|
2055
|
+
sawSilentSite = true;
|
|
2056
|
+
continue;
|
|
2057
|
+
}
|
|
2058
|
+
// `f(5)` does not mean the parameter holds only `5`. A literal argument's
|
|
2059
|
+
// type is a fact about the call, not about the storage it lands in, and a
|
|
2060
|
+
// second call site passing `6` would then read as a disagreement.
|
|
2061
|
+
passed.push(resolved.isLiteral() ? checker.getBaseTypeOfLiteralType(resolved) : resolved);
|
|
2062
|
+
record(argument);
|
|
2063
|
+
}
|
|
2064
|
+
return { passed, sawBackEdge, sawOmitted, sawSilentSite, deferredRecursiveArguments };
|
|
2065
|
+
};
|
|
2066
|
+
/** The type every call site passes at this position, or the reason there is no single one. */
|
|
2067
|
+
const agreedArgumentType = (declaration, parameter, parameterIndex, skipSilentSites) => {
|
|
2068
|
+
const calls = callsByDeclaration.get(declaration) ?? [];
|
|
2069
|
+
const contracts = contractsFor(declaration) ?? [];
|
|
2070
|
+
const contractTypes = [];
|
|
2071
|
+
for (const contract of contracts) {
|
|
2072
|
+
const type = callbackContractParameterType(checker, contract, parameterIndex);
|
|
2073
|
+
if (!type)
|
|
2074
|
+
return { refused: 'callback-contract-has-no-parameter' };
|
|
2075
|
+
contractTypes.push(type);
|
|
2076
|
+
}
|
|
2077
|
+
if (calls.length === 0 && contractTypes.length === 0)
|
|
2078
|
+
return { refused: 'no-call-site' };
|
|
2079
|
+
// A typed boundary must not turn a partially observed direct caller set
|
|
2080
|
+
// into a closed one. Unresolved direct inputs remain a refusal here.
|
|
2081
|
+
const allowSilentDirectSites = skipSilentSites && contracts.length === 0;
|
|
2082
|
+
const excluding = collectPassedArguments(calls, declaration, parameter, parameterIndex, true, allowSilentDirectSites);
|
|
2083
|
+
if ('refused' in excluding)
|
|
2084
|
+
return excluding;
|
|
2085
|
+
// A back edge is worth excluding only where doing so buys something:
|
|
2086
|
+
// real, independent evidence survives once it is set aside. Where the
|
|
2087
|
+
// back edge IS the only evidence -- `excluding.passed` comes back empty
|
|
2088
|
+
// -- there is nothing left to improve on, and refusing here would make
|
|
2089
|
+
// a parameter the OLD, pre-exclusion join could still answer WORSE than
|
|
2090
|
+
// before this exclusion existed: measured on the three.js app, paying the
|
|
2091
|
+
// exclusion unconditionally cost +6002 boxed carriers (+21%) for +14
|
|
2092
|
+
// resolved rows elsewhere, because most exclusions landed exactly here,
|
|
2093
|
+
// on parameters recursion-only in this program. Fall back to the
|
|
2094
|
+
// previous reading instead: join every call site, back edge included.
|
|
2095
|
+
// The same soundness argument above still covers it -- a bare re-read
|
|
2096
|
+
// of the parameter's own storage cannot disagree with whatever that
|
|
2097
|
+
// storage is bound to -- so including it here adds no unsound
|
|
2098
|
+
// information; it just declines to improve on the declaration the way
|
|
2099
|
+
// excluding it would have.
|
|
2100
|
+
const resolution = excluding.passed.length > 0 || !excluding.sawBackEdge
|
|
2101
|
+
? excluding
|
|
2102
|
+
: collectPassedArguments(calls, declaration, parameter, parameterIndex, false, allowSilentDirectSites);
|
|
2103
|
+
if ('refused' in resolution)
|
|
2104
|
+
return resolution;
|
|
2105
|
+
// Reachable for a second reason once silent sites are dropped: every site
|
|
2106
|
+
// was silent -- no evidence at all, named for what actually happened.
|
|
2107
|
+
// Named apart from the per-site refusal above for the same reason: this is
|
|
2108
|
+
// "every site was silent", the relaxed phase's own exhaustion, not one
|
|
2109
|
+
// site handing over an unusable type.
|
|
2110
|
+
// A call site that leaves a defaulted parameter out is a WRITE of the
|
|
2111
|
+
// initializer: it runs at that call, so its type joins the cell's writes
|
|
2112
|
+
// exactly as a passed argument would. `static #m([x] = [1])` reached only
|
|
2113
|
+
// as `C.method()` is the all-silent shape (refusing it left the pattern
|
|
2114
|
+
// reading a dynamic array); `f([3])` beside `f()` is the mixed one, where
|
|
2115
|
+
// dropping `[1]` from the join had the pattern read a position the
|
|
2116
|
+
// default never states.
|
|
2117
|
+
const defaulted = (resolution.passed.length === 0 || resolution.sawOmitted) && parameter.initializer
|
|
2118
|
+
? (propagating.known(parameter.initializer) ?? propagating.resolve(parameter.initializer))
|
|
2119
|
+
: null;
|
|
2120
|
+
// An evaluated `{}` default is the exact empty holder
|
|
2121
|
+
// (`exactEmptyObjectLiteralType`), not the vacuous annotation
|
|
2122
|
+
// `isUnusableEvidence` rightly drops: `function f({} = {})` reached only
|
|
2123
|
+
// as `f()` bound the parameter to `undefined` alone once its one real
|
|
2124
|
+
// write was filtered out. Taken only when no call site supplies a shape
|
|
2125
|
+
// of its own -- beside one, the join would let the empty literal (which
|
|
2126
|
+
// every object is assignable to) swallow the supplied shape, and the
|
|
2127
|
+
// pattern would read every key as absent.
|
|
2128
|
+
const emptyDefault = (resolution.passed.length === 0 || resolution.sawOmitted) &&
|
|
2129
|
+
parameter.initializer &&
|
|
2130
|
+
resolution.passed.every((type) => isNullishType(type))
|
|
2131
|
+
? exactEmptyObjectLiteralType(checker, parameter.initializer)
|
|
2132
|
+
: null;
|
|
2133
|
+
const directPassed = emptyDefault !== null
|
|
2134
|
+
? [...resolution.passed, emptyDefault]
|
|
2135
|
+
: defaulted && !isUnusableEvidence(defaulted)
|
|
2136
|
+
? [...resolution.passed, defaulted]
|
|
2137
|
+
: resolution.passed;
|
|
2138
|
+
// Direct callers remain evidence even beside a typed higher-order slot.
|
|
2139
|
+
// In particular, an any input absorbs the typed contract rather than
|
|
2140
|
+
// letting the convenient boundary erase a real caller.
|
|
2141
|
+
// An assignment in the body writes this same cell, so its value joins the
|
|
2142
|
+
// incoming arguments before agreement is judged -- see the reassignment
|
|
2143
|
+
// note on `notReassigned`. An operator-typed write (`r *= a`) is read off
|
|
2144
|
+
// the assignment expression itself, where the operator states the result;
|
|
2145
|
+
// every other write is resolved like any other value expression.
|
|
2146
|
+
const parameterSymbol = ts.isIdentifier(parameter.name) ? checker.getSymbolAtLocation(parameter.name) : undefined;
|
|
2147
|
+
const writes = (parameterSymbol && assignedEvidence.get(parameterSymbol)) || [];
|
|
2148
|
+
const writtenTypes = writes.map((write) => write.operatorTyped
|
|
2149
|
+
? checker.getTypeAtLocation(write.expression)
|
|
2150
|
+
: (propagating.known(write.expression) ?? propagating.resolve(write.expression)));
|
|
2151
|
+
// A complete numeric incoming frame can seed a numeric storage invariant.
|
|
2152
|
+
// Test EVERY write under that hypothesis before publishing it. In
|
|
2153
|
+
// particular, += is operand-sensitive: its checker answer stays any for an
|
|
2154
|
+
// unannotated self-read, although a numeric input and numeric RHS preserve
|
|
2155
|
+
// number. A string/opaque write, omitted unknown default or incomplete
|
|
2156
|
+
// caller must never borrow this seed to hide another possible value.
|
|
2157
|
+
const numeric = (type) => type !== null && type !== undefined && (type.flags & ts.TypeFlags.NumberLike) !== 0;
|
|
2158
|
+
const incoming = [...directPassed, ...contractTypes];
|
|
2159
|
+
const numericSeed = incoming.length > 0 &&
|
|
2160
|
+
incoming.every(numeric) &&
|
|
2161
|
+
!resolution.sawSilentSite &&
|
|
2162
|
+
!resolution.sawBackEdge &&
|
|
2163
|
+
(index.implicitArgumentsUses.get(declaration)?.length ?? 0) === 0 &&
|
|
2164
|
+
resolution.deferredRecursiveArguments.length === 0 &&
|
|
2165
|
+
(!resolution.sawOmitted || numeric(defaulted));
|
|
2166
|
+
let invariantTypes = null;
|
|
2167
|
+
if (numericSeed && writtenTypes.some((type) => !type || isUnusableEvidence(type))) {
|
|
2168
|
+
const tentative = writes.map((write, position) => {
|
|
2169
|
+
const expression = write.expression;
|
|
2170
|
+
if (!write.operatorTyped || !ts.isBinaryExpression(expression) || expression.operatorToken.kind !== ts.SyntaxKind.PlusEqualsToken)
|
|
2171
|
+
return writtenTypes[position];
|
|
2172
|
+
return derivedExpressionType(checker, expression, (operand) => ts.isIdentifier(operand) && checker.getSymbolAtLocation(operand) === parameterSymbol
|
|
2173
|
+
? checker.getNumberType()
|
|
2174
|
+
: (propagating.known(operand) ?? propagating.resolve(operand)));
|
|
2175
|
+
});
|
|
2176
|
+
if (tentative.every(numeric))
|
|
2177
|
+
invariantTypes = tentative;
|
|
2178
|
+
}
|
|
2179
|
+
const assignedTypes = [];
|
|
2180
|
+
for (const written of invariantTypes ?? writtenTypes) {
|
|
2181
|
+
if (!written || isUnusableEvidence(written))
|
|
2182
|
+
return { refused: 'parameter-reassigned' };
|
|
2183
|
+
assignedTypes.push(written);
|
|
2184
|
+
}
|
|
2185
|
+
const passed = [...directPassed, ...contractTypes, ...assignedTypes];
|
|
2186
|
+
if (passed.length === 0)
|
|
2187
|
+
return { refused: skipSilentSites ? 'every-call-site-silent' : 'no-call-site' };
|
|
2188
|
+
// Omission is a real write, but cannot seed a concrete carrier while
|
|
2189
|
+
// supplied arguments remain unresolved. Otherwise the relaxed sweep
|
|
2190
|
+
// seals an optional forwarding method to Undefined before its callers
|
|
2191
|
+
// settle, then inserts failing unboxes for their actual objects.
|
|
2192
|
+
if (resolution.sawSilentSite && passed.every(isNullishType))
|
|
2193
|
+
return { refused: 'nullish-only-partial-evidence' };
|
|
2194
|
+
// A supplied dynamic value beside the default (`retain(); retain(JSON.
|
|
2195
|
+
// parse('{}'))`, `collectPassedArguments`'s own `supplied` arm) makes the
|
|
2196
|
+
// cell dynamic outright: `any` absorbs every other write, and asking
|
|
2197
|
+
// `joinOfWrites` to find a widest member among `[any, undefined]` only
|
|
2198
|
+
// refuses the site as a disagreement, erasing the input the arm kept.
|
|
2199
|
+
const dynamic = passed.find((type) => (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0);
|
|
2200
|
+
if (dynamic)
|
|
2201
|
+
return { type: dynamic, sawSilentSite: resolution.sawSilentSite, deferredRecursiveArguments: resolution.deferredRecursiveArguments };
|
|
2202
|
+
const joined = joinOfWrites(checker, passed); // same write-set join a cell's writes get
|
|
2203
|
+
if (joined)
|
|
2204
|
+
return { type: joined, sawSilentSite: resolution.sawSilentSite, deferredRecursiveArguments: resolution.deferredRecursiveArguments };
|
|
2205
|
+
// No single covering type -- is the disagreement itself sound? See `disjointUnionMembersOf`.
|
|
2206
|
+
const arms = disjointUnionMembersOf(checker, passed);
|
|
2207
|
+
return arms
|
|
2208
|
+
? { unionArms: arms, sawSilentSite: resolution.sawSilentSite, deferredRecursiveArguments: resolution.deferredRecursiveArguments }
|
|
2209
|
+
: { refused: 'call-sites-disagree' };
|
|
2210
|
+
};
|
|
2211
|
+
// A fixpoint, because an argument can itself be an unannotated parameter one
|
|
2212
|
+
// frame up: `WebGLRenderer`'s own `context` is bound from the application
|
|
2213
|
+
// before `WebGLTextures`'s `_gl` can be bound from `WebGLRenderer`. Rounds
|
|
2214
|
+
// stop when a pass adds nothing, which terminates because a binding is only
|
|
2215
|
+
// ever added and there are finitely many parameters.
|
|
2216
|
+
/**
|
|
2217
|
+
* Attribute every call to the declaration it reaches, using this round's
|
|
2218
|
+
* bindings where the checker had no answer.
|
|
2219
|
+
*
|
|
2220
|
+
* `getResolvedSignature` gives up on `extensions.has( ... )` while
|
|
2221
|
+
* `extensions` is `any`, and never revisits it -- so the functions three's
|
|
2222
|
+
* factories return as members of a record have, from the checker's view, no
|
|
2223
|
+
* callers at all. They have exactly one each, and it becomes visible the
|
|
2224
|
+
* moment the record has a type. That is why attribution belongs INSIDE the
|
|
2225
|
+
* fixpoint: a round that binds `extensions` is what lets the next round see
|
|
2226
|
+
* who calls its members.
|
|
2227
|
+
*/
|
|
2228
|
+
/** Records `call` as a site of `declaration`, once -- the same call can arrive from two different attribution passes below and must not double-count. */
|
|
2229
|
+
const pushCall = (declaration, call) => {
|
|
2230
|
+
if (isRealCallableDeclaration(declaration) &&
|
|
2231
|
+
!declaration.getSourceFile().isDeclarationFile &&
|
|
2232
|
+
'body' in declaration &&
|
|
2233
|
+
declaration.body) {
|
|
2234
|
+
const targets = callTargets.get(call);
|
|
2235
|
+
if (!targets)
|
|
2236
|
+
callTargets.set(call, [declaration]);
|
|
2237
|
+
else if (!targets.includes(declaration))
|
|
2238
|
+
targets.push(declaration);
|
|
2239
|
+
}
|
|
2240
|
+
const existing = callsByDeclaration.get(declaration);
|
|
2241
|
+
if (existing) {
|
|
2242
|
+
if (!existing.includes(call))
|
|
2243
|
+
existing.push(call);
|
|
2244
|
+
}
|
|
2245
|
+
else
|
|
2246
|
+
callsByDeclaration.set(declaration, [call]);
|
|
2247
|
+
};
|
|
2248
|
+
/**
|
|
2249
|
+
* The selector declarations that could have produced the value now held by
|
|
2250
|
+
* `identifier`'s own storage -- `const setter = getSingularSetter(type);`
|
|
2251
|
+
* -- so that a later `setter( gl, v )` can be attributed to whatever that
|
|
2252
|
+
* selector returns, the same way `getSingularSetter(type)( gl, v )` (no
|
|
2253
|
+
* intervening variable) already is.
|
|
2254
|
+
*
|
|
2255
|
+
* Every WRITE to the identifier's binding is considered, same as
|
|
2256
|
+
* `writeSetTypeOf` considers every write to a cell: a write that is not
|
|
2257
|
+
* itself a call, or a call the checker did not attribute to a known
|
|
2258
|
+
* selector, simply contributes nothing here -- it is not a disagreement,
|
|
2259
|
+
* because this is gathering CALL-SITE evidence for whichever function
|
|
2260
|
+
* actually runs, not a claim about every value the variable could ever
|
|
2261
|
+
* hold. A write from an unrelated source and a write from a selector can
|
|
2262
|
+
* coexist on the same variable; the call site's arguments are the same
|
|
2263
|
+
* fixed expressions in the source regardless of which one is live, so
|
|
2264
|
+
* crediting the selector's returned functions with this call's evidence is
|
|
2265
|
+
* sound even when the other write cannot be explained.
|
|
2266
|
+
*/
|
|
2267
|
+
const selectorsFeedingVariable = (identifier) => {
|
|
2268
|
+
const symbol = checker.getSymbolAtLocation(identifier);
|
|
2269
|
+
const declarations = symbol?.declarations;
|
|
2270
|
+
const declaration = declarations && declarations.length === 1 ? declarations[0] : undefined;
|
|
2271
|
+
if (!declaration || !ts.isVariableDeclaration(declaration))
|
|
2272
|
+
return EMPTY_DECLARATIONS;
|
|
2273
|
+
const writes = valueFlow
|
|
2274
|
+
.writesToDeclaration(declaration)
|
|
2275
|
+
.flatMap((write) => (write.slot === 'whole' && write.value !== null ? [write.value] : []));
|
|
2276
|
+
let result = null;
|
|
2277
|
+
for (const write of writes) {
|
|
2278
|
+
if (!ts.isCallExpression(write))
|
|
2279
|
+
continue;
|
|
2280
|
+
const selector = checkerAttribution.get(write);
|
|
2281
|
+
const returned = selector && aliasEvidence.returnedFrom.get(selector);
|
|
2282
|
+
if (!returned)
|
|
2283
|
+
continue;
|
|
2284
|
+
if (!result)
|
|
2285
|
+
result = new Set();
|
|
2286
|
+
for (const declaration of returned)
|
|
2287
|
+
result.add(declaration);
|
|
2288
|
+
}
|
|
2289
|
+
return result ?? EMPTY_DECLARATIONS;
|
|
2290
|
+
};
|
|
2291
|
+
/**
|
|
2292
|
+
* Every declaration this ALIAS pass attributes `call` to, beyond whatever
|
|
2293
|
+
* the checker or the propagating resolver already found -- see
|
|
2294
|
+
* `AliasEvidence`. Both shapes are real, symbol-anchored evidence, not a
|
|
2295
|
+
* guess: a member call matches only when the checker resolves the SAME
|
|
2296
|
+
* member symbol something was published under, and a selector-result call
|
|
2297
|
+
* matches only when the checker (not this census) already attributed the
|
|
2298
|
+
* inner call producing that result. A dynamic receiver or an unresolved
|
|
2299
|
+
* intermediate resolves to no symbol at all, so neither branch fires and
|
|
2300
|
+
* the ordinary refusal stands, exactly as it did before this pass existed.
|
|
2301
|
+
*/
|
|
2302
|
+
// Built on first use rather than up front: `closedArrayCalleeAuthorityOf`
|
|
2303
|
+
// composes three collection/protocol queries, and the vast majority of
|
|
2304
|
+
// programs never reach the element-callee branch that needs it at all.
|
|
2305
|
+
let heldArrayCalleeAuthority = null;
|
|
2306
|
+
const arrayCalleeAuthority = () => (heldArrayCalleeAuthority ??= closedArrayCalleeAuthorityOf(checker, valueFlow, propagatingTypeAt, implicitArgumentsUsesAt));
|
|
2307
|
+
const aliasDeclarationsFor = (call) => {
|
|
2308
|
+
let result = null;
|
|
2309
|
+
const add = (declarations) => {
|
|
2310
|
+
if (!declarations || declarations.size === 0)
|
|
2311
|
+
return;
|
|
2312
|
+
if (!result)
|
|
2313
|
+
result = new Set();
|
|
2314
|
+
for (const declaration of declarations)
|
|
2315
|
+
result.add(declaration);
|
|
2316
|
+
};
|
|
2317
|
+
const heldOperands = invocationOperands.get(call);
|
|
2318
|
+
const heldCallee = heldOperands.callee;
|
|
2319
|
+
// Once a `.call`/`.apply` reading is settled -- statically, by
|
|
2320
|
+
// `unwrapExplicitThisCall`, or by THIS round's own closed-array proof
|
|
2321
|
+
// below -- it must stay settled every later round. `heldOperands` already
|
|
2322
|
+
// carries the readback of whichever `censusExplicitThisAt` answer built
|
|
2323
|
+
// THIS round's flow index, but `aliasDeclarationsFor` only recognizes the
|
|
2324
|
+
// pending (still-`.call`-wrapped) SHAPE further down, not an already-
|
|
2325
|
+
// resolved one (`heldCallee` is `array[ i ]` itself by then, no `.call`
|
|
2326
|
+
// property access left to match) -- so without this, round N proves the
|
|
2327
|
+
// reading, round N+1's index carries it, round N+1's OWN alias pass finds
|
|
2328
|
+
// no pending shape left to re-derive it from, publishes no
|
|
2329
|
+
// `explicitThisAt` answer for this call, and round N+2 reverts to
|
|
2330
|
+
// unresolved. Republishing the settled reading here breaks that
|
|
2331
|
+
// oscillation.
|
|
2332
|
+
if (heldOperands.explicitThis)
|
|
2333
|
+
pendingExplicitThisReadings.set(call, { callee: heldOperands.callee, receiver: heldOperands.receiver, args: heldOperands.args });
|
|
2334
|
+
if (ts.isPropertyAccessExpression(heldCallee) || ts.isElementAccessExpression(heldCallee)) {
|
|
2335
|
+
const key = ts.isPropertyAccessExpression(heldCallee) ? heldCallee.name.text : literalMemberNameOf(heldCallee);
|
|
2336
|
+
const receiver = propagating.known(heldCallee.expression) ?? propagating.resolve(heldCallee.expression);
|
|
2337
|
+
const memberSymbol = checker.getSymbolAtLocation(ts.isPropertyAccessExpression(heldCallee) ? heldCallee.name : heldCallee) ??
|
|
2338
|
+
(receiver && key !== null ? checker.getPropertyOfType(checker.getApparentType(receiver), key) : undefined);
|
|
2339
|
+
add(memberSymbol ? publishedDeclarationsFor(memberSymbol) : undefined);
|
|
2340
|
+
// `array[ i ].call( this, event )` -- a computed element read with no
|
|
2341
|
+
// literal key names no symbol at all, so the lookup above finds nothing
|
|
2342
|
+
// and the call is attributed to no declaration. That is exactly how
|
|
2343
|
+
// three's `EventDispatcher.dispatchEvent` invokes EVERY listener, which
|
|
2344
|
+
// is why each `on*Dispose( event )` body sees an untyped `event` and
|
|
2345
|
+
// every object recovered from `event.target` boxes.
|
|
2346
|
+
//
|
|
2347
|
+
// `arrayCalleeAuthority().arrayElementTargetsOf` is the identical
|
|
2348
|
+
// closed-array proof that `flow/callable-reach.ts`'s own `.call`/
|
|
2349
|
+
// `.apply` branch already trusts for ESCAPE closure (`callableArray-
|
|
2350
|
+
// TargetsOf`, with a record/dictionary-storage fallback via
|
|
2351
|
+
// `arrayStoredValuesOf` for a plain-object listener map like three's
|
|
2352
|
+
// `EventDispatcher._listeners[ type ]`); asked here for TYPE
|
|
2353
|
+
// ATTRIBUTION instead, so a listener's declaration receives this call
|
|
2354
|
+
// site as real parameter evidence the same way an override receives a
|
|
2355
|
+
// base's calls. It returns null unless the array's whole allocation
|
|
2356
|
+
// and use graph is closed, so it can only ever add complete evidence,
|
|
2357
|
+
// never a guess.
|
|
2358
|
+
//
|
|
2359
|
+
// Gated on the ordinary lookup having found nothing, so no attribution
|
|
2360
|
+
// that already worked can change.
|
|
2361
|
+
if (!memberSymbol && ts.isElementAccessExpression(heldCallee)) {
|
|
2362
|
+
const targets = arrayCalleeAuthority().arrayElementTargetsOf?.(heldCallee);
|
|
2363
|
+
if (targets)
|
|
2364
|
+
add(new Set(targets));
|
|
2365
|
+
}
|
|
2366
|
+
// The actual `array[ i ].call( this, event )` spelling: `heldCallee` is
|
|
2367
|
+
// the WRAPPER property access (`.call`/`.apply` itself), not the array
|
|
2368
|
+
// element, because `unwrapExplicitThisCall` left it unresolved -- its
|
|
2369
|
+
// static gate needs the element's checker type to already carry a call
|
|
2370
|
+
// signature, and an untyped push (`listeners[ type ].push( listener )`
|
|
2371
|
+
// onto a field with no declared element type) never does. The same
|
|
2372
|
+
// closed-array proof above still answers "is every value this array
|
|
2373
|
+
// ever held a real function" without needing that type, so ask it of
|
|
2374
|
+
// the WRAPPED element (`heldCallee.expression`) instead of `heldCallee`
|
|
2375
|
+
// itself, and commit to the explicit-this reading -- `thisArg` and the
|
|
2376
|
+
// real arguments, not the array-element receiver and both raw operands
|
|
2377
|
+
// -- only once it holds. `pendingExplicitThisReadings` feeds NEXT
|
|
2378
|
+
// round's `indexValueFlow` (`censusExplicitThisAt`), so `site.operands`
|
|
2379
|
+
// itself carries the corrected frame everywhere it is read (argument
|
|
2380
|
+
// census, host-mutation census, target resolution alike) rather than
|
|
2381
|
+
// this proof living only here.
|
|
2382
|
+
if (!memberSymbol &&
|
|
2383
|
+
ts.isPropertyAccessExpression(heldCallee) &&
|
|
2384
|
+
(key === 'call' || key === 'apply') &&
|
|
2385
|
+
ts.isElementAccessExpression(heldCallee.expression)) {
|
|
2386
|
+
const element = heldCallee.expression;
|
|
2387
|
+
const targets = arrayCalleeAuthority().arrayElementTargetsOf?.(element);
|
|
2388
|
+
if (targets) {
|
|
2389
|
+
add(new Set(targets));
|
|
2390
|
+
if (key === 'call') {
|
|
2391
|
+
pendingExplicitThisReadings.set(call, { callee: element, receiver: call.arguments[0] ?? null, args: call.arguments.slice(1) });
|
|
2392
|
+
}
|
|
2393
|
+
else if (call.arguments.length === 2) {
|
|
2394
|
+
const argumentsArray = call.arguments[1];
|
|
2395
|
+
if (argumentsArray &&
|
|
2396
|
+
ts.isArrayLiteralExpression(argumentsArray) &&
|
|
2397
|
+
argumentsArray.elements.every((e) => !ts.isSpreadElement(e)))
|
|
2398
|
+
pendingExplicitThisReadings.set(call, {
|
|
2399
|
+
callee: element,
|
|
2400
|
+
receiver: call.arguments[0] ?? null,
|
|
2401
|
+
args: argumentsArray.elements
|
|
2402
|
+
});
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
const callee = call.expression;
|
|
2408
|
+
if (ts.isCallExpression(callee)) {
|
|
2409
|
+
const selector = checkerAttribution.get(callee);
|
|
2410
|
+
add(selector ? aliasEvidence.returnedFrom.get(selector) : undefined);
|
|
2411
|
+
}
|
|
2412
|
+
else if (ts.isIdentifier(callee)) {
|
|
2413
|
+
add(selectorsFeedingVariable(callee));
|
|
2414
|
+
}
|
|
2415
|
+
return result ?? EMPTY_DECLARATIONS;
|
|
2416
|
+
};
|
|
2417
|
+
const attributeCalls = () => {
|
|
2418
|
+
callbackContracts.clear();
|
|
2419
|
+
callsByDeclaration.clear();
|
|
2420
|
+
callTargets.clear();
|
|
2421
|
+
resolvedCallDeclarations.clear();
|
|
2422
|
+
for (const call of allCalls) {
|
|
2423
|
+
let declaration = checkerAttribution.get(call) ?? null;
|
|
2424
|
+
if (!declaration) {
|
|
2425
|
+
const calleeExpression = invocationOperands.get(call).callee;
|
|
2426
|
+
const callee = propagating.known(calleeExpression) ?? propagating.resolve(calleeExpression);
|
|
2427
|
+
if (callee) {
|
|
2428
|
+
// The same rule the return-type walk above states: a JavaScript
|
|
2429
|
+
// factory called with `new` declares no construct signature, and its
|
|
2430
|
+
// call signature is what says which function is being entered. Asked
|
|
2431
|
+
// the other way here -- not "what does it return" but "whose
|
|
2432
|
+
// parameters do these arguments land in" -- and answering it the
|
|
2433
|
+
// narrow way silently withheld every argument written at a `new`
|
|
2434
|
+
// against a plain function, so those parameters saw no evidence at
|
|
2435
|
+
// all and stayed unannotated.
|
|
2436
|
+
const constructed = ts.isNewExpression(call) ? callee.getConstructSignatures() : [];
|
|
2437
|
+
const signatures = constructed.length > 0 ? constructed : callee.getCallSignatures();
|
|
2438
|
+
if (signatures.length === 1)
|
|
2439
|
+
declaration = signatures[0]?.declaration ?? null;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
if (declaration) {
|
|
2443
|
+
pushCall(declaration, call);
|
|
2444
|
+
if (ts.isFunctionLike(declaration))
|
|
2445
|
+
resolvedCallDeclarations.set(call, declaration);
|
|
2446
|
+
}
|
|
2447
|
+
// ALIAS value flow: a call through a resolved member/selector-result
|
|
2448
|
+
// whose VALUE is a known function is a real call site of that
|
|
2449
|
+
// function too, in addition to (never instead of) whatever the block
|
|
2450
|
+
// above already attributed. Only `CallExpression`s are asked -- `new`
|
|
2451
|
+
// has no equivalent idiom in either shape.
|
|
2452
|
+
if (ts.isCallExpression(call)) {
|
|
2453
|
+
for (const aliased of aliasDeclarationsFor(call))
|
|
2454
|
+
pushCall(aliased, call);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
// Close the declaration relation with a worklist. A single pass depends on
|
|
2458
|
+
// whether a base/override or sibling happened to be declared first, and can
|
|
2459
|
+
// omit transitive implementations from both parameter and effect edges.
|
|
2460
|
+
const pending = [...callsByDeclaration].flatMap(([declaration, calls]) => calls.map((call) => ({ declaration, call })));
|
|
2461
|
+
for (let cursor = 0; cursor < pending.length; cursor++) {
|
|
2462
|
+
const { declaration, call } = pending[cursor];
|
|
2463
|
+
const destinations = [...(overridesOfBaseMethod.get(declaration) ?? []), ...(siblingMemberDeclarations.get(declaration) ?? [])];
|
|
2464
|
+
for (const destination of destinations) {
|
|
2465
|
+
if (callsByDeclaration.get(destination)?.includes(call))
|
|
2466
|
+
continue;
|
|
2467
|
+
pushCall(destination, call);
|
|
2468
|
+
pending.push({ declaration: destination, call });
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
};
|
|
2472
|
+
/**
|
|
2473
|
+
* A parameter as a refusal's `owner`: the enclosing callable's name (or its
|
|
2474
|
+
* syntax kind, for one `nameOfCallable` cannot name) beside the parameter's
|
|
2475
|
+
* own name and source position -- the same identifying triple
|
|
2476
|
+
* `debugReport` below already prints, so a refusal and the debug line name
|
|
2477
|
+
* the same parameter the same way.
|
|
2478
|
+
*/
|
|
2479
|
+
const describeParameter = (parameter) => {
|
|
2480
|
+
const callableName = nameOfCallable(parameter.parent)?.getText();
|
|
2481
|
+
const parameterName = ts.isIdentifier(parameter.name) ? parameter.name.text : parameter.name.getText();
|
|
2482
|
+
const file = parameter.getSourceFile();
|
|
2483
|
+
const line = file.getLineAndCharacterOfPosition(parameter.getStart()).line + 1;
|
|
2484
|
+
return `${callableName ?? `<${ts.SyntaxKind[parameter.parent.kind]}>`}(${parameterName}) @${file.fileName.split('/').pop()}:${line}`;
|
|
2485
|
+
};
|
|
2486
|
+
/**
|
|
2487
|
+
* The stable cause behind a reason string, for the refusal's `key`.
|
|
2488
|
+
*
|
|
2489
|
+
* Every reason this census writes is already a fixed kebab-case string with
|
|
2490
|
+
* one exception: `escapeReason` appends the escaping reference's syntax
|
|
2491
|
+
* kind (`function-escapes:CallExpression`, `function-escapes:unnamed:Root`,
|
|
2492
|
+
* ...), which varies per site by construction. Collapsing that family to
|
|
2493
|
+
* one root is the same split every other reason already gets for free --
|
|
2494
|
+
* the kind stays in `reason`, where the prose is allowed to vary.
|
|
2495
|
+
*/
|
|
2496
|
+
const rootOf = (reason) => (reason.startsWith('function-escapes') ? 'function-escapes' : reason);
|
|
2497
|
+
const refusals = [];
|
|
2498
|
+
const refuse = (reason, owner, detail = reason) => {
|
|
2499
|
+
refusals.push(censusRefusal('parameter', rootOf(reason), detail, owner));
|
|
2500
|
+
};
|
|
2501
|
+
// The reassignment split is in the index: it asks the checker for a symbol
|
|
2502
|
+
// per candidate and the answer cannot change between rounds. Escape, by
|
|
2503
|
+
// contrast, IS re-tested every round -- it is stated against the calls
|
|
2504
|
+
// attributed so far, and those grow.
|
|
2505
|
+
const notReassigned = index.notReassigned;
|
|
2506
|
+
for (const candidate of index.reassigned)
|
|
2507
|
+
refuse('parameter-reassigned', describeParameter(candidate.parameter));
|
|
2508
|
+
/** The subset of `bindings` that came from a STATED annotation -- see `statedTypeAt`. */
|
|
2509
|
+
const statedBindings = new Map();
|
|
2510
|
+
let lastRefusal = new Map();
|
|
2511
|
+
/**
|
|
2512
|
+
* An optional parameter with no default binds `T | undefined` in its body, whatever
|
|
2513
|
+
* the call sites agree on: the declaration says a caller may omit it, and the
|
|
2514
|
+
* body can observe that omission. `structural-parts.ts`'s `parameterOf`
|
|
2515
|
+
* publishes the SLOT of the same parameter with `optional: true` off the
|
|
2516
|
+
* same checker optionality fact (including JSDoc), so a binding published without the absence made the
|
|
2517
|
+
* two frames disagree ("parameter 2 is bound as `string` but the ABI
|
|
2518
|
+
* declares `optional(string,undefined)`") and the whole body refused to
|
|
2519
|
+
* project. A DEFAULTED parameter is deliberately left alone: its body binding
|
|
2520
|
+
* is the type with the default already applied, and
|
|
2521
|
+
* `producers/bindings.ts`'s `contributeDefaultedParameter` is the one place
|
|
2522
|
+
* that splits the raw slot (with the absence) from that binding.
|
|
2523
|
+
*/
|
|
2524
|
+
const withDeclaredAbsence = (parameter, type) => checker.isOptionalParameter(parameter) && parameter.initializer === undefined
|
|
2525
|
+
? checker.getNullableType(type, ts.TypeFlags.Undefined)
|
|
2526
|
+
: type;
|
|
2527
|
+
/**
|
|
2528
|
+
* The synthesized-union counterpart of `withDeclaredAbsence`.
|
|
2529
|
+
*
|
|
2530
|
+
* `agreedArgumentType` returns `unionArms` when call sites contribute
|
|
2531
|
+
* structurally distinct carriers that have no single checker `ts.Type`.
|
|
2532
|
+
* That path used to bypass declared optionality, so `p?: T` bound only the
|
|
2533
|
+
* present synthesized arms in the body while `parameterOf` independently
|
|
2534
|
+
* widened the callable slot to `present | undefined`. ABI projection then
|
|
2535
|
+
* correctly refused the two physical frames. Preserve the same declared
|
|
2536
|
+
* absence at the one root where the synthesized arms enter the census.
|
|
2537
|
+
*/
|
|
2538
|
+
const withDeclaredAbsenceArms = (parameter, arms) => {
|
|
2539
|
+
if (!checker.isOptionalParameter(parameter) || parameter.initializer !== undefined)
|
|
2540
|
+
return arms;
|
|
2541
|
+
const containsUndefined = (type) => (type.flags & ts.TypeFlags.Undefined) !== 0 || (type.isUnion() && type.types.some(containsUndefined));
|
|
2542
|
+
return arms.some(containsUndefined) ? arms : [...arms, checker.getUndefinedType()];
|
|
2543
|
+
};
|
|
2544
|
+
/**
|
|
2545
|
+
* Prove every derived recursive back edge against the type learned from
|
|
2546
|
+
* independent callers. The temporary binding is visible only to this
|
|
2547
|
+
* resolver invocation; it is removed before any other candidate can observe
|
|
2548
|
+
* it. A subtype is safe because the provisional parameter carrier already
|
|
2549
|
+
* admits every value of that subtype. Anything unresolved, dynamic, or wider
|
|
2550
|
+
* refuses the candidate instead of laundering a cycle into evidence.
|
|
2551
|
+
*/
|
|
2552
|
+
const validateDeferredRecursiveArguments = (parameter, provisional, argumentsToValidate) => {
|
|
2553
|
+
if (argumentsToValidate.length === 0)
|
|
2554
|
+
return null;
|
|
2555
|
+
bindings.set(parameter, provisional);
|
|
2556
|
+
propagating.reset();
|
|
2557
|
+
try {
|
|
2558
|
+
for (const argument of argumentsToValidate) {
|
|
2559
|
+
const resolved = propagating.known(argument) ?? propagating.resolve(argument);
|
|
2560
|
+
if (!resolved)
|
|
2561
|
+
return 'recursive-derived-argument-unresolved';
|
|
2562
|
+
if (isUnusableEvidence(resolved))
|
|
2563
|
+
return 'recursive-derived-argument-states-no-storage';
|
|
2564
|
+
const passed = resolved.isLiteral() ? checker.getBaseTypeOfLiteralType(resolved) : resolved;
|
|
2565
|
+
if (!checker.isTypeAssignableTo(passed, provisional))
|
|
2566
|
+
return 'recursive-derived-argument-disagrees';
|
|
2567
|
+
}
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
finally {
|
|
2571
|
+
bindings.delete(parameter);
|
|
2572
|
+
propagating.reset();
|
|
2573
|
+
}
|
|
2574
|
+
};
|
|
2575
|
+
/** One sweep over every still-unbound candidate, answering how many it bound. */
|
|
2576
|
+
const bindingSweep = (skipSilentSites) => {
|
|
2577
|
+
propagating.reset();
|
|
2578
|
+
attributeCalls();
|
|
2579
|
+
// `hasClosedMemberCallableUses` (`flow/callable-reach.ts`) memoizes its
|
|
2580
|
+
// answer keyed in part by this RECEIVER-TYPE FUNCTION's own identity --
|
|
2581
|
+
// sound for a whole-program compile, where the authority never changes
|
|
2582
|
+
// mid-walk, but `bindingSweep` calls it up to 48 times against a GROWING
|
|
2583
|
+
// `bindings` map behind the SAME `propagatingTypeAt` closure. A member
|
|
2584
|
+
// whose closure needs another parameter's binding (`event` here) to
|
|
2585
|
+
// settle first got asked, refused, and cached BEFORE that binding
|
|
2586
|
+
// existed -- and the cache, keyed on an identity that never changes,
|
|
2587
|
+
// never asked again. `stored-listener-member-closure.test.ts`'s `draw`
|
|
2588
|
+
// parameter is the measured case: refused round 1 (before `event`
|
|
2589
|
+
// settled), then silently served that same stale refusal every round
|
|
2590
|
+
// after, with no new proof activity at all. A fresh function identity
|
|
2591
|
+
// each round costs nothing this proof does not already recompute WITHIN
|
|
2592
|
+
// a round (the cache still hits repeat asks inside one sweep) and makes
|
|
2593
|
+
// the next round's improved `bindings` visible instead of shadowed.
|
|
2594
|
+
memberClosureReceiverTypeAt = (expression) => propagatingTypeAt(expression);
|
|
2595
|
+
lastRefusal = new Map();
|
|
2596
|
+
let added = 0;
|
|
2597
|
+
for (const candidate of notReassigned) {
|
|
2598
|
+
if (bindings.has(candidate.parameter) || unionArms.has(candidate.parameter))
|
|
2599
|
+
continue;
|
|
2600
|
+
const inferCandidate = () => {
|
|
2601
|
+
const escaped = escapeReason(candidate.declaration);
|
|
2602
|
+
if (escaped) {
|
|
2603
|
+
lastRefusal.set(candidate.parameter, escaped);
|
|
2604
|
+
return false;
|
|
2605
|
+
}
|
|
2606
|
+
// Narrowing a declared upper bound needs complete call-site evidence.
|
|
2607
|
+
// Partial inference may discover an unannotated cycle, but cannot prove
|
|
2608
|
+
// that a declared unknown/union excludes its unresolved incoming values.
|
|
2609
|
+
const answer = agreedArgumentType(candidate.declaration, candidate.parameter, candidate.index, skipSilentSites && candidate.stated === null);
|
|
2610
|
+
// A STATED candidate is held to its statement. The agreed type has to be
|
|
2611
|
+
// assignable to it (the floor -- a callable value the annotation forbids
|
|
2612
|
+
// is not what this parameter holds, whatever the call sites say) AND to
|
|
2613
|
+
// differ from it only where the annotation said nothing. A synthesized
|
|
2614
|
+
// disjoint union is refused outright: those arms are a member LIST this
|
|
2615
|
+
// census builds for `table.intern`, never a `ts.Type` the statement can
|
|
2616
|
+
// be tested against, so there is nothing to hold it to.
|
|
2617
|
+
if (candidate.stated) {
|
|
2618
|
+
if (!('type' in answer)) {
|
|
2619
|
+
lastRefusal.set(candidate.parameter, 'unionArms' in answer ? 'stated-parameter-synthesized-union' : answer.refused);
|
|
2620
|
+
return false;
|
|
2621
|
+
}
|
|
2622
|
+
if (carriesUnsubstitutedGeneric(checker, answer.type)) {
|
|
2623
|
+
lastRefusal.set(candidate.parameter, 'stated-parameter-open-generic');
|
|
2624
|
+
return false;
|
|
2625
|
+
}
|
|
2626
|
+
if (!checker.isTypeAssignableTo(answer.type, candidate.stated)) {
|
|
2627
|
+
lastRefusal.set(candidate.parameter, 'stated-parameter-argument-not-assignable');
|
|
2628
|
+
return false;
|
|
2629
|
+
}
|
|
2630
|
+
if (!narrowsOnlyUnstatedPositions(checker, candidate.parameter, candidate.stated, answer.type)) {
|
|
2631
|
+
lastRefusal.set(candidate.parameter, 'stated-parameter-narrows-a-stated-position');
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2634
|
+
const narrowed = withDeclaredAbsence(candidate.parameter, answer.type);
|
|
2635
|
+
const recursiveRefusal = validateDeferredRecursiveArguments(candidate.parameter, narrowed, answer.deferredRecursiveArguments);
|
|
2636
|
+
if (recursiveRefusal) {
|
|
2637
|
+
lastRefusal.set(candidate.parameter, recursiveRefusal);
|
|
2638
|
+
return false;
|
|
2639
|
+
}
|
|
2640
|
+
bindings.set(candidate.parameter, narrowed);
|
|
2641
|
+
statedBindings.set(candidate.parameter, narrowed);
|
|
2642
|
+
return true;
|
|
2643
|
+
}
|
|
2644
|
+
if ('type' in answer) {
|
|
2645
|
+
const narrowed = withDeclaredAbsence(candidate.parameter, answer.type);
|
|
2646
|
+
const recursiveRefusal = validateDeferredRecursiveArguments(candidate.parameter, narrowed, answer.deferredRecursiveArguments);
|
|
2647
|
+
if (recursiveRefusal) {
|
|
2648
|
+
lastRefusal.set(candidate.parameter, recursiveRefusal);
|
|
2649
|
+
return false;
|
|
2650
|
+
}
|
|
2651
|
+
bindings.set(candidate.parameter, narrowed);
|
|
2652
|
+
}
|
|
2653
|
+
else if ('unionArms' in answer) {
|
|
2654
|
+
if (answer.deferredRecursiveArguments.length > 0) {
|
|
2655
|
+
lastRefusal.set(candidate.parameter, 'recursive-derived-argument-synthesized-union');
|
|
2656
|
+
return false;
|
|
2657
|
+
}
|
|
2658
|
+
unionArms.set(candidate.parameter, withDeclaredAbsenceArms(candidate.parameter, answer.unionArms));
|
|
2659
|
+
}
|
|
2660
|
+
else {
|
|
2661
|
+
lastRefusal.set(candidate.parameter, answer.refused);
|
|
2662
|
+
return false;
|
|
2663
|
+
}
|
|
2664
|
+
return true;
|
|
2665
|
+
};
|
|
2666
|
+
const captured = protocolLedger?.capture(inferCandidate) ?? { value: inferCandidate(), requirements: [] };
|
|
2667
|
+
if (captured.value) {
|
|
2668
|
+
protocolRequirements.set(candidate.parameter, captured.requirements);
|
|
2669
|
+
added += 1;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
return added;
|
|
2673
|
+
};
|
|
2674
|
+
// EVIDENCE EXHAUSTED.
|
|
2675
|
+
//
|
|
2676
|
+
// After strict inference stops adding bindings, unstated parameters may
|
|
2677
|
+
// join evidence from resolvable sites while skipping unresolved sites.
|
|
2678
|
+
// This relaxation cannot discard a resolved unusable type, narrow an
|
|
2679
|
+
// explicitly stated boundary, or let a default replace a supplied argument.
|
|
2680
|
+
// For defaulted parameters, an explicitly supplied any/unknown value remains
|
|
2681
|
+
// evidence with its actual checker type. Missing arguments still follow
|
|
2682
|
+
// the separate default/absence rules. All-silent evidence still refuses.
|
|
2683
|
+
//
|
|
2684
|
+
// The phases alternate, because a parameter bound from surviving evidence is
|
|
2685
|
+
// itself evidence: the strict fixpoint re-opens with the new bindings and
|
|
2686
|
+
// gets first refusal on all it can now type strictly. It stops when a relaxed
|
|
2687
|
+
// sweep adds nothing -- terminating because bindings are only ever added.
|
|
2688
|
+
for (let phase = 0; phase < 4; phase += 1) {
|
|
2689
|
+
for (let round = 0; round < 12; round += 1)
|
|
2690
|
+
if (bindingSweep(false) === 0)
|
|
2691
|
+
break;
|
|
2692
|
+
if (bindingSweep(true) === 0)
|
|
2693
|
+
break;
|
|
2694
|
+
}
|
|
2695
|
+
// Relaxed bindings seed cycles; they are not proof that a silent caller
|
|
2696
|
+
// cannot supply another shape. Seal the fixed point against EVERY incoming
|
|
2697
|
+
// argument, then withdraw dependent conclusions until the survivors are
|
|
2698
|
+
// closed. Without this check an unresolved forwarding parameter disappears
|
|
2699
|
+
// from its callee's evidence and an unrelated call dictates a wrong unbox.
|
|
2700
|
+
// Re-attribution and resolver caches must follow each withdrawal as well:
|
|
2701
|
+
// consumers inferred strictly can still depend on a provisional producer.
|
|
2702
|
+
//
|
|
2703
|
+
// ESCAPE is re-tested here too, not only inside `bindingSweep`, for a gap
|
|
2704
|
+
// measured on the three.js app's `WebGLCapabilities( gl, extensions, parameters,
|
|
2705
|
+
// utils )`: `gl` and `parameters` bind in an early sweep, while `extensions`
|
|
2706
|
+
// and `utils` each depend on a whole separate factory (`WebGLExtensions`,
|
|
2707
|
+
// `WebGLUtils`) resolving first and so are still unbound several sweeps
|
|
2708
|
+
// later. `escapeReason`'s export/import check (`isModuleExportedDeclaration`
|
|
2709
|
+
// + `inProgramImportReferencesOf`) is re-evaluated fresh every sweep and, by
|
|
2710
|
+
// the time `extensions`/`utils` finally have argument evidence, it has
|
|
2711
|
+
// started returning `function-escapes:exported` for `WebGLCapabilities`
|
|
2712
|
+
// itself -- confirmed against the real corpus (the app's actual refusal
|
|
2713
|
+
// list carries exactly this reason for both parameters). `bindingSweep`'s
|
|
2714
|
+
// per-candidate loop skips any parameter already in `bindings`, so
|
|
2715
|
+
// `gl`/`parameters` are never asked again and keep a binding taken before
|
|
2716
|
+
// the escape was visible -- while their siblings on the SAME declaration,
|
|
2717
|
+
// tested one sweep later, are correctly refused. Escape is a fact about the
|
|
2718
|
+
// DECLARATION, not about which of its parameters happened to resolve
|
|
2719
|
+
// first: if the function escapes, every one of its parameters is bound
|
|
2720
|
+
// from a caller set this census can no longer prove complete, `gl`/
|
|
2721
|
+
// `parameters` included, and a wrong type is worse than a boxed one (this
|
|
2722
|
+
// module's own charter, above). Withdrawing only the late arrivals leaves
|
|
2723
|
+
// the early ones silently unsound -- answered, never refused, and never
|
|
2724
|
+
// revisited.
|
|
2725
|
+
//
|
|
2726
|
+
// Only the export/import reasons are retested, not the full `escapeReason`
|
|
2727
|
+
// surface: the member-closure reasons (`function-escapes:
|
|
2728
|
+
// uncounted-member-reference`, `function-escapes:uncounted-reference`, and
|
|
2729
|
+
// the generic reference-walk fallback) read `propagating`/`calls` for
|
|
2730
|
+
// OTHER declarations reachable through the same receiver -- three's
|
|
2731
|
+
// `renderer` argument that Object3D's `add` walks through its own
|
|
2732
|
+
// `arguments` frame is exactly this shape, closed by `memberClosed`. Once
|
|
2733
|
+
// any sibling candidate in that same chain withdraws earlier in this same
|
|
2734
|
+
// pass, `propagating.reset()` (a few lines below) clears the cache
|
|
2735
|
+
// `memberClosed` reads, and re-asking the full escape question mid-pass
|
|
2736
|
+
// read a still-valid closure proof as open -- breaking
|
|
2737
|
+
// `arguments-frame-member-closure.test.ts`'s positive case and
|
|
2738
|
+
// `implicit-arguments-tuple.test.ts`'s "closed callers" case. The
|
|
2739
|
+
// export/import check has no such dependency: whether `WebGLCapabilities`
|
|
2740
|
+
// is reachable from outside the compiled program is a property of the
|
|
2741
|
+
// module graph, invariant to which of ITS OWN parameters is currently
|
|
2742
|
+
// bound. Re-running exactly that check, and no more, makes the one
|
|
2743
|
+
// measured gap retroactive without reopening the proof the loosening
|
|
2744
|
+
// above exists to protect.
|
|
2745
|
+
let withdrew;
|
|
2746
|
+
do {
|
|
2747
|
+
withdrew = false;
|
|
2748
|
+
propagating.reset();
|
|
2749
|
+
unionTypes.clear();
|
|
2750
|
+
patternReadTypes.clear();
|
|
2751
|
+
argumentsByParameter.clear();
|
|
2752
|
+
attributeCalls();
|
|
2753
|
+
for (const candidate of notReassigned) {
|
|
2754
|
+
const parameter = candidate.parameter;
|
|
2755
|
+
if (!bindings.has(parameter) && !unionArms.has(parameter))
|
|
2756
|
+
continue;
|
|
2757
|
+
const escaped = escapeReason(candidate.declaration);
|
|
2758
|
+
if (escaped === 'function-escapes:exported' || escaped === 'function-escapes:uncounted-import') {
|
|
2759
|
+
bindings.delete(parameter);
|
|
2760
|
+
unionArms.delete(parameter);
|
|
2761
|
+
protocolRequirements.delete(parameter);
|
|
2762
|
+
unionTypes.clear();
|
|
2763
|
+
statedBindings.delete(parameter);
|
|
2764
|
+
argumentsByParameter.delete(parameter);
|
|
2765
|
+
lastRefusal.set(parameter, escaped);
|
|
2766
|
+
propagating.reset();
|
|
2767
|
+
withdrew = true;
|
|
2768
|
+
continue;
|
|
2769
|
+
}
|
|
2770
|
+
const published = parameterTypeOf(parameter);
|
|
2771
|
+
const answer = agreedArgumentType(candidate.declaration, parameter, candidate.index, true);
|
|
2772
|
+
let reason = null;
|
|
2773
|
+
if ('refused' in answer)
|
|
2774
|
+
reason = answer.refused;
|
|
2775
|
+
else if (answer.sawSilentSite)
|
|
2776
|
+
reason = 'argument-unresolved';
|
|
2777
|
+
else {
|
|
2778
|
+
const incoming = 'type' in answer ? [answer.type] : answer.unionArms;
|
|
2779
|
+
const admits = (type) => published !== null &&
|
|
2780
|
+
((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0 ||
|
|
2781
|
+
(published.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) &&
|
|
2782
|
+
checker.isTypeAssignableTo(type, published);
|
|
2783
|
+
if (!incoming.every(admits))
|
|
2784
|
+
reason = 'call-sites-disagree';
|
|
2785
|
+
else {
|
|
2786
|
+
for (const argument of answer.deferredRecursiveArguments) {
|
|
2787
|
+
const resolved = propagating.known(argument) ?? propagating.resolve(argument);
|
|
2788
|
+
if (!resolved || isUnusableEvidence(resolved) || !admits(resolved)) {
|
|
2789
|
+
reason = 'recursive-derived-argument-disagrees';
|
|
2790
|
+
break;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
if (reason === null)
|
|
2796
|
+
continue;
|
|
2797
|
+
bindings.delete(parameter);
|
|
2798
|
+
unionArms.delete(parameter);
|
|
2799
|
+
protocolRequirements.delete(parameter);
|
|
2800
|
+
unionTypes.clear();
|
|
2801
|
+
statedBindings.delete(parameter);
|
|
2802
|
+
argumentsByParameter.delete(parameter);
|
|
2803
|
+
lastRefusal.set(parameter, reason);
|
|
2804
|
+
propagating.reset();
|
|
2805
|
+
withdrew = true;
|
|
2806
|
+
}
|
|
2807
|
+
} while (withdrew);
|
|
2808
|
+
// A checker narrowing is evidence about the SAME parameter cell, not a
|
|
2809
|
+
// conversion request. Preserve every distinct concrete object kind the
|
|
2810
|
+
// checker exposes under a pure `[string]: any` upper bound, alongside the
|
|
2811
|
+
// dictionary arm used by unnarrowed reads. Preserve the complete finite
|
|
2812
|
+
// member set, as the ordinary synthesized-union path does.
|
|
2813
|
+
for (const candidate of notReassigned) {
|
|
2814
|
+
const upper = candidate.flowCarrierUpperBound;
|
|
2815
|
+
if (!upper || !ts.isIdentifier(candidate.parameter.name))
|
|
2816
|
+
continue;
|
|
2817
|
+
const symbol = checker.getSymbolAtLocation(candidate.parameter.name);
|
|
2818
|
+
const references = symbol ? valueFlow.memberReferencesToSymbol(symbol) : [];
|
|
2819
|
+
const narrowed = [];
|
|
2820
|
+
for (const reference of references) {
|
|
2821
|
+
const type = checker.getTypeAtLocation(reference);
|
|
2822
|
+
if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) !== 0)
|
|
2823
|
+
continue;
|
|
2824
|
+
if (!isFlowContainerType(checker, reference, type))
|
|
2825
|
+
continue;
|
|
2826
|
+
if (!checker.isTypeAssignableTo(type, upper))
|
|
2827
|
+
continue;
|
|
2828
|
+
if (checker.isTypeAssignableTo(upper, type))
|
|
2829
|
+
continue;
|
|
2830
|
+
if (narrowed.some((seen) => checker.isTypeAssignableTo(type, seen) && checker.isTypeAssignableTo(seen, type)))
|
|
2831
|
+
continue;
|
|
2832
|
+
narrowed.push(type);
|
|
2833
|
+
}
|
|
2834
|
+
// A call that explicitly crosses this boundary from `any` contributes a
|
|
2835
|
+
// real dynamic arm. Plain runtime objects must remain the boxes they are;
|
|
2836
|
+
// rebuilding them as dictionaries would change identity and lose
|
|
2837
|
+
// prototype/accessor behavior. Concrete Document/Array/Map callers keep
|
|
2838
|
+
// their native arms beside it.
|
|
2839
|
+
let dynamicCallArm = null;
|
|
2840
|
+
for (const call of callsByDeclaration.get(candidate.declaration) ?? []) {
|
|
2841
|
+
const args = invocationOperands.get(call).args;
|
|
2842
|
+
const argument = args?.[candidate.index];
|
|
2843
|
+
if (!argument)
|
|
2844
|
+
continue;
|
|
2845
|
+
const passed = checker.getTypeAtLocation(argument);
|
|
2846
|
+
if ((passed.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {
|
|
2847
|
+
dynamicCallArm = passed;
|
|
2848
|
+
break;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
const arms = [upper, ...narrowed, ...(dynamicCallArm ? [dynamicCallArm] : [])];
|
|
2852
|
+
if (arms.length === 1)
|
|
2853
|
+
continue;
|
|
2854
|
+
// This late flow-container publication is a second synthesized-arm entry
|
|
2855
|
+
// point. It must preserve the same declared absence as the ordinary
|
|
2856
|
+
// `unionArms` path above; otherwise an optional open-document parameter
|
|
2857
|
+
// (BSON's `DBRef(..., fields?: Document)`) binds a required body union
|
|
2858
|
+
// while its callable slot remains optional.
|
|
2859
|
+
flowCarrierArms.set(candidate.parameter, withDeclaredAbsenceArms(candidate.parameter, arms));
|
|
2860
|
+
flowCarrierBounds.set(candidate.parameter, upper);
|
|
2861
|
+
// The union is the cell's placement. A same-annotation binding learned
|
|
2862
|
+
// from call sites would otherwise outrank it in `parameterOf`.
|
|
2863
|
+
bindings.delete(candidate.parameter);
|
|
2864
|
+
statedBindings.delete(candidate.parameter);
|
|
2865
|
+
lastRefusal.delete(candidate.parameter);
|
|
2866
|
+
}
|
|
2867
|
+
// A stated JS parameter the census does not infer can still be left out by
|
|
2868
|
+
// a caller: three's `colorBuffer.setClear( 0, 0, 0, 1 )` against the
|
|
2869
|
+
// overlay's `@param {boolean} premultipliedAlpha`. Its cell then holds the
|
|
2870
|
+
// statement plus `undefined` -- see `omitted-stated-parameter.ts`. Asked of
|
|
2871
|
+
// the settled attribution after every withdrawal, so no argument binding it
|
|
2872
|
+
// reads can still be taken back; the closure proof runs under the ledger
|
|
2873
|
+
// like any inference, and its obligations are kept with the binding.
|
|
2874
|
+
propagating.reset();
|
|
2875
|
+
// `GEA_STATED_OMISSION_OFF` keeps the arm without this rule runnable, the way `GEA_BAG_OFF` is.
|
|
2876
|
+
for (const site of process.env['GEA_STATED_OMISSION_OFF'] ? [] : index.omissionSites) {
|
|
2877
|
+
if (bindings.has(site.parameter) || unionArms.has(site.parameter))
|
|
2878
|
+
continue;
|
|
2879
|
+
const answer = statedParameterWithOmission(checker, site.stated, site.index, callsByDeclaration.get(site.declaration) ?? [], (call) => invocationOperands.get(call).args, (argument) => propagating.known(argument) ?? propagating.resolve(argument) ?? checker.getTypeAtLocation(argument));
|
|
2880
|
+
if (answer === null)
|
|
2881
|
+
continue;
|
|
2882
|
+
if ('refused' in answer) {
|
|
2883
|
+
lastRefusal.set(site.parameter, answer.refused);
|
|
2884
|
+
continue;
|
|
2885
|
+
}
|
|
2886
|
+
// Deliberately NOT gated on `escapeReason`. A caller the census cannot see
|
|
2887
|
+
// is held to the statement whether or not this binds -- that is what the
|
|
2888
|
+
// parameter's carrier is when it stays unbound -- so it can only add
|
|
2889
|
+
// statement values to the cell, never take away the `undefined` a caller
|
|
2890
|
+
// the census DOES see provably passes. Refusing an open caller set left
|
|
2891
|
+
// the bare statement standing, which is the one answer known to be wrong.
|
|
2892
|
+
const closure = () => ((contractsFor(site.declaration)?.length ?? 0) > 0 ? 'stated-omission-callback-contract' : null);
|
|
2893
|
+
const captured = protocolLedger?.capture(closure) ?? { value: closure(), requirements: [] };
|
|
2894
|
+
if (captured.value !== null) {
|
|
2895
|
+
lastRefusal.set(site.parameter, captured.value);
|
|
2896
|
+
continue;
|
|
2897
|
+
}
|
|
2898
|
+
bindings.set(site.parameter, answer.type);
|
|
2899
|
+
statedBindings.set(site.parameter, answer.type);
|
|
2900
|
+
protocolRequirements.set(site.parameter, captured.requirements);
|
|
2901
|
+
if (process.env['GEA_BINDING_DEBUG'])
|
|
2902
|
+
console.error(`[STATED-OMISSION] ${describeParameter(site.parameter)} :: ${checker.typeToString(answer.type)}`);
|
|
2903
|
+
}
|
|
2904
|
+
for (const [parameter, reason] of lastRefusal)
|
|
2905
|
+
refuse(reason, describeParameter(parameter));
|
|
2906
|
+
/**
|
|
2907
|
+
* A READ of a stated parameter answers the BODY's binding, not the slot's.
|
|
2908
|
+
*
|
|
2909
|
+
* The two differ by exactly the absence a DEFAULT exists to answer: the slot
|
|
2910
|
+
* carries `undefined` (`statedUpperBound` puts it there deliberately, so a
|
|
2911
|
+
* caller passing a possibly-absent value is not refused), and by the time
|
|
2912
|
+
* the body runs the initializer has replaced it, so the cell holds the
|
|
2913
|
+
* undefined-free type -- which is precisely what
|
|
2914
|
+
* `producers/bindings.ts`'s `contributeDefaultedParameter` builds as its
|
|
2915
|
+
* `bodyType`. Answering a read with the slot's union instead is a
|
|
2916
|
+
* cell-versus-read split: hono's `this.#matchResult = matchResult` had the
|
|
2917
|
+
* cell holding `Result<[H, RouterRoute]>` while the read of the very same
|
|
2918
|
+
* parameter published `Result<[H, RouterRoute]> | undefined`.
|
|
2919
|
+
*
|
|
2920
|
+
* An initializer that IS `undefined` replaces nothing and the body really
|
|
2921
|
+
* can observe one -- the same carve-out, for the same reason, that
|
|
2922
|
+
* `contributeDefaultedParameter` states at length. An OPTIONAL parameter
|
|
2923
|
+
* with no default keeps its absence too: nothing filled it in.
|
|
2924
|
+
*/
|
|
2925
|
+
const bodyBindingOf = (declaration, narrowed) => {
|
|
2926
|
+
if (!declaration.initializer)
|
|
2927
|
+
return narrowed;
|
|
2928
|
+
if ((checker.getTypeAtLocation(declaration.initializer).flags & ts.TypeFlags.Undefined) !== 0)
|
|
2929
|
+
return narrowed;
|
|
2930
|
+
return withoutUndefinedMember(checker, narrowed);
|
|
2931
|
+
};
|
|
2932
|
+
// Publication reads a settled binding set, so its memo is built once, after
|
|
2933
|
+
// the fixpoint stops -- unlike the propagating one, which is thrown away
|
|
2934
|
+
// each round because the answers it caches are answers to a smaller view.
|
|
2935
|
+
publishing.reset();
|
|
2936
|
+
/** Answers for `statedTypeAt`, settled once the fixpoint above has stopped. */
|
|
2937
|
+
const statedTypes = new Map();
|
|
2938
|
+
const computeStatedTypeAt = (node) => {
|
|
2939
|
+
// A PRIOR ROUND's stated narrowings are forwarded, the same way `known`
|
|
2940
|
+
// above forwards `upstream.typeAt`: round two exists precisely so a
|
|
2941
|
+
// census that could not see round one's answers gets them, and a
|
|
2942
|
+
// narrowing this round did not make itself is still the settled fact
|
|
2943
|
+
// about that cell. Without this the FIELD census's narrowings (round
|
|
2944
|
+
// one, composed outside this census) were invisible to round two's
|
|
2945
|
+
// return census, which then typed a `return this.#field` from the
|
|
2946
|
+
// checker's un-narrowed annotation and split the two cells apart.
|
|
2947
|
+
//
|
|
2948
|
+
// This closure over `upstream` is the same chain the `upstream` parameter's
|
|
2949
|
+
// own doc above names: it is why the object this function returns keeps
|
|
2950
|
+
// every earlier round's `ValueFlowIndex` (and `source-value-session.ts`'s
|
|
2951
|
+
// ~1 GB solver session for it) reachable for the rest of the compile.
|
|
2952
|
+
const inherited = upstream.statedTypeAt(node);
|
|
2953
|
+
if (inherited)
|
|
2954
|
+
return inherited;
|
|
2955
|
+
if (ts.isParameter(node))
|
|
2956
|
+
return statedBindings.size === 0 ? null : (statedBindings.get(node) ?? null);
|
|
2957
|
+
if (!ts.isIdentifier(node))
|
|
2958
|
+
return null;
|
|
2959
|
+
// Both branches below need the identifier's parameter declaration and
|
|
2960
|
+
// nothing else, so the symbol is resolved ONCE. It used to be asked twice
|
|
2961
|
+
// -- and asked at all even when both maps were empty, which for a program
|
|
2962
|
+
// that states no narrowings is the entire cost for no possible answer.
|
|
2963
|
+
if (flowCarrierBounds.size === 0 && statedBindings.size === 0)
|
|
2964
|
+
return null;
|
|
2965
|
+
const declarations = checker.getSymbolAtLocation(node)?.declarations;
|
|
2966
|
+
if (!declarations)
|
|
2967
|
+
return null;
|
|
2968
|
+
const parameter = declarations.find(ts.isParameter);
|
|
2969
|
+
// A user predicate may give an open dynamic dictionary a named-record
|
|
2970
|
+
// VIEW so its property reads become typed. That does not replace the
|
|
2971
|
+
// object with a record allocation: the same dictionary enters and
|
|
2972
|
+
// leaves the guarded branch. Keep the cell's physical upper-bound
|
|
2973
|
+
// carrier at the identifier; destructuring/property producers still
|
|
2974
|
+
// use the checker's narrowed member types for each value they extract.
|
|
2975
|
+
const upper = parameter ? flowCarrierBounds.get(parameter) : undefined;
|
|
2976
|
+
if (upper) {
|
|
2977
|
+
const read = checker.getTypeAtLocation(node);
|
|
2978
|
+
const isProperNarrowing = checker.isTypeAssignableTo(read, upper) && !checker.isTypeAssignableTo(upper, read);
|
|
2979
|
+
if (isProperNarrowing && !isFlowContainerType(checker, node, read))
|
|
2980
|
+
return upper;
|
|
2981
|
+
}
|
|
2982
|
+
if (statedBindings.size === 0)
|
|
2983
|
+
return null;
|
|
2984
|
+
for (const declaration of declarations) {
|
|
2985
|
+
if (!ts.isParameter(declaration))
|
|
2986
|
+
continue;
|
|
2987
|
+
const narrowed = statedBindings.get(declaration);
|
|
2988
|
+
if (narrowed)
|
|
2989
|
+
return bodyBindingOf(declaration, narrowed);
|
|
2990
|
+
}
|
|
2991
|
+
return null;
|
|
2992
|
+
};
|
|
2993
|
+
// Implicit slots have an owning signature but no parameter declaration.
|
|
2994
|
+
// Reuse the settled call attribution and resolver; never start a second
|
|
2995
|
+
// call-site inventory or infer from only the successfully typed callers.
|
|
2996
|
+
const implicitTuples = new Map();
|
|
2997
|
+
/** Refused frames, for `debugReport` to print the open use behind each. */
|
|
2998
|
+
const implicitRefusals = new Map();
|
|
2999
|
+
// A `spread` edge names the spread's SOURCE, which it reads in bulk and never stores into.
|
|
3000
|
+
const writeNames = new Set(valueFlow.allWrites.flatMap((write) => (write.naming && write.edge !== 'spread' ? [write.naming] : [])));
|
|
3001
|
+
if (process.env['GEA_IMPLICIT_FRAME_DEBUG'])
|
|
3002
|
+
console.error(`[IMPLICIT-FRAME] owners in index: ${index.implicitArgumentsUses.size}`);
|
|
3003
|
+
for (const [owner, uses] of index.implicitArgumentsUses) {
|
|
3004
|
+
const infer = () => {
|
|
3005
|
+
const signature = checker.getSignatureFromDeclaration(owner);
|
|
3006
|
+
const slot = signature ? implicitArgumentsSlotOf(signature) : null;
|
|
3007
|
+
if (!slot)
|
|
3008
|
+
return { refused: 'implicit-arguments-no-frame' };
|
|
3009
|
+
const escape = escapeReason(owner, true);
|
|
3010
|
+
if (escape)
|
|
3011
|
+
return { refused: escape };
|
|
3012
|
+
if (owner.parameters.some((parameter) => parameter.initializer || parameter.dotDotDotToken))
|
|
3013
|
+
return { refused: 'implicit-arguments-non-simple-parameters' };
|
|
3014
|
+
if (owner.parameters.some((parameter) => {
|
|
3015
|
+
const symbol = checker.getSymbolAtLocation(parameter.name);
|
|
3016
|
+
return symbol !== undefined && index.assigned.has(symbol);
|
|
3017
|
+
}))
|
|
3018
|
+
return { refused: 'implicit-arguments-parameter-reassigned' };
|
|
3019
|
+
const usable = (type, node) => !!type && !isUnusableEvidence(type) && !annotationStatesNothing(checker, node, type);
|
|
3020
|
+
return inferImplicitArgumentsTuple({
|
|
3021
|
+
checker,
|
|
3022
|
+
owner,
|
|
3023
|
+
slot,
|
|
3024
|
+
uses,
|
|
3025
|
+
calls: callsByDeclaration.get(owner) ?? [],
|
|
3026
|
+
argumentsOf: (call) => invocationOperands.get(call).args,
|
|
3027
|
+
isWritten: (node) => writeNames.has(node),
|
|
3028
|
+
isRecursiveCall: (call) => isRecursiveCallWithin(call, owner),
|
|
3029
|
+
argumentTypeOf: (argument) => {
|
|
3030
|
+
const type = propagating.known(argument) ?? propagating.resolve(argument);
|
|
3031
|
+
return usable(type, argument) ? type : null;
|
|
3032
|
+
},
|
|
3033
|
+
statedElements: owner.parameters.flatMap((parameter) => {
|
|
3034
|
+
const type = checker.getTypeAtLocation(parameter);
|
|
3035
|
+
return usable(type, parameter) ? [type] : [];
|
|
3036
|
+
})
|
|
3037
|
+
});
|
|
3038
|
+
};
|
|
3039
|
+
const captured = protocolLedger?.capture(infer) ?? { value: infer(), requirements: [] };
|
|
3040
|
+
const answer = captured.value;
|
|
3041
|
+
if ('tuple' in answer) {
|
|
3042
|
+
implicitTuples.set(owner, answer.tuple);
|
|
3043
|
+
protocolRequirements.set(owner, captured.requirements);
|
|
3044
|
+
// A SETTLED frame can still be settled on a type nothing can carry, and
|
|
3045
|
+
// that outcome has no refusal to read: the census records only the
|
|
3046
|
+
// frames it turned down, so a frame that joined to a union no consumer
|
|
3047
|
+
// can lower looks identical to one that joined to a class. The three.js app's
|
|
3048
|
+
// largest nested-dynamic group -- 1382 carriers, every `.add( ... )` in
|
|
3049
|
+
// the program -- is one such frame, and nothing printed it.
|
|
3050
|
+
if (process.env['GEA_IMPLICIT_FRAME_DEBUG']) {
|
|
3051
|
+
const file = owner.getSourceFile();
|
|
3052
|
+
const line = file.getLineAndCharacterOfPosition(owner.getStart()).line + 1;
|
|
3053
|
+
const shape = answer.tuple.frame === 'array'
|
|
3054
|
+
? `array element=${checker.typeToString(answer.tuple.element)}`
|
|
3055
|
+
: `tuple required=${answer.tuple.required} elements=[${answer.tuple.elements.map((element) => checker.typeToString(element)).join(', ')}]`;
|
|
3056
|
+
console.error(`[IMPLICIT-FRAME] ${nameOfCallable(owner)?.getText() ?? '<anonymous>'} @${file.fileName.split('/').pop()}:${line} ${shape}`);
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
else {
|
|
3060
|
+
const file = owner.getSourceFile();
|
|
3061
|
+
const line = file.getLineAndCharacterOfPosition(owner.getStart()).line + 1;
|
|
3062
|
+
if (process.env['GEA_IMPLICIT_FRAME_DEBUG'])
|
|
3063
|
+
console.error(`[IMPLICIT-FRAME] REFUSED ${nameOfCallable(owner)?.getText() ?? '<anonymous>'} @${file.fileName.split('/').pop()}:${line} ${answer.refused}`);
|
|
3064
|
+
const evidence = answer.evidence;
|
|
3065
|
+
const evidenceFile = evidence?.getSourceFile();
|
|
3066
|
+
const detail = evidence && evidenceFile
|
|
3067
|
+
? `${answer.refused}: ${evidenceFile.fileName}:${evidenceFile.getLineAndCharacterOfPosition(evidence.getStart()).line + 1} ${evidence.getText().slice(0, 160)}`
|
|
3068
|
+
: answer.refused;
|
|
3069
|
+
implicitRefusals.set(owner, answer.refused);
|
|
3070
|
+
refuse(answer.refused, `${nameOfCallable(owner)?.getText() ?? '<anonymous>'}(arguments) @${file.fileName.split('/').pop()}:${line}`, detail);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
/**
|
|
3074
|
+
* The element join `restElementTypeAt` publishes, per rest parameter.
|
|
3075
|
+
*
|
|
3076
|
+
* `ECMA-262 10.2.11` step 28 (`FunctionDeclarationInstantiation`) binds a
|
|
3077
|
+
* rest parameter to a fresh Array unconditionally, whatever arity each
|
|
3078
|
+
* caller uses -- so unlike an ordinary parameter there is no "position 0
|
|
3079
|
+
* disagrees with position 1" question to ask here: every argument at or
|
|
3080
|
+
* past the rest's own ordinal, at EVERY call, is one more member of the
|
|
3081
|
+
* SAME array, and this asks for the one type that join settles on.
|
|
3082
|
+
*
|
|
3083
|
+
* Built from `callsByDeclaration`/`propagating` exactly as the implicit-
|
|
3084
|
+
* arguments frame above is -- both read the settled call attribution this
|
|
3085
|
+
* census's own fixpoint already closed, and neither needs a fixpoint of
|
|
3086
|
+
* its own: a rest parameter's element can only be as good as the call
|
|
3087
|
+
* sites it is joined from, and nothing downstream of THIS census ever
|
|
3088
|
+
* feeds back into what a call site passes.
|
|
3089
|
+
*
|
|
3090
|
+
* Deliberately conservative, matching `collectPassedArguments`'s own
|
|
3091
|
+
* refusal philosophy: a spread argument anywhere in the tail, or any
|
|
3092
|
+
* argument this census cannot resolve to usable evidence, refuses the
|
|
3093
|
+
* WHOLE parameter rather than joining a partial view of what a caller
|
|
3094
|
+
* passes -- leaving the checker's own (dynamic) answer exactly as it was
|
|
3095
|
+
* before this existed.
|
|
3096
|
+
*
|
|
3097
|
+
* The call-site tail is not the only writer of this cell: three's
|
|
3098
|
+
* `utils.js` reassigns its own rest parameter outright --
|
|
3099
|
+
* `params = enhanceLogMessage( params )` inside `warn`/`error` -- and that
|
|
3100
|
+
* write replaces the SAME binding the tail above is joined into, not a
|
|
3101
|
+
* different question. `assignedEvidence` already carries it: the ordinary
|
|
3102
|
+
* sweep's own reassignment index (`indexParameterBindingProgram`, above)
|
|
3103
|
+
* walks `valueFlow.allWrites` by `ts.isParameter(declaration)` alone, never
|
|
3104
|
+
* filtering out a `dotDotDotToken` parameter, so a rest parameter's own
|
|
3105
|
+
* reassignment was captured from the start even though nothing read it
|
|
3106
|
+
* until now. `enhanceLogMessage` is declared `@returns {Array<any>}`, so
|
|
3107
|
+
* the honest join over BOTH sources is dynamic -- declining here, exactly
|
|
3108
|
+
* as the tail loop above declines on a spread or unusable argument, is
|
|
3109
|
+
* what keeps `warn`'s declaration and every one of its call sites agreeing
|
|
3110
|
+
* on the same (boxed) carrier instead of the census narrowing one end past
|
|
3111
|
+
* what the body's own write still produces.
|
|
3112
|
+
*/
|
|
3113
|
+
const restElementTypes = new Map();
|
|
3114
|
+
for (const candidate of index.restParameterCandidates) {
|
|
3115
|
+
const calls = callsByDeclaration.get(candidate.declaration) ?? [];
|
|
3116
|
+
if (calls.length === 0)
|
|
3117
|
+
continue;
|
|
3118
|
+
const passed = [];
|
|
3119
|
+
let refused = false;
|
|
3120
|
+
for (const call of calls) {
|
|
3121
|
+
if (refused)
|
|
3122
|
+
break;
|
|
3123
|
+
const effectiveArguments = invocationOperands.get(call).args;
|
|
3124
|
+
for (const argument of (effectiveArguments ?? []).slice(candidate.index)) {
|
|
3125
|
+
if (ts.isSpreadElement(argument)) {
|
|
3126
|
+
refused = true;
|
|
3127
|
+
break;
|
|
3128
|
+
}
|
|
3129
|
+
const resolved = propagating.known(argument) ?? propagating.resolve(argument);
|
|
3130
|
+
if (!resolved || isUnusableEvidence(resolved)) {
|
|
3131
|
+
refused = true;
|
|
3132
|
+
break;
|
|
3133
|
+
}
|
|
3134
|
+
passed.push(resolved.isLiteral() ? checker.getBaseTypeOfLiteralType(resolved) : resolved);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
if (!refused) {
|
|
3138
|
+
const symbol = ts.isIdentifier(candidate.parameter.name) ? checker.getSymbolAtLocation(candidate.parameter.name) : undefined;
|
|
3139
|
+
const writes = symbol ? assignedEvidence.get(symbol) : undefined;
|
|
3140
|
+
if (writes === null)
|
|
3141
|
+
refused = true;
|
|
3142
|
+
else
|
|
3143
|
+
for (const write of writes ?? []) {
|
|
3144
|
+
if (refused)
|
|
3145
|
+
break;
|
|
3146
|
+
const written = write.operatorTyped
|
|
3147
|
+
? checker.getTypeAtLocation(write.expression)
|
|
3148
|
+
: (propagating.known(write.expression) ?? propagating.resolve(write.expression));
|
|
3149
|
+
// The write replaces the WHOLE array, not one element -- read its
|
|
3150
|
+
// own element out through the numeric index signature (the same
|
|
3151
|
+
// primitive `indexedTypeOf` above uses for `array[i]`) before
|
|
3152
|
+
// joining it against the tail's per-element evidence, or a
|
|
3153
|
+
// reassignment to a plain `Array<any>` would join `string` against
|
|
3154
|
+
// the ARRAY type itself rather than against its `any` element.
|
|
3155
|
+
const element = written && checker.getIndexTypeOfType(checker.getNonNullableType(written), ts.IndexKind.Number);
|
|
3156
|
+
if (!element || carriesNoEvidence(element)) {
|
|
3157
|
+
refused = true;
|
|
3158
|
+
break;
|
|
3159
|
+
}
|
|
3160
|
+
passed.push(element.isLiteral() ? checker.getBaseTypeOfLiteralType(element) : element);
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
if (refused || passed.length === 0)
|
|
3164
|
+
continue;
|
|
3165
|
+
const joined = joinOfWrites(checker, passed);
|
|
3166
|
+
if (joined)
|
|
3167
|
+
restElementTypes.set(candidate.parameter, joined);
|
|
3168
|
+
}
|
|
3169
|
+
/**
|
|
3170
|
+
* A rest parameter whose body forwards its ENTIRE array on, via a bare
|
|
3171
|
+
* `...name` spread naming its own binding as a call's ONLY argument, hands
|
|
3172
|
+
* that same array to whatever the call resolves to. `warnOnce( ...params )`
|
|
3173
|
+
* calling `warn( ...params )` in three's `utils.js` is exactly this: the
|
|
3174
|
+
* array `warnOnce` narrowed to `string` from its own callers' tail is the
|
|
3175
|
+
* IDENTICAL array `warn` receives, and `warn`'s own reassignment above
|
|
3176
|
+
* already forces it dynamic. A forwarder narrower than what its own target
|
|
3177
|
+
* settles on is not a smaller carrier for the same value, it is a SECOND,
|
|
3178
|
+
* disagreeing carrier for it -- exactly the shape `representation/verify.ts`
|
|
3179
|
+
* refuses to certify (`array-object(string) -> array-object(dynamic)`, no
|
|
3180
|
+
* conversion installed, and none may be: a mutable `ArrayObject` recast at a
|
|
3181
|
+
* call argument is a recorded aliasing miscompile, not a fallback here).
|
|
3182
|
+
*
|
|
3183
|
+
* Folded in as a SECOND pass, after every candidate's own call-site/
|
|
3184
|
+
* reassignment evidence has already settled, so which rest parameter
|
|
3185
|
+
* happens to be declared first in the file never changes the answer -- the
|
|
3186
|
+
* forward is asked once the map already holds every candidate's own-
|
|
3187
|
+
* evidence verdict, never a partial one.
|
|
3188
|
+
*
|
|
3189
|
+
* One-directional on purpose: the target's settled element folds into the
|
|
3190
|
+
* forwarder, never the reverse. `warn`'s own element is decided entirely by
|
|
3191
|
+
* `warn`'s OWN callers and its OWN body -- `warnOnce` forwarding into it is
|
|
3192
|
+
* not evidence about what `warn` does with the array, only about what
|
|
3193
|
+
* `warnOnce` must agree to.
|
|
3194
|
+
*/
|
|
3195
|
+
const restCandidateByDeclaration = new Map(index.restParameterCandidates.map((candidate) => [candidate.declaration, candidate]));
|
|
3196
|
+
const isWithin = (node, ancestor) => {
|
|
3197
|
+
for (let current = node; current; current = current.parent)
|
|
3198
|
+
if (current === ancestor)
|
|
3199
|
+
return true;
|
|
3200
|
+
return false;
|
|
3201
|
+
};
|
|
3202
|
+
for (const candidate of index.restParameterCandidates) {
|
|
3203
|
+
const symbol = ts.isIdentifier(candidate.parameter.name) ? checker.getSymbolAtLocation(candidate.parameter.name) : undefined;
|
|
3204
|
+
if (!symbol)
|
|
3205
|
+
continue;
|
|
3206
|
+
for (const call of allCalls) {
|
|
3207
|
+
if (!isWithin(call, candidate.declaration))
|
|
3208
|
+
continue;
|
|
3209
|
+
const effectiveArguments = invocationOperands.get(call).args;
|
|
3210
|
+
if (!effectiveArguments || effectiveArguments.length !== 1)
|
|
3211
|
+
continue;
|
|
3212
|
+
const sole = effectiveArguments[0];
|
|
3213
|
+
if (!ts.isSpreadElement(sole) || !ts.isIdentifier(sole.expression) || checker.getSymbolAtLocation(sole.expression) !== symbol)
|
|
3214
|
+
continue;
|
|
3215
|
+
const resolvedTarget = resolvedCallDeclarations.get(call);
|
|
3216
|
+
const targetCandidate = resolvedTarget ? restCandidateByDeclaration.get(resolvedTarget) : undefined;
|
|
3217
|
+
if (!targetCandidate || targetCandidate.parameter === candidate.parameter)
|
|
3218
|
+
continue;
|
|
3219
|
+
const targetJoined = restElementTypes.get(targetCandidate.parameter);
|
|
3220
|
+
const ownJoined = restElementTypes.get(candidate.parameter);
|
|
3221
|
+
if (!targetJoined)
|
|
3222
|
+
restElementTypes.delete(candidate.parameter);
|
|
3223
|
+
else if (!ownJoined)
|
|
3224
|
+
restElementTypes.set(candidate.parameter, targetJoined);
|
|
3225
|
+
else {
|
|
3226
|
+
const combined = joinOfWrites(checker, [ownJoined, targetJoined]);
|
|
3227
|
+
if (combined)
|
|
3228
|
+
restElementTypes.set(candidate.parameter, combined);
|
|
3229
|
+
else
|
|
3230
|
+
restElementTypes.delete(candidate.parameter);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
// Later composed censuses can retain earlier inferred facts. Their protocol
|
|
3235
|
+
// dependencies remain required while any dependent parameter facts survive.
|
|
3236
|
+
protocolLedger?.replace('parameter-bindings', [...upstreamProtocolRequirements, ...[...protocolRequirements.values()].flat()]);
|
|
3237
|
+
// `typeAt`, `patternReadTypeAt` and `statedTypeAt` below are the only fields
|
|
3238
|
+
// here that are not plain Map lookups -- see the `upstream` parameter's own
|
|
3239
|
+
// doc for why: they close over `publishing`/`computeStatedTypeAt`, which
|
|
3240
|
+
// close over this round's `valueFlow` and the whole `upstream` argument, so
|
|
3241
|
+
// THIS returned object is what keeps every earlier round's `ValueFlowIndex`
|
|
3242
|
+
// (and its `source-value-session.ts` solver session) reachable once it
|
|
3243
|
+
// becomes the next round's `upstream` -- and, for the settling round, for
|
|
3244
|
+
// the rest of the compile. `implicitArgumentsTupleAt`, `restElementTypeAt`,
|
|
3245
|
+
// `callDeclarationAt`, `callTargetsAt` and `argumentsAt` name neither and
|
|
3246
|
+
// are the ones a caller can hold onto for free.
|
|
3247
|
+
return {
|
|
3248
|
+
implicitArgumentsTupleAt: (owner) => implicitTuples.get(owner) ?? null,
|
|
3249
|
+
restElementTypeAt: (parameter) => restElementTypes.get(parameter) ?? null,
|
|
3250
|
+
callDeclarationAt: (call) => resolvedCallDeclarations.get(call) ?? null,
|
|
3251
|
+
explicitThisAt: (call) => pendingExplicitThisReadings.get(call) ?? null,
|
|
3252
|
+
callTargetsAt: (call) => callTargets.get(call) ?? null,
|
|
3253
|
+
typeAt: (node) => implicitArgumentsReadTypeAt(checker, node, (owner) => implicitTuples.get(owner) ?? null) ??
|
|
3254
|
+
(ts.isParameter(node) ? parameterTypeOf(node) : publishing.resolve(node)),
|
|
3255
|
+
argumentsAt: (parameter) => {
|
|
3256
|
+
const known = argumentsByParameter.get(parameter);
|
|
3257
|
+
return known === undefined || !bindings.has(parameter) ? null : [...known];
|
|
3258
|
+
},
|
|
3259
|
+
patternReadTypeAt: (element) => {
|
|
3260
|
+
publishing.resolve(element);
|
|
3261
|
+
return patternReadTypes.get(element) ?? null;
|
|
3262
|
+
},
|
|
3263
|
+
statedTypeAt: (node) => {
|
|
3264
|
+
// Memoized, and safe to memoize only HERE: this is the published view,
|
|
3265
|
+
// returned after the fixpoint has stopped, so `statedBindings` and
|
|
3266
|
+
// `flowCarrierBounds` no longer change and the answer for a node is
|
|
3267
|
+
// settled. The propagating view deliberately keeps no memo, because its
|
|
3268
|
+
// answers are answers to a smaller set of bindings.
|
|
3269
|
+
//
|
|
3270
|
+
// It earns the memo: this was 23% of a 58s three.js compile. Every
|
|
3271
|
+
// identifier in the program reaches it, and the body below asks the
|
|
3272
|
+
// checker for a symbol -- the single most expensive thing this compiler
|
|
3273
|
+
// can ask -- once per call.
|
|
3274
|
+
const remembered = statedTypes.get(node);
|
|
3275
|
+
if (remembered !== undefined)
|
|
3276
|
+
return remembered;
|
|
3277
|
+
const answer = computeStatedTypeAt(node);
|
|
3278
|
+
statedTypes.set(node, answer);
|
|
3279
|
+
return answer;
|
|
3280
|
+
},
|
|
3281
|
+
// A parameter and every UNNARROWED read of it are one storage cell. A
|
|
3282
|
+
// narrowed read keeps the checker's concrete arm so ordinary union loads
|
|
3283
|
+
// can select it from the placement rather than replacing the narrowing
|
|
3284
|
+
// with the full union again.
|
|
3285
|
+
unionArmsAt: (node) => {
|
|
3286
|
+
const declaration = ts.isParameter(node)
|
|
3287
|
+
? node
|
|
3288
|
+
: ts.isIdentifier(node)
|
|
3289
|
+
? checker.getSymbolAtLocation(node)?.declarations?.find(ts.isParameter)
|
|
3290
|
+
: undefined;
|
|
3291
|
+
if (declaration) {
|
|
3292
|
+
const arms = flowCarrierArms.get(declaration);
|
|
3293
|
+
const upper = flowCarrierBounds.get(declaration);
|
|
3294
|
+
if (arms && upper) {
|
|
3295
|
+
if (ts.isParameter(node))
|
|
3296
|
+
return arms;
|
|
3297
|
+
const read = checker.getTypeAtLocation(node);
|
|
3298
|
+
if (checker.isTypeAssignableTo(read, upper) && checker.isTypeAssignableTo(upper, read))
|
|
3299
|
+
return arms;
|
|
3300
|
+
// An optional parameter read still carries every physical object
|
|
3301
|
+
// arm; the checker's added nullish member only describes the
|
|
3302
|
+
// dynamic arm's possible absent state. Preserve the same placement
|
|
3303
|
+
// until control flow removes an actual container arm.
|
|
3304
|
+
const present = checker.getNonNullableType(read);
|
|
3305
|
+
if (checker.isTypeAssignableTo(present, upper) && checker.isTypeAssignableTo(upper, present))
|
|
3306
|
+
return arms;
|
|
3307
|
+
return null;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
return synthesizedUnionArmsAt(checker, node, unionArms);
|
|
3311
|
+
},
|
|
3312
|
+
boundCount: bindings.size + unionArms.size + flowCarrierArms.size + implicitTuples.size,
|
|
3313
|
+
refusals,
|
|
3314
|
+
refusalOf: (parameter) => lastRefusal.get(parameter) ?? null,
|
|
3315
|
+
debugReport: () => {
|
|
3316
|
+
const describeOpen = (entry) => {
|
|
3317
|
+
const reference = entry.reference;
|
|
3318
|
+
const file = reference.getSourceFile();
|
|
3319
|
+
const location = `${file.fileName.split('/').pop()}:${file.getLineAndCharacterOfPosition(reference.getStart()).line + 1}`;
|
|
3320
|
+
const parent = `${ts.SyntaxKind[reference.parent.kind]} ${reference.parent.getText().slice(0, 160)}`;
|
|
3321
|
+
return `${entry.kind} ${reference.getText().slice(0, 120)} in ${parent} @${location}`;
|
|
3322
|
+
};
|
|
3323
|
+
const row = (reason, subject, at, owner) => {
|
|
3324
|
+
const path = memberOpenUses.get(owner) ?? [];
|
|
3325
|
+
const open = path[0];
|
|
3326
|
+
const context = open ? `; ${describeOpen(open)}` : '';
|
|
3327
|
+
const continuation = path
|
|
3328
|
+
.slice(1)
|
|
3329
|
+
.map((entry) => ` via ${describeOpen(entry)}`)
|
|
3330
|
+
.join('\n');
|
|
3331
|
+
const file = at.getSourceFile();
|
|
3332
|
+
return ` ${reason} <- ${subject} @${file.fileName.split('/').pop()}:${file.getLineAndCharacterOfPosition(at.getStart()).line + 1}${context}${continuation ? `\n${continuation}` : ''}`;
|
|
3333
|
+
};
|
|
3334
|
+
const parameters = [...lastRefusal].map(([parameter, reason]) => row(reason, parameter.getText().slice(0, 80), parameter, parameter.parent));
|
|
3335
|
+
const frames = [...implicitRefusals].map(([owner, reason]) => row(reason, `${nameOfCallable(owner)?.getText() ?? '<anonymous>'}(arguments)`, owner, owner));
|
|
3336
|
+
return [...parameters, ...frames].join('\n') + '\n';
|
|
3337
|
+
}
|
|
3338
|
+
};
|
|
3339
|
+
};
|