Skip to content

feat: Support default parameter values in TGSL functions - #2870

Open
wcandillon wants to merge 3 commits into
software-mansion:mainfrom
wcandillon:default-param-values
Open

wcandillon wants to merge 3 commits into
software-mansion:mainfrom
wcandillon:default-param-values

Conversation

@wcandillon

Copy link
Copy Markdown
Contributor

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.

Copilot AI lite review requested due to automatic review settings August 18, 2026 21:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 tinyest function parameter nodes to optionally carry a default Expression.
  • Teach tinyest-for-wgsl to 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.

Comment on lines +449 to +453
// 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) {
Comment thread packages/typegpu/src/resolutionCtx.ts Outdated
Comment on lines +722 to +726
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) {

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: FuncParameter identifier variant now carries an optional default?: Expression (packages/tinyest/src/nodes.ts).
  • tinyest-for-wgsl: extractFunctionParts accepts AssignmentPattern params with an Identifier target, returns them aligned with a new paramDefaults array, and transpileFn attaches 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 const statements, giving each arity its own concrete overload (packages/typegpu/src/resolutionCtx.ts).
  • eslint-plugin: no-unsupported-syntax stops 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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment on lines +449 to +454
// 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) {
param.default = transpile(ctx, defaultNode) as tinyest.Expression;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new default field on FuncParameter is not rewritten by obfuscate() in packages/unplugin-typegpu/src/core/obfuscate.ts. Obfuscation renames param names, body identifiers, and externalNames keys, but the default expression attached here is a tinyest expression carrying pre-obfuscation identifiers. Under unstable_obfuscate, (alpha, beta = alpha) => ... becomes params [{name:'a'},{name:'b', default:'alpha'}] with a body that references the obfuscated names, so the WGSL emitted for the default overload (const b = alpha;) lands on a stale identifier that resolves as an unknown external instead of the renamed argument. Defaults referencing external names have the same problem. Please thread the default expression through the obfuscator (e.g. walk param.default with the existing obf() traversal when rebuilding identifier params) or reject defaults under obfuscation.

@iwoplaza

Copy link
Copy Markdown
Collaborator

@wcandillon Thanks for the contribution! 💜
We have a couple of behaviors we'd like to enforce in this feature, I'll update it inline on this PR

@iwoplaza
iwoplaza force-pushed the default-param-values branch from 4b14390 to a46cf3f Compare October 21, 2026 16:19
wcandillon and others added 3 commits October 23, 2026 21:57
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>
@iwoplaza
iwoplaza force-pushed the default-param-values branch from a46cf3f to ef6a513 Compare October 23, 2026 20:03

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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._paramDefaults evaluates each omitted parameter's default; when it is comptime-known it is bound via ctx.defineVariable and inlined at every use (no statement emitted), while runtime defaults emit a declaration through the newly extracted _constDeclaration helper.
  • undefined marks omission: ShelllessRepository.get maps an argument snippet whose value === undefined to Void (throwing when the parameter has no default) and trims trailing Voids, so fn(a, undefined) resolves like fn(a).
  • paramDefaults threaded to codegen: resolveFunction skips Void arg types, collects omitted identifier params with defaults into a ParamDefault[], and passes it through FunctionDefinitionOptions to both the WGSL and GLSL generators.
  • Parser refactor: extractFunctionParts now returns raw paramDefaults and transpileFn attaches 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, undefined omission, 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.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants