@geastack/compiler 1.0.16 → 1.0.18

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 (48) 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/lower-operands.d.ts +10 -1
  7. package/dist/ir/lower-operands.js +58 -2
  8. package/dist/ir/lower.js +12 -3
  9. package/dist/ir/reflection-demand.js +5 -0
  10. package/dist/plugins/apple/host.d.ts +2 -0
  11. package/dist/plugins/apple/host.js +13 -6
  12. package/dist/plugins/apple/jsx.js +20 -2
  13. package/dist/plugins/gea/host.d.ts +2 -0
  14. package/dist/plugins/gea/host.js +11 -4
  15. package/dist/plugins/host-package.d.ts +37 -0
  16. package/dist/plugins/host-package.js +28 -0
  17. package/dist/plugins/load.js +15 -0
  18. package/dist/plugins/webgl/host.d.ts +9 -3
  19. package/dist/plugins/webgl/host.js +15 -6
  20. package/dist/plugins/webgl/plugin.js +4 -1
  21. package/dist/representation/embedded-carriers.d.ts +22 -0
  22. package/dist/representation/embedded-carriers.js +105 -0
  23. package/dist/semantics/model/operands.d.ts +17 -0
  24. package/dist/semantics/model/operations.d.ts +28 -1
  25. package/dist/semantics/normalize/producers/allocations.js +2 -0
  26. package/dist/semantics/normalize/producers/bindings.js +5 -1
  27. package/dist/semantics/normalize/producers/boundary.d.ts +1 -0
  28. package/dist/semantics/normalize/producers/boundary.js +6 -2
  29. package/dist/semantics/normalize/producers/class-lifecycle.js +3 -0
  30. package/dist/semantics/normalize/producers/control.js +2 -2
  31. package/dist/semantics/normalize/producers/erasure.d.ts +7 -0
  32. package/dist/semantics/normalize/producers/erasure.js +19 -0
  33. package/dist/semantics/normalize/producers/exact-arms.d.ts +12 -0
  34. package/dist/semantics/normalize/producers/exact-arms.js +12 -0
  35. package/dist/semantics/normalize/producers/invocations.js +33 -1
  36. package/dist/targets/cpp/class-properties/emit-class-properties.js +4 -4
  37. package/dist/targets/cpp/conversions.js +63 -1
  38. package/dist/targets/cpp/emit-callable.js +5 -5
  39. package/dist/targets/cpp/emit-context.d.ts +38 -1
  40. package/dist/targets/cpp/emit-context.js +28 -2
  41. package/dist/targets/cpp/emit-narrowing.d.ts +1 -1
  42. package/dist/targets/cpp/emit-narrowing.js +47 -1
  43. package/dist/targets/cpp/emit-record-view.js +31 -0
  44. package/dist/targets/cpp/emit.d.ts +3 -3
  45. package/dist/targets/cpp/emit.js +11 -5
  46. package/dist/targets/cpp/translation-unit.js +24 -15
  47. package/package.json +1 -1
  48. package/src/targets/cpp/runtime/gea_runtime.h +123 -44
@@ -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
+ };
@@ -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
@@ -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');
@@ -8,6 +8,7 @@ import { implementationSignatureOf } from '../structural-declarations.js';
8
8
  import { physicalGeneratorOverloadResultAt } from '../physical-overload-result.js';
9
9
  import { regionId, semanticResultId } from '../../../identity/ids.js';
10
10
  import { bindingKindOf } from './binding-kind.js';
11
+ import { declaresExactArms } from './exact-arms.js';
11
12
  import { blocked, mintOperationId, mintResult, operand } from './mint.js';
12
13
  import { calleeAwareTypeAt, objectDescriptorReturnTypeAt, resolvedCalleeSignatureType, sourceForValue, unwrapErased, valueEdgesInto } from './shared.js';
13
14
  import { isShortCircuitingCall, optionalChainGuardOf, optionalCallGuardOf, presentReturnTypeOf } from './optional-chain.js';
@@ -152,6 +153,34 @@ const argumentConversionRolesOf = (selected, operands) => {
152
153
  }
153
154
  return roles;
154
155
  };
156
+ /**
157
+ * The `exact-arm` roles of a call into an `@gea-exact-arms` implementation
158
+ * (`ConversionRoleTarget.owner`): each argument's type under the overload the
159
+ * checker resolved. Published beside the implementation's own
160
+ * `parameter-slot` roles rather than instead of them, because the slot is
161
+ * still the implementation's union -- this only says which arm of it the
162
+ * argument enters. Nothing for a call that resolved straight to the body
163
+ * (no overload was chosen, so there is no arm to name) or to a generic
164
+ * overload (the instantiated parameter types are not public, exactly as
165
+ * `argumentConversionRolesOf` refuses them).
166
+ */
167
+ const exactArmArgumentRolesOf = (context, node, resolved, implementation, operands) => {
168
+ if (!resolved || !implementation?.declaration || !declaresExactArms(implementation.declaration))
169
+ return [];
170
+ const selected = buildSelectedSignature(context, node, resolved);
171
+ if (!selected || selected.typeArguments.kind !== 'none')
172
+ return [];
173
+ const roles = [];
174
+ for (const argument of operands) {
175
+ if (argument.role !== 'argument')
176
+ continue;
177
+ const parameter = selected.parameters[argument.ordinal];
178
+ if (!parameter || parameter.rest)
179
+ continue;
180
+ roles.push({ role: 'argument', ordinal: argument.ordinal, owner: 'exact-arm', type: parameter.slot });
181
+ }
182
+ return roles;
183
+ };
155
184
  const buildSelectedSignature = (context, node, signature) => {
156
185
  const declaration = signature.declaration;
157
186
  // No declaration node (the implicit constructor of a class with no written
@@ -1740,7 +1769,10 @@ export const createInvocationProducer = (context) => ({
1740
1769
  ? { intrinsicReflection: intrinsicPropertyCall }
1741
1770
  : {}),
1742
1771
  operands,
1743
- conversionRoles: argumentConversionRolesOf(selectedSignature, operands),
1772
+ conversionRoles: [
1773
+ ...argumentConversionRolesOf(selectedSignature, operands),
1774
+ ...exactArmArgumentRolesOf(context, node, resolvedSignature, implementationSignature, operands)
1775
+ ],
1744
1776
  // Two results, and they are different values. `value` is what the call
1745
1777
  // returned, which exists only on the branch where the guard was present;
1746
1778
  // `short-circuit` is what the *expression* evaluates to, which is that
@@ -3,7 +3,7 @@ import { abiKey, representationKey } from '../../../representation/model.js';
3
3
  import { abiOfCallee } from '../../../projection/callee.js';
4
4
  import { runtimeClassLayoutsOf } from '../../../projection/classes.js';
5
5
  import { classMethodOverrideOf, classPrototypeMethodMutableOf } from '../../../projection/fields.js';
6
- import { bindingReference, captureFieldText, cppEnvironmentStructName, cppThunkName, createCppEmitBlockedError, operandText } from '../emit-context.js';
6
+ import { bindingReference, captureFieldText, cppEnvironmentStructName, cppThunkEntryText, createCppEmitBlockedError, operandText } from '../emit-context.js';
7
7
  import { classFamilyOverridesOf, classMemberOf, dispatchesStatically, reachableClassMethodsOf, classStaticFieldStorageOf, classStaticMemberOf } from '../class-layout.js';
8
8
  import { cppVirtualMemberName, virtualDispatchKey } from '../virtual-methods.js';
9
9
  import { cppAbiParameterType, cppBodyName, cppCallableDeclarationTagName, cppClassName, cppConstructName, cppRecordFieldName, cppRecordFieldPresenceName, cppResultTypeOf, cppStringLiteral, cppTypeOf, cppUndefinedIn } from '../types.js';
@@ -160,7 +160,7 @@ valueRepresentation = boundMethodValueRepresentation(operation), receiverText =
160
160
  const owner = [...ctx.classes.values()].find((layout) => layout.methods.some((entry) => entry.callable === method.callable && entry.key === key));
161
161
  if (owner === undefined)
162
162
  throw createCppEmitBlockedError('call-abi:class-method-owner', `method "${key}" has no declaring prototype`);
163
- const payload = `${cppTypeOf(bodyRepresentation)}{&${cppThunkName(method.callable)}, ${environment}}`;
163
+ const payload = `${cppTypeOf(bodyRepresentation)}{${cppThunkEntryText(ctx, method.callable)}, ${environment}}`;
164
164
  const override = receiverRepresentation.kind === 'class-ref' && !dispatchesStatically(ctx, operation.receiver)
165
165
  ? classMethodOverrideOf(ctx.classes, receiverRepresentation.declaration, key)
166
166
  : null;
@@ -813,7 +813,7 @@ receiverText) => {
813
813
  throw createCppEmitBlockedError('property-access:class-prototype:method-abi', 'prototype method has no native convention');
814
814
  const representation = { kind: 'function-value-dispatch', abi };
815
815
  const environment = methodEnvironmentText(ctx, method.callable, `prototype method ${method.key}`);
816
- const payload = `${cppTypeOf(representation)}{&${cppThunkName(method.callable)}, ${environment}}`;
816
+ const payload = `${cppTypeOf(representation)}{${cppThunkEntryText(ctx, method.callable)}, ${environment}}`;
817
817
  return {
818
818
  representation,
819
819
  text: `gea::nativeClassMethodValue<${cppClassName(owner.declaration)}, &${cppCallableDeclarationTagName(method.callable)}>(${state}, ${payload})`
@@ -869,7 +869,7 @@ receiverText) => {
869
869
  // `gea::Value{&thunk, env}` for a computed read, which is not a boxed
870
870
  // callable at all.
871
871
  const methodAbi = ctx.abiOfCallable(site.method.callable);
872
- const object = `{&${cppThunkName(site.method.callable)}, ${methodEnvironmentText(ctx, site.method.callable, `a "get" of static "${key}"`)}}`;
872
+ const object = `{${cppThunkEntryText(ctx, site.method.callable)}, ${methodEnvironmentText(ctx, site.method.callable, `a "get" of static "${key}"`)}}`;
873
873
  if (methodAbi === null)
874
874
  return `${cppTypeOf(result)}${object}`;
875
875
  const methodRepresentation = { kind: 'function-value-dispatch', abi: methodAbi };