typegpu 0.11.2 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/core/buffer/buffer.js +17 -53
  3. package/core/function/autoIO.js +2 -2
  4. package/core/function/entryInputRouter.js +10 -16
  5. package/core/function/fnCore.js +16 -12
  6. package/core/function/shelllessImpl.js +1 -1
  7. package/core/function/tgpuComputeFn.js +1 -1
  8. package/core/function/tgpuFn.js +1 -1
  9. package/core/function/tgpuFragmentFn.d.ts +1 -1
  10. package/core/function/tgpuFragmentFn.js +1 -1
  11. package/core/function/tgpuVertexFn.js +1 -1
  12. package/core/pipeline/computePipeline.d.ts +1 -1
  13. package/core/root/init.d.ts +1 -0
  14. package/core/root/init.js +6 -0
  15. package/core/root/rootTypes.d.ts +21 -15
  16. package/core/slot/accessor.js +1 -1
  17. package/data/dataIO.d.ts +11 -0
  18. package/data/dataIO.js +45 -1
  19. package/data/dataTypes.js +2 -11
  20. package/data/index.d.ts +3 -3
  21. package/data/numeric.js +16 -14
  22. package/data/partialIO.d.ts +8 -0
  23. package/data/partialIO.js +6 -1
  24. package/data/snippet.d.ts +1 -6
  25. package/data/wgslTypes.d.ts +2 -0
  26. package/index.d.ts +3 -1
  27. package/index.js +3 -1
  28. package/indexNamedExports.d.ts +2 -0
  29. package/package.js +1 -1
  30. package/package.json +1 -1
  31. package/resolutionCtx.js +82 -86
  32. package/shared/stringify.js +1 -0
  33. package/shared/tseynit.js +90 -0
  34. package/tgsl/accessIndex.js +4 -3
  35. package/tgsl/accessProp.js +7 -2
  36. package/tgsl/consoleLog/deserializers.js +1 -1
  37. package/tgsl/consoleLog/logGenerator.js +16 -14
  38. package/tgsl/consoleLog/types.d.ts +4 -5
  39. package/tgsl/conversion.js +15 -5
  40. package/tgsl/forOfUtils.js +14 -6
  41. package/tgsl/generationHelpers.d.ts +2 -1
  42. package/tgsl/generationHelpers.js +1 -4
  43. package/tgsl/jsPolyfills.d.ts +25 -0
  44. package/tgsl/jsPolyfills.js +44 -0
  45. package/tgsl/shaderGenerator.d.ts +2 -3
  46. package/tgsl/shaderGenerator_members.d.ts +15 -2
  47. package/tgsl/wgslGenerator.d.ts +3 -1
  48. package/tgsl/wgslGenerator.js +64 -59
  49. package/types.d.ts +11 -6
  50. package/tgsl/consoleLog/types.js +0 -12
  51. package/tgsl/math.js +0 -45
@@ -1,16 +1,17 @@
1
1
  import { $internal } from "../../shared/symbols.js";
2
2
  import { Void } from "../../data/wgslTypes.js";
3
- import { UnknownData } from "../../data/dataTypes.js";
3
+ import { UnknownData, unptr } from "../../data/dataTypes.js";
4
4
  import { snip } from "../../data/snippet.js";
5
+ import { invariant } from "../../errors.js";
5
6
  import { stitch } from "../../core/resolve/stitch.js";
7
+ import { convertToCommonType } from "../conversion.js";
6
8
  import { u32 } from "../../data/numeric.js";
7
- import { concretizeSnippets } from "../generationHelpers.js";
9
+ import { concretizeSnippet } from "../generationHelpers.js";
8
10
  import { struct } from "../../data/struct.js";
9
11
  import { shaderStageSlot } from "../../core/slot/internalSlots.js";
10
12
  import { arrayOf } from "../../data/array.js";
11
13
  import { atomic } from "../../data/atomic.js";
12
14
  import { createLoggingFunction } from "./serializers.js";
13
- import { supportedLogOps } from "./types.js";
14
15
 
15
16
  //#region src/tgsl/consoleLog/logGenerator.ts
16
17
  const defaultOptions = {
@@ -54,23 +55,24 @@ var LogGeneratorImpl = class {
54
55
  */
55
56
  generateLog(ctx, op, args) {
56
57
  if (shaderStageSlot.$ === "vertex") {
57
- console.warn(`'console.${op}' is not supported in vertex shaders.`);
58
+ console.warn(`'console' operations are not supported in vertex shaders.`);
58
59
  return fallbackSnippet;
59
60
  }
60
- if (!supportedLogOps.includes(op)) {
61
- console.warn(`Unsupported log method '${op}'.`);
62
- return fallbackSnippet;
63
- }
64
- const concreteArgs = concretizeSnippets(args);
65
61
  const id = this.#firstUnusedId++;
66
- const nonStringArgs = concreteArgs.filter((e) => e.dataType !== UnknownData);
67
- const logFn = createLoggingFunction(id, nonStringArgs.map((e) => e.dataType), this.#dataBuffer, this.#indexBuffer, this.#options);
68
- const argTypes = concreteArgs.map((e) => e.dataType === UnknownData ? e.value : e.dataType);
62
+ const concreteArgsWithStrings = args.map((arg) => {
63
+ if (arg.dataType === UnknownData) return arg;
64
+ const converted = convertToCommonType(ctx, [arg], [unptr(arg.dataType)])?.[0];
65
+ invariant(converted, `Internal error. Expected type ${arg.dataType} to be convertible to ${unptr(arg.dataType)}`);
66
+ return converted;
67
+ }).map(concretizeSnippet);
68
+ const concreteArgs = concreteArgsWithStrings.filter((arg) => arg.dataType !== UnknownData);
69
+ const logFn = createLoggingFunction(id, concreteArgs.map((e) => e.dataType), this.#dataBuffer, this.#indexBuffer, this.#options);
70
+ const functionSnippet = snip(stitch`${ctx.resolve(logFn).value}(${concreteArgs})`, Void, "runtime");
69
71
  this.#logIdToMeta.set(id, {
70
72
  op,
71
- argTypes
73
+ argTypes: concreteArgsWithStrings.map((e) => e?.dataType === UnknownData ? e?.value : e?.dataType)
72
74
  });
73
- return snip(stitch`${ctx.resolve(logFn).value}(${nonStringArgs})`, Void, "runtime");
75
+ return functionSnippet;
74
76
  }
75
77
  get logResources() {
76
78
  return this.#firstUnusedId === 1 ? void 0 : {
@@ -1,10 +1,11 @@
1
1
  import "../../data/snippet.js";
2
2
  import { TgpuMutable } from "../../core/buffer/bufferShorthand.js";
3
3
  import "../generationHelpers.js";
4
+ import { supportedLogOps } from "../jsPolyfills.js";
4
5
  import { AnyWgslData, Atomic, U32, WgslArray, WgslStruct } from "../../data/wgslTypes.js";
5
6
 
6
7
  //#region src/tgsl/consoleLog/types.d.ts
7
-
8
+ type SupportedLogOp = ReturnType<typeof supportedLogOps>[number];
8
9
  /**
9
10
  * Options for configuring GPU log generation.
10
11
  */
@@ -32,7 +33,7 @@ type SerializedLogCallData = WgslStruct<{
32
33
  serializedData: WgslArray<U32>;
33
34
  }>;
34
35
  interface LogMeta {
35
- op: SupportedLogOps;
36
+ op: SupportedLogOp;
36
37
  argTypes: (string | AnyWgslData)[];
37
38
  }
38
39
  /**
@@ -49,7 +50,5 @@ interface LogResources {
49
50
  options: Required<LogGeneratorOptions>;
50
51
  logIdToMeta: Map<number, LogMeta>;
51
52
  }
52
- declare const supportedLogOps: readonly ["log", "debug", "info", "warn", "error", "clear"];
53
- type SupportedLogOps = (typeof supportedLogOps)[number];
54
53
  //#endregion
55
- export { LogGeneratorOptions, LogResources };
54
+ export { LogGeneratorOptions, LogResources, SupportedLogOp };
@@ -90,14 +90,19 @@ function getImplicitConversionRank(src, dest) {
90
90
  };
91
91
  }
92
92
  }
93
+ if ((trueSrc.type === "u32" || trueSrc.type === "i32") && trueDst.type === "abstractFloat") return {
94
+ rank: 1,
95
+ action: "cast",
96
+ targetType: trueDst.concretized
97
+ };
93
98
  if (trueSrc.type === "abstractFloat") {
94
- if (trueDst.type === "u32") return {
99
+ if (trueDst.type === "i32") return {
95
100
  rank: 2,
96
101
  action: "cast",
97
102
  targetType: trueDst
98
103
  };
99
- if (trueDst.type === "i32") return {
100
- rank: 1,
104
+ if (trueDst.type === "u32") return {
105
+ rank: 3,
101
106
  action: "cast",
102
107
  targetType: trueDst
103
108
  };
@@ -113,6 +118,10 @@ function getConversionRank(src, dest, allowImplicit) {
113
118
  function findBestType(types, uniqueTypes, allowImplicit) {
114
119
  let bestResult;
115
120
  for (const targetType of uniqueTypes) {
121
+ /**
122
+ * The type we end up converting to. Will be different than `targetType` if `targetType === abstractFloat`
123
+ */
124
+ let destType = targetType;
116
125
  const details = [];
117
126
  let sum = 0;
118
127
  for (const sourceType of types) {
@@ -120,9 +129,10 @@ function findBestType(types, uniqueTypes, allowImplicit) {
120
129
  sum += conversion.rank;
121
130
  if (conversion.rank === Number.POSITIVE_INFINITY) break;
122
131
  details.push(conversion);
132
+ if (conversion.action === "cast") destType = conversion.targetType;
123
133
  }
124
134
  if (sum < (bestResult?.sum ?? Number.POSITIVE_INFINITY)) bestResult = {
125
- type: targetType,
135
+ type: destType,
126
136
  details,
127
137
  sum
128
138
  };
@@ -168,7 +178,7 @@ function convertToCommonType(ctx, values, restrictTo, verbose = true) {
168
178
  if (DEV && Array.isArray(restrictTo) && restrictTo.length === 0) console.warn("convertToCommonType was called with an empty restrictTo array, which prevents any conversions from being made. If you intend to allow all conversions, pass undefined instead. If this was intended call the function conditionally since the result will always be undefined.");
169
179
  const conversion = getBestConversion(types, restrictTo);
170
180
  if (!conversion) return;
171
- if ((TEST || DEV) && verbose && conversion.hasImplicitConversions) console.warn(`Implicit conversions from [\n${values.map((v) => ` ${v.value}: ${safeStringify(v.dataType)}`).join(",\n")}\n] to ${conversion.targetType.type} are supported, but not recommended.
181
+ if ((TEST || DEV) && verbose && conversion.hasImplicitConversions) console.warn(`Implicit conversions from [\n${values.map((v) => ` ${ctx.resolveSnippet(v).value}: ${safeStringify(v.dataType)}`).join(",\n")}\n] to ${conversion.targetType.type} are supported, but not recommended.
172
182
  Consider using explicit conversions instead.`);
173
183
  return values.map((value, index) => {
174
184
  const action = conversion.actions[index];
@@ -33,12 +33,20 @@ function getElementType(elementSnippet, iterableSnippet) {
33
33
  }
34
34
  function getRangeSnippets(ctx, iterableSnippet, unroll = false) {
35
35
  const { value, dataType } = iterableSnippet;
36
- if (isTgpuRange(value)) return {
37
- start: snip(value.start, i32, "constant"),
38
- end: snip(value.end, i32, "constant"),
39
- step: snip(value.step, i32, "constant"),
40
- comparison: value.step < 0 ? ">" : "<"
41
- };
36
+ if (isTgpuRange(value)) {
37
+ const { start, end, step } = value;
38
+ const dataType$1 = [
39
+ start,
40
+ end,
41
+ step
42
+ ].every((v) => v >= 0) ? u32 : i32;
43
+ return {
44
+ start: snip(start, dataType$1, "constant"),
45
+ end: snip(end, dataType$1, "constant"),
46
+ step: snip(step, dataType$1, "constant"),
47
+ comparison: step < 0 ? ">" : "<"
48
+ };
49
+ }
42
50
  if (!unroll && isEphemeralSnippet(iterableSnippet)) throw new Error(`\`for ... of ...\` loops only support std.range or iterables stored in variables.
43
51
  -----
44
52
  You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at comptime, the loop will be unrolled.
@@ -1,5 +1,6 @@
1
1
  import { Snippet } from "../data/snippet.js";
2
2
  import { ShelllessRepository } from "./shellless.js";
3
+ import { SupportedLogOp } from "./consoleLog/types.js";
3
4
  import { FunctionScopeLayer, ResolutionCtx } from "../types.js";
4
5
  import { BaseData } from "../data/wgslTypes.js";
5
6
 
@@ -21,7 +22,7 @@ type GenerationCtx = ResolutionCtx & {
21
22
  dedent(): string;
22
23
  pushBlockScope(): void;
23
24
  popBlockScope(): void;
24
- generateLog(op: string, args: Snippet[]): Snippet;
25
+ generateLog(op: SupportedLogOp, args: Snippet[]): Snippet;
25
26
  getById(id: string): Snippet | null;
26
27
  defineVariable(id: string, snippet: Snippet): void;
27
28
  setBlockExternals(externals: Record<string, Snippet>): void;
@@ -25,9 +25,6 @@ function concretize(type) {
25
25
  function concretizeSnippet(snippet) {
26
26
  return snip(snippet.value, concretize(snippet.dataType), snippet.origin);
27
27
  }
28
- function concretizeSnippets(args) {
29
- return args.map(concretizeSnippet);
30
- }
31
28
  function coerceToSnippet(value) {
32
29
  if (isSnippet(value)) return value;
33
30
  if (isRef(value)) throw new Error("Cannot use refs (d.ref(...)) from the outer scope.");
@@ -66,4 +63,4 @@ var ArrayExpression = class {
66
63
  };
67
64
 
68
65
  //#endregion
69
- export { ArrayExpression, coerceToSnippet, concretize, concretizeSnippets, numericLiteralToSnippet };
66
+ export { ArrayExpression, coerceToSnippet, concretize, concretizeSnippet, numericLiteralToSnippet };
@@ -0,0 +1,25 @@
1
+ import "../core/function/fnTypes.js";
2
+ import "../types.js";
3
+
4
+ //#region src/tgsl/jsPolyfills.d.ts
5
+ declare const supportedLogOps: () => readonly [{
6
+ (...data: any[]): void;
7
+ (message?: any, ...optionalParams: any[]): void;
8
+ }, {
9
+ (...data: any[]): void;
10
+ (message?: any, ...optionalParams: any[]): void;
11
+ }, {
12
+ (...data: any[]): void;
13
+ (message?: any, ...optionalParams: any[]): void;
14
+ }, {
15
+ (...data: any[]): void;
16
+ (message?: any, ...optionalParams: any[]): void;
17
+ }, {
18
+ (...data: any[]): void;
19
+ (message?: any, ...optionalParams: any[]): void;
20
+ }, {
21
+ (): void;
22
+ (): void;
23
+ }];
24
+ //#endregion
25
+ export { supportedLogOps };
@@ -0,0 +1,44 @@
1
+ import { f32 } from "../data/numeric.js";
2
+ import { abs, acos, acosh, asin, asinh, atan, atan2, atanh, ceil, cos, cosh, countLeadingZeros, exp, floor, log, log2, max, min, pow, sign, sin, sinh, sqrt, tan, tanh, trunc } from "../std/numeric.js";
3
+
4
+ //#region src/tgsl/jsPolyfills.ts
5
+ const mathToStd = new Map([
6
+ [Math.abs, abs],
7
+ [Math.acos, acos],
8
+ [Math.acosh, acosh],
9
+ [Math.asin, asin],
10
+ [Math.asinh, asinh],
11
+ [Math.atan, atan],
12
+ [Math.atan2, atan2],
13
+ [Math.atanh, atanh],
14
+ [Math.ceil, ceil],
15
+ [Math.cos, cos],
16
+ [Math.cosh, cosh],
17
+ [Math.exp, exp],
18
+ [Math.floor, floor],
19
+ [Math.fround, f32],
20
+ [Math.clz32, countLeadingZeros],
21
+ [Math.trunc, trunc],
22
+ [Math.log, log],
23
+ [Math.log2, log2],
24
+ [Math.pow, pow],
25
+ [Math.sign, sign],
26
+ [Math.sin, sin],
27
+ [Math.sinh, sinh],
28
+ [Math.sqrt, sqrt],
29
+ [Math.tan, tan],
30
+ [Math.tanh, tanh],
31
+ [Math.max, max],
32
+ [Math.min, min]
33
+ ]);
34
+ const supportedLogOps = () => [
35
+ console.log,
36
+ console.debug,
37
+ console.info,
38
+ console.warn,
39
+ console.error,
40
+ console.clear
41
+ ];
42
+
43
+ //#endregion
44
+ export { mathToStd, supportedLogOps };
@@ -1,8 +1,7 @@
1
1
  import { ResolvedSnippet, Snippet } from "../data/snippet.js";
2
2
  import { GenerationCtx } from "./generationHelpers.js";
3
- import "./shaderGenerator_members.js";
3
+ import { FunctionDefinitionOptions } from "./shaderGenerator_members.js";
4
4
  import { BaseData } from "../data/wgslTypes.js";
5
- import { Block } from "tinyest";
6
5
 
7
6
  //#region src/tgsl/shaderGenerator.d.ts
8
7
 
@@ -14,7 +13,7 @@ import { Block } from "tinyest";
14
13
  */
15
14
  interface ShaderGenerator {
16
15
  initGenerator(ctx: GenerationCtx): void;
17
- functionDefinition(body: Block): string;
16
+ functionDefinition(options: FunctionDefinitionOptions): string;
18
17
  typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet;
19
18
  typeAnnotation(schema: BaseData): string;
20
19
  }
@@ -1,2 +1,15 @@
1
- import { ResolutionCtx } from "../types.js";
2
- import { UnknownData } from "../data/dataTypes.js";
1
+ import { Origin, Snippet } from "../data/snippet.js";
2
+ import { FunctionArgument, ResolutionCtx, TgpuShaderStage } from "../types.js";
3
+ import { BaseData } from "../data/wgslTypes.js";
4
+ import { UnknownData } from "../data/dataTypes.js";
5
+ import { Block } from "tinyest";
6
+
7
+ //#region src/tgsl/shaderGenerator_members.d.ts
8
+ interface FunctionDefinitionOptions {
9
+ readonly functionType: 'normal' | TgpuShaderStage;
10
+ readonly args: readonly FunctionArgument[];
11
+ readonly body: Block;
12
+ determineReturnType(): BaseData;
13
+ }
14
+ //#endregion
15
+ export { FunctionDefinitionOptions };
@@ -1,5 +1,6 @@
1
1
  import { Origin, ResolvedSnippet, Snippet } from "../data/snippet.js";
2
2
  import { GenerationCtx } from "./generationHelpers.js";
3
+ import { FunctionDefinitionOptions } from "./shaderGenerator_members.js";
3
4
  import { ShaderGenerator } from "./shaderGenerator.js";
4
5
  import { BaseData, StorableData } from "../data/wgslTypes.js";
5
6
  import { UnknownData } from "../data/dataTypes.js";
@@ -22,7 +23,7 @@ declare class WgslGenerator implements ShaderGenerator {
22
23
  */
23
24
  _typedExpression(expression: tinyest.Expression, expectedType: BaseData | BaseData[]): Snippet;
24
25
  _expression(expression: tinyest.Expression): Snippet;
25
- functionDefinition(body: tinyest.Block): string;
26
+ functionDefinition(options: FunctionDefinitionOptions): string;
26
27
  /**
27
28
  * Generates a WGSL type string for the given data type, and adds necessary
28
29
  * definitions to the shader preamble. This shouldn't be called directly, only
@@ -30,6 +31,7 @@ declare class WgslGenerator implements ShaderGenerator {
30
31
  */
31
32
  typeAnnotation(data: BaseData): string;
32
33
  typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet;
34
+ _return(statement: tinyest.Return): string;
33
35
  _statement(statement: tinyest.Statement): string;
34
36
  }
35
37
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { $gpuCallable, $internal, $providing, isMarkedInternal } from "../shared/symbols.js";
2
2
  import { getName } from "../shared/meta.js";
3
3
  import { Void, isBool, isNaturallyEphemeral, isNumericSchema, isPtr, isVec, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
4
- import { ConsoleLog, InfixDispatch, UnknownData, isLooseData, unptr } from "../data/dataTypes.js";
4
+ import { InfixDispatch, UnknownData, unptr } from "../data/dataTypes.js";
5
5
  import { fallthroughCopyOrigin, isEphemeralOrigin, isEphemeralSnippet, snip } from "../data/snippet.js";
6
6
  import { ResolutionError, WgslTypeError, invariant } from "../errors.js";
7
7
  import { isGPUCallable, isKnownAtComptime } from "../types.js";
@@ -13,6 +13,7 @@ import { convertStructValues, convertToCommonType, tryConvertSnippet } from "./c
13
13
  import { bool, i32, u32 } from "../data/numeric.js";
14
14
  import { ArrayExpression, coerceToSnippet, concretize, numericLiteralToSnippet } from "./generationHelpers.js";
15
15
  import { vec2u, vec3u, vec4u } from "../data/vector.js";
16
+ import { getAttributesString } from "../data/attributes.js";
16
17
  import { AutoStruct } from "../data/autoStruct.js";
17
18
  import { add, div, mul, neg, sub } from "../std/operators.js";
18
19
  import { accessProp } from "./accessProp.js";
@@ -23,9 +24,10 @@ import { arrayOf } from "../data/array.js";
23
24
  import { pow } from "../std/numeric.js";
24
25
  import { resolveData } from "../core/resolve/resolveData.js";
25
26
  import { UnrollableIterable } from "../core/unroll/tgpuUnroll.js";
26
- import { mathToStd } from "./math.js";
27
+ import { mathToStd, supportedLogOps } from "./jsPolyfills.js";
27
28
  import { isTgpuRange } from "../std/range.js";
28
29
  import { getElementSnippet, getElementType, getLoopVarKind, getRangeSnippets } from "./forOfUtils.js";
30
+ import { stringifyNode } from "../shared/tseynit.js";
29
31
  import * as tinyest from "tinyest";
30
32
 
31
33
  //#region src/tgsl/wgslGenerator.ts
@@ -224,8 +226,9 @@ ${this.ctx.pre}}`;
224
226
  return snip(rhsStr$1, bool, "runtime");
225
227
  }
226
228
  const rhsExpr = this._expression(rhs);
227
- if (rhsExpr.value instanceof RefOperator) throw new WgslTypeError(stitch`Cannot assign a ref to an existing variable '${lhsExpr}', define a new variable instead.`);
229
+ if (rhsExpr.value instanceof RefOperator) throw new WgslTypeError(stitch`Cannot assign a ref to an existing variable '${stringifyNode(lhs)}', define a new variable instead.`);
228
230
  if (op === "==") throw new Error("Please use the === operator instead of ==");
231
+ if (op === "!=") throw new Error("Please use the !== operator instead of !=");
229
232
  if (op === "===" && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) return snip(lhsExpr.value === rhsExpr.value, bool, "constant");
230
233
  if (lhsExpr.dataType === UnknownData) throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`);
231
234
  if (rhsExpr.dataType === UnknownData) throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
@@ -249,10 +252,13 @@ ${this.ctx.pre}}`;
249
252
  const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value;
250
253
  const type = operatorToType(convLhs.dataType, op, convRhs.dataType);
251
254
  if (exprType === NODE.assignmentExpr) {
252
- if (convLhs.origin === "constant" || convLhs.origin === "constant-tgpu-const-ref" || convLhs.origin === "runtime-tgpu-const-ref") throw new WgslTypeError(`'${lhsStr} = ${rhsStr}' is invalid, because ${lhsStr} is a constant. This error may also occur when assigning to a value defined outside of a TypeGPU function's scope.`);
253
- if (lhsExpr.origin === "argument") throw new WgslTypeError(`'${lhsStr} ${op} ${rhsStr}' is invalid, because non-pointer arguments cannot be mutated.`);
254
- if (op === "=" && rhsExpr.origin === "argument" && !isNaturallyEphemeral(rhsExpr.dataType)) throw new WgslTypeError(`'${lhsStr} = ${rhsStr}' is invalid, because argument references cannot be assigned.\n-----\nTry '${lhsStr} = ${this.ctx.resolve(rhsExpr.dataType).value}(${rhsStr})' to copy the value instead.\n-----`);
255
- if (op === "=" && !isEphemeralSnippet(rhsExpr)) throw new WgslTypeError(`'${lhsStr} = ${rhsStr}' is invalid, because references cannot be assigned.\n-----\nTry '${lhsStr} = ${this.ctx.resolve(rhsExpr.dataType).value}(${rhsStr})' to copy the value instead.\n-----`);
255
+ if (convLhs.origin === "constant" || convLhs.origin === "constant-tgpu-const-ref" || convLhs.origin === "runtime-tgpu-const-ref") {
256
+ if (isKnownAtComptime(convLhs)) throw new WgslTypeError(`'${stringifyNode(expression)}' 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.`);
257
+ throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because the left side is a constant.`);
258
+ }
259
+ if (lhsExpr.origin === "argument") throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because non-pointer arguments cannot be mutated.`);
260
+ if (op === "=" && rhsExpr.origin === "argument" && !isNaturallyEphemeral(rhsExpr.dataType)) throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because argument references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(rhsExpr.dataType).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
261
+ if (op === "=" && !isEphemeralSnippet(rhsExpr)) throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(rhsExpr.dataType).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
256
262
  }
257
263
  return snip(parenthesizedOps.includes(op) ? `(${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr})` : `${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr}`, type, "runtime");
258
264
  }
@@ -273,37 +279,28 @@ ${this.ctx.pre}}`;
273
279
  }
274
280
  if (expression[0] === NODE.memberAccess) {
275
281
  const [_, targetNode, property] = expression;
276
- const target = this._expression(targetNode);
277
- if (target.value === console) return snip(new ConsoleLog(property), UnknownData, "runtime");
278
- if (target.value === Math) {
279
- if (property in mathToStd && mathToStd[property]) return snip(mathToStd[property], UnknownData, "runtime");
280
- if (typeof Math[property] === "function") throw new Error(`Unsupported functionality 'Math.${property}'. Use an std alternative, or implement the function manually.`);
281
- }
282
- const accessed = accessProp(target, property);
283
- if (!accessed) throw new Error(stitch`Property '${property}' not found on value '${target}' of type ${this.ctx.resolve(target.dataType)}`);
282
+ const accessed = accessProp(this._expression(targetNode), property);
283
+ if (!accessed) throw new Error(`Property '${property}' not found on '${stringifyNode(targetNode)}'`);
284
284
  return accessed;
285
285
  }
286
286
  if (expression[0] === NODE.indexAccess) {
287
287
  const [_, targetNode, propertyNode] = expression;
288
288
  const target = this._expression(targetNode);
289
289
  const inProperty = this._expression(propertyNode);
290
- const property = convertToCommonType(this.ctx, [inProperty], [u32, i32], false)?.[0] ?? inProperty;
291
- const accessed = accessIndex(target, property);
292
- if (!accessed) {
293
- const targetStr = this.ctx.resolve(target.value, target.dataType).value;
294
- const propertyStr = this.ctx.resolve(property.value, property.dataType).value;
295
- throw new Error(`Unable to index value ${targetStr} of unknown type with index ${propertyStr}. If the value is an array, to address this, consider one of the following approaches: (1) declare the array using 'tgpu.const', (2) store the array in a buffer, or (3) define the array within the GPU function scope.`);
296
- }
290
+ const accessed = accessIndex(target, convertToCommonType(this.ctx, [inProperty], [u32, i32], false)?.[0] ?? inProperty);
291
+ if (!accessed) throw new Error(`Index access '${stringifyNode(expression)}' is invalid. If the value is an array, to address this, consider one of the following approaches: (1) declare the array using 'tgpu.const', (2) store the array in a buffer, or (3) define the array within the GPU function scope.`);
297
292
  return accessed;
298
293
  }
299
294
  if (expression[0] === NODE.numericLiteral) {
300
295
  const type = typeof expression[1] === "string" ? numericLiteralToSnippet(parseNumericString(expression[1])) : numericLiteralToSnippet(expression[1]);
301
- if (!type) throw new Error(`Invalid numeric literal ${expression[1]}`);
296
+ invariant(type, `Expected ${stringifyNode(expression)} to be valid numeric literal`);
302
297
  return type;
303
298
  }
304
299
  if (expression[0] === NODE.call) {
305
300
  const [_, calleeNode, argNodes] = expression;
306
- const callee = this._expression(calleeNode);
301
+ const _callee = this._expression(calleeNode);
302
+ const callee = mathToStd.has(_callee.value) ? snip(mathToStd.get(_callee.value), UnknownData, "runtime") : _callee;
303
+ if (supportedLogOps().includes(callee.value)) return this.ctx.generateLog(callee.value, argNodes.map((arg) => this._expression(arg)));
307
304
  if (isWgslStruct(callee.value)) {
308
305
  if (argNodes.length > 1) throw new WgslTypeError("Struct schemas should always be called with at most 1 argument");
309
306
  if (!argNodes[0]) return snip(`${this.ctx.resolve(callee.value).value}()`, callee.value, "runtime");
@@ -323,14 +320,13 @@ ${this.ctx.pre}}`;
323
320
  const rhs = this._expression(argNodes[0]);
324
321
  return callee.value.operator(this.ctx, [callee.value.lhs, rhs]);
325
322
  }
326
- if (callee.value instanceof ConsoleLog) return this.ctx.generateLog(callee.value.op, argNodes.map((arg) => this._expression(arg)));
327
323
  if (isGPUCallable(callee.value)) {
328
324
  const callable = callee.value[$gpuCallable];
329
325
  const strictSignature = callable.strictSignature;
330
326
  let convertedArguments;
331
327
  if (strictSignature) convertedArguments = argNodes.map((arg, i) => {
332
328
  const argType = strictSignature.argTypes[i];
333
- if (!argType) throw new WgslTypeError(`Function '${getName(callee.value)}' was called with too many arguments`);
329
+ if (!argType) throw new WgslTypeError(`Call '${stringifyNode(expression)}' is invalid since the function expected fewer arguments`);
334
330
  return this._typedExpression(arg, argType);
335
331
  });
336
332
  else convertedArguments = argNodes.map((arg) => this._expression(arg));
@@ -360,6 +356,10 @@ ${this.ctx.pre}}`;
360
356
  }));
361
357
  if (shelllessCall) return shelllessCall;
362
358
  }
359
+ const maybeMathMethod = Object.getOwnPropertyNames(Math).find((prop) => Math[prop] === callee.value);
360
+ if (maybeMathMethod) throw new Error(`Unsupported Math functionality 'Math.${maybeMathMethod}()'. Use an std alternative, or implement the function manually.`);
361
+ const maybeConsoleMethod = Object.getOwnPropertyNames(console).find((prop) => console[prop] === callee.value);
362
+ if (maybeConsoleMethod) throw new Error(`Unsupported console functionality 'console.${maybeConsoleMethod}()'.`);
363
363
  throw new Error(`Function '${getName(callee.value) ?? String(callee.value)}' is not marked with the 'use gpu' directive and cannot be used in a shader`);
364
364
  }
365
365
  if (expression[0] === NODE.objectExpr) {
@@ -390,7 +390,7 @@ ${this.ctx.pre}}`;
390
390
  const convertedSnippets = convertStructValues(this.ctx, structType, entries);
391
391
  return snip(stitch`${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType, "runtime");
392
392
  }
393
- throw new WgslTypeError(`No target type could be inferred for object with keys [${Object.keys(obj).join(", ")}], please wrap the object in the corresponding schema.`);
393
+ throw new WgslTypeError(`No target type could be inferred for object '${stringifyNode(expression)}', please wrap the object in the corresponding schema.`);
394
394
  }
395
395
  if (expression[0] === NODE.arrayExpr) {
396
396
  const [_, valueNodes] = expression;
@@ -405,7 +405,7 @@ ${this.ctx.pre}}`;
405
405
  const valuesSnippets = valueNodes.map((value) => this._expression(value));
406
406
  if (valuesSnippets.length === 0) throw new WgslTypeError("Cannot infer the type of an empty array literal.");
407
407
  const converted = convertToCommonType(this.ctx, valuesSnippets);
408
- if (!converted) throw new WgslTypeError("The given values cannot be automatically converted to a common type. Consider wrapping the array in an appropriate schema");
408
+ if (!converted) throw new WgslTypeError(`Values '${stringifyNode(expression)}' cannot be automatically converted to a common type. Consider wrapping the array in an appropriate schema`);
409
409
  values = converted;
410
410
  elemType = concretize(values[0]?.dataType);
411
411
  }
@@ -416,14 +416,19 @@ ${this.ctx.pre}}`;
416
416
  const [_, test, consequent, alternative] = expression;
417
417
  const testExpression = this._expression(test);
418
418
  if (isKnownAtComptime(testExpression)) return testExpression.value ? this._expression(consequent) : this._expression(alternative);
419
- else throw new Error(`Ternary operator is only supported for comptime-known checks (used with '${testExpression.value}'). For runtime checks, please use 'std.select' or if/else statements.`);
419
+ else throw new Error(`Ternary operator '${stringifyNode(expression)}' is invalid, because only comptime-known checks are supported. For runtime checks, please use 'std.select' or if/else statements.`);
420
420
  }
421
421
  if (expression[0] === NODE.stringLiteral) return snip(expression[1], UnknownData, "constant");
422
422
  if (expression[0] === NODE.preUpdate) throw new Error("Cannot use pre-updates in TypeGPU functions.");
423
423
  assertExhaustive(expression);
424
424
  }
425
- functionDefinition(body) {
426
- return this._block(body);
425
+ functionDefinition(options) {
426
+ const body = this._block(options.body);
427
+ const returnType = options.determineReturnType();
428
+ const argList = options.args.filter((arg) => arg.used || options.functionType === "normal").map((arg) => {
429
+ return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`;
430
+ }).join(", ");
431
+ return `${returnType.type !== "void" ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} ` : `(${argList}) `}${body}`;
427
432
  }
428
433
  /**
429
434
  * Generates a WGSL type string for the given data type, and adds necessary
@@ -437,6 +442,28 @@ ${this.ctx.pre}}`;
437
442
  if (args.length === 1 && args[0]?.dataType === schema) return snip(stitch`${args[0]}`, schema, fallthroughCopyOrigin(args[0].origin));
438
443
  return snip(stitch`${this.ctx.resolve(schema).value}(${args})`, schema, "runtime");
439
444
  }
445
+ _return(statement) {
446
+ const returnNode = statement[1];
447
+ if (returnNode !== void 0) {
448
+ const expectedReturnType = this.ctx.topFunctionReturnType;
449
+ let returnSnippet = expectedReturnType ? this._typedExpression(returnNode, expectedReturnType) : this._expression(returnNode);
450
+ if (returnSnippet.value instanceof RefOperator) throw new WgslTypeError(`Cannot return '${stringifyNode(returnNode)}' because it is a d.ref`);
451
+ if (returnSnippet.origin === "argument" && !isNaturallyEphemeral(returnSnippet.dataType) && this.ctx.topFunctionScope?.functionType === "normal") throw new WgslTypeError(`'${stringifyNode(statement)}' is invalid, cannot return references to arguments. Copy the argument before returning it.`);
452
+ if (!expectedReturnType && !isEphemeralSnippet(returnSnippet) && returnSnippet.origin !== "this-function") {
453
+ const str = stringifyNode(returnNode);
454
+ const typeStr = this.ctx.resolve(unptr(returnSnippet.dataType)).value;
455
+ throw new WgslTypeError(`'return ${str};' is invalid, cannot return references.
456
+ -----
457
+ Try 'return ${typeStr}(${str});' instead.
458
+ -----`);
459
+ }
460
+ returnSnippet = tryConvertSnippet(this.ctx, returnSnippet, unptr(returnSnippet.dataType), false);
461
+ invariant(returnSnippet.dataType !== UnknownData, "Return type should be known");
462
+ this.ctx.reportReturnType(returnSnippet.dataType);
463
+ return stitch`${this.ctx.pre}return ${returnSnippet};`;
464
+ }
465
+ return `${this.ctx.pre}return;`;
466
+ }
440
467
  _statement(statement) {
441
468
  if (typeof statement === "string") {
442
469
  const id = this._identifier(statement);
@@ -444,28 +471,7 @@ ${this.ctx.pre}}`;
444
471
  return resolved$1 ? `${this.ctx.pre}${resolved$1};` : "";
445
472
  }
446
473
  if (typeof statement === "boolean") return `${this.ctx.pre}${statement ? "true" : "false"};`;
447
- if (statement[0] === NODE.return) {
448
- const returnNode = statement[1];
449
- if (returnNode !== void 0) {
450
- const expectedReturnType = this.ctx.topFunctionReturnType;
451
- let returnSnippet = expectedReturnType ? this._typedExpression(returnNode, expectedReturnType) : this._expression(returnNode);
452
- if (returnSnippet.value instanceof RefOperator) throw new WgslTypeError(stitch`Cannot return references, returning '${returnSnippet.value.snippet}'`);
453
- if (returnSnippet.origin === "argument" && !isNaturallyEphemeral(returnSnippet.dataType) && this.ctx.topFunctionScope?.functionType === "normal") throw new WgslTypeError(stitch`Cannot return references to arguments, returning '${returnSnippet}'. Copy the argument before returning it.`);
454
- if (!expectedReturnType && !isEphemeralSnippet(returnSnippet) && returnSnippet.origin !== "this-function") {
455
- const str = this.ctx.resolve(returnSnippet.value, returnSnippet.dataType).value;
456
- const typeStr = this.ctx.resolve(unptr(returnSnippet.dataType)).value;
457
- throw new WgslTypeError(`'return ${str};' is invalid, cannot return references.
458
- -----
459
- Try 'return ${typeStr}(${str});' instead.
460
- -----`);
461
- }
462
- returnSnippet = tryConvertSnippet(this.ctx, returnSnippet, unptr(returnSnippet.dataType), false);
463
- invariant(returnSnippet.dataType !== UnknownData, "Return type should be known");
464
- this.ctx.reportReturnType(returnSnippet.dataType);
465
- return stitch`${this.ctx.pre}return ${returnSnippet};`;
466
- }
467
- return `${this.ctx.pre}return;`;
468
- }
474
+ if (statement[0] === NODE.return) return this._return(statement);
469
475
  if (statement[0] === NODE.if) {
470
476
  const [_, condNode, consNode, altNode] = statement;
471
477
  const condition = this._typedExpression(condNode, bool);
@@ -488,11 +494,10 @@ ${this.ctx.pre}else ${alternate}`;
488
494
  let varType = "var";
489
495
  const [stmtType, rawId, rawValue] = statement;
490
496
  const eq = rawValue !== void 0 ? this._expression(rawValue) : void 0;
491
- if (!eq) throw new Error(`Cannot create variable '${rawId}' without an initial value.`);
497
+ if (!eq) throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
492
498
  const ephemeral = isEphemeralSnippet(eq);
493
499
  let dataType = eq.dataType;
494
500
  const naturallyEphemeral = isNaturallyEphemeral(dataType);
495
- if (isLooseData(eq.dataType)) throw new Error(`Cannot create variable '${rawId}' with loose data type.`);
496
501
  if (eq.value instanceof RefOperator) {
497
502
  if (eq.dataType !== UnknownData) throw new WgslTypeError(`Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.`);
498
503
  const refSnippet = eq.value.snippet;
@@ -501,7 +506,7 @@ ${this.ctx.pre}else ${alternate}`;
501
506
  }
502
507
  if (!ephemeral) {
503
508
  if (stmtType === NODE.let) {
504
- const rhsStr$1 = this.ctx.resolve(eq.value).value;
509
+ const rhsStr$1 = stringifyNode(rawValue ?? "");
505
510
  const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
506
511
  throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, because references cannot be assigned to 'let' variable declarations.
507
512
  -----
@@ -525,7 +530,7 @@ ${this.ctx.pre}else ${alternate}`;
525
530
  else if (naturallyEphemeral) varType = eq.origin === "constant" ? "const" : "let";
526
531
  } else if (eq.origin === "argument") {
527
532
  if (!naturallyEphemeral) {
528
- const rhsStr$1 = this.ctx.resolve(eq.value).value;
533
+ const rhsStr$1 = stringifyNode(rawValue ?? "");
529
534
  const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
530
535
  throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, because references to arguments cannot be assigned to 'let' variable declarations.
531
536
  -----
@@ -593,14 +598,14 @@ ${this.ctx.pre}else ${alternate}`;
593
598
  if (length === 0) return "";
594
599
  const { value } = iterableSnippet;
595
600
  const elements = isTgpuRange(value) ? value.map((i) => coerceToSnippet(i)) : value instanceof ArrayExpression ? value.elements : Array.from({ length }, (_$1, i) => getElementSnippet(iterableSnippet, snip(i, u32, "constant")));
596
- if (isEphemeralSnippet(elements[0]) && !isNaturallyEphemeral(elements[0]?.dataType)) throw new WgslTypeError("Cannot unroll loop. The elements of iterable are emphemeral but not naturally ephemeral.");
601
+ if (isEphemeralSnippet(elements[0]) && !isNaturallyEphemeral(elements[0]?.dataType)) throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
597
602
  return elements.map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this._block(blockified, { [originalLoopVarName]: e })}`).join("\n");
598
603
  }
599
604
  this.#unrolling = false;
600
605
  const index = this.ctx.makeNameValid("i");
601
606
  const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`;
602
607
  let bodyStr = "";
603
- if (isTgpuRange(iterableSnippet.value)) bodyStr = this._block(blockified, { [originalLoopVarName]: snip(index, u32, "runtime") });
608
+ if (isTgpuRange(iterableSnippet.value)) bodyStr = this._block(blockified, { [originalLoopVarName]: snip(index, range.start.dataType, "runtime") });
604
609
  else {
605
610
  this.ctx.indent();
606
611
  ctxIndent = true;