Description
Issue description
Functions in JS and TS can be called with template strings as their arguments, which passes the literal parts as an array and the variables inserted into the pattern as vaargs.
I've been using this in LÖVE2D for shaders, with this simple function that pieces together the bits of the shader code and the variables evaluated at runtime into a single string then passed into the newShader
function.
export const glsl = (code: TemplateStringsArray, ...vars: unknown[]) => {
const len = code.length
if (len === 1) return code[0]
let i = 0
let buff = ''
do {
buff += code[i] + tostring(vars[i])
} while (++i < len - 1)
buff += code[i]
return buff
}
const code = glsl`
vec4 effect(vec4 colour, Image tex, vec2 texpos, vec2 scrpos) {
vec4 r;
// shader logic ...
// Since this is a template string, can conveniently pull variables from the scope with ${}
return r
}
`
const shader = love.graphics.newShader()
It's great because it pairs with glsl-literal to provide syntactic highlighting inside my TS sources.
Wanting to experiment with this pattern, I tried to use it for simple tile map creation with a factory that would return a function that works similarly. Something along the lines of
const buildMap = (displaySize: WH, cellSize: WH) => {
if (displaySize.width % cellSize.width !== 0)
error('Cell width is not an exact denominator of display width')
if (displaySize.height % cellSize.height !== 0)
error('Cell height is not an exact denominator of display height')
return ((template: TemplateStringsArray) => {
const canvas = love.graphics.newCanvas()
const str = template.raw.join().replace('%s', '') //strip all whitespaces
const map: Nullable<Rectangle>[][] = []
// Build the tile map and stuff
const update = () => {/* ... */}
const draw = () => { /* ... */ }
return {
map,
update,
draw,
}
})
}
const { map, update, draw } = buildMap({width: 10, height: 10}, {width: 2, height: 2})/* CRASHES HERE */`
+++++
+...+
+.#.+
+...+
+++++
`
But TSTL doesn't seem to like immediately calling the returned function. It does however work if you take the function returned by the factory and then use the template literal call patten with it
const mapLambda = buildMap({width: 10, height: 10}, {width: 2, height: 2})
const { map, update, draw } = mapLambda`
+++++
+...+
+.#.+
+...+
+++++
`
Minimal reproduction
Here's a minimal reproduction setup:
package.json
:
{
"devDependencies": {
"typescript": "^5.8.2",
"typescript-to-lua": "^1.31.1"
}
}
reproduction.ts
declare function print(...msg: string[]): void
// Note: crashes regardless of using the `function` keyword or arrow pattern.
const factory = () => {
return (template: TemplateStringsArray) => {
for (let part of template) print(part)
}
}
factory()`Some string template`
Stack trace:
PS [REDACTED]> pnpm tstl
[REDACTED]\node_modules\.pnpm\[email protected]\node_modules\typescript\lib\typescript.js:126991
throw e;
^
Error: Unsupported LeftHandSideExpression kind: CallExpression
at transformContextualCallExpression ([REDACTED]\node_modules\.pnpm\[email protected][email protected]\node_modules\typescript-to-lua\dist\transformation\visitors\call.js:120:15)
at transformTaggedTemplateExpression ([REDACTED]\node_modules\.pnpm\[email protected][email protected]\node_modules\typescript-to-lua\dist\transformation\visitors\template.js:71:61)
at TransformationContext.transformNodeRaw ([REDACTED]\node_modules\.pnpm\[email protected][email protected]\node_modules\typescript-to-lua\dist\transformation\context\context.js:67:24)
at TransformationContext.transformExpression ([REDACTED]\node_modules\.pnpm\[email protected][email protected]\node_modules\typescript-to-lua\dist\transformation\context\context.js:83:29)
at transformExpressionStatement ([REDACTED]\node_modules\.pnpm\[email protected][email protected]\node_modules\typescript-to-lua\dist\transformation\visitors\expression-statement.js:18:36)
at TransformationContext.transformNodeRaw ([REDACTED]\node_modules\.pnpm\[email protected][email protected]\node_modules\typescript-to-lua\dist\transformation\context\context.js:67:24)
at TransformationContext.transformNode ([REDACTED]\repro\node_modules\.pnpm\[email protected][email protected]\node_modules\typescript-to-lua\dist\transformation\context\context.js:47:56)
at [REDACTED]\node_modules\.pnpm\[email protected][email protected]\node_modules\typescript-to-lua\dist\transformation\context\context.js:102:37
at Array.flatMap (<anonymous>)
at TransformationContext.transformStatements ([REDACTED]\node_modules\.pnpm\[email protected][email protected]\node_modules\typescript-to-lua\dist\transformation\context\context.js:100:45)
Node.js v24.0.2