From a7abca9f3a639d81e07a6b334bce3d2674811781 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:15:54 +0200 Subject: [PATCH 1/8] Initial rewrite --- .../typegpu/src/core/resolve/externals.ts | 102 ++++++++++-------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/packages/typegpu/src/core/resolve/externals.ts b/packages/typegpu/src/core/resolve/externals.ts index 0dca0ae376..44c0e15392 100644 --- a/packages/typegpu/src/core/resolve/externals.ts +++ b/packages/typegpu/src/core/resolve/externals.ts @@ -66,11 +66,8 @@ export function addReturnTypeToExternals( } } -function identifierRegex(name: string) { - return new RegExp( - `(? { - const externalRegex = identifierRegex(externalName); - if (wgsl && externalName !== 'Out' && externalName !== 'in' && !externalRegex.test(wgsl)) { - console.warn(`The external '${externalName}' wasn't used in the resolved template.`); - // continue anyway, we still might need to resolve the external - } + const keys = Object.keys(externalMap); + if (keys.length === 0) { + return wgsl; + } + + // Avoid resolving the same item multiple times during one call. + const cache: Map = new Map(); + + const anyIdent = /([$_\p{XID_Start}][$\p{XID_Continue}]*)/u; + const initialIdents = new RegExp( + keys.map((key) => key.replaceAll('.', '\\.').replaceAll('$', '\\$')).join('|'), + 'u', + ); + const matcher = boundedRegex( + new RegExp(`(${initialIdents.source})(\\.${anyIdent.source})*`, 'ug'), + ); - if (isResolvable(external)) { - if (isNamable(external) && getName(external) === undefined) { - setName(external, externalName.split('.').at(-1) as string); + return wgsl.replaceAll(matcher, (match) => { + const chain = match.split('.'); + let currentItem: unknown = externalMap; + let name: string | undefined = undefined; + let suffix = ''; + let resolvedItem = false; + for (const [i, elem] of chain.entries()) { + currentItem = (currentItem as ExternalMap)[elem]; + name = elem; + if (isResolvable(currentItem)) { + suffix = chain + .slice(i + 1) + .map((s) => `.${s}`) + .join(''); + resolvedItem = true; + break; + } + + if (typeof currentItem !== 'object' || currentItem === null) { + console.warn( + `During resolution, the external '${name}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, + ); + return match; } - return acc.replaceAll(externalRegex, ctx.resolve(external).value); } - if (external !== null && typeof external === 'object') { - const foundProperties = [ - ...wgsl.matchAll( - new RegExp( - `${externalName - .replaceAll('.', '\\.') - .replaceAll('$', '\\$')}\\.(?.*?)(?![\\w\\$_])`, - 'g', - ), - ), - ].map((found) => found[1]); - const uniqueProperties = [...new Set(foundProperties)]; - - return uniqueProperties.reduce( - (innerAcc: string, prop) => - prop && prop in external - ? replaceExternalsInWgsl( - ctx, - { - [`${externalName}.${prop}`]: external[prop as keyof typeof external], - }, - innerAcc, - ) - : innerAcc, - acc, - ); + // The chain ended on a nested external map rather than a resolvable value + // (e.g. a bare `in`). Nothing to substitute — leave it untouched. + if (!resolvedItem) { + return match; } - console.warn( - `During resolution, the external '${externalName}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, - ); + if (isNamable(currentItem) && getName(currentItem) === undefined) { + setName(currentItem, name); + } + + if (cache.has(currentItem)) { + return cache.get(currentItem) + suffix; + } + const resolved = ctx.resolve(currentItem).value; + cache.set(currentItem, resolved); - return acc; - }, wgsl); + return resolved + suffix; // cache!!!!! + }); } From b0b547264dc0fa3d9d1b558ce23fbc9f5b3e9cc3 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:25:57 +0200 Subject: [PATCH 2/8] Add an end of chain test --- .../typegpu/src/core/resolve/externals.ts | 23 +++++++++++++------ packages/typegpu/tests/resolve.test.ts | 18 +++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/typegpu/src/core/resolve/externals.ts b/packages/typegpu/src/core/resolve/externals.ts index 44c0e15392..ed94d2d781 100644 --- a/packages/typegpu/src/core/resolve/externals.ts +++ b/packages/typegpu/src/core/resolve/externals.ts @@ -103,10 +103,12 @@ export function replaceExternalsInWgsl( return wgsl.replaceAll(matcher, (match) => { const chain = match.split('.'); + let currentItem: unknown = externalMap; let name: string | undefined = undefined; let suffix = ''; let resolvedItem = false; + for (const [i, elem] of chain.entries()) { currentItem = (currentItem as ExternalMap)[elem]; name = elem; @@ -119,7 +121,11 @@ export function replaceExternalsInWgsl( break; } - if (typeof currentItem !== 'object' || currentItem === null) { + if ( + typeof currentItem !== 'object' || + currentItem === null /* || i === chain.length - 1 */ + ) { + // throw new Error('aaa'); console.warn( `During resolution, the external '${name}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, ); @@ -130,19 +136,22 @@ export function replaceExternalsInWgsl( // The chain ended on a nested external map rather than a resolvable value // (e.g. a bare `in`). Nothing to substitute — leave it untouched. if (!resolvedItem) { + console.warn( + `During resolution, the external '${chain.join('.')}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, + ); return match; } - if (isNamable(currentItem) && getName(currentItem) === undefined) { + if (isNamable(currentItem) && getName(currentItem) === undefined && name !== undefined) { setName(currentItem, name); } - if (cache.has(currentItem)) { - return cache.get(currentItem) + suffix; + let resolved = cache.get(currentItem); + if (resolved === undefined) { + resolved = ctx.resolve(currentItem).value; + cache.set(currentItem, resolved); } - const resolved = ctx.resolve(currentItem).value; - cache.set(currentItem, resolved); - return resolved + suffix; // cache!!!!! + return resolved + suffix; }); } diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 83e8e1e787..8dfa7f7a03 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -527,6 +527,24 @@ describe('tgpu resolveWithContext', () => { ); }); + it('should warn when the end of external chain was reached without a resolvable', () => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const getColor = tgpu.fn([])`() { + let color = EXT.p.q; + }`.$uses({ EXT: { p: { q: { r: d.vec3f() } } } }); + + expect(tgpu.resolve([getColor])).toMatchInlineSnapshot(` + "fn getColor() { + let color = EXT.p.q; + }" + `); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + "During resolution, the external 'EXT.p.q' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", + ); + }); + it('should not warn when In/Out are unused', () => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); From ca3e9e1a37c418d60a932db7c8ba8c47778dbd1e Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:30:14 +0200 Subject: [PATCH 3/8] Simplify end of chain tracking --- .../typegpu/src/core/resolve/externals.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/packages/typegpu/src/core/resolve/externals.ts b/packages/typegpu/src/core/resolve/externals.ts index ed94d2d781..0e733c452d 100644 --- a/packages/typegpu/src/core/resolve/externals.ts +++ b/packages/typegpu/src/core/resolve/externals.ts @@ -107,7 +107,6 @@ export function replaceExternalsInWgsl( let currentItem: unknown = externalMap; let name: string | undefined = undefined; let suffix = ''; - let resolvedItem = false; for (const [i, elem] of chain.entries()) { currentItem = (currentItem as ExternalMap)[elem]; @@ -117,31 +116,17 @@ export function replaceExternalsInWgsl( .slice(i + 1) .map((s) => `.${s}`) .join(''); - resolvedItem = true; break; } - if ( - typeof currentItem !== 'object' || - currentItem === null /* || i === chain.length - 1 */ - ) { - // throw new Error('aaa'); + if (typeof currentItem !== 'object' || currentItem === null || i === chain.length - 1) { console.warn( - `During resolution, the external '${name}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, + `During resolution, the external '${chain.slice(0, i + 1).join('.')}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, ); return match; } } - // The chain ended on a nested external map rather than a resolvable value - // (e.g. a bare `in`). Nothing to substitute — leave it untouched. - if (!resolvedItem) { - console.warn( - `During resolution, the external '${chain.join('.')}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, - ); - return match; - } - if (isNamable(currentItem) && getName(currentItem) === undefined && name !== undefined) { setName(currentItem, name); } From 48727bb14b985b01baca761a9db09552d0779c09 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:38:54 +0200 Subject: [PATCH 4/8] Update snapshots --- .../individual-example-tests/log-test.test.ts | 56 +++++++++---------- packages/typegpu/tests/resolve.test.ts | 6 +- packages/typegpu/tests/tgpuGenericFn.test.ts | 4 +- .../typegpu/tests/tgsl/consoleLog.test.ts | 42 +++++++------- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/log-test.test.ts b/apps/typegpu-docs/tests/individual-example-tests/log-test.test.ts index 8cd55aa412..86ba2283ac 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/log-test.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/log-test.test.ts @@ -39,6 +39,8 @@ describe('console log example', () => { expect(shaderCodes).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -48,8 +50,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -90,6 +90,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -99,8 +101,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -150,6 +150,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -159,8 +161,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -203,6 +203,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -212,8 +214,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn log1serializer() { @@ -834,6 +834,8 @@ describe('console log example', () => { num: u32, } + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -843,8 +845,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -995,6 +995,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1004,8 +1006,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn log1serializer() { @@ -1052,6 +1052,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1061,8 +1063,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1103,6 +1103,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1112,8 +1114,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1158,6 +1158,8 @@ describe('console log example', () => { @group(0) @binding(1) var logCountUniform: u32; + var dataBlockIndex: u32; + @group(0) @binding(2) var indexBuffer: atomic; struct SerializedLogData { @@ -1167,8 +1169,6 @@ describe('console log example', () => { @group(0) @binding(3) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1212,6 +1212,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1221,8 +1223,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1299,6 +1299,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1308,8 +1310,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn log1serializer() { @@ -1453,6 +1453,8 @@ describe('console log example', () => { return mainVertex_Output(vec4f(positions[vertexIndex], 0f, 1f)); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -1462,8 +1464,6 @@ describe('console log example', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1506,6 +1506,8 @@ describe('console log example', () => { return mainVertex_Output(vec4f(positions[vertexIndex], 0f, 1f)); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -1515,8 +1517,6 @@ describe('console log example', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -1552,6 +1552,8 @@ describe('console log example', () => { @group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -1561,8 +1563,6 @@ describe('console log example', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 8dfa7f7a03..6e97b6bbea 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -310,12 +310,12 @@ fn main() { }); expect(resolved).toMatchInlineSnapshot(` - "@group(0) @binding(0) var intensity: u32; - - fn get_color() -> vec3f { + "fn get_color() -> vec3f { let color = vec3f(); return color; } + + @group(0) @binding(0) var intensity: u32; fn main () { let c = get_color() * intensity; }" diff --git a/packages/typegpu/tests/tgpuGenericFn.test.ts b/packages/typegpu/tests/tgpuGenericFn.test.ts index fd136ced01..df737ded64 100644 --- a/packages/typegpu/tests/tgpuGenericFn.test.ts +++ b/packages/typegpu/tests/tgpuGenericFn.test.ts @@ -318,6 +318,8 @@ describe('TgpuGenericFn - shellless callback wrapper', () => { expect(tgpu.resolve([pipeline.pipeline])).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -327,8 +329,6 @@ describe('TgpuGenericFn - shellless callback wrapper', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { diff --git a/packages/typegpu/tests/tgsl/consoleLog.test.ts b/packages/typegpu/tests/tgsl/consoleLog.test.ts index 997b15d46f..c1a8e19607 100644 --- a/packages/typegpu/tests/tgsl/consoleLog.test.ts +++ b/packages/typegpu/tests/tgsl/consoleLog.test.ts @@ -84,6 +84,8 @@ describe('wgslGenerator with console.log', () => { return vs_Output(vec4f()); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -93,8 +95,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -166,6 +166,8 @@ describe('wgslGenerator with console.log', () => { return vs_Output(vec4f()); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -175,8 +177,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -257,6 +257,8 @@ describe('wgslGenerator with console.log', () => { return VertexOut(vec4f()); } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -266,8 +268,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -317,7 +317,9 @@ describe('wgslGenerator with console.log', () => { const pipeline = root.createComputePipeline({ compute: fn }); expect(tgpu.resolve([pipeline])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var indexBuffer: atomic; + "var dataBlockIndex: u32; + + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { id: u32, @@ -326,8 +328,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -375,7 +375,9 @@ describe('wgslGenerator with console.log', () => { }); expect(tgpu.resolve([pipeline])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var indexBuffer: atomic; + "var dataBlockIndex: u32; + + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { id: u32, @@ -384,8 +386,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -453,7 +453,9 @@ describe('wgslGenerator with console.log', () => { }); expect(tgpu.resolve([pipeline])).toMatchInlineSnapshot(` - "@group(0) @binding(0) var indexBuffer: atomic; + "var dataBlockIndex: u32; + + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { id: u32, @@ -462,8 +464,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -539,6 +539,8 @@ describe('wgslGenerator with console.log', () => { data: array, } + var dataBlockIndex: u32; + @group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { @@ -548,8 +550,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -690,6 +690,8 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(1) var myUniform: vec2f; + var dataBlockIndex: u32; + @group(0) @binding(2) var indexBuffer: atomic; struct SerializedLogData { @@ -699,8 +701,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(3) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn nextByteIndex() -> u32 { @@ -753,6 +753,8 @@ describe('wgslGenerator with console.log', () => { expect(tgpu.resolve([myPipeline.pipeline])).toMatchInlineSnapshot(` "@group(0) @binding(0) var sizeUniform: vec3u; + var dataBlockIndex: u32; + @group(0) @binding(1) var indexBuffer: atomic; struct SerializedLogData { @@ -762,8 +764,6 @@ describe('wgslGenerator with console.log', () => { @group(0) @binding(2) var dataBuffer: array; - var dataBlockIndex: u32; - var dataByteIndex: u32; fn log1serializer() { From e346c8e7d9d6f2fae2774181c7f79495ccd3e9d4 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:41:15 +0200 Subject: [PATCH 5/8] Update declare tests --- packages/typegpu/tests/declare.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/typegpu/tests/declare.test.ts b/packages/typegpu/tests/declare.test.ts index 2b018c3192..bcaf64d09f 100644 --- a/packages/typegpu/tests/declare.test.ts +++ b/packages/typegpu/tests/declare.test.ts @@ -5,7 +5,7 @@ describe('tgpu.declare', () => { it('should inject provided declaration when resolving a function', () => { const declaration = tgpu['~unstable'].declare('@group(0) @binding(0) var val: f32;'); - const empty = tgpu.fn([])`() { /* do nothing */ }`.$uses({ declaration }); + const empty = tgpu.fn([])`declaration () { /* do nothing */ }`.$uses({ declaration }); expect(tgpu.resolve([empty])).toMatchInlineSnapshot(` "@group(0) @binding(0) var val: f32; @@ -33,7 +33,7 @@ struct Output { x: u32, }`); - const empty = tgpu.fn([])`() { /* do nothing */ }`.$uses({ decl1, decl2 }); + const empty = tgpu.fn([])`decl1 decl2 () { /* do nothing */ }`.$uses({ decl1, decl2 }); expect(tgpu.resolve([empty])).toMatchInlineSnapshot(` "@group(0) @binding(0) var val: f32; @@ -48,17 +48,17 @@ struct Output { it('should replace nested declarations', () => { const declaration = tgpu['~unstable'] - .declare('@group(0) @binding(0) var val: f32;') + .declare('@group(0) @binding(0) var val: f32; nested') .$uses({ nested: tgpu['~unstable'].declare('struct Output { x: u32 }'), }); - const empty = tgpu.fn([])`() { /* do nothing */ }`.$uses({ declaration }); + const empty = tgpu.fn([])`declaration () { /* do nothing */ }`.$uses({ declaration }); expect(tgpu.resolve([empty])).toMatchInlineSnapshot(` "struct Output { x: u32 } - @group(0) @binding(0) var val: f32; + @group(0) @binding(0) var val: f32; fn empty() { /* do nothing */ }" `); @@ -73,7 +73,7 @@ struct Output { .declare('@group(0) @binding(0) var val: Output;') .$uses({ Output }); - const empty = tgpu.fn([])`() { /* do nothing */ }`.$uses({ declaration }); + const empty = tgpu.fn([])`declaration () { /* do nothing */ }`.$uses({ declaration }); expect(tgpu.resolve([empty])).toMatchInlineSnapshot(` "struct Output { From 49743050b07ac307da04c1b1196dd3bbddde9088 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:44:43 +0200 Subject: [PATCH 6/8] Remove warn test --- packages/typegpu/tests/resolve.test.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 6e97b6bbea..4e215f25a4 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -495,25 +495,6 @@ describe('tgpu resolveWithContext', () => { expect(configSpy.mock.lastCall?.[0].bindings).toEqual([[colorSlot, v]]); }); - it('should warn when external WGSL is not used', () => { - using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - - tgpu.resolveWithContext({ - template: 'fn testFn() { return; }', - externals: { - ArraySchema: d.arrayOf(d.u32, 4), - JavaScriptObject: { field: d.vec2f() }, - }, - }); - - expect(consoleWarnSpy).toHaveBeenCalledWith( - "The external 'ArraySchema' wasn't used in the resolved template.", - ); - expect(consoleWarnSpy).toHaveBeenCalledWith( - "The external 'JavaScriptObject' wasn't used in the resolved template.", - ); - }); - it('should warn when external is neither wgsl nor an object', () => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); From 65fcedf3032d607ed950f106d68a54b9d2923754 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 24 Jun 2026 12:03:01 +0200 Subject: [PATCH 7/8] Simplify regex --- .../typegpu/src/core/resolve/externals.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/typegpu/src/core/resolve/externals.ts b/packages/typegpu/src/core/resolve/externals.ts index 0e733c452d..8c04f2088b 100644 --- a/packages/typegpu/src/core/resolve/externals.ts +++ b/packages/typegpu/src/core/resolve/externals.ts @@ -66,15 +66,15 @@ export function addReturnTypeToExternals( } } -function boundedRegex(inner: RegExp) { - return new RegExp(`(? = new Map(); - const anyIdent = /([$_\p{XID_Start}][$\p{XID_Continue}]*)/u; - const initialIdents = new RegExp( - keys.map((key) => key.replaceAll('.', '\\.').replaceAll('$', '\\$')).join('|'), - 'u', - ); - const matcher = boundedRegex( - new RegExp(`(${initialIdents.source})(\\.${anyIdent.source})*`, 'ug'), - ); - - return wgsl.replaceAll(matcher, (match) => { + return wgsl.replaceAll(boundedPropChain, (match) => { const chain = match.split('.'); + if (!((chain.at(0) as string) in externalMap)) { + // this prop access does not start with an external + return match; + } + let currentItem: unknown = externalMap; let name: string | undefined = undefined; let suffix = ''; From 38a06439424b7b37cbc1d1f8b3b8d88008464498 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 24 Jun 2026 12:37:00 +0200 Subject: [PATCH 8/8] Update packages/typegpu/src/core/resolve/externals.ts Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> --- packages/typegpu/src/core/resolve/externals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typegpu/src/core/resolve/externals.ts b/packages/typegpu/src/core/resolve/externals.ts index 8c04f2088b..9213fc8551 100644 --- a/packages/typegpu/src/core/resolve/externals.ts +++ b/packages/typegpu/src/core/resolve/externals.ts @@ -95,7 +95,7 @@ export function replaceExternalsInWgsl( return wgsl.replaceAll(boundedPropChain, (match) => { const chain = match.split('.'); - if (!((chain.at(0) as string) in externalMap)) { + if (!Object.hasOwn(externalMap, chain.at(0) as string)) { // this prop access does not start with an external return match; }