Rule 8.18
On this page

Rule 8.18

Chapter 8, Functions and entry points Test

A function whose parameter has a function type, written out (f: (x: f32) => f32) or through a type alias of one, takes a function: a function declared at the top of the file or of a namespace, a method, a static method, a constructor, a field that holds a function (Rule 8.16) and a local function (Rule 8.17). It is compiled once for each set of functions its calls hand it, as a generic function is once for each set of type arguments (Rule 8.9), and in each copy a call of the parameter calls the function handed over (apply_sq, apply_run_f, Swarm_each_sq, run_twice_run_f). A call hands a function over by its name (a module function, a local function, or a parameter of function type of the function the call is in) or as an arrow function or a function expression written in the call, which is a local function of the calling body (Rule 8.17) and takes its parameter types, and its return type where it writes none, from the parameter’s type. What the function handed over captures, the copy takes as parameters too and passes on, by reference where the function writes it. A variable the copy and a function handed over both reach is passed once, as TypeScript has it once: a variable a local function and the function it is handed both capture, and the object a method is called on when a function handed over captures that very variable (this, or a local named as the object), which the copy then takes as its object, by reference where the caller may write it (Rule 8.10). A function handed over takes the parameter’s types and returns its type, or any type where the parameter’s returns void, whose value is not used; an arrow function may leave parameters off at the end, as TypeScript allows, and one whose body is an expression and whose type returns void runs the expression as a statement. Inside the function that takes it, a parameter of function type may only be called or handed on to another such parameter. A fold’s function (any(xs, (x) => x > k), zip(xs, ys, (a, b) => a * b)) is handed over the same way, its types read off the arrays, and an arrow function whose return type nothing gives returns what its body does. An array’s method (xs.map(f), xs.forEach(f), xs.some(p), xs.every(p), xs.reduce(f, init), surface §63) takes a function the same way, a function the file declares taking as few of the arguments as it declares: each call is a call of a function of the module made once for each array type and function handed over (array_map_sq), a counted loop over the indices (Rule 7.5) that hands the function the element, its index as an i32 and the array, and for reduce the running value first, which has the type of the function’s first parameter where that is written and of the value to start from otherwise. The loop reads the array as it goes: a module variable, a module constant or a binding in place, the variable a function handed over captures through the one reference both use, and any other array by value, an index on the way to it read once. Of an array’s methods, one Array.prototype has beyond those five, map on an array with no size, whose value would be an array with none, reduce with no value to start from on an array with no size, which may be empty, a second argument to any but reduce (thisArg), and a function a method passes an array with no size to must be refused. A function type anywhere else (a return, a field, a variable), a parameter of function type on a setter or an entry point, an argument that would choose a function at run time (c ? f : g), a function whose copies would never end (one that hands itself a function it builds anew each time), and a call on a field or an element of a variable that a function handed over reaches, where one of the two may write it, which would be two references into one variable, must be refused.

The rule text and the parts under it are the compiler's own English, as the design document writes them.

Rationale

TypeScript code hands functions to functions all the time: a callback to a loop helper, a predicate to a search, an operator to a fold. The GPU has no function value, but every call names the function it hands over, so each call reaches exactly one function, and a copy for each is what the program means; the copies are a generic function’s instances by another key, and the captures, the TDZ and the cycle check are Rule 8.17’s and Rule 8.4’s. Before this rule a function type was “Unsupported type syntax” and an arrow function written as an argument “Unsupported expression”. A copy of a method has an object, and a copy of a local function has captures of its own; each is one more parameter of the copy, and where one of them and a function handed over reach one variable, the copy takes one reference to it, since two references into one variable are refused by WGSL where either is written (Alias Analysis) and would let the method read a stale copy where they are not. A setter’s value is given by an assignment, which hands it no function. An array’s method is a loop, and a method call is an expression, which may stand in an argument, beside && or in a loop’s condition, where no loop can be written in place; a function of the module is a call anywhere one may be, and every target and the CPU oracle run one already, so the IR gains no node. Unrolled, as the folds are, the code would grow with the array and would have no length to unroll to for a runtime-sized array, the one a compute kernel meets first. Before this rule every method of an array was refused.

Derives from

ECMAScript function objects (a function is a value a call hands over); ECMAScript Array.prototype.map and its four siblings, which read each element as the loop reaches it; TypeScript’s contextual typing of a function expression and its rule that a function with fewer parameters is assignable; WGSL’s Alias Analysis; Rule 8.4; Rule 8.9; Rule 8.10; Rule 8.16; Rule 8.17; surface §14, §26 and §63.

How it is verified

Checked by a test. A test, a gate script or a CI workflow names this rule, and the traceability check fails when a file listed below stops naming it.

Where the rule says the compiler enforces it:

TS8003 TYPE_MISMATCH for a function that does not fit ("add" takes 2 argument(s), and "(x: f32) => f32" passes 1, so it cannot be "f" of "apply".), TS8099 for a choice at run time ("f" of "apply" takes a function, which a call hands over by its name or as an arrow function written there; "c ? sq : cube" would choose one at run time, and a shader has no function value to choose with.), TS8020 FUNCTION_SHAPE for a parameter of function type where none may be ("f" takes a function, and "G.each" is a setter, whose value an assignment gives it: a shader has no function value to assign. Take the function in a method instead (Rule 8.18).), TS8002 for a function type anywhere else, TS8031 RECURSION for copies without end, and TS8099 for two references into one variable ("f" of "Inner.each" reaches "this", which holds "this.inner", the object the call is on, so the call would take two references into one variable, which WGSL refuses where either is written. Call it on a copy in a let, and assign the copy back if the call changes it (Rule 8.18).); for an array’s methods, TS8099 for another method (".filter" is not one of an array's methods here, which are map, forEach, some, every and reduce. …), for map on an array with no size and for a function handed that array, and TS8019 ARITY_MISMATCH for reduce with no value to start from on one and for a thisArg; pinned by src/compiler/ts/higher-order.test.ts and src/compiler/ts/array-methods.test.ts, which hold WGSL, GLSL ES 3.00, the CPU oracle, the codegen and the debugger to one value for each form, on a fixed-size array and on a runtime-sized storage one; examples/higher-order.shade.ts and examples/array-methods.shade.ts in the compile gate; the light-list journey on WebGPU.

The files that verify it at commit 26de7be8, each at the first line that names the rule:

Explained in

The sections of the surface document that explain this rule, at commit 26de7be8:

Error codes that enforce it

The diagnostic codes the rule names under Enforced by, or whose registry text names the rule:

TS8002 UNKNOWN_TYPE
A type name nothing declares, said once for the file wherever it is written (a parameter, a claim, a type argument, B<vec3<Foo>> included, an alias, an implements clause, the base a class or an interface extends, a body no call lowers), a type the library declares for TypeScript alone (Number) where the file declares none of its name, or a type this surface does not take in a position the mapper reads (Rule 2.1).
TS8003 TYPE_MISMATCH
Two types that do not fit where they meet (an operator, a condition, a declaration, a return or an argument), or a constant the compiler folds that WGSL refuses: a shift amount past 31, a divisor of 0, clamp bounds that cross or an f32 past its range.
TS8019 ARITY_MISMATCH
Wrong number of arguments, elements, or fields at a call or constructor site.
TS8020 FUNCTION_SHAPE
A function declaration or parameter shape TypeShade does not support (missing name or body, optional/rest/destructured parameter).
TS8031 RECURSION
A call cycle: a function that reaches itself, directly or through other functions.
TS8099 UNSUPPORTED
UNSUPPORTED (TS8099) is the one deliberate exception to "sequential": it is the catch-all for a diagnostic whose site does not yet deserve its own code, so it stays parked past the sequential range instead of at its head.

See also

Source

The rule at commit 26de7be8:

Edit this page Report a problem