diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 33d64ec786..604f94572b 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -218,6 +218,17 @@ const usageToVarTemplateMap: Record */ const functionInitialBlockDepth = 2; +function schemaWrappingSuggestion(declaration: string, rhs: string, value: unknown): string { + if (value === null || value === undefined || typeof value === 'string') { + return ''; + } + + return ` +----- +- Try using or defining a schema that matches your desired value the most, and wrap the value with it: '${declaration} = Schema(${rhs})' +-----`; +} + export class WgslGenerator implements ShaderGenerator { #ctx: ResolutionCtx | undefined = undefined; // used to detect `continue` and `break` nodes in loop body, as well as label @@ -1334,13 +1345,11 @@ Try 'return ${typeStr}(${str});' instead. const definitionDataType = eq.dataType; - if (definitionDataType === UnknownData) { + if (definitionDataType === UnknownData || wgsl.isVoid(definitionDataType)) { const rhsStr = stringifyNode(eqNode); + const declaration = `let ${rawId}`; throw new WgslTypeError( - `'let ${rawId} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}' ------ -- Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'let ${rawId} = Schema(${rhsStr})' ------`, + `'${declaration} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'${schemaWrappingSuggestion(declaration, rhsStr, eq.value)}`, ); } @@ -1423,13 +1432,11 @@ Try 'return ${typeStr}(${str});' instead. let varType: 'var' | 'let' | 'const' | '' = ''; let definitionDataType = eq.dataType; - if (definitionDataType === UnknownData) { + if (definitionDataType === UnknownData || wgsl.isVoid(definitionDataType)) { const rhsStr = stringifyNode(eqNode); + const declaration = `const ${rawId}`; throw new WgslTypeError( - `'const ${rawId} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}' ------ -- Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'const ${rawId} = Schema(${rhsStr})' ------`, + `'${declaration} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'${schemaWrappingSuggestion(declaration, rhsStr, eq.value)}`, ); } diff --git a/packages/typegpu/tests/tgsl/letDeclaration.test.ts b/packages/typegpu/tests/tgsl/letDeclaration.test.ts index ed88e09832..3b7dafe8e4 100644 --- a/packages/typegpu/tests/tgsl/letDeclaration.test.ts +++ b/packages/typegpu/tests/tgsl/letDeclaration.test.ts @@ -4,6 +4,20 @@ import { tgpu, d } from 'typegpu'; import { expectSnippetOf } from '../utils/parseResolved.ts'; describe('let declarations', () => { + it('rejects let assigning the result of a void function', () => { + const noop = tgpu.fn([])(() => {}); + + const f = tgpu.fn([])(() => { + let a = noop(); + }); + + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn:f: 'let a = noop()' is invalid, cannot determine WGSL type of 'noop()'] + `); + }); + it('initializes a local definition with a scalar value', () => { function foo() { 'use gpu'; @@ -49,10 +63,37 @@ describe('let declarations', () => { [Error: Resolution of the following tree failed: - - fn*:foo - - fn*:foo(): 'let a = "12"' is invalid, cannot determine WGSL type of '"12"' - ----- - - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'let a = Schema("12")' - -----] + - fn*:foo(): 'let a = "12"' is invalid, cannot determine WGSL type of '"12"'] + `); + }); + + it('throws when initializing with null without a schema hint', () => { + function foo() { + 'use gpu'; + let a = null; + return a; + } + + expect(() => tgpu.resolve([foo])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:foo + - fn*:foo(): 'let a = null' is invalid, cannot determine WGSL type of 'null'] + `); + }); + + it('rejects bare undefined because it has no WGSL type', () => { + function foo() { + 'use gpu'; + let a = undefined; + return a; + } + + expect(() => tgpu.resolve([foo])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:foo + - fn*:foo(): 'let a = undefined' is invalid, cannot determine WGSL type of 'undefined'] `); }); }); diff --git a/packages/typegpu/tests/tgsl/typeInference.test.ts b/packages/typegpu/tests/tgsl/typeInference.test.ts index 89269b63a2..ae51799429 100644 --- a/packages/typegpu/tests/tgsl/typeInference.test.ts +++ b/packages/typegpu/tests/tgsl/typeInference.test.ts @@ -317,6 +317,62 @@ describe('wgsl generator type inference', () => { `); }); + it('does not suggest wrapping null with a schema', () => { + const myFn = () => { + 'use gpu'; + const a = null; + }; + + expect(() => tgpu.resolve([myFn])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:myFn + - fn*:myFn(): 'const a = null' is invalid, cannot determine WGSL type of 'null'] + `); + }); + + it('rejects bare undefined because it has no WGSL type', () => { + const myFn = () => { + 'use gpu'; + const a = undefined; + }; + + expect(() => tgpu.resolve([myFn])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:myFn + - fn*:myFn(): 'const a = undefined' is invalid, cannot determine WGSL type of 'undefined'] + `); + }); + + it('does not suggest wrapping a string with a schema', () => { + const myFn = () => { + 'use gpu'; + const a = 'hello'; + }; + + expect(() => tgpu.resolve([myFn])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:myFn + - fn*:myFn(): 'const a = "hello"' is invalid, cannot determine WGSL type of '"hello"'] + `); + }); + + it('rejects assigning the result of a void function', () => { + const noop = tgpu.fn([])(() => {}); + + const f = tgpu.fn([])(() => { + const a = noop(); + }); + + expect(() => tgpu.resolve([f])).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn:f: 'const a = noop()' is invalid, cannot determine WGSL type of 'noop()'] + `); + }); + it('throws when creating an empty untyped array', () => { const myFn = tgpu.fn([])(() => { const myArr = []; diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 198cfec524..4ec8a43a97 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -1312,23 +1312,6 @@ describe('string injection', () => { }); describe('nulls in TGSL', () => { - it('throws when assigning to a variable', () => { - const myFn = () => { - 'use gpu'; - const a = null; - }; - - expect(() => tgpu.resolve([myFn])).toThrowErrorMatchingInlineSnapshot(` - [Error: Resolution of the following tree failed: - - - - fn*:myFn - - fn*:myFn(): 'const a = null' is invalid, cannot determine WGSL type of 'null' - ----- - - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'const a = Schema(null)' - -----] - `); - }); - it('allows comptime usage', () => { let externalNum: number | null; const myFn = () => {