@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.
- 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/integers.js +24 -2
- 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/ir/transfer.js +17 -8
- 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/frontend.js +4 -1
- package/dist/semantics/model/operands.d.ts +17 -0
- package/dist/semantics/model/operations.d.ts +28 -1
- package/dist/semantics/normalize/flow/callable-reach.js +1 -3
- package/dist/semantics/normalize/global-host-mutations.js +2 -2
- 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/semantics/normalize/reachability.d.ts +20 -5
- package/dist/semantics/normalize/reachability.js +119 -14
- package/dist/semantics/normalize/specialization.js +1 -4
- package/dist/targets/cpp/class-layout.d.ts +40 -0
- package/dist/targets/cpp/class-layout.js +112 -0
- 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 +7 -6
- 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 +54 -1
- package/dist/targets/cpp/emit-properties.js +13 -3
- 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/prototype/emit-prototype-regexp.d.ts +1 -1
- package/dist/targets/cpp/prototype/emit-prototype-regexp.js +58 -2
- package/dist/targets/cpp/translation-unit.js +32 -16
- package/package.json +5 -5
- package/src/targets/cpp/runtime/gea_dynamic_proxy.h +38 -0
- package/src/targets/cpp/runtime/gea_runtime.h +247 -45
|
@@ -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
|
|
@@ -37,17 +37,16 @@ import ts from 'typescript';
|
|
|
37
37
|
* - A **function declaration**. Its evaluation is `InstantiateFunctionObject`:
|
|
38
38
|
* a closure is created and bound. Nothing observable happens, and the body
|
|
39
39
|
* does not run.
|
|
40
|
-
* - A **class declaration** with no decorators, no
|
|
40
|
+
* - A **class declaration** with no decorators, no static
|
|
41
41
|
* block, no static field initializer and no computed member name.
|
|
42
42
|
* `ClassDefinitionEvaluation` for such a class evaluates nothing at all, and
|
|
43
43
|
* this compiler lowers class evaluation to *no runtime step at all*
|
|
44
44
|
* (`ir/lower.ts`, `case 'class-lifecycle'`): the struct and the construct
|
|
45
45
|
* function it emits already are the layout the definition events record. A
|
|
46
46
|
* static block or a static initializer is the opposite -- each is a real
|
|
47
|
-
* region with a body -- so a class carrying one is kept
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* `classDefinitionIsInert`).
|
|
47
|
+
* region with a body -- so a class carrying one is kept. An `extends`
|
|
48
|
+
* clause is tolerated only in the two spellings `heritageIsInert` proves
|
|
49
|
+
* evaluate nothing; any other base keeps the class.
|
|
51
50
|
* - A **variable statement** whose every declarator binds a plain name and has
|
|
52
51
|
* either no initializer or an initializer drawn from `initializerIsInert`'s
|
|
53
52
|
* whitelist: literals, function/arrow/class expressions, and object/array
|
|
@@ -185,5 +184,21 @@ export interface ReachabilityInput {
|
|
|
185
184
|
*/
|
|
186
185
|
readonly hostReachedMemberKeys?: ReadonlySet<string>;
|
|
187
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Whether the statements this program REACHES in `file` evaluate anything.
|
|
189
|
+
*
|
|
190
|
+
* `evaluatesNothing` above is the syntactic answer for a whole file;
|
|
191
|
+
* this is the same question after pruning, and it is the one the entry needs:
|
|
192
|
+
* a module body is called because something in it runs. A class retained for
|
|
193
|
+
* its LAYOUT alone is the case that separates them. It is a live statement --
|
|
194
|
+
* it publishes a class-lifecycle operation, so the census gives its file a
|
|
195
|
+
* module-body region -- and by `layoutClasses`' own definition it runs no
|
|
196
|
+
* constructor, no initializer and no module effect, so lowering gives that
|
|
197
|
+
* region no body. A rooted script whose every class is pruned except one some
|
|
198
|
+
* annotation names (node-compat's `whatwg-streams.ts`, in a program that
|
|
199
|
+
* touches no stream) was scheduled on the strength of the region and refused
|
|
200
|
+
* by the printer for want of the body.
|
|
201
|
+
*/
|
|
202
|
+
export declare const fileEvaluates: (reachable: ProgramReachability, file: ts.SourceFile) => boolean;
|
|
188
203
|
export declare const moduleEvaluationOrder: (input: ReachabilityInput) => readonly ts.SourceFile[];
|
|
189
204
|
export declare const censusReachability: (input: ReachabilityInput) => ProgramReachability;
|
|
@@ -92,21 +92,63 @@ const computedKeyIsInert = (checker, name) => {
|
|
|
92
92
|
const declarations = checker.getSymbolAtLocation(root)?.declarations ?? [];
|
|
93
93
|
return declarations.length > 0 && declarations.every((declaration) => declaration.getSourceFile().isDeclarationFile);
|
|
94
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* Whether evaluating a class's `extends` clause is provably not an action.
|
|
97
|
+
*
|
|
98
|
+
* `ClassDefinitionEvaluation` (15.7.14) evaluates the heritage expression and
|
|
99
|
+
* requires a constructor. Two spellings cannot do anything else. A bare
|
|
100
|
+
* identifier naming a class DECLARED EARLIER at the top level of the same file
|
|
101
|
+
* is a read of an initialized binding -- past its temporal dead zone, never a
|
|
102
|
+
* getter, always a constructor. And a bare identifier whose every declaration
|
|
103
|
+
* is ambient names the language's own intrinsic (`Error`), for the reason
|
|
104
|
+
* `computedKeyIsInert` gives. Everything else is kept: a call (`mixin(Base)`),
|
|
105
|
+
* a property read (`ns.Base`, which can be a getter), a base declared later
|
|
106
|
+
* (a TDZ throw), and an imported base, whose module a cycle may not have
|
|
107
|
+
* evaluated yet.
|
|
108
|
+
*
|
|
109
|
+
* This used to answer `false` for every `extends`, for a reason that was never
|
|
110
|
+
* about evaluation: `projection/classes.ts` takes a struct's base from the
|
|
111
|
+
* heritage event a LIVE class publishes, so pruning a subclass some type still
|
|
112
|
+
* named left `struct One final {}` with no base under an upcast. That is a
|
|
113
|
+
* question about who names the class, and `markTypeNamedClasses` now answers
|
|
114
|
+
* it where it belongs -- a class any live annotation, alias or interface names
|
|
115
|
+
* is opened for its layout and publishes that event. What is pruned here is a
|
|
116
|
+
* subclass NOTHING names, whose struct nothing emits.
|
|
117
|
+
*
|
|
118
|
+
* The blanket rule's cost was every unused hierarchy in a rooted script:
|
|
119
|
+
* node-compat's `whatwg-streams.ts` and `abort-events.ts` are roots of every
|
|
120
|
+
* program, so every program carried `ReadableByteStreamController`,
|
|
121
|
+
* `TextEncoderStream`, `DOMException` and, through them, the whole stream
|
|
122
|
+
* implementation -- ~2.4k emitted lines in a program that prints one number.
|
|
123
|
+
*/
|
|
124
|
+
const heritageIsInert = (checker, node) => {
|
|
125
|
+
for (const clause of node.heritageClauses ?? []) {
|
|
126
|
+
if (clause.token !== ts.SyntaxKind.ExtendsKeyword)
|
|
127
|
+
continue;
|
|
128
|
+
for (const type of clause.types) {
|
|
129
|
+
if (!ts.isIdentifier(type.expression))
|
|
130
|
+
return false;
|
|
131
|
+
const declarations = checker.getSymbolAtLocation(type.expression)?.declarations ?? [];
|
|
132
|
+
if (declarations.length === 0)
|
|
133
|
+
return false;
|
|
134
|
+
if (declarations.every((declaration) => declaration.getSourceFile().isDeclarationFile))
|
|
135
|
+
continue;
|
|
136
|
+
const file = node.getSourceFile();
|
|
137
|
+
const settled = declarations.every((declaration) => ts.isClassDeclaration(declaration) &&
|
|
138
|
+
declaration.getSourceFile() === file &&
|
|
139
|
+
declaration.parent === file &&
|
|
140
|
+
declaration.end <= node.pos);
|
|
141
|
+
if (!settled)
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
};
|
|
95
147
|
/** Whether a class's *definition* evaluates anything beyond binding its own name. */
|
|
96
148
|
const classDefinitionIsInert = (checker, node) => {
|
|
97
149
|
if (ts.canHaveDecorators(node) && (ts.getDecorators(node)?.length ?? 0) > 0)
|
|
98
150
|
return false;
|
|
99
|
-
|
|
100
|
-
// class is its heritage read, and that step is not inert in THIS compiler
|
|
101
|
-
// even though it calls nothing: `projection/classes.ts` reads a class's
|
|
102
|
-
// `base` link off the class-lifecycle heritage event the census publishes
|
|
103
|
-
// for the definition, and `records.ts` spells `struct D : B` from that
|
|
104
|
-
// link alone -- while the struct itself is emitted whenever the class's
|
|
105
|
-
// shape is reachable, which a type position keeps it. Pruning an
|
|
106
|
-
// un-instantiated `class One extends Base` whose type still names a union
|
|
107
|
-
// arm emitted `struct One final {}` with no base, and the upcast
|
|
108
|
-
// `Ref<Base>(Ref<One>)` the union's recast renders stopped compiling.
|
|
109
|
-
if (node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword))
|
|
151
|
+
if (!heritageIsInert(checker, node))
|
|
110
152
|
return false;
|
|
111
153
|
return node.members.every((member) => {
|
|
112
154
|
if (ts.canHaveDecorators(member) && (ts.getDecorators(member)?.length ?? 0) > 0)
|
|
@@ -238,6 +280,31 @@ const objectMemberIsInert = (checker, member) => {
|
|
|
238
280
|
* module (TS2448, which this compiler refuses the program for), it does not
|
|
239
281
|
* across an import cycle.
|
|
240
282
|
*/
|
|
283
|
+
/**
|
|
284
|
+
* `Symbol()` / `Symbol('description')` on the language's own `Symbol`.
|
|
285
|
+
*
|
|
286
|
+
* ECMA-262 20.4.1.1 does one thing a program could observe: `ToString` of the
|
|
287
|
+
* description, and `ToString` of a string literal (or of nothing) runs no
|
|
288
|
+
* code. What is left is minting a symbol, and a symbol bound to a name nothing
|
|
289
|
+
* reads is not observable -- which is the only case this is ever asked about,
|
|
290
|
+
* because a binding something names is opened regardless. The callee must be
|
|
291
|
+
* the ambient intrinsic for the reason `computedKeyIsInert` gives: a user
|
|
292
|
+
* `Symbol` is an ordinary call.
|
|
293
|
+
*
|
|
294
|
+
* A module-private brand token is what reaches it: node-compat's
|
|
295
|
+
* `abort-events.ts` is a root of every program and declares
|
|
296
|
+
* `const abortSignalConstructionToken = Symbol('AbortSignal construction')`,
|
|
297
|
+
* so every program kept a module body whose whole content was that call.
|
|
298
|
+
*/
|
|
299
|
+
const intrinsicSymbolCallIsInert = (checker, node) => {
|
|
300
|
+
if (!ts.isIdentifier(node.expression) || node.expression.text !== 'Symbol' || node.arguments.length > 1)
|
|
301
|
+
return false;
|
|
302
|
+
const description = node.arguments[0];
|
|
303
|
+
if (description !== undefined && !ts.isStringLiteral(description) && !ts.isNoSubstitutionTemplateLiteral(description))
|
|
304
|
+
return false;
|
|
305
|
+
const declarations = checker.getSymbolAtLocation(node.expression)?.declarations ?? [];
|
|
306
|
+
return declarations.length > 0 && declarations.every((declaration) => declaration.getSourceFile().isDeclarationFile);
|
|
307
|
+
};
|
|
241
308
|
const initializerIsInert = (checker, node) => {
|
|
242
309
|
if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node))
|
|
243
310
|
return initializerIsInert(checker, node.expression);
|
|
@@ -286,7 +353,7 @@ const initializerIsInert = (checker, node) => {
|
|
|
286
353
|
declarations.every((declaration) => ts.isClassDeclaration(declaration) && declaration.getSourceFile() === node.getSourceFile()));
|
|
287
354
|
}
|
|
288
355
|
if (ts.isCallExpression(node))
|
|
289
|
-
return closureFactoryCallIsInert(checker, node);
|
|
356
|
+
return intrinsicSymbolCallIsInert(checker, node) || closureFactoryCallIsInert(checker, node);
|
|
290
357
|
// `-1` and `+1` are how a negative numeric constant is spelled; nothing else
|
|
291
358
|
// unary is admitted, because every other operand form can reach user code.
|
|
292
359
|
if (ts.isPrefixUnaryExpression(node)) {
|
|
@@ -547,9 +614,33 @@ const evaluatesNothing = (file) => file.statements.every((statement) => {
|
|
|
547
614
|
return true;
|
|
548
615
|
if (statement.moduleSpecifier !== undefined)
|
|
549
616
|
return false;
|
|
550
|
-
return statement.exportClause !== undefined && ts.isNamedExports(statement.exportClause) && statement.exportClause.elements.length === 0;
|
|
617
|
+
return (statement.exportClause !== undefined && ts.isNamedExports(statement.exportClause) && statement.exportClause.elements.length === 0);
|
|
551
618
|
}
|
|
552
|
-
return ts.canHaveModifiers(statement) &&
|
|
619
|
+
return (ts.canHaveModifiers(statement) &&
|
|
620
|
+
(ts.getModifiers(statement) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword));
|
|
621
|
+
});
|
|
622
|
+
/**
|
|
623
|
+
* Whether the statements this program REACHES in `file` evaluate anything.
|
|
624
|
+
*
|
|
625
|
+
* `evaluatesNothing` above is the syntactic answer for a whole file;
|
|
626
|
+
* this is the same question after pruning, and it is the one the entry needs:
|
|
627
|
+
* a module body is called because something in it runs. A class retained for
|
|
628
|
+
* its LAYOUT alone is the case that separates them. It is a live statement --
|
|
629
|
+
* it publishes a class-lifecycle operation, so the census gives its file a
|
|
630
|
+
* module-body region -- and by `layoutClasses`' own definition it runs no
|
|
631
|
+
* constructor, no initializer and no module effect, so lowering gives that
|
|
632
|
+
* region no body. A rooted script whose every class is pruned except one some
|
|
633
|
+
* annotation names (node-compat's `whatwg-streams.ts`, in a program that
|
|
634
|
+
* touches no stream) was scheduled on the strength of the region and refused
|
|
635
|
+
* by the printer for want of the body.
|
|
636
|
+
*/
|
|
637
|
+
export const fileEvaluates = (reachable, file) => reachable.statementsOf(file).some((statement) => {
|
|
638
|
+
if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement))
|
|
639
|
+
return false;
|
|
640
|
+
if (ts.isClassDeclaration(statement) && reachable.classIsLayoutOnly(statement))
|
|
641
|
+
return false;
|
|
642
|
+
return !(ts.canHaveModifiers(statement) &&
|
|
643
|
+
(ts.getModifiers(statement) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword));
|
|
553
644
|
});
|
|
554
645
|
export const moduleEvaluationOrder = (input) => {
|
|
555
646
|
const compiled = new Set(input.files);
|
|
@@ -787,11 +878,25 @@ export const censusReachability = (input) => {
|
|
|
787
878
|
* header already gives, and layout-only is the smallest promotion there is:
|
|
788
879
|
* no module evaluation, no constructor, no member body.
|
|
789
880
|
*/
|
|
881
|
+
// A type alias or interface is a NAME for the types inside it, so a class it
|
|
882
|
+
// names is named by whatever names the alias: `type Arm = One | Two` then
|
|
883
|
+
// `let v: Arm` types a live cell as `One`. The walk never enters a type
|
|
884
|
+
// declaration on its own (`markReferences` returns on both), so it is entered
|
|
885
|
+
// here, once, from the reference that makes it matter. This is what lets
|
|
886
|
+
// `heritageIsInert` prune an unnamed subclass without losing a named one.
|
|
887
|
+
const followedTypeDeclarations = new Set();
|
|
790
888
|
const markTypeNamedClasses = (node) => {
|
|
791
889
|
const named = ts.isTypeReferenceNode(node) ? node.typeName : ts.isExpressionWithTypeArguments(node) ? node.expression : null;
|
|
792
890
|
if (named !== null) {
|
|
793
891
|
const symbol = input.checker.getSymbolAtLocation(named);
|
|
794
892
|
for (const declaration of symbol ? declarationsOf(input.checker, symbol) : []) {
|
|
893
|
+
if (ts.isTypeAliasDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) {
|
|
894
|
+
if (followedTypeDeclarations.has(declaration))
|
|
895
|
+
continue;
|
|
896
|
+
followedTypeDeclarations.add(declaration);
|
|
897
|
+
ts.forEachChild(declaration, markTypeNamedClasses);
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
795
900
|
if (!ts.isClassDeclaration(declaration))
|
|
796
901
|
continue;
|
|
797
902
|
const file = declaration.getSourceFile();
|
|
@@ -1781,10 +1781,7 @@ export const censusSpecializations = (checker, sourceFiles, reachable, namespace
|
|
|
1781
1781
|
// refusal it replaces. Those copies are two layouts and split.
|
|
1782
1782
|
const stored = parametersInInstanceStorage(checker, declaration);
|
|
1783
1783
|
const foldable = (candidate) => ![...layoutRelevant].some((index) => stored.has(index) &&
|
|
1784
|
-
fillings.some((other, at) => minted[at] === true &&
|
|
1785
|
-
other[index] !== candidate[index] &&
|
|
1786
|
-
!openFilling(other[index]) &&
|
|
1787
|
-
!openFilling(candidate[index])));
|
|
1784
|
+
fillings.some((other, at) => minted[at] === true && other[index] !== candidate[index] && !openFilling(other[index]) && !openFilling(candidate[index])));
|
|
1788
1785
|
// The chosen layout must be one the program actually builds.
|
|
1789
1786
|
return (fillings.find((candidate, at) => minted[at] === true && differs(candidate) && foldable(candidate) && fillings.every((other) => absorbs(candidate, other))) ?? null);
|
|
1790
1787
|
})();
|
|
@@ -6,6 +6,7 @@ import { classFamilyOverridesOf } from '../../projection/dispatch.js';
|
|
|
6
6
|
export { classFamilyOverridesOf };
|
|
7
7
|
import { classMemberOf, classStaticMemberOf, type ClassMemberSite } from '../../projection/fields.js';
|
|
8
8
|
export { classMemberOf, classStaticMemberOf, type ClassMemberSite };
|
|
9
|
+
import type { ConstantLiteral } from '../../semantics/model/operands.js';
|
|
9
10
|
import { type RecordField, type Representation } from '../../representation/model.js';
|
|
10
11
|
import { type ConversionSite } from './emit-narrowing.js';
|
|
11
12
|
/**
|
|
@@ -167,6 +168,45 @@ export declare const lazyArrowFieldGetPlanOf: (ctx: Pick<EmitContext, "classes"
|
|
|
167
168
|
* the snapshot in hand.
|
|
168
169
|
*/
|
|
169
170
|
export declare const lazyCalleeReadsOf: (ctx: EmitContext, body: IrBody) => ReadonlySet<IrValueId>;
|
|
171
|
+
/**
|
|
172
|
+
* A class field initializer that is one constant and nothing else.
|
|
173
|
+
*
|
|
174
|
+
* Carried as the constant's own `text`/`literal` rather than as a decided
|
|
175
|
+
* boolean because the question "is this redundant" cannot be answered here:
|
|
176
|
+
* it depends on the PHYSICAL storage the field was given, which
|
|
177
|
+
* `cppFieldInitializerStatements` learns from its `storedFieldOf` and this
|
|
178
|
+
* census never sees. This says what the initializer is; that site says what
|
|
179
|
+
* the storage is; the two together decide.
|
|
180
|
+
*/
|
|
181
|
+
export interface ConstantFieldInitializer {
|
|
182
|
+
readonly text: string;
|
|
183
|
+
readonly literal: ConstantLiteral;
|
|
184
|
+
}
|
|
185
|
+
/** Publishes one compile's constant-field-initializer census. Called once, before any body renders -- mirrors `publishLazyArrowFieldPlans`. */
|
|
186
|
+
export declare const publishConstantFieldInitializers: (classes: ReadonlyMap<DeclarationId, ClassLayout>, constants: ReadonlyMap<DeclarationId, ReadonlyMap<string, ConstantFieldInitializer>>) => void;
|
|
187
|
+
/** The single constant one class field's initializer evaluates to, or `null` when it is anything else (including in a compile that published no census). */
|
|
188
|
+
export declare const constantFieldInitializerOf: (classes: ReadonlyMap<DeclarationId, ClassLayout>, declaration: DeclarationId, key: string) => ConstantFieldInitializer | null;
|
|
189
|
+
/**
|
|
190
|
+
* Every class field whose initializer is a single constant.
|
|
191
|
+
*
|
|
192
|
+
* The shape admitted is the narrowest one that proves "evaluating this thunk
|
|
193
|
+
* does nothing but produce this value": one block, one operation in it, that
|
|
194
|
+
* operation a `constant`, and the block returning that constant's own result.
|
|
195
|
+
* A thunk with two operations may have computed something; one that returns a
|
|
196
|
+
* different value than it built is not what it looks like. Neither gets in.
|
|
197
|
+
*
|
|
198
|
+
* A REACTIVE field is refused. Its storage is `Cell<T>`, not `T`
|
|
199
|
+
* (`records.ts`'s `fieldStorageType`), so the assignment runs the cell's own
|
|
200
|
+
* `operator=` and what a value-initialized cell holds is the cell's business,
|
|
201
|
+
* not this function's. The refusal reads the plugin's own per-class statement
|
|
202
|
+
* (`ReactiveCellPlan.fields`) rather than `cell`: gea names a cell spelling in
|
|
203
|
+
* every compile, reactive or not, so testing `cell !== null` would switch the
|
|
204
|
+
* census off for every program. It also cannot wait for the settled
|
|
205
|
+
* `celledFields`, which `cppRecordDeclarations` produces only after this
|
|
206
|
+
* census has to be published -- but that set is derived from these same
|
|
207
|
+
* `fields`, so refusing all of them refuses a superset.
|
|
208
|
+
*/
|
|
209
|
+
export declare const censusConstantFieldInitializers: (bodies: readonly IrBody[], classes: ReadonlyMap<DeclarationId, ClassLayout>, reactiveFields: ReadonlyMap<DeclarationId, ReadonlySet<string>>) => ReadonlyMap<DeclarationId, ReadonlyMap<string, ConstantFieldInitializer>>;
|
|
170
210
|
/**
|
|
171
211
|
* Which class fields qualify for lazy materialization, computed once for the
|
|
172
212
|
* whole translation unit.
|
|
@@ -62,6 +62,31 @@ classDeclaration, fields, receiver, storedFieldOf) => {
|
|
|
62
62
|
return `field "${field.key}" initializer has no complete physical storage contract`;
|
|
63
63
|
}
|
|
64
64
|
const stored = storage.value;
|
|
65
|
+
// A field whose initializer writes the value its storage ALREADY holds.
|
|
66
|
+
//
|
|
67
|
+
// `makeRef<T>()` value-initializes -- these structs have no user-provided
|
|
68
|
+
// constructor, so `T()` zero-initializes and then default-constructs every
|
|
69
|
+
// member: a `std::string` field is `""` and a `bool` field is `false`
|
|
70
|
+
// before any statement here runs. `statusMessage = ''` then calls the
|
|
71
|
+
// field's initializer thunk and assigns its result, and for a
|
|
72
|
+
// `std::string` that assignment is an out-of-line libstdc++ `_M_replace`
|
|
73
|
+
// -- per field, per construction. `node:http`'s `ServerResponse` alone
|
|
74
|
+
// declares five, and it is built once per request.
|
|
75
|
+
//
|
|
76
|
+
// Only the store is dropped, and only where dropping it is invisible:
|
|
77
|
+
// the initializer must BE one constant (`censusConstantFieldInitializers`
|
|
78
|
+
// proved the thunk is a single `constant` and a `return` of it, so it has
|
|
79
|
+
// no side effect to lose), its representation must already be the
|
|
80
|
+
// storage's (no conversion to skip), the field must be required (an
|
|
81
|
+
// optional's presence bit is `false` by declaration and would still need
|
|
82
|
+
// setting), and the constant must be the carrier's own value-initialized
|
|
83
|
+
// value.
|
|
84
|
+
const constant = constantFieldInitializerOf(site.classes, classDeclaration, field.key);
|
|
85
|
+
if (constant !== null &&
|
|
86
|
+
storage.required &&
|
|
87
|
+
representationKey(field.representation) === representationKey(stored) &&
|
|
88
|
+
storesTheValueInitializedDefault(stored, constant))
|
|
89
|
+
continue;
|
|
65
90
|
const markPresent = !storage.required ? ` ${receiver}->${cppRecordFieldPresenceName(field.key)} = true;` : '';
|
|
66
91
|
// A widening may inspect a tagged union's live arm and therefore name its
|
|
67
92
|
// input more than once. A field initializer runs exactly once, so bind its
|
|
@@ -236,6 +261,93 @@ export const lazyCalleeReadsOf = (ctx, body) => {
|
|
|
236
261
|
}
|
|
237
262
|
return result;
|
|
238
263
|
};
|
|
264
|
+
const constantFieldInitializerSidecar = new WeakMap();
|
|
265
|
+
/** Publishes one compile's constant-field-initializer census. Called once, before any body renders -- mirrors `publishLazyArrowFieldPlans`. */
|
|
266
|
+
export const publishConstantFieldInitializers = (classes, constants) => {
|
|
267
|
+
constantFieldInitializerSidecar.set(classes, constants);
|
|
268
|
+
};
|
|
269
|
+
/** The single constant one class field's initializer evaluates to, or `null` when it is anything else (including in a compile that published no census). */
|
|
270
|
+
export const constantFieldInitializerOf = (classes, declaration, key) => constantFieldInitializerSidecar.get(classes)?.get(declaration)?.get(key) ?? null;
|
|
271
|
+
/**
|
|
272
|
+
* Whether `storage` value-initializes to exactly what `constant` spells.
|
|
273
|
+
*
|
|
274
|
+
* Deliberately only the two carriers whose C++ value-initialized state is a
|
|
275
|
+
* single known value: `std::string()` is empty and a scalar is zero. Anything
|
|
276
|
+
* else -- an optional, a union, a `Ref`, a record -- either has a presence or
|
|
277
|
+
* tag byte the caller must still write or a default this cannot state, and
|
|
278
|
+
* gets no answer rather than a guessed one.
|
|
279
|
+
*
|
|
280
|
+
* `-0` is refused although `Number('-0') === 0`: a value-initialized `double`
|
|
281
|
+
* is `+0.0`, and the two differ under `Object.is` and `1 / x`. Any negative
|
|
282
|
+
* spelling that reaches here is one of that family, so the sign test is the
|
|
283
|
+
* whole check.
|
|
284
|
+
*/
|
|
285
|
+
const storesTheValueInitializedDefault = (storage, constant) => {
|
|
286
|
+
if (storage.kind === 'string')
|
|
287
|
+
return constant.literal === 'string' && constant.text === '';
|
|
288
|
+
if (storage.kind !== 'scalar')
|
|
289
|
+
return false;
|
|
290
|
+
if (storage.domain === 'boolean')
|
|
291
|
+
return constant.literal === 'boolean' && constant.text === 'false';
|
|
292
|
+
if (storage.domain === 'bigint')
|
|
293
|
+
return false;
|
|
294
|
+
return constant.literal === 'number' && !constant.text.startsWith('-') && Number(constant.text) === 0;
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* Every class field whose initializer is a single constant.
|
|
298
|
+
*
|
|
299
|
+
* The shape admitted is the narrowest one that proves "evaluating this thunk
|
|
300
|
+
* does nothing but produce this value": one block, one operation in it, that
|
|
301
|
+
* operation a `constant`, and the block returning that constant's own result.
|
|
302
|
+
* A thunk with two operations may have computed something; one that returns a
|
|
303
|
+
* different value than it built is not what it looks like. Neither gets in.
|
|
304
|
+
*
|
|
305
|
+
* A REACTIVE field is refused. Its storage is `Cell<T>`, not `T`
|
|
306
|
+
* (`records.ts`'s `fieldStorageType`), so the assignment runs the cell's own
|
|
307
|
+
* `operator=` and what a value-initialized cell holds is the cell's business,
|
|
308
|
+
* not this function's. The refusal reads the plugin's own per-class statement
|
|
309
|
+
* (`ReactiveCellPlan.fields`) rather than `cell`: gea names a cell spelling in
|
|
310
|
+
* every compile, reactive or not, so testing `cell !== null` would switch the
|
|
311
|
+
* census off for every program. It also cannot wait for the settled
|
|
312
|
+
* `celledFields`, which `cppRecordDeclarations` produces only after this
|
|
313
|
+
* census has to be published -- but that set is derived from these same
|
|
314
|
+
* `fields`, so refusing all of them refuses a superset.
|
|
315
|
+
*/
|
|
316
|
+
export const censusConstantFieldInitializers = (bodies, classes, reactiveFields) => {
|
|
317
|
+
const result = new Map();
|
|
318
|
+
const bodyBySourceOwner = new Map();
|
|
319
|
+
for (const body of bodies)
|
|
320
|
+
bodyBySourceOwner.set(body.sourceOwner, body);
|
|
321
|
+
for (const [declaration, layout] of classes) {
|
|
322
|
+
for (const field of layout.fields) {
|
|
323
|
+
if (field.initializer === null)
|
|
324
|
+
continue;
|
|
325
|
+
if (reactiveFields.get(declaration)?.has(field.key))
|
|
326
|
+
continue;
|
|
327
|
+
const thunkBody = bodyBySourceOwner.get(field.initializer);
|
|
328
|
+
if (!thunkBody || thunkBody.blocks.size !== 1)
|
|
329
|
+
continue;
|
|
330
|
+
const block = [...thunkBody.blocks.values()][0];
|
|
331
|
+
if (!block || block.operations.length !== 1)
|
|
332
|
+
continue;
|
|
333
|
+
const only = block.operations[0];
|
|
334
|
+
if (!only || only.kind !== 'constant')
|
|
335
|
+
continue;
|
|
336
|
+
const terminator = block.terminator;
|
|
337
|
+
if (terminator.kind !== 'return' || terminator.value === null)
|
|
338
|
+
continue;
|
|
339
|
+
if (terminator.value.value !== only.result.id)
|
|
340
|
+
continue;
|
|
341
|
+
let byKey = result.get(declaration);
|
|
342
|
+
if (!byKey) {
|
|
343
|
+
byKey = new Map();
|
|
344
|
+
result.set(declaration, byKey);
|
|
345
|
+
}
|
|
346
|
+
byKey.set(field.key, { text: only.text, literal: only.literal });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
};
|
|
239
351
|
/**
|
|
240
352
|
* Which class fields qualify for lazy materialization, computed once for the
|
|
241
353
|
* whole translation unit.
|
|
@@ -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 };
|
|
@@ -11,7 +11,7 @@ import { cppTypeOf } from './types.js';
|
|
|
11
11
|
import { nativeSumWidenable } from '../../conversion/native-sum.js';
|
|
12
12
|
import { nativeSelectionRecipeOf, nativeTotalSelectionRecipeOf } from '../../conversion/native-selection.js';
|
|
13
13
|
import { nativeClassReferenceIdentityOf } from '../../conversion/native-class-reference.js';
|
|
14
|
-
import { ownedRecordMaterializationPlan, recordViewUsesOnlyDirectFields } from '../../conversion/record-view.js';
|
|
14
|
+
import { isRecordViewTarget, ownedRecordMaterializationPlan, recordViewDispatchesArms, recordViewUsesOnlyDirectFields } from '../../conversion/record-view.js';
|
|
15
15
|
import { cppStringObjectNativeType } from './regexp-types.js';
|
|
16
16
|
import { viewPlanFor } from './emit-record-view.js';
|
|
17
17
|
/** Called only after the native converting-constructor predicate has selected this pair. */
|
|
@@ -446,6 +446,37 @@ const dynamicCallablePair = (target) => {
|
|
|
446
446
|
materializer: { id: 'gea::detail::DynamicCarrier::in', domain, allocates: true }
|
|
447
447
|
};
|
|
448
448
|
};
|
|
449
|
+
/**
|
|
450
|
+
* A shared class handle among a sum's arms, at a record-shaped target it
|
|
451
|
+
* reaches by neither the chain nor the structural view.
|
|
452
|
+
*
|
|
453
|
+
* Nothing in the program proves that arm dead: an interface-typed slot holds
|
|
454
|
+
* whatever object the program put there (`semantics/interface-implementors.ts`),
|
|
455
|
+
* and a class this cannot view as the record is still a value the slot can be
|
|
456
|
+
* handed -- skytail's `const ctx: AudioContextLike | null = Ctor ? new Ctor()
|
|
457
|
+
* : createNativeAudioContext()`, whose `NativeAudioContext` carries class-typed
|
|
458
|
+
* fields and a promise the view cannot rebuild. Selecting the record arm
|
|
459
|
+
* there read the class instance's bytes as the record and crashed at launch,
|
|
460
|
+
* certified. A record arm beside the exact one is the opposite case: a record
|
|
461
|
+
* the view cannot rebuild as the target is not assignable to it either, so the
|
|
462
|
+
* checker's own narrowing is what put the pair here, and the selection stands.
|
|
463
|
+
*
|
|
464
|
+
* Asked by every table that could answer the pair -- `narrowing` and
|
|
465
|
+
* `staticRecipe` -- so the census refuses it outright rather than one table
|
|
466
|
+
* declining and the next selecting.
|
|
467
|
+
*/
|
|
468
|
+
const classArmWithoutHome = (layouts, source, target) => {
|
|
469
|
+
const union = source.kind === 'optional' ? source.payload : source;
|
|
470
|
+
const selected = target.kind === 'optional' ? target.payload : target;
|
|
471
|
+
if (union.kind !== 'tagged-union' || !isRecordViewTarget(selected))
|
|
472
|
+
return false;
|
|
473
|
+
const key = representationKey(selected);
|
|
474
|
+
return union.arms.some(({ value }) => value.kind === 'class-ref' &&
|
|
475
|
+
value.ownership === 'shared-refcount' &&
|
|
476
|
+
representationKey(value) !== key &&
|
|
477
|
+
conversionRecipeOf(value, selected)?.renders !== true &&
|
|
478
|
+
viewPlanFor(layouts, value, selected) === null);
|
|
479
|
+
};
|
|
449
480
|
export const createCppConversionRegistry = (layouts = defaultRecordLayoutPolicy) => {
|
|
450
481
|
// A sum narrowing's contract is composed from the contracts this same
|
|
451
482
|
// registry states for its leaf pairs (`native-narrowing-transport.ts`), so
|
|
@@ -791,6 +822,8 @@ const cppConversionTables = (layouts, nativeNarrowing, nativeWrappedPayload) =>
|
|
|
791
822
|
// can see, and whose lifetime the program never stated.
|
|
792
823
|
if ('ownership' in target && target.ownership === 'borrowed')
|
|
793
824
|
return null;
|
|
825
|
+
if (classArmWithoutHome(layouts, source, target))
|
|
826
|
+
return null;
|
|
794
827
|
const setPair = genericFunctionSetPair(source, target);
|
|
795
828
|
if (setPair)
|
|
796
829
|
return setPair;
|
|
@@ -949,6 +982,18 @@ const cppConversionTables = (layouts, nativeNarrowing, nativeWrappedPayload) =>
|
|
|
949
982
|
const selected = source.kind === 'optional' && target.kind === 'optional' ? target.payload : target;
|
|
950
983
|
if (union.kind !== 'tagged-union')
|
|
951
984
|
return null;
|
|
985
|
+
// A record-shaped target some OTHER arm reaches only through the
|
|
986
|
+
// structural view is not a selection: that arm is live at a store, and a
|
|
987
|
+
// selection would read it as the exact arm. The view's `dispatch` plan
|
|
988
|
+
// (`conversion/record-view.ts`) is the conversion, installed by
|
|
989
|
+
// `staticRecipe` below once this table declines -- the same plan
|
|
990
|
+
// `emit-narrowing.ts`'s `narrowedLoadText` defers to, asked here so the
|
|
991
|
+
// admission and the render agree.
|
|
992
|
+
if (isRecordViewTarget(selected)) {
|
|
993
|
+
const view = viewPlanFor(layouts, source, target);
|
|
994
|
+
if (view !== null && recordViewDispatchesArms(view))
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
952
997
|
const targetKey = representationKey(selected);
|
|
953
998
|
// A SUB-union target: `selected` is still a tagged union, just missing one
|
|
954
999
|
// or more of `union`'s arms -- `h instanceof Headers`'s `else` branch
|
|
@@ -1972,6 +2017,22 @@ const cppConversionTables = (layouts, nativeNarrowing, nativeWrappedPayload) =>
|
|
|
1972
2017
|
staticRecipe: (source, target) => {
|
|
1973
2018
|
if (!isSpellable(source) || !isSpellable(target))
|
|
1974
2019
|
return null;
|
|
2020
|
+
if (classArmWithoutHome(layouts, source, target))
|
|
2021
|
+
return null;
|
|
2022
|
+
// A sum some arm of which reaches the record-shaped target only through
|
|
2023
|
+
// the structural view is that view's `dispatch` plan, asked BEFORE the
|
|
2024
|
+
// chain: the chain answers the pair too, by selecting the exact arm, and
|
|
2025
|
+
// that selection is the narrowing this table's `narrowing` entry has
|
|
2026
|
+
// already declined for the pair (`recordViewDispatchesArms`). The
|
|
2027
|
+
// printer renders in the same order (`emit-narrowing.ts`'s `recipeText`).
|
|
2028
|
+
const dispatching = viewPlanFor(layouts, source, target);
|
|
2029
|
+
if (dispatching !== null && recordViewDispatchesArms(dispatching))
|
|
2030
|
+
return {
|
|
2031
|
+
id: 'view:structural-record',
|
|
2032
|
+
domain: 'static:structural-record-view',
|
|
2033
|
+
allocates: true,
|
|
2034
|
+
...(recordViewUsesOnlyDirectFields(dispatching) ? { nativeFieldProtocol: 'unused' } : {})
|
|
2035
|
+
};
|
|
1975
2036
|
const recipe = conversionRecipeOf(source, target);
|
|
1976
2037
|
if (recipe !== null && recipe.renders) {
|
|
1977
2038
|
// `unreachable-value` spells either `unreachableValue<T>()` (a throw that
|
|
@@ -2022,6 +2083,7 @@ const allocatingRecipes = new Set([
|
|
|
2022
2083
|
'record-recast',
|
|
2023
2084
|
'optional-record-recast',
|
|
2024
2085
|
'record-to-dictionary',
|
|
2086
|
+
'dictionary-to-record',
|
|
2025
2087
|
'record-to-array',
|
|
2026
2088
|
'promise-payload'
|
|
2027
2089
|
]);
|