From aebf5e68f0fd003d6a176b5b27c1cc7ffd569fa4 Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 3 Aug 2026 12:52:17 +0200 Subject: [PATCH 1/2] fix: Reject decorated element types in arrayOf --- packages/typegpu/src/data/array.ts | 36 ++++++++++++++++++++++++--- packages/typegpu/tests/array.test.ts | 27 ++++++++++++++++++++ packages/typegpu/tests/buffer.test.ts | 2 +- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/packages/typegpu/src/data/array.ts b/packages/typegpu/src/data/array.ts index ae67de7891..2cc4450a62 100644 --- a/packages/typegpu/src/data/array.ts +++ b/packages/typegpu/src/data/array.ts @@ -2,13 +2,30 @@ import { comptime, type TgpuComptime } from '../core/function/comptime.ts'; import { $internal } from '../shared/symbols.ts'; import { schemaCallWrapper } from './schemaCallWrapper.ts'; import { sizeOf } from './sizeOf.ts'; -import type { AnyWgslData, WgslArray } from './wgslTypes.ts'; +import type { AnyWgslData, Decorated, Location, WgslArray } from './wgslTypes.ts'; +import { isDecorated, isLocationAttrib } from './wgslTypes.ts'; // ---------- // Public API // ---------- +type ForbiddenDecoratedArrayElement = + T extends Decorated + ? Attribs[number] extends Location + ? never + : T + : never; + interface WgslArrayConstructor { + /** + * @deprecated Error: Arrays cannot hold decorated types other than @location. + * Wrap align/size in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n). + */ + ( + elementType: ForbiddenDecoratedArrayElement, + count?: number, + ): 'Error: Arrays cannot hold decorated types other than @location. Wrap it in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n)'; + ( elementType: TElement, ): (elementCount: number) => WgslArray; @@ -20,6 +37,10 @@ interface WgslArrayConstructor { * Creates an array schema that can be used to construct gpu buffers. * Describes arrays with fixed-size length, storing elements of the same type. * + * The only decoration allowed on element types is `d.location`. Decorators like + * `d.align` and `d.size` cannot be applied directly — wrap them in a struct instead, + * e.g. `d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n)`. + * * @example * const LENGTH = 3; * const array = d.arrayOf(d.u32, LENGTH); @@ -31,16 +52,23 @@ interface WgslArrayConstructor { * * @param elementType The type of elements in the array. * @param elementCount The number of elements in the array. + * @throws If `elementType` is decorated with anything other than `d.location`. */ export const arrayOf: TgpuComptime = comptime((( - elementType, - elementCount, + elementType: AnyWgslData, + elementCount?: number, ) => { + if (isDecorated(elementType) && !elementType.attribs.every(isLocationAttrib)) { + throw new Error( + 'Arrays cannot hold decorated types other than @location. Wrap it in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).', + ); + } + if (elementCount === undefined) { return comptime((count: number) => cpu_arrayOf(elementType, count)); } return cpu_arrayOf(elementType, elementCount); -}) as WgslArrayConstructor).$name('arrayOf'); +}) as unknown as WgslArrayConstructor).$name('arrayOf'); // -------------- // Implementation diff --git a/packages/typegpu/tests/array.test.ts b/packages/typegpu/tests/array.test.ts index 6388ac0cfd..efa4e3e29d 100644 --- a/packages/typegpu/tests/array.test.ts +++ b/packages/typegpu/tests/array.test.ts @@ -551,6 +551,33 @@ describe('array', () => { - fn*:main(): Value [1, 2, 3] is not resolvable] `); }); + + it('allows @location-decorated element types', () => { + const located = d.arrayOf(d.location(0, d.u32), 4); + expect(located.elementCount).toBe(4); + expectTypeOf(located).toEqualTypeOf]>>>(); + }); + + it('should allow calling arrayOf inside generic helpers', () => { + function arrayOf32(schema: T) { + return d.arrayOf(schema, 32); + } + + arrayOf32(d.f32); + }); + + it('throws when a non-location decorated element type is passed', () => { + expect(() => d.arrayOf(d.align(16, d.u32), 4)).toThrowErrorMatchingInlineSnapshot( + `[Error: Arrays cannot hold decorated types other than @location. Wrap it in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).]`, + ); + expect(() => d.arrayOf(d.size(16, d.u32), 3)).toThrow(); + expect(() => d.arrayOf(d.location(0, d.align(16, d.u32)), 3)).toThrow(); + + const aligned = () => d.arrayOf(d.align(16, d.u32), 4); + expectTypeOf( + aligned, + ).returns.toEqualTypeOf<'Error: Arrays cannot hold decorated types other than @location. Wrap it in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n)'>(); + }); }); describe('array.length', () => { diff --git a/packages/typegpu/tests/buffer.test.ts b/packages/typegpu/tests/buffer.test.ts index 3473bb79ae..1eb1d6f32d 100644 --- a/packages/typegpu/tests/buffer.test.ts +++ b/packages/typegpu/tests/buffer.test.ts @@ -861,7 +861,7 @@ describe('TgpuBuffer', () => { }); it('should ignore decorated types when determining validity usage', ({ root }) => { - const validSchema = d.size(1024, d.arrayOf(d.align(16, d.u32), 32)); + const validSchema = d.size(1024, d.arrayOf(d.u32, 32)); const buffer = root.createBuffer(validSchema); From d11f0723047b188be41135f53f180e2ed2066529 Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 3 Aug 2026 12:52:17 +0200 Subject: [PATCH 2/2] docs: Note that arrayOf rejects decorated elements other than @location --- apps/typegpu-docs/src/content/docs/apis/data-schemas.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/typegpu-docs/src/content/docs/apis/data-schemas.mdx b/apps/typegpu-docs/src/content/docs/apis/data-schemas.mdx index ef950cd8b1..19a806716a 100644 --- a/apps/typegpu-docs/src/content/docs/apis/data-schemas.mdx +++ b/apps/typegpu-docs/src/content/docs/apis/data-schemas.mdx @@ -355,6 +355,9 @@ const ArrayPartialSchema = d.arrayOf(d.f32); const array = ArrayPartialSchema(2)([1.2, 19.29]); // ^? ``` +:::caution +The only decoration allowed on array element types is `d.location`. Decorators like `d.align` and `d.size` cannot be applied directly - wrap them in a struct instead, e.g. `d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n)`. +::: ## Textures