diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
index 0b68b42d..885bdc01 100644
--- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
@@ -1,8 +1,93 @@
-import { compile } from '../../src'
+import {
+  parse,
+  transform,
+  ExpressionNode,
+  ElementNode,
+  DirectiveNode
+} from '../../src'
+import { transformFor } from '../..//src/transforms/vFor'
+import { transformExpression } from '../../src/transforms/transformExpression'
 
-test(`should work`, () => {
-  const { code } = compile(`
{{ foo }} bar
`, {
-    prefixIdentifiers: true
+function parseWithExpressionTransform(template: string) {
+  const ast = parse(template)
+  transform(ast, {
+    prefixIdentifiers: true,
+    nodeTransforms: [transformFor, transformExpression]
   })
-  expect(code).toContain(`foo`)
+  return ast.children[0]
+}
+
+describe('compiler: expression transform', () => {
+  test('interpolation (root)', () => {
+    const node = parseWithExpressionTransform(`{{ foo }}`) as ExpressionNode
+    expect(node.content).toBe(`_ctx.foo`)
+  })
+
+  test('interpolation (children)', () => {
+    const node = parseWithExpressionTransform(
+      `{{ foo }}
`
+    ) as ElementNode
+    expect((node.children[0] as ExpressionNode).content).toBe(`_ctx.foo`)
+  })
+
+  test('directive value', () => {
+    const node = parseWithExpressionTransform(
+      ``
+    ) as ElementNode
+    expect((node.props[0] as DirectiveNode).arg!.content).toBe(`arg`)
+    expect((node.props[0] as DirectiveNode).exp!.content).toBe(`_ctx.baz`)
+  })
+
+  test('dynamic directive arg', () => {
+    const node = parseWithExpressionTransform(
+      ``
+    ) as ElementNode
+    expect((node.props[0] as DirectiveNode).arg!.content).toBe(`_ctx.arg`)
+    expect((node.props[0] as DirectiveNode).exp!.content).toBe(`_ctx.baz`)
+  })
+
+  test('should prefix complex expressions', () => {
+    const node = parseWithExpressionTransform(
+      `{{ foo(baz + 1, { key: kuz }) }}`
+    ) as ExpressionNode
+    // should parse into compound expression
+    expect(node.children).toMatchObject([
+      { content: `_ctx.foo` },
+      `(`,
+      { content: `_ctx.baz` },
+      ` + 1, { key: `,
+      { content: `_ctx.kuz` },
+      ` })`
+    ])
+  })
+
+  // TODO FIXME
+  test('should not prefix v-for aliases', () => {
+    // const node = parseWithExpressionTransform(`{{ { foo } }}`) as ExpressionNode
+    // expect(node.children).toMatchObject([
+    //   `{ foo: `,
+    //   { content: `_ctx.foo` },
+    //   ` }`
+    // ])
+  })
+
+  test('should prefix id outside of v-for', () => {})
+
+  test('nested v-for', () => {})
+
+  test('should not prefix whitelisted globals', () => {})
+
+  test('should not prefix id of a function declaration', () => {})
+
+  test('should not prefix params of a function expression', () => {
+    // also test object + array destructure
+  })
+
+  test('should not prefix an object property key', () => {})
+
+  test('should prefix a computed object property key', () => {})
+
+  test('should prefix object property shorthand value', () => {})
+
+  test('should not prefix id in a member expression', () => {})
 })
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index 0259c3bb..e07a20f7 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -294,17 +294,18 @@ function genExpressionAsPropertyKey(
   node: ExpressionNode,
   context: CodegenContext
 ) {
-  if (node.children) {
-    return genCompoundExpression(node, context)
-  }
-  if (node.isStatic) {
+  const { push } = context
+  const { content, children, isStatic } = node
+  if (children) {
+    push(`[`)
+    genCompoundExpression(node, context)
+    push(`]`)
+  } else if (isStatic) {
     // only quote keys if necessary
-    const text = /^\d|[^\w]/.test(node.content)
-      ? JSON.stringify(node.content)
-      : node.content
-    context.push(text, node)
+    const text = /^\d|[^\w]/.test(content) ? JSON.stringify(content) : content
+    push(text, node)
   } else {
-    context.push(`[${node.content}]`, node)
+    push(`[${content}]`, node)
   }
 }
 
diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts
index adc9ee1c..55c24b53 100644
--- a/packages/compiler-core/src/transforms/transformExpression.ts
+++ b/packages/compiler-core/src/transforms/transformExpression.ts
@@ -74,11 +74,13 @@ export function processExpression(
   walk(ast, {
     enter(node, parent) {
       if (node.type === 'Identifier') {
-        if (ids.indexOf(node) === -1) {
+        if (
+          ids.indexOf(node) === -1 &&
+          !knownIds[node.name] &&
+          shouldPrefix(node, parent)
+        ) {
+          node.name = `_ctx.${node.name}`
           ids.push(node)
-          if (!knownIds[node.name] && shouldPrefix(node, parent)) {
-            node.name = `_ctx.${node.name}`
-          }
         }
       } else if (isFunction(node)) {
         node.params.forEach(p =>
@@ -123,11 +125,13 @@ export function processExpression(
       })
     )
     if (i === ids.length - 1 && id.end < full.length - 1) {
-      children.push(full.slice(id.end))
+      children.push(full.slice(id.end - 1))
     }
   })
 
-  node.children = children
+  if (children.length) {
+    node.children = children
+  }
 }
 
 const globals = new Set(