fix: validate unresolved declaration initializers - #2946
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves diagnostics during TGSL let/const type inference by treating a bare undefined initializer (represented as wgsl.Void) the same way as other “untyped/unknown” RHS values, emitting the actionable “wrap with Schema(...)” error message and adding regression tests.
Changes:
- Detect
const/let x = undefinedinitializers represented asVoidand raise the same schema-wrapping diagnostic as forUnknownData. - Add snapshot regressions for
null/undefinedinference errors inconstdeclarations. - Add a snapshot regression for
undefinedinference errors inletdeclarations.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| packages/typegpu/src/tgsl/wgslGenerator.ts | Extends UnknownData initializer checks to also cover bare undefined (Void) initializers for let and const. |
| packages/typegpu/tests/tgsl/typeInference.test.ts | Adds snapshot tests asserting schema-wrapping suggestions for const a = null and const a = undefined. |
| packages/typegpu/tests/tgsl/letDeclaration.test.ts | Adds snapshot test asserting schema-wrapping suggestion for let a = undefined. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
wgslGenerator.ts— bareundefinedinitializers now reported as untyped. Both_letStatement(L1337) and_constStatement(L1429) now enter the existing schema-wrappingWgslTypeErrorpath wheneq.value === undefined && wgsl.isVoid(definitionDataType), alongside theUnknownDatacheck. Previouslylet/const a = undefinedsurfaced the crypticValue undefined is not resolvable to type void.- Regression tests for
undefinedin both declaration forms (letDeclaration.test.ts,typeInference.test.ts) plus a guard for the already-workingnullcase.
I verified the fix empirically by reverting both conditions to base form: the pre-fix failure is Value undefined is not resolvable to type void, and the two undefined tests genuinely fail without the change. Worth noting the null test passes pre-fix too — null already resolves to UnknownData, so that snapshot guards existing behavior rather than pinning this fix. The check mirrors the bare-return idiom at _returnStatement (L1245), the only Void/undefined snippet producers are the undefined identifier and the void unary operator, let/const are the only declaration statement types (var is rejected at parse), and GlslGenerator extends WgslGenerator inherits the fix. Well-scoped, minimal, and consistent — thanks.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
Appreciate the effort, but issue is not solved. |
|
@cieplypolar Fixed in |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — delta since the last Pullfrog review of 2e80d3c8, i.e. commit 11511f3f:
- New
schemaWrappingSuggestionhelper — the schema-wrapping hint was extracted out of thelet/constWgslTypeErrormessages so it can be emitted conditionally instead of unconditionally. - Hint suppressed for non-wrappable RHS values —
null,undefined, and string RHS values now only reportcannot determine WGSL type of '...'; the previously suggestedSchema(null)/Schema(undefined)/Schema("...")wrappings are dropped since none of those can produce a valid schema. - Snapshot updates + new tests — existing string/
nullsnapshots inletDeclaration,typeInference, andtgslFnwere updated, and new tests pin that the hint is dropped forundefined,null, and string literals while object-backed RHS values (e.g.structValue,arrayValueattypeInference.test.ts:628/644) keep it.
I checked the other throw sites that reference the same cannot determine WGSL type of wording — those two _letStatement/_constStatement branches are the only producers, and GlslGenerator extends WgslGenerator without overriding them, so GLSL inherits the change. All three touched test files pass (100/100).
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
it('...', () => {
const noop = tgpu.fn([])(() => {});
const f = tgpu.fn([])(() => {
const a = noop();
});
expect(tgpu.resolve([f])).toMatchInlineSnapshot(`
"fn noop() {}
fn f() {
let a = noop();
}"
`);
});
|
|
Addressed in
Verification:
|
There was a problem hiding this comment.
Important
The delta under review (182e8c90) reverts the bare-undefined guard from the earlier commits, so the PR no longer emits a schema-wrapping diagnostic for undefined — the delivered behavior no longer matches the PR title, the description, or the author's comment on #2934, all of which still claim that diagnostic. The code itself is in good shape and, on the merits, resolves #2934's actual complaint (bogus Schema(...) hints); the mismatch is the framing. Decide before merge whether to re-scope the description or re-implement the diagnostic in a form that skips void-call results.
Reviewed changes — delta since the Pullfrog review of 11511f3f (commit 182e8c90, "fix: preserve void declaration inference"):
- Reverted the void-initializer guard — removed
(eq.value === undefined && wgsl.isVoid(definitionDataType))from both_letStatementand_constStatement(wgslGenerator.ts:1348/1435); bareundefinedin declarations is back to the pre-PRValue undefined is not resolvable to type void, andconst a = voidFn()style declarations are valid again. - New regression test —
letDeclaration.test.tspins that assigning a void function call to a declaration still resolves (let a = noop();). - Repurposed
undefined-hint tests — the "does not suggest wrapping undefined" tests became the null-hint test and new assertions that bareundefinedreports not resolvable to void in bothlet(letDeclaration) andconst(typeInference) forms.
⚠️ PR description, title, and linked-issue comment no longer describe the delivered behavior
At head, let/const a = undefined produces Value undefined is not resolvable to type void — the same pre-PR error the PR was opened against — and the tests now pin that as expected. Meanwhile the title ("fix: improve undefined variable diagnostics"), both summary bullets ("detect bare undefined initializers … report the same actionable schema-wrapping diagnostic"), and the author's comment on #2934 all assert the opposite.
The result is nonetheless coherent and arguably the better outcome: #2934's complaint is the bogus Schema(null) / Schema("...") hints, and suppressing them for null/string/undefined is exactly what head does (object/array-backed RHS values still get the hint). Two ways forward — pick one so the merged record matches reality:
- Re-scope the description (and the comment on #2934) to say the change suppresses invalid schema-wrapping hints for
null, strings, andundefined, and preserves void-call inference — and reconsider whether "Closes #2934" is the right framing. - Re-implement the bare-
undefineddiagnostic restricted to the literal identifier:_identifier('undefined')(wgslGenerator.ts:370-371) yieldsorigin: 'constant'withvalue === undefinedanddataTypeVoid, while a void function call's result does not — so a guard can discriminate. The new void-call regression test covers the false-positive side.
Technical details
# Align PR scope/description with head behavior
## Affected sites
- packages/typegpu/src/tgsl/wgslGenerator.ts:1348, 1435 — UnknownData-only guards; the void/undefined check from 11511f3f is removed at head
- packages/typegpu/tests/tgsl/letDeclaration.test.ts:87-98 — new test pins `Value undefined is not resolvable to type void`
- packages/typegpu/tests/tgsl/typeInference.test.ts:334-346 — same, const form
- packages/typegpu/src/tgsl/wgslGenerator.ts:370-371 — `_identifier('undefined')` → origin `'constant'`; the discriminator available if re-adding the fix
## Required outcome
- The PR title/description and the #2934 comment must describe what the merged branch actually does (suppress bogus hints; void-call declarations supported), OR the undefined diagnostic must be re-added in a way that does not fire for void-call results.
## Open questions for the human
- Is "Closes #2934" still accurate once the undefined diagnostic is dropped? (#2934's complaint was the bogus hint, which is fixed — but the issue text also names `undefined`.)
- Should bare `undefined` keep the cryptic `not resolvable to type void` message, or get a dedicated one?DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
Updated the PR title/description and corrected my earlier #2934 comment so the record now matches |
|
The following test should fail it('supports assigning the result of a void function', () => {
const noop = tgpu.fn([])(() => {});
const f = tgpu.fn([])(() => {
const a = noop();
});
expect(tgpu.resolve([f])).toMatchInlineSnapshot(`
"fn noop() {}
fn f() {
let a = noop();
}"
`);
});Sorry for being unclear. |
I got it |
|
Fixed in Verification:
|
|
This test shouldn't be in 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:
- <root>
- fn:f: 'const a = noop()' is invalid, cannot determine WGSL type of 'noop()']
`);
}); |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — delta since the Pullfrog review of 182e8c90 (080c9cec, "fix: reject void declaration initializers"), which re-adds void-initializer rejection in the broader form the earlier commits lacked:
- Void-typed declaration initializers are now rejected. Both
_letStatement(wgslGenerator.ts:1348) and_constStatement(wgslGenerator.ts:1435) throw thecannot determine WGSL type of '...'WgslTypeErrorwheneq.dataTypeisUnknownDataorvoid, covering bareundefined, thevoidunary, and void-returning function calls. A variable cannot be void-typed in WGSL, so the previous resolution ofconst a = noop()tolet a = noop();produced unusable output — rejecting it is the more principled outcome. - Regression tests updated to pin the new behavior. The former
supports assigning the result of a void functiontest is now two rejection tests (const a = noop()andlet a = noop()), and the bare-undefinedtests assert the new diagnostic message for bothletandconstforms. The tests genuinely fail without the void guard. - PR framing re-aligned with head behavior. The title ("fix: validate unresolved declaration initializers") and description now describe rejecting void-valued initializers and suppressing bogus
Schema(null)/Schema(undefined)/ string hints, resolving the narrative mismatch flagged in the last review.
The rejection is well-scoped: wgsl.isVoid is strict, var is rejected at parse, the only Void snippet producers (undefined identifier, void unary, void-returning calls, and a few side-effect std builtins) are all non-assignable in valid WGSL, and no existing tests, docs, or examples assign a void-returning call. I re-ran the modified test files plus the full tests/tgsl suite (420 tests) — all green. GlslGenerator inherits both statement paths unchanged.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
Fixed in Verification:
|
|
Almost there, just delete duplicated test: |
|
Fixed in Verification:
|

Summary
letandconstdeclarations whose initializer resolves tovoid, including void-returning function calls and bareundefinedSchema(null),Schema(undefined), and string suggestionslet/const, null/string diagnostics, bareundefined, and void-call resultsCloses #2934
Verification
pnpm --filter typegpu test:types: passedpnpm test:style: passedpnpm test:fast-unit: 216 files passed, 2,815 tests passed, 2 skipped