Repository navigation
feat: Support default parameter values and optional params - #2870
wcandillon wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds end-to-end support for default values on identifier parameters in TGSL/TypeGPU functions, spanning AST representation, JS→tinyest transpilation, resolution/codegen behavior, and linting.
Changes:
- Extend
tinyestfunction parameter nodes to optionally carry a defaultExpression. - Teach
tinyest-for-wgslto parse/transpile defaulted identifier params and to account for externals referenced only from defaults. - Update TypeGPU resolution to handle calls with fewer arguments by injecting trailing defaults into the generated function body; relax ESLint rule to allow defaulted identifier params (while still rejecting destructuring defaults).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/typegpu/tests/tgsl/shellless.test.ts | Adds integration tests covering omitted args, comptime-style flags, and defaults referencing earlier params. |
| packages/typegpu/src/resolutionCtx.ts | Injects trailing default expressions into function bodies for shorter-arity overload generation. |
| packages/tinyest/src/nodes.ts | Extends FuncParameter identifier variant with optional default expression. |
| packages/tinyest-for-wgsl/tests/parsers.test.ts | Adds parser/transpiler tests for default params and external-name gathering behavior. |
| packages/tinyest-for-wgsl/src/parsers.ts | Parses AssignmentPattern params with identifier targets and transpiles attached defaults. |
| packages/eslint-plugin/tests/rules/noUnsupportedSyntax.test.ts | Updates rule tests to allow identifier-param defaults and still reject destructuring defaults. |
| packages/eslint-plugin/src/rules/noUnsupportedSyntax.ts | Adjusts AssignmentPattern handling to permit defaulted identifier parameters in TGSL functions. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // Transpiled with the param names already declared, so identifiers used in | ||
| // a default value only count as externals when they're not other params. | ||
| for (const [i, defaultNode] of paramDefaults.entries()) { | ||
| const param = params[i]; | ||
| if (defaultNode && param?.type === FuncParameterType.identifier) { |
| let body = options.body; | ||
| if (!options.entryInput) { | ||
| const defaultStatements: Statement[] = []; | ||
| for (const param of options.params.slice(options.argTypes.length)) { | ||
| if (param.type === FuncParameterType.identifier && param.default !== undefined) { |
There was a problem hiding this comment.
Important
One integration gap: default parameter values are not handled by the experimental unstable_obfuscate path, so obfuscated builds with defaults referencing other params or externals emit stale identifier references. Details anchored inline.
Reviewed changes
- tinyest:
FuncParameteridentifier variant now carries an optionaldefault?: Expression(packages/tinyest/src/nodes.ts). - tinyest-for-wgsl:
extractFunctionPartsacceptsAssignmentPatternparams with anIdentifiertarget, returns them aligned with a newparamDefaultsarray, andtranspileFnattaches the transcompiled default expression to each param (params are pre-declared in the context so default references to other params don't leak as externals) (packages/tinyest-for-wgsl/src/parsers.ts). - typegpu: when a call site provides fewer arguments than declared params, the trailing identifier params with defaults are prepended to the body as
conststatements, giving each arity its own concrete overload (packages/typegpu/src/resolutionCtx.ts). - eslint-plugin:
no-unsupported-syntaxstops flagging defaulted identifier params across arrow/function declaration/function expression forms while still flagging defaults inside destructuring patterns (packages/eslint-plugin/src/rules/noUnsupportedSyntax.ts). - Tests: new parsers coverage (default literals, param-referencing defaults, external defaults) and shellless tests (numeric/vec/bool defaults, param-alias defaults, shelled fn defaults, comptime-mode-flag pattern).
I probed the behavior beyond the shipped snapshots: defaults referencing earlier params (including earlier defaulted params) resolve correctly, full-arity calls emit no default statements, and default expressions that reference runtime resources like uniform.$ fail with the same clear "cannot determine WGSL type" error that identical in-body expressions already produce. All touched test suites pass and pnpm --filter typegpu test:types is green.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
@wcandillon Thanks for the contribution! 💜 |
a46cf3f to
ef6a513
Compare
There was a problem hiding this comment.
ℹ️ No new issues in this delta — the omitted-parameter rework looks correct and well-covered.
Reviewed changes
Since the last pullfrog review (a46cf3f), the PR reworks how omitted arguments are handled and adds undefined support:
- Comptime defaults are inlined:
WgslGenerator._paramDefaultsevaluates each omitted parameter's default; when it is comptime-known it is bound viactx.defineVariableand inlined at every use (no statement emitted), while runtime defaults emit a declaration through the newly extracted_constDeclarationhelper. undefinedmarks omission:ShelllessRepository.getmaps an argument snippet whosevalue === undefinedtoVoid(throwing when the parameter has no default) and trims trailingVoids, sofn(a, undefined)resolves likefn(a).paramDefaultsthreaded to codegen:resolveFunctionskipsVoidarg types, collects omitted identifier params with defaults into aParamDefault[], and passes it throughFunctionDefinitionOptionsto both the WGSL and GLSL generators.- Parser refactor:
extractFunctionPartsnow returns rawparamDefaultsandtranspileFnattaches the transpiled defaults to the params; direct resolution now requires every parameter to have a default. - Tests: added shellless coverage for comptime inlining, branch pruning,
undefinedomission, defaults referencing other defaults, runtime defaults, direct resolution, and both new error paths, plus a GLSL default-parameter test.
The delta introduces no new correctness issues. I verified the expanded suites pass (shellless, parsers, glslGenerator, and the eslint-plugin rules) and that pnpm --filter typegpu test:types and pnpm --filter @typegpu/gl test:types are green. This review is left non-approving because the previously reported unstable_obfuscate gap for param.default (thread still open) remains unaddressed; that is the only outstanding concern.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Allows 'use gpu' functions to declare defaulted identifier parameters, e.g. (t: number, cyclic = false) => ... - tinyest: FuncParameter identifier variant carries an optional default Expression. - tinyest-for-wgsl: AssignmentPattern params (with an Identifier target) transpile their default expression; identifiers used only in defaults are gathered as externals, other params are not. - typegpu: when a call site provides fewer arguments than declared params, the trailing defaults are prepended to the function body as const statements, so each arity resolves to its own concrete overload. - eslint-plugin: no-unsupported-syntax no longer flags defaulted identifier params, still flags destructuring defaults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Moves default value extraction into functionParts.ts and transpiles defaults in createParser's transpileFn, so the acorn, babel and legacy parsers all support them.
…fined` - Default values of omitted parameters are passed to `functionDefinition` instead of being prepended to the tinyest body. Defaults known at comptime are inlined at every use; only runtime defaults are declared at the top of the function body. - Passing `undefined` to a shellless function's defaulted parameter is treated as omitting it (`fn(a, undefined)` resolves the same as `fn(a)`). - Default values must have a known WGSL data type. - Shellless functions whose every parameter has a default value can be resolved directly. - The parser no longer mutates params after creating them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b835d75 to
b33932c
Compare
There was a problem hiding this comment.
Important
The omitted-parameter rework is mostly correct and well-tested, but this delta introduces one correctness regression and a notable behavior change. The previously reported unstable_obfuscate gap for param.default also remains unaddressed.
Reviewed changes
Since ef6a513, commit b835d75 reworks omitted parameters:
ParamDefault→OmittedParam(optionaldefault), threaded throughresolutionCtxand both generators._omittedParamsnow declares defaults as local variables instead of inlining comptime-known values; optional params without defaults bind to comptimeundefined.- Adds
===/!==folding againstundefinedfor runtime operands. ShelllessRepository.getallowsundefinedfor identifier params and trims trailingVoids; direct resolution still requires a default on every param.- Tests updated to expect declarations.
Findings
- [Correctness regression] Reassigning a defaulted comptime parameter emits invalid WGSL. Routing defaults through
_constDeclarationbinds a comptime scalar with originlocal-defwhile emitting aconstdeclaration, so a body assignment (e.g.b = b + 1) passes mutation validation and generatesb = ...;againstconst b = ...;. Before this delta the comptime default was bound with a non-mutable origin and reassignment errored clearly. See inline. - [Behavior change] Defaults are no longer comptime-known. The prior design inlined comptime defaults and pruned branches like
if (cyclic = false); now they are ordinary local declarations. Functions whose branches return different types now fail return-type unification, and comptime-only constructs consuming a defaulted param (array lengths, unroll bounds,std.isKnownAtComptime) no longer work. This appears intentional per the renamed test, but it reduces capability and should be confirmed/documented. - [Minor] Direct resolution is inconsistent for optional params. An all-optional function
(t: number, period?: number)cannot be resolved directly even though call sites allow omission; the "expects arguments" error is misleading. See inline. - Obfuscation gap remains.
unplugin-typegpu'sobfuscate()still only rewritesparam.name, notparam.default, and this delta does not touch it. Declaring defaults as locals does not mitigate it because resolution still reads the unrewritten identifier. The open thread remains the blocking concern.
Tests: shellless (31), glslGenerator (102), wgslGenerator (118), tinyest-for-wgsl parsers (30), and pnpm --filter typegpu test:types all pass. I also probed the two regressions above.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
b33932c to
aa96300
Compare
There was a problem hiding this comment.
The rebased head (b33932c) has a byte-identical incremental delta to the previously reviewed b835d75, so the findings from the prior review are unchanged. Adding the inline anchors referenced there:
- Reassigning a defaulted comptime parameter emits invalid WGSL (
wgslGenerator.ts:374). - Direct resolution is inconsistent for all-optional params (
shellless.ts:27).
The open unstable_obfuscate thread also remains unresolved and still blocks approval.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta — the only change since
b33932cis a test refactor inglslGenerator.test.ts(arrow functions →functiondeclarations,std.add/std.mul→ operators). The GLSL suite still passes (102/102).
The prior findings remain unresolved and still block approval: the invalid-WGSL regression when reassigning a defaulted comptime parameter (wgslGenerator.ts:374) and the open unstable_obfuscate gap for param.default.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
aa96300 to
2431130
Compare
There was a problem hiding this comment.
ℹ️ No new issues in this delta — the added shelled-function guard is correct, and the remaining test edits are cosmetic.
Reviewed changes
Since the prior pullfrog review (aa96300), only two files changed:
- Shelled functions reject defaults:
resolveFunctionnow derivesshelllessfromreturnType === undefinedand throws when a shelled function declares an identifier parameter with a default value, instead of silently injecting the default into the body. - Tests updated: the shelled-default case now asserts the new resolution error, and the default-parameter tests were converted from arrow functions to
functiondeclarations.
I verified the guard's discriminator: only createShelllessImpl passes an undefined return type, while tgpu.fn, entry functions, and auto-IO functions always supply one, so no shelled resolution path is misclassified. shellless passes 31/31.
Approval is withheld only because the earlier unstable_obfuscate gap for param.default remains open and unaddressed; there are no new findings from this delta.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
2431130 to
77085ef
Compare
…al parameters Adapts default parameter values to the approach described in software-mansion#3277: - Default values are declared at the top of the function body instead of being inlined when comptime-known. - Parameters omitted without a default (optional parameters) are comptime-known `undefined`. - Comparing a runtime value against `undefined` is folded at comptime, allowing branches to be pruned. - Default parameter values are only supported in shellless functions. - Default parameter values are obfuscated along with the rest of the function. - `undefined` is resolved through externals like any other identifier, so it keeps working when renamed by obfuscation.
77085ef to
ac48079
Compare
There was a problem hiding this comment.
ℹ️ No new issues in this delta — the obfuscation gap is fixed and the new
undefinedhandling is correct.
Reviewed changes
Since the prior pullfrog review (2431130, with a test-only delta at 77085ef):
- Fixed obfuscation of parameter defaults:
obfuscate()now walksparam.defaultwithobf(), so defaults referencing other params or externals are rewritten consistently. New coverage:(param1, param2 = param1 * ext.value). - Handled obfuscated/renamed
undefined:coerceToSnippet(undefined)now yields aVoidsnippet and the_identifierspecial case for the literal'undefined'was removed, so an external holdingundefined(e.g. renamed by obfuscation) folds like literalundefined. Newtreats externals holding undefined like undefinedtest. - Tests:
generationHelpers.test.tsupdated for the Void result;shellless.test.tsgains the renamed-undefined case.
( ◦°^°◦) 🏴☠️ I swept the new commits with me spyglass, and the obfuscation gap be patched clean, aye!
Approval remains withheld on the previously reported reassignment regression: _omittedParams still emits const b = 2; while binding it with the mutable local-def origin, so b = b + 1 in the body generates b = (b + 1i); against a const (reproduced on ac48079). Not re-raising it as a new inline comment since its root cause predates this delta.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Allows 'use gpu' functions to declare defaulted identifier parameters, e.g. (t: number, cyclic = false) => ...