Skip to content

[BUG] Crash when using template literal call pattern on a returned function #1637

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
SirWrexes opened this issue May 28, 2025 · 0 comments · Fixed by #1639
Closed

[BUG] Crash when using template literal call pattern on a returned function #1637

SirWrexes opened this issue May 28, 2025 · 0 comments · Fixed by #1639
Labels
bug scope: transformation Transformation of TS to Lua

Comments

@SirWrexes
Copy link

SirWrexes commented May 28, 2025

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\typescript@5.8.3\node_modules\typescript\lib\typescript.js:126991
      throw e;
      ^

Error: Unsupported LeftHandSideExpression kind: CallExpression
    at transformContextualCallExpression ([REDACTED]\node_modules\.pnpm\typescript-to-lua@1.31.1_typescript@5.8.3\node_modules\typescript-to-lua\dist\transformation\visitors\call.js:120:15)
    at transformTaggedTemplateExpression ([REDACTED]\node_modules\.pnpm\typescript-to-lua@1.31.1_typescript@5.8.3\node_modules\typescript-to-lua\dist\transformation\visitors\template.js:71:61)
    at TransformationContext.transformNodeRaw ([REDACTED]\node_modules\.pnpm\typescript-to-lua@1.31.1_typescript@5.8.3\node_modules\typescript-to-lua\dist\transformation\context\context.js:67:24)
    at TransformationContext.transformExpression ([REDACTED]\node_modules\.pnpm\typescript-to-lua@1.31.1_typescript@5.8.3\node_modules\typescript-to-lua\dist\transformation\context\context.js:83:29)
    at transformExpressionStatement ([REDACTED]\node_modules\.pnpm\typescript-to-lua@1.31.1_typescript@5.8.3\node_modules\typescript-to-lua\dist\transformation\visitors\expression-statement.js:18:36)
    at TransformationContext.transformNodeRaw ([REDACTED]\node_modules\.pnpm\typescript-to-lua@1.31.1_typescript@5.8.3\node_modules\typescript-to-lua\dist\transformation\context\context.js:67:24)
    at TransformationContext.transformNode ([REDACTED]\repro\node_modules\.pnpm\typescript-to-lua@1.31.1_typescript@5.8.3\node_modules\typescript-to-lua\dist\transformation\context\context.js:47:56)
    at [REDACTED]\node_modules\.pnpm\typescript-to-lua@1.31.1_typescript@5.8.3\node_modules\typescript-to-lua\dist\transformation\context\context.js:102:37
    at Array.flatMap (<anonymous>)
    at TransformationContext.transformStatements ([REDACTED]\node_modules\.pnpm\typescript-to-lua@1.31.1_typescript@5.8.3\node_modules\typescript-to-lua\dist\transformation\context\context.js:100:45)

Node.js v24.0.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug scope: transformation Transformation of TS to Lua
Projects
None yet
2 participants