typegpu 0.12.6 → 0.12.8
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/common/writeSoA.js +16 -0
- package/core/buffer/buffer.js +47 -11
- package/core/commandEncoder/attachments.js +5 -2
- package/core/function/autoIO.js +5 -3
- package/core/function/comptime.js +7 -5
- package/core/function/createCallableSchema.js +4 -3
- package/core/function/dualImpl.js +20 -19
- package/core/function/ioSchema.d.ts +17 -2
- package/core/function/ioSchema.js +74 -27
- package/core/function/tgpuFn.d.ts +11 -1
- package/core/function/tgpuFn.js +9 -2
- package/core/function/tgpuFragmentFn.d.ts +1 -1
- package/core/function/tgpuFragmentFn.js +2 -1
- package/core/function/tgpuVertexFn.js +2 -1
- package/core/pipeline/connectAttachmentToShader.d.ts +2 -2
- package/core/pipeline/connectAttachmentToShader.js +16 -21
- package/core/pipeline/connectTargetsToShader.d.ts +5 -1
- package/core/pipeline/connectTargetsToShader.js +23 -18
- package/core/pipeline/renderPipeline.d.ts +9 -6
- package/core/pipeline/renderPipeline.js +22 -22
- package/core/pipeline/timeable.js +15 -2
- package/core/resolve/namespace.js +2 -2
- package/core/root/configurableImpl.js +1 -2
- package/core/root/rootTypes.d.ts +6 -2
- package/core/slot/accessor.d.ts +1 -1
- package/core/slot/accessor.js +72 -5
- package/core/texture/externalTexture.d.ts +1 -1
- package/core/texture/externalTexture.js +3 -1
- package/core/texture/texture.d.ts +1 -1
- package/core/texture/texture.js +12 -5
- package/core/unroll/tgpuUnroll.d.ts +4 -3
- package/core/unroll/tgpuUnroll.js +7 -1
- package/core/valueProxyUtils.d.ts +4 -2
- package/core/valueProxyUtils.js +4 -2
- package/data/autoStruct.d.ts +1 -1
- package/data/autoStruct.js +8 -4
- package/data/compiledIO.js +12 -9
- package/data/dataIO.d.ts +10 -6
- package/data/dataIO.js +19 -4
- package/data/dataTypes.d.ts +0 -5
- package/data/dataTypes.js +0 -6
- package/data/deepEqual.js +19 -9
- package/data/matrix.js +10 -3
- package/data/matrixColumnsAccess.d.ts +8 -0
- package/data/matrixColumnsAccess.js +15 -0
- package/data/offsetUtils.js +181 -102
- package/data/partialIO.d.ts +8 -1
- package/data/partialIO.js +36 -19
- package/data/ref.d.ts +12 -5
- package/data/ref.js +51 -24
- package/data/snippet.d.ts +13 -4
- package/data/snippet.js +17 -4
- package/data/struct.js +3 -0
- package/data/wgslTypes.d.ts +6 -3
- package/internal.d.ts +1 -0
- package/nameUtils.d.ts +7 -0
- package/nameUtils.js +60 -0
- package/package.json +1 -1
- package/resolutionCtx.js +1 -1
- package/shared/meta.js +1 -1
- package/shared/symbols.d.ts +5 -0
- package/shared/symbols.js +6 -1
- package/shared/tseynit.js +54 -50
- package/std/boolean.js +9 -10
- package/std/numeric.js +1 -4
- package/std/operators.js +9 -4
- package/std/packing.js +10 -10
- package/std/texture.d.ts +2 -2
- package/tgpuLogger.d.ts +1 -1
- package/tgpuLogger.js +0 -2
- package/tgsl/accessIndex.js +37 -38
- package/tgsl/accessProp.js +10 -7
- package/tgsl/conversion.js +3 -2
- package/tgsl/forOfUtils.js +8 -8
- package/tgsl/generationHelpers.d.ts +4 -3
- package/tgsl/generationHelpers.js +18 -5
- package/tgsl/infixDispatch.d.ts +4 -1
- package/tgsl/infixDispatch.js +13 -1
- package/tgsl/makeDereferenceable.d.ts +1 -1
- package/tgsl/makeDereferenceable.js +3 -1
- package/tgsl/shellless.js +5 -9
- package/tgsl/stringifySnippet.d.ts +9 -0
- package/tgsl/stringifySnippet.js +24 -0
- package/tgsl/wgslGenerator.d.ts +1 -0
- package/tgsl/wgslGenerator.js +180 -86
- package/types.d.ts +23 -2
- package/types.js +30 -5
package/tgsl/wgslGenerator.js
CHANGED
|
@@ -4,7 +4,7 @@ import { arrayOf } from "../data/array.js";
|
|
|
4
4
|
import { UnknownData, unptr } from "../data/dataTypes.js";
|
|
5
5
|
import { bool, i32, u32 } from "../data/numeric.js";
|
|
6
6
|
import { vec2u, vec3u, vec4u } from "../data/vector.js";
|
|
7
|
-
import { fallthroughCopyOrigin,
|
|
7
|
+
import { fallthroughCopyOrigin, isMutableOrigin, isStoredInMemory, snip, } from "../data/snippet.js";
|
|
8
8
|
import * as wgsl from "../data/wgslTypes.js";
|
|
9
9
|
import { invariant, ResolutionError, WgslForbiddenStatementError, WgslTypeError, } from "../errors.js";
|
|
10
10
|
import { getName } from "../shared/meta.js";
|
|
@@ -13,7 +13,7 @@ import { safeStringify } from "../shared/stringify.js";
|
|
|
13
13
|
import { pow } from "../std/numeric.js";
|
|
14
14
|
import { add, div, mul, neg, sub } from "../std/operators.js";
|
|
15
15
|
import { eq, ne, lt, le, gt, ge, not } from "../std/boolean.js";
|
|
16
|
-
import { isConstant, isGPUCallable,
|
|
16
|
+
import { asComptime, isConstant, isGPUCallable, } from "../types.js";
|
|
17
17
|
import { convertStructValues, convertToCommonType, tryConvertSnippet } from "./conversion.js";
|
|
18
18
|
import { ArrayExpression, coerceToSnippet, concretize, numericLiteralToSnippet, } from "./generationHelpers.js";
|
|
19
19
|
import { accessIndex } from "./accessIndex.js";
|
|
@@ -29,6 +29,7 @@ import { mathToStd, supportedLogOps } from "./jsPolyfills.js";
|
|
|
29
29
|
import * as forOfUtils from "./forOfUtils.js";
|
|
30
30
|
import { isTgpuRange } from "../std/range.js";
|
|
31
31
|
import { stringifyNode, stringifyObjectProperty } from "../shared/tseynit.js";
|
|
32
|
+
import { setSourceNode, stringifySnippet } from "./stringifySnippet.js";
|
|
32
33
|
import { getAttributesString } from "../data/attributes.js";
|
|
33
34
|
import { validSelectBranchTypes } from "../std/boolean.js";
|
|
34
35
|
import { isInfixDispatch } from "./infixDispatch.js";
|
|
@@ -127,12 +128,13 @@ const unaryOpCodeToCodegen = {
|
|
|
127
128
|
if (argExpr === undefined) {
|
|
128
129
|
throw new Error('The unary operator `!` expects 1 argument, but 0 were provided.');
|
|
129
130
|
}
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
const knownArg = asComptime(argExpr);
|
|
132
|
+
if (knownArg) {
|
|
133
|
+
return snip(!knownArg.value, bool, 'constant', false);
|
|
132
134
|
}
|
|
133
135
|
const argStr = ctx.resolveSnippet(argExpr).value;
|
|
134
136
|
if (!wgsl.isBool(argExpr.dataType)) {
|
|
135
|
-
throw new WgslTypeError(`Unary operator ! requires boolean operand. Got ${String(argExpr.dataType)}.${wgsl.isVecBool(argExpr.dataType)
|
|
137
|
+
throw new WgslTypeError(`Unary operator ! requires boolean operand. Got '${stringifySnippet(argExpr)}' of type ${String(argExpr.dataType)}.${wgsl.isVecBool(argExpr.dataType)
|
|
136
138
|
? ` For component-wise negation, use 'std.${not.toString()}'.`
|
|
137
139
|
: ''}`);
|
|
138
140
|
}
|
|
@@ -279,25 +281,41 @@ export class WgslGenerator {
|
|
|
279
281
|
return res;
|
|
280
282
|
}
|
|
281
283
|
_emitSwitchStatement(discriminantExpr, groupedCaseExprs) {
|
|
282
|
-
if (groupedCaseExprs.flatMap(([tests]) => tests).every((test) => test.value !== 'default')) {
|
|
283
|
-
// default clause is required in WGSL
|
|
284
|
-
groupedCaseExprs.push([[switchDefault], []]);
|
|
285
|
-
}
|
|
286
|
-
this.ctx.indent();
|
|
287
284
|
const cases = groupedCaseExprs.map(([tests, consequent]) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.join(', ');
|
|
291
|
-
// Purely cosmetic break pruning (it is legal, but there's no need to generate it).
|
|
285
|
+
// It is legal to generate a break at the end of a consequent, but there is no need to.
|
|
286
|
+
// Also, removing this lets us simplify more pruned switch statements to blocks.
|
|
292
287
|
const last = consequent.at(-1);
|
|
293
288
|
if (last && /^\s*break\s*;\s*$/.test(last.code) && last.endsWithControlFlow === 'break') {
|
|
294
289
|
consequent.pop();
|
|
295
290
|
}
|
|
296
291
|
const resolvedConsequent = consequent.map((s) => s.code).join('\n');
|
|
297
|
-
return
|
|
292
|
+
return { tests, resolvedConsequent };
|
|
293
|
+
});
|
|
294
|
+
if (cases.flatMap(({ tests }) => tests).every((test) => test !== switchDefault)) {
|
|
295
|
+
// default clause is required in WGSL
|
|
296
|
+
cases.push({ tests: [switchDefault], resolvedConsequent: '' });
|
|
297
|
+
}
|
|
298
|
+
// If the switch was pruned, and it has no breaks, we can simplify it to a block.
|
|
299
|
+
const onlyCase = cases.length === 1 ? cases.at(0) : undefined;
|
|
300
|
+
if (asComptime(discriminantExpr) !== undefined &&
|
|
301
|
+
onlyCase &&
|
|
302
|
+
onlyCase.tests.length === 1 &&
|
|
303
|
+
onlyCase.tests.at(0) === switchDefault) {
|
|
304
|
+
const { resolvedConsequent } = onlyCase;
|
|
305
|
+
if (!/\bbreak\b/.test(resolvedConsequent)) {
|
|
306
|
+
return `${this.ctx.pre}{\n${this.ctx.getDedented(resolvedConsequent)}\n${this.ctx.pre}}`;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.ctx.indent();
|
|
310
|
+
const resolvedCases = cases.map(({ tests, resolvedConsequent }) => {
|
|
311
|
+
const resolvedTests = tests
|
|
312
|
+
.map((test) => (test.value === 'default' ? 'default' : this.ctx.resolveSnippet(test).value))
|
|
313
|
+
.join(', ');
|
|
314
|
+
return `${this.ctx.pre}case ${resolvedTests}: {\n${resolvedConsequent}\n${this.ctx.pre}}`;
|
|
298
315
|
});
|
|
299
316
|
this.ctx.dedent();
|
|
300
|
-
|
|
317
|
+
const body = resolvedCases.join('\n');
|
|
318
|
+
return stitch `${this.ctx.pre}switch ${discriminantExpr} {\n${body}\n${this.ctx.pre}}`;
|
|
301
319
|
}
|
|
302
320
|
_callShellless(callee, args) {
|
|
303
321
|
const isGeneric = isGenericFn(callee);
|
|
@@ -335,13 +353,17 @@ export class WgslGenerator {
|
|
|
335
353
|
// convert the result.
|
|
336
354
|
return result;
|
|
337
355
|
}
|
|
338
|
-
return tryConvertSnippet(this.ctx, result, expectedType);
|
|
356
|
+
return setSourceNode(tryConvertSnippet(this.ctx, result, expectedType), expression);
|
|
339
357
|
}
|
|
340
358
|
finally {
|
|
341
359
|
this.ctx.expectedType = prevExpectedType;
|
|
342
360
|
}
|
|
343
361
|
}
|
|
344
362
|
_expression(expression) {
|
|
363
|
+
// Remembering which JS expression produced the snippet, for more descriptive errors
|
|
364
|
+
return setSourceNode(this._expressionImpl(expression), expression);
|
|
365
|
+
}
|
|
366
|
+
_expressionImpl(expression) {
|
|
345
367
|
if (isId(expression)) {
|
|
346
368
|
return this._identifier(extractId(expression));
|
|
347
369
|
}
|
|
@@ -352,21 +374,23 @@ export class WgslGenerator {
|
|
|
352
374
|
const [_, lhs, op, rhs] = expression;
|
|
353
375
|
const lhsExpr = this._expression(lhs);
|
|
354
376
|
// Short Circuit Evaluation
|
|
355
|
-
|
|
377
|
+
const knownLhs = asComptime(lhsExpr);
|
|
378
|
+
if (knownLhs) {
|
|
356
379
|
const castToBool = wgsl.isBool(this.ctx.expectedType);
|
|
357
|
-
const evalRhs = op === '&&' ?
|
|
380
|
+
const evalRhs = op === '&&' ? knownLhs.value : !knownLhs.value;
|
|
358
381
|
if (!evalRhs) {
|
|
359
382
|
return castToBool
|
|
360
383
|
? snip(op === '||', bool, 'constant', false)
|
|
361
|
-
: coerceToSnippet(
|
|
384
|
+
: coerceToSnippet(knownLhs.value);
|
|
362
385
|
}
|
|
363
386
|
const rhsExpr = this._expression(rhs);
|
|
364
|
-
|
|
365
|
-
|
|
387
|
+
const knownRhs = asComptime(rhsExpr);
|
|
388
|
+
if (knownRhs) {
|
|
389
|
+
const rhsSnippet = coerceToSnippet(knownRhs.value);
|
|
366
390
|
return castToBool ? tryConvertSnippet(this.ctx, rhsSnippet, bool, false) : rhsSnippet;
|
|
367
391
|
}
|
|
368
392
|
if (rhsExpr.dataType === UnknownData) {
|
|
369
|
-
throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type
|
|
393
|
+
throw new WgslTypeError(`Right-hand side '${stringifySnippet(rhsExpr)}' of '${op}' is of unknown type.`);
|
|
370
394
|
}
|
|
371
395
|
// we can skip lhs
|
|
372
396
|
return castToBool ? tryConvertSnippet(this.ctx, rhsExpr, bool, false) : rhsExpr;
|
|
@@ -374,13 +398,13 @@ export class WgslGenerator {
|
|
|
374
398
|
const rhsExpr = this._expression(rhs);
|
|
375
399
|
// they are not known at comptime
|
|
376
400
|
if (lhsExpr.dataType === UnknownData) {
|
|
377
|
-
throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type
|
|
401
|
+
throw new WgslTypeError(`Left-hand side '${stringifySnippet(lhsExpr)}' of '${op}' is of unknown type.`);
|
|
378
402
|
}
|
|
379
|
-
if (!
|
|
380
|
-
throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type
|
|
403
|
+
if (!asComptime(rhsExpr) && rhsExpr.dataType === UnknownData) {
|
|
404
|
+
throw new WgslTypeError(`Right-hand side '${stringifySnippet(rhsExpr)}' of '${op}' is of unknown type.`);
|
|
381
405
|
}
|
|
382
406
|
if (!wgsl.isBool(lhsExpr.dataType) || !wgsl.isBool(rhsExpr.dataType)) {
|
|
383
|
-
throw new WgslTypeError(`Logical expression '${
|
|
407
|
+
throw new WgslTypeError(`Logical expression '${stringifyNode(expression)}' requires boolean operands. Got '${String(lhsExpr.dataType)}' and '${String(rhsExpr.dataType)}'.`);
|
|
384
408
|
}
|
|
385
409
|
const lhsStr = this.ctx.resolveSnippet(lhsExpr).value;
|
|
386
410
|
const rhsStr = this.ctx.resolveSnippet(rhsExpr).value;
|
|
@@ -393,7 +417,7 @@ export class WgslGenerator {
|
|
|
393
417
|
const lhsExpr = this._expression(lhs);
|
|
394
418
|
const rhsExpr = this._expression(rhs);
|
|
395
419
|
if (rhsExpr.value instanceof RefOperator) {
|
|
396
|
-
throw new WgslTypeError(
|
|
420
|
+
throw new WgslTypeError(`Cannot assign a ref to an existing variable '${stringifyNode(lhs)}', define a new variable instead.`);
|
|
397
421
|
}
|
|
398
422
|
if (op === '==') {
|
|
399
423
|
throw new Error('Please use the === operator instead of ==');
|
|
@@ -402,9 +426,11 @@ export class WgslGenerator {
|
|
|
402
426
|
throw new Error('Please use the !== operator instead of !=');
|
|
403
427
|
}
|
|
404
428
|
const stdBinaryRelationalOp = binaryRelationalOpToStdMap[op];
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
429
|
+
const knownLhs = stdBinaryRelationalOp && asComptime(lhsExpr);
|
|
430
|
+
const knownRhs = knownLhs && asComptime(rhsExpr);
|
|
431
|
+
if (stdBinaryRelationalOp && knownLhs && knownRhs) {
|
|
432
|
+
const left = knownLhs.value;
|
|
433
|
+
const right = knownRhs.value;
|
|
408
434
|
switch (op) {
|
|
409
435
|
case '===':
|
|
410
436
|
return snip(left === right, bool, 'constant', false);
|
|
@@ -413,7 +439,7 @@ export class WgslGenerator {
|
|
|
413
439
|
}
|
|
414
440
|
if (typeof left !== 'number' || typeof right !== 'number') {
|
|
415
441
|
const bothVectors = wgsl.isVec(lhsExpr.dataType) && wgsl.isVec(rhsExpr.dataType);
|
|
416
|
-
throw new WgslTypeError(`Comparison '${
|
|
442
|
+
throw new WgslTypeError(`Comparison '${stringifyNode(expression)}' requires numeric operands.${bothVectors
|
|
417
443
|
? ` For component-wise comparison, use 'std.${stdBinaryRelationalOp}'.`
|
|
418
444
|
: ''}`);
|
|
419
445
|
}
|
|
@@ -429,10 +455,10 @@ export class WgslGenerator {
|
|
|
429
455
|
}
|
|
430
456
|
}
|
|
431
457
|
if (lhsExpr.dataType === UnknownData) {
|
|
432
|
-
throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type
|
|
458
|
+
throw new WgslTypeError(`Left-hand side '${stringifySnippet(lhsExpr)}' of '${op}' is of unknown type.`);
|
|
433
459
|
}
|
|
434
460
|
if (rhsExpr.dataType === UnknownData) {
|
|
435
|
-
throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type
|
|
461
|
+
throw new WgslTypeError(`Right-hand side '${stringifySnippet(rhsExpr)}' of '${op}' is of unknown type.`);
|
|
436
462
|
}
|
|
437
463
|
const codegen = binaryOpCodeToCodegen[op];
|
|
438
464
|
if (codegen) {
|
|
@@ -476,7 +502,9 @@ export class WgslGenerator {
|
|
|
476
502
|
validateSnippetMutation(convLhs, expression);
|
|
477
503
|
this.tryMarkModified(lhs);
|
|
478
504
|
// Compound assignment operators are okay, e.g. +=, -=, *=, /=, ...
|
|
479
|
-
if (op === '=' &&
|
|
505
|
+
if (op === '=' &&
|
|
506
|
+
isStoredInMemory(rhsExpr) &&
|
|
507
|
+
!wgsl.isNaturallyEphemeral(rhsExpr.dataType)) {
|
|
480
508
|
throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(unptr(rhsExpr.dataType)).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
|
|
481
509
|
}
|
|
482
510
|
}
|
|
@@ -486,7 +514,7 @@ export class WgslGenerator {
|
|
|
486
514
|
(equalityCheck && wgsl.isBool(convLhs.dataType) && wgsl.isBool(convRhs.dataType));
|
|
487
515
|
if (!correctOperandTypes) {
|
|
488
516
|
const bothVectors = wgsl.isVec(convLhs.dataType) && wgsl.isVec(convRhs.dataType);
|
|
489
|
-
throw new WgslTypeError(`Comparison '${
|
|
517
|
+
throw new WgslTypeError(`Comparison '${stringifyNode(expression)}' requires numeric${equalityCheck ? ' or boolean' : ''} operands. Got '${String(convLhs.dataType)}' and '${String(convRhs.dataType)}'.${bothVectors
|
|
490
518
|
? ` For component-wise comparison, use 'std.${stdBinaryRelationalOp}'.`
|
|
491
519
|
: ''}`);
|
|
492
520
|
}
|
|
@@ -660,7 +688,7 @@ export class WgslGenerator {
|
|
|
660
688
|
if (maybeConsoleMethod) {
|
|
661
689
|
throw new Error(`Unsupported console functionality 'console.${maybeConsoleMethod}()'.`);
|
|
662
690
|
}
|
|
663
|
-
throw new Error(`Function '${getName(callee.value) ??
|
|
691
|
+
throw new Error(`Function '${getName(callee.value) ?? stringifySnippet(callee)}' is not marked with the 'use gpu' directive and cannot be used in a shader`);
|
|
664
692
|
}
|
|
665
693
|
if (expression[0] === NODE.objectExpr) {
|
|
666
694
|
// Normalize to `objectProperty[]`
|
|
@@ -692,7 +720,7 @@ export class WgslGenerator {
|
|
|
692
720
|
// Generating the expression and inferring the type instead
|
|
693
721
|
expr = this._expression(value);
|
|
694
722
|
if (expr.dataType === UnknownData) {
|
|
695
|
-
throw new WgslTypeError(
|
|
723
|
+
throw new WgslTypeError(`Value '${stringifySnippet(expr)}' of property '${key}' in object literal is of unknown type.`);
|
|
696
724
|
}
|
|
697
725
|
// Taking care of abstract numerics and implicit pointers
|
|
698
726
|
accessed = structType.provideProp(key, unptr(concretize(expr.dataType)));
|
|
@@ -707,23 +735,43 @@ export class WgslGenerator {
|
|
|
707
735
|
}
|
|
708
736
|
if (wgsl.isWgslStruct(structType)) {
|
|
709
737
|
const entries = {};
|
|
738
|
+
const keysWithSideEffectsInSourceOrder = [];
|
|
710
739
|
for (const prop of properties) {
|
|
711
740
|
const key = resolveUniqueKey(prop);
|
|
712
741
|
const value = prop[1];
|
|
713
742
|
const propType = structType.propTypes[key];
|
|
714
743
|
if (propType === undefined) {
|
|
715
744
|
// Evaluate every field even if it gets stripped by the struct schema
|
|
716
|
-
|
|
745
|
+
const expr = this._expression(value);
|
|
746
|
+
if (expr.possibleSideEffects) {
|
|
747
|
+
logger.warn('suspicious', `\
|
|
748
|
+
Object property '${stringifyObjectProperty(prop)}' in '${stringifyNode(expression)}' does not exist on type '${String(structType)}'.
|
|
749
|
+
The generated shader will omit it, so its runtime side effects will not occur.`);
|
|
750
|
+
}
|
|
717
751
|
continue;
|
|
718
752
|
}
|
|
719
753
|
const expr = this._typedExpression(value, propType);
|
|
720
754
|
entries[key] = expr;
|
|
755
|
+
if (expr.possibleSideEffects) {
|
|
756
|
+
keysWithSideEffectsInSourceOrder.push(key);
|
|
757
|
+
}
|
|
721
758
|
}
|
|
722
759
|
for (const key of Object.keys(structType.propTypes)) {
|
|
723
760
|
if (entries[key] === undefined) {
|
|
724
761
|
throw new WgslTypeError(`Missing property ${key} in object literal for struct ${structType}`);
|
|
725
762
|
}
|
|
726
763
|
}
|
|
764
|
+
const keysWithSideEffectsInSchemaOrder = Object.keys(structType.propTypes).filter((key) => entries[key].possibleSideEffects);
|
|
765
|
+
const changesSideEffectsOrder = keysWithSideEffectsInSourceOrder.some((key, index) => key !== keysWithSideEffectsInSchemaOrder[index]);
|
|
766
|
+
if (changesSideEffectsOrder) {
|
|
767
|
+
logger.warn('suspicious', `\
|
|
768
|
+
Properties with possible side effects in '${stringifyNode(expression)}' do not match '${String(structType)}' declaration order:
|
|
769
|
+
|
|
770
|
+
Source order: [${keysWithSideEffectsInSourceOrder.join(', ')}]
|
|
771
|
+
Declaration order: [${keysWithSideEffectsInSchemaOrder.join(', ')}]
|
|
772
|
+
|
|
773
|
+
The generated shader will evaluate them in declaration order.`);
|
|
774
|
+
}
|
|
727
775
|
const convertedSnippets = convertStructValues(this.ctx, structType, entries);
|
|
728
776
|
return snip(stitch `${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType,
|
|
729
777
|
/* origin */ 'runtime', convertedSnippets.some((s) => s.possibleSideEffects));
|
|
@@ -767,8 +815,11 @@ export class WgslGenerator {
|
|
|
767
815
|
// ternary operator
|
|
768
816
|
const [_, testNode, consequentNode, alternativeNode] = expression;
|
|
769
817
|
const test = this._expression(testNode);
|
|
770
|
-
|
|
771
|
-
|
|
818
|
+
const knownTest = asComptime(test);
|
|
819
|
+
if (knownTest) {
|
|
820
|
+
return knownTest.value
|
|
821
|
+
? this._expression(consequentNode)
|
|
822
|
+
: this._expression(alternativeNode);
|
|
772
823
|
}
|
|
773
824
|
else {
|
|
774
825
|
const convertedTest = tryConvertSnippet(this.ctx, test, bool, false);
|
|
@@ -779,8 +830,8 @@ export class WgslGenerator {
|
|
|
779
830
|
!alt ||
|
|
780
831
|
consequent.possibleSideEffects ||
|
|
781
832
|
alternative.possibleSideEffects ||
|
|
782
|
-
(
|
|
783
|
-
(
|
|
833
|
+
(isStoredInMemory(consequent) && !wgsl.isNaturallyEphemeral(consequent.dataType)) ||
|
|
834
|
+
(isStoredInMemory(alternative) && !wgsl.isNaturallyEphemeral(alternative.dataType))) {
|
|
784
835
|
throw new Error(`Ternary operator '${stringifyNode(expression)}' is invalid. For more complex branching, please use 'std.select' or if/else statements.`);
|
|
785
836
|
}
|
|
786
837
|
return snip(stitch `select(${alt}, ${con}, ${convertedTest})`, con.dataType, 'runtime',
|
|
@@ -958,7 +1009,7 @@ export class WgslGenerator {
|
|
|
958
1009
|
// The existence of `expectedReturnType` implies a function shell, which in turn implies that the
|
|
959
1010
|
// value will be copied on return anyway
|
|
960
1011
|
!expectedReturnType &&
|
|
961
|
-
|
|
1012
|
+
isStoredInMemory(returnSnippet) &&
|
|
962
1013
|
!wgsl.isNaturallyEphemeral(returnSnippet.dataType) &&
|
|
963
1014
|
returnSnippet.origin !== 'local-def') {
|
|
964
1015
|
const str = stringifyNode(returnNode);
|
|
@@ -996,7 +1047,7 @@ Try 'return ${typeStr}(${str});' instead.
|
|
|
996
1047
|
const declaration = `let ${rawId}`;
|
|
997
1048
|
throw new WgslTypeError(`'${declaration} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'${schemaWrappingSuggestion(declaration, rhsStr, eq.value)}`);
|
|
998
1049
|
}
|
|
999
|
-
if (
|
|
1050
|
+
if (isStoredInMemory(eq) && !wgsl.isNaturallyEphemeral(eq.dataType)) {
|
|
1000
1051
|
// `let` declarations cannot store references
|
|
1001
1052
|
const rhsStr = stringifyNode(eqNode);
|
|
1002
1053
|
const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
|
|
@@ -1033,9 +1084,11 @@ Try 'return ${typeStr}(${str});' instead.
|
|
|
1033
1084
|
const eq = this._expression(eqNode);
|
|
1034
1085
|
if (eq.value instanceof RefOperator) {
|
|
1035
1086
|
// We're assigning a newly created `d.ref()`
|
|
1036
|
-
if (eq.
|
|
1037
|
-
|
|
1087
|
+
if (eq.value.addressable) {
|
|
1088
|
+
const refStr = stringifyNode(eqNode);
|
|
1089
|
+
throw new WgslTypeError(`'const ${rawId} = ${refStr}' is invalid, cannot store ${refStr} in a variable if it references another value. Copy the value passed into d.ref() instead, or inline ${refStr}.`);
|
|
1038
1090
|
}
|
|
1091
|
+
// Unwrapping the ref, and storing the value it was created from in a new variable
|
|
1039
1092
|
const refSnippet = eq.value.snippet;
|
|
1040
1093
|
const varName = this.refVariable(rawId, concretize(refSnippet.dataType));
|
|
1041
1094
|
return {
|
|
@@ -1077,7 +1130,7 @@ Try 'return ${typeStr}(${str});' instead.
|
|
|
1077
1130
|
// function code has been processed, so at least currently, we lose that info.
|
|
1078
1131
|
varOrigin = 'local-def';
|
|
1079
1132
|
}
|
|
1080
|
-
else if (!
|
|
1133
|
+
else if (!isStoredInMemory(eq)) {
|
|
1081
1134
|
// Not a reference, but also not naturally ephemeral, so we cannot guarantee it won't be mutated.
|
|
1082
1135
|
// We defer the decision for now.
|
|
1083
1136
|
varType = '<deferred>';
|
|
@@ -1265,7 +1318,8 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1265
1318
|
const originalLoopVarName = extractId(loopVar[1]);
|
|
1266
1319
|
const blockified = blockifySingleStatement(body);
|
|
1267
1320
|
if (shouldUnroll) {
|
|
1268
|
-
|
|
1321
|
+
const knownEnd = asComptime(range.end);
|
|
1322
|
+
if (!knownEnd) {
|
|
1269
1323
|
throw new Error('Cannot unroll loop. Length of iterable is unknown at comptime.');
|
|
1270
1324
|
}
|
|
1271
1325
|
const { value } = iterableSnippet;
|
|
@@ -1273,12 +1327,13 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1273
1327
|
? value.map((i) => coerceToSnippet(i))
|
|
1274
1328
|
: value instanceof ArrayExpression
|
|
1275
1329
|
? value.elements
|
|
1276
|
-
: Array.from({ length:
|
|
1330
|
+
: Array.from({ length: knownEnd.value }, (_, i) => forOfUtils.getElementSnippet(iterableSnippet, snip(i, u32, 'constant')));
|
|
1277
1331
|
if (elements.length === 0) {
|
|
1278
1332
|
return { code: '', definesInNearestScope: false };
|
|
1279
1333
|
}
|
|
1280
1334
|
const firstElement = elements[0];
|
|
1281
|
-
if (!
|
|
1335
|
+
if (!isStoredInMemory(firstElement) &&
|
|
1336
|
+
!wgsl.isNaturallyEphemeral(firstElement.dataType)) {
|
|
1282
1337
|
throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
|
|
1283
1338
|
}
|
|
1284
1339
|
let blocksCode = '';
|
|
@@ -1343,11 +1398,50 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1343
1398
|
if (statement[0] === NODE.switch) {
|
|
1344
1399
|
// Switch statement
|
|
1345
1400
|
const [_, discriminant, cases] = statement;
|
|
1346
|
-
|
|
1401
|
+
let discriminantExpr = this._expression(discriminant);
|
|
1402
|
+
let caseExprs = cases.map(([test, consequent]) => {
|
|
1403
|
+
const testExpr = test === null ? switchDefault : this._expression(test);
|
|
1404
|
+
return [testExpr, consequent];
|
|
1405
|
+
});
|
|
1406
|
+
// comptime folding
|
|
1407
|
+
let matchedCaseWasNotLast = false;
|
|
1408
|
+
const knownDiscriminant = asComptime(discriminantExpr);
|
|
1409
|
+
const knownTests = knownDiscriminant && caseExprs.map(([test]) => asComptime(test));
|
|
1410
|
+
if (knownDiscriminant && knownTests?.every((test) => test !== undefined)) {
|
|
1411
|
+
let matchedCaseIndex = knownTests.findIndex((test) => test !== switchDefault && test?.value === knownDiscriminant.value);
|
|
1412
|
+
if (matchedCaseIndex === -1) {
|
|
1413
|
+
matchedCaseIndex = caseExprs.findIndex(([test]) => test === switchDefault);
|
|
1414
|
+
}
|
|
1415
|
+
if (matchedCaseIndex === -1) {
|
|
1416
|
+
return { code: '', definesInNearestScope: false };
|
|
1417
|
+
}
|
|
1418
|
+
const matchedConsequent = caseExprs
|
|
1419
|
+
.slice(matchedCaseIndex)
|
|
1420
|
+
.find(([, consequent]) => consequent.length !== 0)?.[1];
|
|
1421
|
+
if (matchedConsequent === undefined) {
|
|
1422
|
+
return { code: '', definesInNearestScope: false };
|
|
1423
|
+
}
|
|
1424
|
+
if (matchedConsequent !== caseExprs.at(-1)?.[1]) {
|
|
1425
|
+
// The JS behavior will be different if this case does not end with control flow.
|
|
1426
|
+
matchedCaseWasNotLast = true;
|
|
1427
|
+
}
|
|
1428
|
+
discriminantExpr = snip(0, u32, 'constant', false);
|
|
1429
|
+
caseExprs = [[switchDefault, matchedConsequent]];
|
|
1430
|
+
}
|
|
1431
|
+
// type concretization
|
|
1432
|
+
discriminantExpr = tryConvertSnippet(this.ctx, discriminantExpr, [i32, u32]);
|
|
1347
1433
|
const switchType = discriminantExpr.dataType;
|
|
1348
1434
|
invariant(switchType !== UnknownData);
|
|
1349
|
-
|
|
1350
|
-
|
|
1435
|
+
caseExprs = caseExprs.map(([test, consequent]) => {
|
|
1436
|
+
// TODO(#3148): Remove this throw.
|
|
1437
|
+
if (test !== switchDefault && test.dataType === UnknownData) {
|
|
1438
|
+
throw new Error(`Failed to convert one of the switch tests to '${switchType.type}'`);
|
|
1439
|
+
}
|
|
1440
|
+
const convertedTest = test === switchDefault ? test : tryConvertSnippet(this.ctx, test, [switchType]);
|
|
1441
|
+
return [convertedTest, consequent];
|
|
1442
|
+
});
|
|
1443
|
+
// consequent resolution
|
|
1444
|
+
const resolvedCaseExprs = caseExprs.map(([test, consequent]) => {
|
|
1351
1445
|
// In WGSL, each case is a different block. This block scope forbids scope leaking.
|
|
1352
1446
|
// TODO(#3001): Consider using NODE.block here
|
|
1353
1447
|
this.ctx.pushBlockScope();
|
|
@@ -1355,7 +1449,7 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1355
1449
|
this.ctx.indent();
|
|
1356
1450
|
try {
|
|
1357
1451
|
const consequentStmts = consequent.map((s) => this._statement(s));
|
|
1358
|
-
return [
|
|
1452
|
+
return [test, consequentStmts];
|
|
1359
1453
|
}
|
|
1360
1454
|
finally {
|
|
1361
1455
|
this.ctx.dedent();
|
|
@@ -1363,6 +1457,15 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1363
1457
|
this.ctx.popBlockScope();
|
|
1364
1458
|
}
|
|
1365
1459
|
});
|
|
1460
|
+
const groupedCaseExprs = [];
|
|
1461
|
+
let currentGroup = [];
|
|
1462
|
+
for (const [index, [test, consequent]] of resolvedCaseExprs.entries()) {
|
|
1463
|
+
currentGroup.push(test);
|
|
1464
|
+
if (consequent.length > 0 || index === caseExprs.length - 1) {
|
|
1465
|
+
groupedCaseExprs.push([currentGroup, consequent]);
|
|
1466
|
+
currentGroup = [];
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1366
1469
|
// Validation
|
|
1367
1470
|
{
|
|
1368
1471
|
// Tests should be constant
|
|
@@ -1380,7 +1483,9 @@ ${stringifyNode(statement)}`);
|
|
|
1380
1483
|
// We skip this check, because WGSL errors are readable,
|
|
1381
1484
|
// and we cannot easily access non-comptime known constants.
|
|
1382
1485
|
// Tests should not have non-trivial fallthrough
|
|
1383
|
-
|
|
1486
|
+
resolvedCaseExprs
|
|
1487
|
+
.slice(0, matchedCaseWasNotLast ? undefined : -1)
|
|
1488
|
+
.forEach(([_, consequent]) => {
|
|
1384
1489
|
const last = consequent.at(-1);
|
|
1385
1490
|
if (last && !last.endsWithControlFlow) {
|
|
1386
1491
|
throw new Error(`Switch statement cannot have non-trivial fallthrough.
|
|
@@ -1389,15 +1494,6 @@ ${stringifyNode(statement)}`);
|
|
|
1389
1494
|
}
|
|
1390
1495
|
});
|
|
1391
1496
|
}
|
|
1392
|
-
const groupedCaseExprs = [];
|
|
1393
|
-
let currentGroup = [];
|
|
1394
|
-
for (const [index, [test, consequent]] of caseExprs.entries()) {
|
|
1395
|
-
currentGroup.push(test);
|
|
1396
|
-
if (consequent.length > 0 || index === caseExprs.length - 1) {
|
|
1397
|
-
groupedCaseExprs.push([currentGroup, consequent]);
|
|
1398
|
-
currentGroup = [];
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
1497
|
return {
|
|
1402
1498
|
code: this._emitSwitchStatement(discriminantExpr, groupedCaseExprs),
|
|
1403
1499
|
definesInNearestScope: false,
|
|
@@ -1455,8 +1551,8 @@ ${stringifyNode(statement)}`);
|
|
|
1455
1551
|
if (!computed) {
|
|
1456
1552
|
return property[0];
|
|
1457
1553
|
}
|
|
1458
|
-
const key = this._expression(property[0]);
|
|
1459
|
-
if (!
|
|
1554
|
+
const key = asComptime(this._expression(property[0]));
|
|
1555
|
+
if (!key) {
|
|
1460
1556
|
throw new WgslTypeError(`Computed object property key '${stringifyObjectProperty(property)}' must be known at comptime.`);
|
|
1461
1557
|
}
|
|
1462
1558
|
if (typeof key.value !== 'string') {
|
|
@@ -1490,27 +1586,25 @@ ${stringifyNode(statement)}`);
|
|
|
1490
1586
|
}
|
|
1491
1587
|
}
|
|
1492
1588
|
}
|
|
1589
|
+
const immutabilityReasons = {
|
|
1590
|
+
constant: 'the left side is a constant',
|
|
1591
|
+
'constant-immutable-def': 'the left side is a constant',
|
|
1592
|
+
'runtime-immutable-def': 'the left side is immutable',
|
|
1593
|
+
uniform: 'uniform buffers cannot be mutated',
|
|
1594
|
+
readonly: 'readonly buffers cannot be mutated',
|
|
1595
|
+
immediate: 'immediate variables cannot be mutated',
|
|
1596
|
+
argument: 'non-pointer arguments cannot be mutated',
|
|
1597
|
+
handle: 'textures and samplers cannot be mutated',
|
|
1598
|
+
};
|
|
1493
1599
|
function validateSnippetMutation(mutated, expr) {
|
|
1494
|
-
if (mutated.origin
|
|
1495
|
-
|
|
1496
|
-
mutated.origin === 'runtime-immutable-def') {
|
|
1497
|
-
if (isKnownAtComptime(mutated)) {
|
|
1498
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is defined outside of the shader, and therefore is immutable during its execution. Try using tgpu.privateVar or buffers.`);
|
|
1499
|
-
}
|
|
1500
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is a constant.`);
|
|
1501
|
-
}
|
|
1502
|
-
if (mutated.origin === 'uniform') {
|
|
1503
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because uniform buffers cannot be mutated.`);
|
|
1504
|
-
}
|
|
1505
|
-
if (mutated.origin === 'readonly') {
|
|
1506
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because readonly buffers cannot be mutated.`);
|
|
1507
|
-
}
|
|
1508
|
-
if (mutated.origin === 'immediate') {
|
|
1509
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because immediate variables cannot be mutated.`);
|
|
1510
|
-
}
|
|
1511
|
-
if (mutated.origin === 'argument') {
|
|
1512
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because non-pointer arguments cannot be mutated.`);
|
|
1600
|
+
if (isMutableOrigin(mutated.origin)) {
|
|
1601
|
+
return;
|
|
1513
1602
|
}
|
|
1603
|
+
const reason = asComptime(mutated)
|
|
1604
|
+
? 'the left side is immutable during shader execution. Try using tgpu.privateVar or buffers'
|
|
1605
|
+
: (immutabilityReasons[mutated.origin] ??
|
|
1606
|
+
'the left side is not a reference to an existing value');
|
|
1607
|
+
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because ${reason}.`);
|
|
1514
1608
|
}
|
|
1515
1609
|
function assertExhaustive(value) {
|
|
1516
1610
|
throw new Error(`'${safeStringify(value)}' was not handled by the WGSL generator.`);
|
package/types.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { TgpuVar } from './core/variable/tgpuVariable.ts';
|
|
|
18
18
|
import { type AnyData, UnknownData } from './data/dataTypes.ts';
|
|
19
19
|
import type { MapValueToSnippet, ResolvedSnippet, Snippet } from './data/snippet.ts';
|
|
20
20
|
import { type AnyMatInstance, type AnyVecInstance, type BaseData } from './data/wgslTypes.ts';
|
|
21
|
-
import { $cast, $gpuCallable, $gpuValueOf, $internal, $ownSnippet, $resolve } from './shared/symbols.ts';
|
|
21
|
+
import { $cast, $comptimeValueOf, $gpuCallable, $gpuValueOf, $internal, $ownSnippet, $resolve } from './shared/symbols.ts';
|
|
22
22
|
import type { TgpuBindGroupLayout, TgpuLayoutEntry } from './tgpuBindGroupLayout.ts';
|
|
23
23
|
import type { WgslEnableExtension } from './wgslExtensions.ts';
|
|
24
24
|
import type { Infer } from './shared/repr.ts';
|
|
@@ -342,6 +342,23 @@ export interface WithOwnSnippet {
|
|
|
342
342
|
readonly [$ownSnippet]: Snippet;
|
|
343
343
|
}
|
|
344
344
|
export declare function getOwnSnippet(value: unknown): Snippet | undefined;
|
|
345
|
+
export type ComptimeValue = {
|
|
346
|
+
comptime: false;
|
|
347
|
+
} | {
|
|
348
|
+
comptime: true;
|
|
349
|
+
value: unknown;
|
|
350
|
+
};
|
|
351
|
+
export interface WithComptimeValue {
|
|
352
|
+
/**
|
|
353
|
+
* @returns The value that this object represents at comptime,
|
|
354
|
+
* or `{ comptime: false }` if it is only known at runtime.
|
|
355
|
+
*/
|
|
356
|
+
[$comptimeValueOf](): ComptimeValue;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* A `[$comptimeValueOf]` implementation for resources that are only known at runtime
|
|
360
|
+
*/
|
|
361
|
+
export declare function runtimeOnly(): ComptimeValue;
|
|
345
362
|
export interface GPUCallable<TArgs extends unknown[] = unknown[]> {
|
|
346
363
|
[$gpuCallable]: {
|
|
347
364
|
strictSignature?: {
|
|
@@ -358,7 +375,11 @@ export type WithCast<T = BaseData> = GPUCallable<[v?: Infer<T>]> & {
|
|
|
358
375
|
export declare function hasCast(value: unknown): value is WithCast;
|
|
359
376
|
type AnyFn = (...args: never[]) => unknown;
|
|
360
377
|
export type DualFn<T extends AnyFn> = T & GPUCallable<Parameters<T>>;
|
|
361
|
-
|
|
378
|
+
/**
|
|
379
|
+
* @returns A snippet holding the JS value that `snippet` represents at comptime,
|
|
380
|
+
* or `undefined` if it is only known at runtime.
|
|
381
|
+
*/
|
|
382
|
+
export declare function asComptime(snippet: Snippet): Snippet | undefined;
|
|
362
383
|
export declare function isConstant(snippet: Snippet): boolean;
|
|
363
384
|
export declare function isWgsl(value: unknown): value is Wgsl;
|
|
364
385
|
export type BindableBufferUsage = 'uniform' | 'readonly' | 'mutable';
|
package/types.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isLazy, isProviding, isSlot, } from "./core/slot/slotTypes.js";
|
|
2
2
|
import { UnknownData } from "./data/dataTypes.js";
|
|
3
|
+
import { withValue } from "./data/snippet.js";
|
|
3
4
|
import { isWgslData, } from "./data/wgslTypes.js";
|
|
4
|
-
import { $cast, $gpuCallable, $gpuValueOf, $internal, $ownSnippet, $resolve, } from "./shared/symbols.js";
|
|
5
|
+
import { $cast, $comptimeValueOf, $gpuCallable, $gpuValueOf, $internal, $ownSnippet, $resolve, } from "./shared/symbols.js";
|
|
5
6
|
export class NormalState {
|
|
6
7
|
type = 'normal';
|
|
7
8
|
}
|
|
@@ -23,18 +24,42 @@ export function isSelfResolvable(value) {
|
|
|
23
24
|
export function getOwnSnippet(value) {
|
|
24
25
|
return value?.[$ownSnippet];
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* A `[$comptimeValueOf]` implementation for resources that are only known at runtime
|
|
29
|
+
*/
|
|
30
|
+
export function runtimeOnly() {
|
|
31
|
+
return { comptime: false };
|
|
32
|
+
}
|
|
26
33
|
export function isGPUCallable(value) {
|
|
27
34
|
return !!value?.[$gpuCallable];
|
|
28
35
|
}
|
|
29
36
|
export function hasCast(value) {
|
|
30
37
|
return !!value?.[$cast];
|
|
31
38
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
/**
|
|
40
|
+
* @returns A snippet holding the JS value that `snippet` represents at comptime,
|
|
41
|
+
* or `undefined` if it is only known at runtime.
|
|
42
|
+
*/
|
|
43
|
+
export function asComptime(snippet) {
|
|
44
|
+
const { value } = snippet;
|
|
45
|
+
if (typeof value === 'string' && snippet.dataType !== UnknownData) {
|
|
46
|
+
// Resolved WGSL code
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const getter = value?.[$comptimeValueOf];
|
|
50
|
+
if (!getter) {
|
|
51
|
+
// Objects that meet the criteria and didn't explicitly opt out of
|
|
52
|
+
// being known at comptime, are treated as known at comptime.
|
|
53
|
+
// That's mainly because there are far more objects that are eligible
|
|
54
|
+
// and we want to be treated as known at comptime than those that are
|
|
55
|
+
// special cases and need to opt out.
|
|
56
|
+
return snippet;
|
|
57
|
+
}
|
|
58
|
+
const result = getter.call(value);
|
|
59
|
+
return result.comptime ? withValue(result.value, snippet) : undefined;
|
|
35
60
|
}
|
|
36
61
|
export function isConstant(snippet) {
|
|
37
|
-
return (
|
|
62
|
+
return (asComptime(snippet) !== undefined ||
|
|
38
63
|
snippet.origin === 'constant' ||
|
|
39
64
|
snippet.origin === 'constant-immutable-def');
|
|
40
65
|
}
|