@geastack/compiler 1.0.16 → 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.
- package/dist/conversion/nodes.d.ts +31 -0
- package/dist/conversion/nodes.js +46 -2
- package/dist/conversion/record-view.d.ts +24 -0
- package/dist/conversion/record-view.js +54 -0
- package/dist/ir/certify.js +23 -1
- package/dist/ir/lower-operands.d.ts +10 -1
- package/dist/ir/lower-operands.js +58 -2
- package/dist/ir/lower.js +12 -3
- package/dist/ir/reflection-demand.js +5 -0
- package/dist/plugins/apple/host.d.ts +2 -0
- package/dist/plugins/apple/host.js +13 -6
- package/dist/plugins/gea/host.d.ts +2 -0
- package/dist/plugins/gea/host.js +11 -4
- package/dist/plugins/host-package.d.ts +37 -0
- package/dist/plugins/host-package.js +28 -0
- package/dist/plugins/load.js +15 -0
- package/dist/plugins/webgl/host.d.ts +9 -3
- package/dist/plugins/webgl/host.js +15 -6
- package/dist/plugins/webgl/plugin.js +4 -1
- package/dist/representation/embedded-carriers.d.ts +22 -0
- package/dist/representation/embedded-carriers.js +105 -0
- package/dist/semantics/model/operands.d.ts +17 -0
- package/dist/semantics/model/operations.d.ts +28 -1
- package/dist/semantics/normalize/producers/allocations.js +2 -0
- package/dist/semantics/normalize/producers/bindings.js +5 -1
- package/dist/semantics/normalize/producers/boundary.d.ts +1 -0
- package/dist/semantics/normalize/producers/boundary.js +6 -2
- package/dist/semantics/normalize/producers/class-lifecycle.js +3 -0
- package/dist/semantics/normalize/producers/control.js +2 -2
- package/dist/semantics/normalize/producers/erasure.d.ts +7 -0
- package/dist/semantics/normalize/producers/erasure.js +19 -0
- package/dist/semantics/normalize/producers/exact-arms.d.ts +12 -0
- package/dist/semantics/normalize/producers/exact-arms.js +12 -0
- package/dist/semantics/normalize/producers/invocations.js +33 -1
- package/dist/targets/cpp/class-properties/emit-class-properties.js +4 -4
- package/dist/targets/cpp/conversions.js +63 -1
- package/dist/targets/cpp/emit-callable.js +5 -5
- package/dist/targets/cpp/emit-context.d.ts +38 -1
- package/dist/targets/cpp/emit-context.js +28 -2
- package/dist/targets/cpp/emit-narrowing.d.ts +1 -1
- package/dist/targets/cpp/emit-narrowing.js +47 -1
- package/dist/targets/cpp/emit-record-view.js +31 -0
- package/dist/targets/cpp/emit.d.ts +3 -3
- package/dist/targets/cpp/emit.js +11 -5
- package/dist/targets/cpp/translation-unit.js +24 -15
- package/package.json +1 -1
- package/src/targets/cpp/runtime/gea_runtime.h +123 -44
package/dist/plugins/load.js
CHANGED
|
@@ -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
|
-
* - `
|
|
58
|
-
*
|
|
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 {
|
|
2
|
-
const
|
|
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_ =
|
|
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
|
-
* - `
|
|
112
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
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:
|
|
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,
|
|
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)}{
|
|
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)}{
|
|
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 = `{
|
|
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 };
|