Skip to content

compiler-core: <template v-for> + non-identifier :key + v-memo crashes with "Cannot read properties of undefined (reading 'trim')" #14859

@rsauget

Description

@rsauget

Vue version

3.5.32 — also reproduces on every 3.5.13 through 3.5.34 (current latest), and on 3.6.0-beta.12. Last working version is 3.5.12.

Link to minimal reproduction

https://play.vuejs.org/#eNp9UctOwzAQ/JXVnttUPE6hrQSoBzgAAm51D1GyLW4T27KdUBT531k7tPRQ9eadmd2ZkXu8NybrWsIcp6600nhw5FszF6rUynmQnhoHM1j2IKscriCMYHheQ1jdCTWdDHu8wQOrTV144gngOEE3Xms7ExivgVTDVYGQ7+jnD85kxUA3bqjRDC0jthKYDvEpZwo179l5kEIIbByxZDQ58T0ZcITecY+13GRbpxW37KNeYKkbI2uyr8ZL7ikwh8RErqhr/f2cMG9bGh3w8ovK3Rl86/YRE/hmyZHtSOCR84XdkB/oxccL7fl9JBtdtTWrL5Dv5HTdxoyD7KFVFcc+0aW0T43R1ku1+XSLvSflDqVi0KgMSS+Qv/rxQvX/uDfZbdoTKmD4BW+dtWY=

Steps to reproduce

Compile the following SFC (the playground link above contains the exact same source):

<script setup>
const items = [{ id: 1 }, { id: 2 }];
</script>

<template>
  <template v-for="item in items" :key="item.id" v-memo="[item]">
    <span>{{ item.id }}</span>
  </template>
</template>

Equivalent programmatic repro (no SFC needed):

const { compileTemplate } = require('@vue/compiler-sfc');
compileTemplate({
  id: 'x',
  filename: 'x.vue',
  source: \`<template v-for=\"item in items\" :key=\"item.id\" v-memo=\"[item]\"><span>{{ item.id }}</span></template>\`,
});

What is expected?

The template compiles successfully, as it does in 3.5.12 and as the existing test compiler: v-memo transform > on template v-for does for a simple-identifier key (:key=\"x\").

What is actually happening?

compileTemplate throws:

TypeError: Cannot read properties of undefined (reading 'trim')
    at processExpression (.../@vue/compiler-core/dist/compiler-core.cjs.prod.js:4406:51)
    at .../compiler-core.cjs.prod.js:4840:31
    at processFor (.../compiler-core.cjs.prod.js:4997:36)

The three conditions required to trigger the crash:

  1. v-for is on a <template> element (not a regular element).
  2. :key is a non-simple-identifier expression (e.g. member expression item.id, call expression getId(item)).
  3. v-memo is present on the same <template>.

Removing any one of the three (move v-memo to a child, use a destructured/simple-identifier key, or drop v-memo) makes the bug disappear.

Affected versions

Version Result
3.5.12 OK
3.5.13 → 3.5.34 (current `latest`) THROW
3.6.0-beta.12 THROW

System Info

System:
  OS: macOS 26.4.1
  CPU: (14) arm64 Apple M4 Pro
  Memory: 2.81 GB / 48.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 24.13.0
  npm: 11.6.2
  pnpm: 10.30.3
Browsers:
  Chrome: 148.0.7778.168
npmPackages:
  vue: 3.5.32
  @vue/compiler-sfc: 3.5.32 (resolved through vue)
  @vitejs/plugin-vue: 6.0.6
  vite: 8.0.9

Any additional comments?

The regression was introduced by 99009ee (PR #12014, closes #12013). In transformFor (packages/compiler-core/src/transforms/vFor.ts), the key expression is now processed eagerly whenever memo && keyExp && isDirKey:

if (memo && keyExp && isDirKey) {
  if (!__BROWSER__) {
    keyProp.exp = keyExp = processExpression(
      keyExp as SimpleExpressionNode,
      context,
    )
  }
}

For a non-simple-identifier expression like item.id, processExpression returns a CompoundExpression, which has no .content property.

A few lines below, the isTemplate branch processes the same expression a second time:

if (isTemplate) {
  if (memo) {
    memo.exp = processExpression(memo.exp, context)
  }
  if (keyProperty && keyProp.type !== NodeTypes.ATTRIBUTE) {
    keyProperty.value = processExpression(keyProperty.value, context)
  }
}

This second call hits the early guard !node.content.trim() in processExpression, where node.content is now undefined (compound expressions don't carry it), hence the TypeError.

Possible fixes:

  • Skip the second processExpression call for the key inside the isTemplate branch when the eager call already happened (memo && isDirKey).
  • Or short-circuit processExpression when it receives a node that is not a SimpleExpression.

Metadata

Metadata

Assignees

No one assigned

    Labels

    🔨 p3-minor-bugPriority 3: this fixes a bug, but is an edge case that only affects very specific usage.scope: compiler

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions