@geastack/compiler 1.0.15 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/conversion/nodes.d.ts +31 -0
  2. package/dist/conversion/nodes.js +46 -2
  3. package/dist/conversion/record-view.d.ts +24 -0
  4. package/dist/conversion/record-view.js +54 -0
  5. package/dist/ir/certify.js +23 -1
  6. package/dist/ir/integers.js +24 -2
  7. package/dist/ir/lower-operands.d.ts +10 -1
  8. package/dist/ir/lower-operands.js +58 -2
  9. package/dist/ir/lower.js +12 -3
  10. package/dist/ir/reflection-demand.js +5 -0
  11. package/dist/ir/transfer.js +17 -8
  12. package/dist/plugins/apple/host.d.ts +2 -0
  13. package/dist/plugins/apple/host.js +13 -6
  14. package/dist/plugins/gea/host.d.ts +2 -0
  15. package/dist/plugins/gea/host.js +11 -4
  16. package/dist/plugins/host-package.d.ts +37 -0
  17. package/dist/plugins/host-package.js +28 -0
  18. package/dist/plugins/load.js +15 -0
  19. package/dist/plugins/webgl/host.d.ts +9 -3
  20. package/dist/plugins/webgl/host.js +15 -6
  21. package/dist/plugins/webgl/plugin.js +4 -1
  22. package/dist/representation/embedded-carriers.d.ts +22 -0
  23. package/dist/representation/embedded-carriers.js +105 -0
  24. package/dist/semantics/frontend.js +4 -1
  25. package/dist/semantics/model/operands.d.ts +17 -0
  26. package/dist/semantics/model/operations.d.ts +28 -1
  27. package/dist/semantics/normalize/flow/callable-reach.js +1 -3
  28. package/dist/semantics/normalize/global-host-mutations.js +2 -2
  29. package/dist/semantics/normalize/producers/allocations.js +2 -0
  30. package/dist/semantics/normalize/producers/bindings.js +5 -1
  31. package/dist/semantics/normalize/producers/boundary.d.ts +1 -0
  32. package/dist/semantics/normalize/producers/boundary.js +6 -2
  33. package/dist/semantics/normalize/producers/class-lifecycle.js +3 -0
  34. package/dist/semantics/normalize/producers/control.js +2 -2
  35. package/dist/semantics/normalize/producers/erasure.d.ts +7 -0
  36. package/dist/semantics/normalize/producers/erasure.js +19 -0
  37. package/dist/semantics/normalize/producers/exact-arms.d.ts +12 -0
  38. package/dist/semantics/normalize/producers/exact-arms.js +12 -0
  39. package/dist/semantics/normalize/producers/invocations.js +33 -1
  40. package/dist/semantics/normalize/reachability.d.ts +20 -5
  41. package/dist/semantics/normalize/reachability.js +119 -14
  42. package/dist/semantics/normalize/specialization.js +1 -4
  43. package/dist/targets/cpp/class-layout.d.ts +40 -0
  44. package/dist/targets/cpp/class-layout.js +112 -0
  45. package/dist/targets/cpp/class-properties/emit-class-properties.js +4 -4
  46. package/dist/targets/cpp/conversions.js +63 -1
  47. package/dist/targets/cpp/emit-callable.js +7 -6
  48. package/dist/targets/cpp/emit-context.d.ts +38 -1
  49. package/dist/targets/cpp/emit-context.js +28 -2
  50. package/dist/targets/cpp/emit-narrowing.d.ts +1 -1
  51. package/dist/targets/cpp/emit-narrowing.js +54 -1
  52. package/dist/targets/cpp/emit-properties.js +13 -3
  53. package/dist/targets/cpp/emit-record-view.js +31 -0
  54. package/dist/targets/cpp/emit.d.ts +3 -3
  55. package/dist/targets/cpp/emit.js +11 -5
  56. package/dist/targets/cpp/prototype/emit-prototype-regexp.d.ts +1 -1
  57. package/dist/targets/cpp/prototype/emit-prototype-regexp.js +58 -2
  58. package/dist/targets/cpp/translation-unit.js +32 -16
  59. package/package.json +5 -5
  60. package/src/targets/cpp/runtime/gea_dynamic_proxy.h +38 -0
  61. package/src/targets/cpp/runtime/gea_runtime.h +247 -45
@@ -0,0 +1,37 @@
1
+ /**
2
+ * How a built-in host adapter loads the package that states its tables.
3
+ *
4
+ * Each adapter reads its host's own statement -- gea's `host-shims` and
5
+ * `cpp-ir`, Apple's SDK fixture and plugin, the ANGLE plugin -- by package
6
+ * name, which resolves from THIS compiler's `node_modules`. That copy and the
7
+ * checkout a build names with `--plugin <path>` are two spellings of one
8
+ * package, and they drift: the compiler's is an `npm pack` snapshot, the path
9
+ * the build passes is the sibling checkout. A table added to the checkout then
10
+ * never reached a compile that named the checkout explicitly, and the failure
11
+ * surfaced far downstream with nothing saying why (an ambient-type
12
+ * realization stated by the checkout fell through to another host's by-name
13
+ * protocols; an absent global stated by the checkout stayed live and emitted
14
+ * lib.dom's whole `Window`).
15
+ *
16
+ * The stated path is the authority. When `plugins/load.ts` answers a legacy
17
+ * package entry with its built-in adapter, the adapter ADOPTS that file:
18
+ * every later load resolves from the file's own package first -- a package
19
+ * self-reference through its `exports`, or its own dependencies -- and falls
20
+ * back to the compiler's copy only for a specifier the stated package cannot
21
+ * see at all (Apple's SDK is a peer the plugin package does not carry).
22
+ * `MODULE_NOT_FOUND` is the only failure the fallback absorbs: a package that
23
+ * resolves and then fails to load is a defect wherever it lives, and the
24
+ * adapters' own two-failures rule (`gea/host.ts`) still tells it apart.
25
+ *
26
+ * Adoption must precede every read. An adapter table computed at module
27
+ * initialization would freeze the compiler's copy before the CLI ever saw
28
+ * `--plugin`, so the adapters read their tables lazily and `adopt` resets
29
+ * whatever they cached.
30
+ */
31
+ export interface HostPackageLoader {
32
+ readonly load: (specifier: string) => unknown;
33
+ readonly resolve: (specifier: string) => string;
34
+ /** Resolve from this file's package first; the caller resets its own caches. */
35
+ readonly adopt: (file: string) => void;
36
+ }
37
+ export declare const createHostPackageLoader: (ownUrl: string) => HostPackageLoader;
@@ -0,0 +1,28 @@
1
+ import { createRequire } from 'node:module';
2
+ const absent = (error) => error.code === 'MODULE_NOT_FOUND';
3
+ export const createHostPackageLoader = (ownUrl) => {
4
+ const own = createRequire(ownUrl);
5
+ let adopted = null;
6
+ const attempt = (run) => {
7
+ const bases = adopted === null ? [own] : [adopted, own];
8
+ let missing = null;
9
+ for (const base of bases) {
10
+ try {
11
+ return run(base);
12
+ }
13
+ catch (error) {
14
+ if (!absent(error))
15
+ throw error;
16
+ missing = error;
17
+ }
18
+ }
19
+ throw missing;
20
+ };
21
+ return {
22
+ load: (specifier) => attempt((base) => base(specifier)),
23
+ resolve: (specifier) => attempt((base) => base.resolve(specifier)),
24
+ adopt: (file) => {
25
+ adopted = createRequire(file);
26
+ }
27
+ };
28
+ };
@@ -4,6 +4,12 @@ import { dirname, isAbsolute, join, resolve } from 'node:path';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
  import { installedPlugins } from './installed.js';
6
6
  import { noPluginCapabilities } from './model.js';
7
+ import { adoptApplePackage } from './apple/host.js';
8
+ import { applePlugin } from './apple/plugin.js';
9
+ import { adoptGeaPackage } from './gea/host.js';
10
+ import { geaPlugin } from './gea/plugin.js';
11
+ import { adoptWebglPackage } from './webgl/host.js';
12
+ import { webglPlugin } from './webgl/plugin.js';
7
13
  const isRecord = (value) => typeof value === 'object' && value !== null;
8
14
  const messageOf = (error) => (error instanceof Error ? error.message : String(error));
9
15
  const resolvePlugin = (specifier) => {
@@ -172,6 +178,15 @@ export const loadCliPlugins = async (specifiers) => {
172
178
  const adapter = typeof plugin['configure'] === 'function' ? await legacyAdapter(file, name, exported) : undefined;
173
179
  if (!adapter)
174
180
  throw new Error(`plugin "${name}" is incompatible: expected instantiate(options); legacy hooks are not supported`);
181
+ // The adapter's tables come from the package's own statement, and the
182
+ // statement must be the package this path names -- not the copy the
183
+ // compiler's own `node_modules` happens to hold (`host-package.ts`).
184
+ if (adapter === webglPlugin)
185
+ adoptWebglPackage(file);
186
+ else if (adapter === geaPlugin)
187
+ adoptGeaPackage(file);
188
+ else if (adapter === applePlugin)
189
+ adoptApplePackage(file);
175
190
  process.stderr.write(`compile: --plugin ${specifier}: using built-in "${adapter.name}" adapter for the legacy package\n`);
176
191
  loadedPaths.add(file);
177
192
  continue;
@@ -1,5 +1,7 @@
1
1
  import type { AmbientTypeRealization } from '../../semantics/ambient-type-realization-transform.js';
2
2
  import type { PluginSourceFile } from '../model.js';
3
+ /** The package entry the CLI loaded is the one this host's tables come from -- see `plugins/host-package.ts`. */
4
+ export declare const adoptWebglPackage: (file: string) => void;
3
5
  /**
4
6
  * The ambient free functions this host defines, to the C++ each is.
5
7
  *
@@ -54,10 +56,14 @@ export declare const webglSourceTransform: (input: PluginSourceFile) => string |
54
56
  * for programs that use it to detect WebGL 2. The WebGL 1 constructor is
55
57
  * absent: this host only creates NativeWebGL2RenderingContext. In three's
56
58
  * WebGL 1 rejection guard, absence correctly skips the rejected path.
57
- * - `self`, `postMessage`, `importScripts`: worker globals, and whether this
58
- * target has workers is not a fact about its GL context.
59
+ * - `postMessage`, `importScripts`: worker globals, and whether this target
60
+ * has workers is not a fact about its GL context. `self` IS stated -- by the
61
+ * package, in its own `absentGlobals` -- because the package ships the
62
+ * `self`-less animation driver three's renderer is aliased to
63
+ * (`nativeWebGLAnimation.ts`): the one fact the package does know about
64
+ * `self` is that three's only use of it has already been answered natively.
59
65
  */
60
- export declare const webglAbsentGlobals: ReadonlySet<string>;
66
+ export declare const webglAbsentGlobals: () => ReadonlySet<string>;
61
67
  /**
62
68
  * The ambient WebGL context names three's own source (and `@types/three`'s
63
69
  * mirrored declarations) states, to the concrete class
@@ -1,5 +1,5 @@
1
- import { createRequire } from 'node:module';
2
- const require_ = createRequire(import.meta.url);
1
+ import { createHostPackageLoader } from '../host-package.js';
2
+ const packageLoader = createHostPackageLoader(import.meta.url);
3
3
  let loaded;
4
4
  /**
5
5
  * The package's own statement, loaded once.
@@ -18,7 +18,7 @@ const shims = () => {
18
18
  const specifier = requested ?? '@geastack/native-webgl-angle/geatsc-plugin';
19
19
  let module_;
20
20
  try {
21
- module_ = require_(specifier);
21
+ module_ = packageLoader.load(specifier);
22
22
  }
23
23
  catch (error) {
24
24
  const absent = error.code === 'MODULE_NOT_FOUND';
@@ -39,6 +39,11 @@ const shims = () => {
39
39
  loaded = configured;
40
40
  return configured;
41
41
  };
42
+ /** The package entry the CLI loaded is the one this host's tables come from -- see `plugins/host-package.ts`. */
43
+ export const adoptWebglPackage = (file) => {
44
+ packageLoader.adopt(file);
45
+ loaded = undefined;
46
+ };
42
47
  /**
43
48
  * The ambient free functions this host defines, to the C++ each is.
44
49
  *
@@ -108,10 +113,14 @@ export const webglSourceTransform = (input) => shims()?.transformSource?.(input)
108
113
  * for programs that use it to detect WebGL 2. The WebGL 1 constructor is
109
114
  * absent: this host only creates NativeWebGL2RenderingContext. In three's
110
115
  * WebGL 1 rejection guard, absence correctly skips the rejected path.
111
- * - `self`, `postMessage`, `importScripts`: worker globals, and whether this
112
- * target has workers is not a fact about its GL context.
116
+ * - `postMessage`, `importScripts`: worker globals, and whether this target
117
+ * has workers is not a fact about its GL context. `self` IS stated -- by the
118
+ * package, in its own `absentGlobals` -- because the package ships the
119
+ * `self`-less animation driver three's renderer is aliased to
120
+ * (`nativeWebGLAnimation.ts`): the one fact the package does know about
121
+ * `self` is that three's only use of it has already been answered natively.
113
122
  */
114
- export const webglAbsentGlobals = new Set([
123
+ export const webglAbsentGlobals = () => new Set([
115
124
  ...(shims()?.absentGlobals ?? []),
116
125
  'WebGLRenderingContext',
117
126
  'ImageData',
@@ -65,7 +65,10 @@ export const webglPlugin = {
65
65
  hostNamespaceRootTypes: new Map(),
66
66
  hostSingletons: new Set(),
67
67
  hostSingletonsByDeclaration: new Map(),
68
- absentGlobals: webglAbsentGlobals,
68
+ // Read at instantiate, never at import: the package's statement is the
69
+ // one `plugins/load.ts` adopted from `--plugin`, and adoption happens
70
+ // after this module is loaded.
71
+ absentGlobals: webglAbsentGlobals(),
69
72
  hostPreambles: webglHostPreambles(),
70
73
  nativeBases: new Map(),
71
74
  // Declarations arrive per spelling through `hostPreambles`, not per
@@ -0,0 +1,22 @@
1
+ import type { Representation } from './model.js';
2
+ /**
3
+ * Every carrier a representation embeds, visited once each, depth first.
4
+ *
5
+ * A carrier is not only its own kind: a `record` renders as a struct whose
6
+ * every field is a carrier of its own, a `tagged-union` as an alternative of
7
+ * each arm's, a callable as a signature spelling each parameter's. What the
8
+ * emitted C++ names is the whole tree, so a fact that must hold of every
9
+ * carrier the program spells -- that a `native-handle` names a protocol the
10
+ * host registered -- has to be asked of the whole tree, not of its root. A
11
+ * census that stopped at the root let lib.dom's `Window` reach clang as a
12
+ * struct whose `Navigator`/`History`/`ScreenOrientation` fields named tag
13
+ * types no host had declared: a certified program that did not compile.
14
+ *
15
+ * The walk mirrors what the backend renders (`targets/cpp/records.ts`'s
16
+ * `visitRepresentation`): `native-record-ref` names a struct defined
17
+ * elsewhere and carries no fields, `class-ref` names a class whose layout
18
+ * is censused with the class, and a callable contributes its ABI carriers.
19
+ * A representation tree is finite -- recursion is broken by
20
+ * `native-record-ref` -- but each node is visited once regardless.
21
+ */
22
+ export declare const forEachEmbeddedRepresentation: (root: Representation, visit: (carrier: Representation) => void) => void;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Every carrier a representation embeds, visited once each, depth first.
3
+ *
4
+ * A carrier is not only its own kind: a `record` renders as a struct whose
5
+ * every field is a carrier of its own, a `tagged-union` as an alternative of
6
+ * each arm's, a callable as a signature spelling each parameter's. What the
7
+ * emitted C++ names is the whole tree, so a fact that must hold of every
8
+ * carrier the program spells -- that a `native-handle` names a protocol the
9
+ * host registered -- has to be asked of the whole tree, not of its root. A
10
+ * census that stopped at the root let lib.dom's `Window` reach clang as a
11
+ * struct whose `Navigator`/`History`/`ScreenOrientation` fields named tag
12
+ * types no host had declared: a certified program that did not compile.
13
+ *
14
+ * The walk mirrors what the backend renders (`targets/cpp/records.ts`'s
15
+ * `visitRepresentation`): `native-record-ref` names a struct defined
16
+ * elsewhere and carries no fields, `class-ref` names a class whose layout
17
+ * is censused with the class, and a callable contributes its ABI carriers.
18
+ * A representation tree is finite -- recursion is broken by
19
+ * `native-record-ref` -- but each node is visited once regardless.
20
+ */
21
+ export const forEachEmbeddedRepresentation = (root, visit) => {
22
+ const seen = new Set([root]);
23
+ const abi = (value) => {
24
+ if (value === null)
25
+ return;
26
+ for (const parameter of value.parameters)
27
+ walk(parameter.value);
28
+ walk(value.result);
29
+ if (value.receiver !== null)
30
+ walk(value.receiver);
31
+ };
32
+ const walk = (value) => {
33
+ if (seen.has(value))
34
+ return;
35
+ seen.add(value);
36
+ visit(value);
37
+ children(value);
38
+ };
39
+ const children = (value) => {
40
+ switch (value.kind) {
41
+ case 'record':
42
+ for (const field of value.fields)
43
+ walk(field.value);
44
+ for (const accessor of value.accessors)
45
+ walk(accessor.value);
46
+ return;
47
+ case 'record-with-index':
48
+ for (const field of value.fields)
49
+ walk(field.value);
50
+ for (const index of value.indexes)
51
+ walk(index.value);
52
+ return;
53
+ case 'proxy-object':
54
+ walk(value.target);
55
+ walk(value.handler);
56
+ return;
57
+ case 'borrowed-ref':
58
+ walk(value.referent);
59
+ return;
60
+ case 'array-object':
61
+ case 'dense-buffer':
62
+ case 'native-sequence':
63
+ walk(value.element);
64
+ return;
65
+ case 'iterator':
66
+ walk(value.element);
67
+ walk(value.resume);
68
+ walk(value.completion);
69
+ return;
70
+ case 'promise':
71
+ walk(value.value);
72
+ return;
73
+ case 'keyed-collection':
74
+ walk(value.key);
75
+ if (value.value !== null)
76
+ walk(value.value);
77
+ return;
78
+ case 'dictionary':
79
+ walk(value.value);
80
+ return;
81
+ case 'optional':
82
+ walk(value.payload);
83
+ return;
84
+ case 'tagged-union':
85
+ for (const arm of value.arms)
86
+ walk(arm.value);
87
+ return;
88
+ case 'function':
89
+ case 'function-family':
90
+ case 'constructor-family':
91
+ case 'constructor-value-dispatch':
92
+ case 'function-value-family':
93
+ case 'function-value-dispatch':
94
+ abi(value.abi);
95
+ return;
96
+ case 'function-and-constructor':
97
+ abi(value.call);
98
+ abi(value.construct);
99
+ return;
100
+ default:
101
+ return;
102
+ }
103
+ };
104
+ children(root);
105
+ };
@@ -44,7 +44,7 @@ import { censusRefusalCounts } from './normalize/census-refusal.js';
44
44
  import { createCellPolicyRegistry, defaultCellEvidencePolicies, buildCellFactsTable, publishCellFacts } from './normalize/cells/index.js';
45
45
  import { settleBindingCensus } from './normalize/binding-fixpoint.js';
46
46
  import { withJsDocTypeNames } from './normalize/jsdoc-type-names.js';
47
- import { censusReachability, moduleEvaluationOrder, nodeIsReachable } from './normalize/reachability.js';
47
+ import { censusReachability, fileEvaluates, moduleEvaluationOrder, nodeIsReachable } from './normalize/reachability.js';
48
48
  import { createProgram, defaultCompilerOptions } from './program.js';
49
49
  import { createFrontendTiming } from './frontend-timing.js';
50
50
  import { classHeritageOf } from './class-heritage.js';
@@ -1105,7 +1105,10 @@ export const runFrontend = (input) => {
1105
1105
  // reachability walk pruned whole, or one whose top level censused nothing,
1106
1106
  // has no region -- and a name in this list that no region answers would be
1107
1107
  // a call to a function nothing defines.
1108
+ // `fileEvaluates` is the reached-statement half of that: a file kept only
1109
+ // for a layout-only class has a region and, by design, no body.
1108
1110
  moduleOrder: moduleEvaluationOrder({ checker: compiled.checker, files: compiled.sourceFiles, entries: compiled.entryFiles })
1111
+ .filter((file) => fileEvaluates(reachable, file))
1109
1112
  .map((file) => regionId(identities.nodeIdOf(file), 'module-body'))
1110
1113
  .filter((region) => normalized.graph.regions.has(region)),
1111
1114
  sourceFileNames: identities.sourceFileNames,
@@ -98,6 +98,23 @@ export interface SemanticOperand {
98
98
  * (`spread-arguments.ts`). Absent means 0, the whole source.
99
99
  */
100
100
  readonly from?: number;
101
+ /**
102
+ * The program's own type assertion (`x as T`, `<T>x`) wraps the expression
103
+ * this operand cites. The assertion is erased -- it evaluates nothing and
104
+ * changes no value, which is why the operand cites the wrapped expression's
105
+ * result and carries its type -- but it is also the author STATING which
106
+ * arm of a union the value holds here, the same promise `@gea-exact-arms`
107
+ * makes for a whole body (`ConversionRoleTarget.owner`'s `exact-arm`). A
108
+ * lowering that must put the value into a slot that is exactly one arm of
109
+ * its union, and that the census has no sound per-arm answer for, may take
110
+ * the stated arm -- checked at runtime, a `TypeError` when the assertion
111
+ * was false. hono's `Context.executionCtx` returns `this.#executionCtx as
112
+ * ExecutionContext` off a `FetchEventLike | ExecutionContext` field: the
113
+ * class arm has no view as the interface, so without the assertion the
114
+ * store is refused, and with it the store is the projection the author
115
+ * wrote. An assertion to `any`/`unknown` states no arm and is not marked.
116
+ */
117
+ readonly asserted?: true;
101
118
  }
102
119
  /** One result an operation publishes, keyed by the role it fills. */
103
120
  export interface SemanticResult {
@@ -17,7 +17,17 @@ import type { HostMethodBinding } from '../host-methods.js';
17
17
  export interface ConversionRoleTarget {
18
18
  readonly role: string;
19
19
  readonly ordinal: number;
20
- readonly owner: 'parameter-slot' | 'array-element' | 'declared-field';
20
+ /**
21
+ * `exact-arm` is the caller's half of `AllocationOperation.exactArms`: for
22
+ * an argument into an `@gea-exact-arms` implementation, `type` is the
23
+ * parameter type of the overload the checker RESOLVED, not the
24
+ * implementation's union slot the `parameter-slot` role names. The lowering
25
+ * enters that arm of the union and no other, so the arm the body projects
26
+ * is the arm the checker chose -- without it a `() => 2` under `Typed |
27
+ * Generic` widens into whichever arm can adapt it first, and the body's
28
+ * projection then throws on a call the checker accepted.
29
+ */
30
+ readonly owner: 'parameter-slot' | 'array-element' | 'declared-field' | 'exact-arm';
21
31
  readonly type: StructuralTypeId;
22
32
  }
23
33
  /**
@@ -311,6 +321,23 @@ export interface AllocationOperation extends SemanticOperationBase {
311
321
  * the same condition as `functionSource`.
312
322
  */
313
323
  readonly generatorFunction?: boolean;
324
+ /**
325
+ * Whether the declaration carries `@gea-exact-arms`: inside this body, a
326
+ * tagged-union value entering a slot that is EXACTLY one of its arms
327
+ * projects that arm and throws a `TypeError` on any other, instead of the
328
+ * per-arm dispatch that converts every arm into the target.
329
+ *
330
+ * A fact of the declaration because the checker cannot state it. TS
331
+ * function assignability makes any two callable arms convertible in at
332
+ * least one direction, so `fn as RequestListener` over `EventHandler |
333
+ * RequestListener` always installs an adapter for the catch-all arm -- and
334
+ * that adapter publishes the typed arm's parameters to a dynamic listener,
335
+ * which is precisely what an overload implementation keeping typed and
336
+ * generic listeners in separate storage never does. Only the author knows
337
+ * the branch guarding the cast makes the other arms unreachable, so the
338
+ * author states it, per body, and pays with a runtime check.
339
+ */
340
+ readonly exactArms?: boolean;
314
341
  /**
315
342
  * Whether the declaration performs ECMA-262 10.2.5 `MakeConstructor` and so
316
343
  * owns a `prototype` object -- true for an ordinary `function` declaration
@@ -5146,9 +5146,7 @@ const closedMemberCallableUses = (checker, flow, member, countedCalls, givenRece
5146
5146
  const origins = key === null ? null : allocationOriginsOf(receiver);
5147
5147
  traceClosedCallee(call, 'invocationTargetsOf/origins', `key=${key ?? 'null'} origins=${origins === null ? 'null' : origins.classes.size}`);
5148
5148
  if (origins !== null) {
5149
- const privateDeclarations = ts.isPropertyAccessExpression(callee) && ts.isPrivateIdentifier(callee.name)
5150
- ? privateMemberDeclarationsOf(callee.name)
5151
- : null;
5149
+ const privateDeclarations = ts.isPropertyAccessExpression(callee) && ts.isPrivateIdentifier(callee.name) ? privateMemberDeclarationsOf(callee.name) : null;
5152
5150
  const selected = new Set();
5153
5151
  for (const owner of origins.classes) {
5154
5152
  const slotDeclarations = privateDeclarations ??
@@ -4416,7 +4416,7 @@ computedKeysOf = () => null, trustSeed = initialCensusSeed) => {
4416
4416
  // value.
4417
4417
  if ((ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent)) && parent.propertyName === node)
4418
4418
  return true;
4419
- return (((ts.isVariableDeclaration(parent) ||
4419
+ return ((ts.isVariableDeclaration(parent) ||
4420
4420
  ts.isImportSpecifier(parent) ||
4421
4421
  ts.isExportSpecifier(parent) ||
4422
4422
  ts.isImportClause(parent) ||
@@ -4441,7 +4441,7 @@ computedKeysOf = () => null, trustSeed = initialCensusSeed) => {
4441
4441
  ts.isPropertyAssignment(parent) ||
4442
4442
  ts.isShorthandPropertyAssignment(parent) ||
4443
4443
  ts.isTypeParameterDeclaration(parent)) &&
4444
- parent.name === node));
4444
+ parent.name === node);
4445
4445
  };
4446
4446
  let globalEscapeAnswer = null;
4447
4447
  const globalObjectEscapesIntoData = () => {
@@ -1,6 +1,7 @@
1
1
  import ts from 'typescript';
2
2
  import { operationId, semanticResultId } from '../../../identity/ids.js';
3
3
  import { bindingKindOf } from './binding-kind.js';
4
+ import { declaresExactArms } from './exact-arms.js';
4
5
  import { blocked, mintOperationId, mintResult, operand } from './mint.js';
5
6
  import { citeExpressionResult } from './references.js';
6
7
  import { sourceForValue, valueEdgesInto, hasNativeIterationCursor, staticPropertyKeyTextOf, isRuntimeSymbolMember, staticSpreadMembersOf, staticFunctionNameOf, staticClassNameOf, staticClassLengthOf, expectedParameterCountOf, ownPrototypePropertyOf } from './shared.js';
@@ -797,6 +798,7 @@ export const createAllocationProducer = (context) => ({
797
798
  functionName: staticFunctionNameOf(node),
798
799
  functionLength: expectedParameterCountOf(node),
799
800
  generatorFunction: 'asteriskToken' in node && node.asteriskToken !== undefined,
801
+ ...(declaresExactArms(node) ? { exactArms: true } : {}),
800
802
  ...(ownPrototypePropertyOf(node) === null ? {} : { ownPrototypeProperty: ownPrototypePropertyOf(node) === true })
801
803
  }
802
804
  : {}),
@@ -6,6 +6,7 @@ import { normalCompletion, pureEffects } from '../../model/operands.js';
6
6
  import { blocked, mintOperationId, mintResult, operand } from './mint.js';
7
7
  import { bindingKindOf, bindingKindOfElement } from './binding-kind.js';
8
8
  import { boundElementType, citeBoundElementValue } from './destructuring.js';
9
+ import { assertsType } from './erasure.js';
9
10
  import { citeExpressionResult } from './references.js';
10
11
  import { resultEdge } from './shared.js';
11
12
  /**
@@ -643,7 +644,10 @@ const contributeVariableDeclaration = (candidate, node, context) => {
643
644
  const cited = citeExpressionResult(initializer, context);
644
645
  if (cited.kind === 'unmodelled')
645
646
  return { kind: 'blocked', blocker: blocked(candidate.id, 'binding', cited.reason, null) };
646
- operands.push(operand('initializer', 0, cited.source, context.types.typeAt(initializer)));
647
+ operands.push({
648
+ ...operand('initializer', 0, cited.source, context.types.typeAt(initializer)),
649
+ ...(assertsType(initializer, context.checker) ? { asserted: true } : {})
650
+ });
647
651
  }
648
652
  else if (loopValueResult) {
649
653
  operands.push(operand('initializer', 0, { kind: 'result', result: loopValueResult }, context.types.typeAt(node)));
@@ -21,6 +21,7 @@ import type { ProducerContext } from '../producer-context.js';
21
21
  export declare const resolveExpressionOperand: (context: ProducerContext, node: ts.Node) => {
22
22
  readonly source: OperandSource;
23
23
  readonly type: StructuralTypeId;
24
+ readonly asserted?: true;
24
25
  } | null;
25
26
  /**
26
27
  * Catch clauses: the one candidate family `'boundary'` receives from the
@@ -1,6 +1,6 @@
1
1
  import ts from 'typescript';
2
2
  import { normalCompletion, pureEffects } from '../../model/operands.js';
3
- import { unwrapErased } from './erasure.js';
3
+ import { assertsType, unwrapErased } from './erasure.js';
4
4
  import { blocked, mintOperationId, mintResult, operand } from './mint.js';
5
5
  import { citeExpressionResult } from './references.js';
6
6
  import { valueEdgesInto } from './shared.js';
@@ -25,7 +25,11 @@ export const resolveExpressionOperand = (context, node) => {
25
25
  const cited = citeExpressionResult(node, context);
26
26
  if (cited.kind === 'unmodelled')
27
27
  return null;
28
- return { source: cited.source, type: context.types.typeAt(unwrapErased(node)) };
28
+ return {
29
+ source: cited.source,
30
+ type: context.types.typeAt(unwrapErased(node)),
31
+ ...(assertsType(node, context.checker) ? { asserted: true } : {})
32
+ };
29
33
  };
30
34
  /**
31
35
  * Catch clauses: the one candidate family `'boundary'` receives from the
@@ -4,6 +4,7 @@ import { normalCompletion, pureEffects, throwingCompletion } from '../../model/o
4
4
  import { isParameterProperty } from '../census.js';
5
5
  import { parameterBindingResultOf } from './bindings.js';
6
6
  import { parameterSlotTypeOf } from '../parameter-slot.js';
7
+ import { declaresExactArms } from './exact-arms.js';
7
8
  import { mintOperationId, mintResult, operand } from './mint.js';
8
9
  import { resultOf } from '../../model/operands.js';
9
10
  import { asBlocked, resultEdge, uniqueSymbolKeyTextOf, staticFunctionNameOf, staticClassNameOf, staticClassLengthOf, expectedParameterCountOf, ownPrototypePropertyOf } from './shared.js';
@@ -209,6 +210,7 @@ export const createClassLifecycleProducer = (context) => {
209
210
  ? {
210
211
  functionSource: callable.getText(),
211
212
  generatorFunction: 'asteriskToken' in callable && callable.asteriskToken !== undefined,
213
+ ...(declaresExactArms(callable) ? { exactArms: true } : {}),
212
214
  ...(ownPrototypePropertyOf(callable) === null ? {} : { ownPrototypeProperty: ownPrototypePropertyOf(callable) === true }),
213
215
  ...(isNamedCallableMember(callable)
214
216
  ? { functionName: staticFunctionNameOf(callable), functionLength: expectedParameterCountOf(callable) }
@@ -301,6 +303,7 @@ export const createClassLifecycleProducer = (context) => {
301
303
  classConstructorBodyOf: context.identities.declarationIdOf(node),
302
304
  functionSource: written.getText(),
303
305
  generatorFunction: 'asteriskToken' in written && written.asteriskToken !== undefined,
306
+ ...(declaresExactArms(written) ? { exactArms: true } : {}),
304
307
  ...(ownPrototypePropertyOf(written) === null ? {} : { ownPrototypeProperty: ownPrototypePropertyOf(written) === true }),
305
308
  caller: candidate.caller,
306
309
  operands: [],
@@ -436,7 +436,7 @@ const contributeReturn = (context, candidate, node) => {
436
436
  const resolved = resolveExpressionOperand(context, node.expression);
437
437
  if (!resolved)
438
438
  return blockedContribution(candidate, 'no normalized operation identifies the returned value');
439
- valueOperand = operand('value', 0, resolved.source, resolved.type);
439
+ valueOperand = { ...operand('value', 0, resolved.source, resolved.type), ...(resolved.asserted ? { asserted: true } : {}) };
440
440
  }
441
441
  else {
442
442
  const undefinedType = context.table.intern({ kind: 'primitive', primitive: 'undefined' });
@@ -487,7 +487,7 @@ const contributeImplicitReturn = (context, candidate, node) => {
487
487
  id,
488
488
  form: 'return',
489
489
  caller: candidate.caller,
490
- operands: [operand('value', 0, resolved.source, resolved.type)],
490
+ operands: [{ ...operand('value', 0, resolved.source, resolved.type), ...(resolved.asserted ? { asserted: true } : {}) }],
491
491
  results: [mintResult(id, 'completion', resolved.type)],
492
492
  completion: { canThrow: false, canReturn: true, canBreak: false, canContinue: false, canSuspend: false },
493
493
  effects: pureEffects,
@@ -18,6 +18,13 @@ import ts from 'typescript';
18
18
  export declare const unwrapErased: (node: ts.Node) => ts.Node;
19
19
  /** The same rule where the caller already knows it holds an expression. */
20
20
  export declare const unwrapErasedExpression: (expression: ts.Expression) => ts.Expression;
21
+ /**
22
+ * Whether one of the erased wrappers around `expression` is a type assertion
23
+ * that names a type -- `SemanticOperand.asserted`. `as any`/`as unknown`
24
+ * assert nothing about which arm a value holds, so they do not count; `!` and
25
+ * `satisfies` are not assertions of a type at all.
26
+ */
27
+ export declare const assertsType: (expression: ts.Expression, checker: ts.TypeChecker) => boolean;
21
28
  /**
22
29
  * The node whose PARENT decides this expression's syntactic position.
23
30
  *
@@ -31,6 +31,25 @@ export const unwrapErased = (node) => {
31
31
  };
32
32
  /** The same rule where the caller already knows it holds an expression. */
33
33
  export const unwrapErasedExpression = (expression) => unwrapErased(expression);
34
+ /**
35
+ * Whether one of the erased wrappers around `expression` is a type assertion
36
+ * that names a type -- `SemanticOperand.asserted`. `as any`/`as unknown`
37
+ * assert nothing about which arm a value holds, so they do not count; `!` and
38
+ * `satisfies` are not assertions of a type at all.
39
+ */
40
+ export const assertsType = (expression, checker) => {
41
+ let current = expression;
42
+ for (;;) {
43
+ if (ts.isAsExpression(current) || ts.isTypeAssertionExpression(current)) {
44
+ if ((checker.getTypeFromTypeNode(current.type).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0)
45
+ return true;
46
+ }
47
+ else if (!ts.isParenthesizedExpression(current) && !ts.isNonNullExpression(current) && !ts.isSatisfiesExpression(current)) {
48
+ return false;
49
+ }
50
+ current = current.expression;
51
+ }
52
+ };
34
53
  /**
35
54
  * The node whose PARENT decides this expression's syntactic position.
36
55
  *
@@ -0,0 +1,12 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * Whether a callable declaration states `@gea-exact-arms` -- see
4
+ * `AllocationOperation.exactArms` for what the tag promises and why only the
5
+ * author can promise it.
6
+ *
7
+ * Read off the declaration that owns the body, which for an overloaded method
8
+ * is the implementation signature: the overloads are types, and the tag
9
+ * changes how the one body lowers, so the one body's own JSDoc is where it
10
+ * belongs.
11
+ */
12
+ export declare const declaresExactArms: (node: ts.Node) => boolean;
@@ -0,0 +1,12 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * Whether a callable declaration states `@gea-exact-arms` -- see
4
+ * `AllocationOperation.exactArms` for what the tag promises and why only the
5
+ * author can promise it.
6
+ *
7
+ * Read off the declaration that owns the body, which for an overloaded method
8
+ * is the implementation signature: the overloads are types, and the tag
9
+ * changes how the one body lowers, so the one body's own JSDoc is where it
10
+ * belongs.
11
+ */
12
+ export const declaresExactArms = (node) => ts.getJSDocTags(node).some((tag) => tag.tagName.text === 'gea-exact-arms');