From 26d580b79ef2a73c5b40eea90bb32edfb41fbb6e Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Fri, 23 Jan 2026 11:07:42 -0800 Subject: [PATCH] [commit] Fix for nested optional chaining within other value blocks Fixes a longstanding issue where we didn't support code like `useFoo(value?.bar(), value?.bar()) ?? {}` - we would attempt to construct a ReactiveFunction, recursively processing the blocks, but the inner optional `value?.bar()` wouldn't match with what the outer optional was expecting to find. It's a one-line fix! Note: memoization in the examples is not ideal, but i've confirmed that it is not strictly related to the optional issue. --- .../ReactiveScopes/BuildReactiveFunction.ts | 2 +- ...ith-nullish-coalesce-with-method.expect.md | 109 ++++++++++++++ ...nals-with-nullish-coalesce-with-method.tsx | 20 +++ ...-optionals-with-nullish-coalesce.expect.md | 53 +++++++ ...chained-optionals-with-nullish-coalesce.ts | 17 +++ ...onal-ternary-logical-with-method.expect.md | 135 ++++++++++++++++++ ...d-optional-ternary-logical-with-method.tsx | 34 +++++ ...-nested-optional-ternary-logical.expect.md | 64 +++++++++ .../deeply-nested-optional-ternary-logical.ts | 24 ++++ ...ional-call-chain-in-logical-expr.expect.md | 37 ----- ...o-optional-call-chain-in-ternary.expect.md | 37 ----- ...nside-optional-chain-with-method.expect.md | 122 ++++++++++++++++ ...ical-inside-optional-chain-with-method.tsx | 22 +++ .../logical-inside-optional-chain.expect.md | 60 ++++++++ .../compiler/logical-inside-optional-chain.ts | 23 +++ ...-in-ternary-branches-with-method.expect.md | 134 +++++++++++++++++ ...ionals-in-ternary-branches-with-method.tsx | 23 +++ ...le-optionals-in-ternary-branches.expect.md | 61 ++++++++ .../multiple-optionals-in-ternary-branches.ts | 24 ++++ ...hain-in-logical-expr-with-method.expect.md | 85 +++++++++++ ...call-chain-in-logical-expr-with-method.tsx | 19 +++ ...ional-call-chain-in-logical-expr.expect.md | 40 ++++++ ...=> optional-call-chain-in-logical-expr.ts} | 1 + ...all-chain-in-ternary-with-method.expect.md | 87 +++++++++++ ...onal-call-chain-in-ternary-with-method.tsx | 19 +++ .../optional-call-chain-in-ternary.expect.md | 40 ++++++ ...y.ts => optional-call-chain-in-ternary.ts} | 1 + ...l-inside-logical-and-with-method.expect.md | 108 ++++++++++++++ ...ptional-inside-logical-and-with-method.tsx | 20 +++ .../optional-inside-logical-and.expect.md | 53 +++++++ .../compiler/optional-inside-logical-and.ts | 17 +++ ...al-inside-logical-or-with-method.expect.md | 92 ++++++++++++ ...optional-inside-logical-or-with-method.tsx | 20 +++ .../optional-inside-logical-or.expect.md | 53 +++++++ .../compiler/optional-inside-logical-or.ts | 17 +++ ...-optional-consequent-with-method.expect.md | 135 ++++++++++++++++++ ...inside-optional-consequent-with-method.tsx | 24 ++++ ...rnary-inside-optional-consequent.expect.md | 56 ++++++++ .../ternary-inside-optional-consequent.ts | 21 +++ 39 files changed, 1834 insertions(+), 75 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce-with-method.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce-with-method.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical-with-method.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical-with-method.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical.ts delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-logical-expr.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-ternary.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain-with-method.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain-with-method.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches-with-method.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches-with-method.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr-with-method.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr-with-method.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{error.todo-optional-call-chain-in-logical-expr.ts => optional-call-chain-in-logical-expr.ts} (95%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary-with-method.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary-with-method.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{error.todo-optional-call-chain-in-ternary.ts => optional-call-chain-in-ternary.ts} (95%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and-with-method.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and-with-method.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or-with-method.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or-with-method.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent-with-method.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent-with-method.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveFunction.ts index 2e8584050e..2574116234 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveFunction.ts @@ -1104,7 +1104,7 @@ class Driver { loc, }; return { - block: init.fallthrough, + block: final.block, value: sequence, place: final.place, id: final.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce-with-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce-with-method.expect.md new file mode 100644 index 0000000000..a770e7aa9f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce-with-method.expect.md @@ -0,0 +1,109 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +// Test chained optional property access with nullish coalescing and method call +function Component(props: {obj: {a?: {b?: {getC(): string}}} | null}) { + 'use memo'; + const result = props.obj?.a?.b?.getC() ?? 'default'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{obj: {a: {b: {getC: () => 'deep'}}}}], + sequentialRenders: [ + {obj: {a: {b: {getC: () => 'deep'}}}}, + {obj: null}, + {obj: {a: null}}, + {obj: {a: {b: null}}}, + {obj: {a: {b: {getC: () => 'other'}}}}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +// Test chained optional property access with nullish coalescing and method call +function Component(props) { + "use memo"; + const $ = _c(4); + let t0; + if ($[0] !== props.obj?.a?.b) { + t0 = props.obj?.a?.b?.getC() ?? "default"; + $[0] = props.obj?.a?.b; + $[1] = t0; + } else { + t0 = $[1]; + } + const result = t0; + let t1; + if ($[2] !== result) { + t1 = ; + $[2] = result; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + obj: { + a: { + b: { + getC: () => { + return "deep"; + }, + }, + }, + }, + }, + ], + sequentialRenders: [ + { + obj: { + a: { + b: { + getC: () => { + return "deep"; + }, + }, + }, + }, + }, + { obj: null }, + { obj: { a: null } }, + { obj: { a: { b: null } } }, + { + obj: { + a: { + b: { + getC: () => { + return "other"; + }, + }, + }, + }, + }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"value":"deep"}
+
{"value":"default"}
+
{"value":"default"}
+
{"value":"default"}
+
{"value":"other"}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce-with-method.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce-with-method.tsx new file mode 100644 index 0000000000..9c4048e8c8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce-with-method.tsx @@ -0,0 +1,20 @@ +import {Stringify} from 'shared-runtime'; + +// Test chained optional property access with nullish coalescing and method call +function Component(props: {obj: {a?: {b?: {getC(): string}}} | null}) { + 'use memo'; + const result = props.obj?.a?.b?.getC() ?? 'default'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{obj: {a: {b: {getC: () => 'deep'}}}}], + sequentialRenders: [ + {obj: {a: {b: {getC: () => 'deep'}}}}, + {obj: null}, + {obj: {a: null}}, + {obj: {a: {b: null}}}, + {obj: {a: {b: {getC: () => 'other'}}}}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce.expect.md new file mode 100644 index 0000000000..8b8dce7597 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// Test chained optional property access with nullish coalescing +function Component(props: {obj: {a?: {b?: {c: string}}} | null}) { + 'use memo'; + return props.obj?.a?.b?.c ?? 'default'; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{obj: {a: {b: {c: 'deep'}}}}], + sequentialRenders: [ + {obj: {a: {b: {c: 'deep'}}}}, + {obj: null}, + {obj: {a: null}}, + {obj: {a: {b: null}}}, + {obj: {a: {b: {c: 'other'}}}}, + ], +}; + +``` + +## Code + +```javascript +// Test chained optional property access with nullish coalescing +function Component(props) { + "use memo"; + return props.obj?.a?.b?.c ?? "default"; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ obj: { a: { b: { c: "deep" } } } }], + sequentialRenders: [ + { obj: { a: { b: { c: "deep" } } } }, + { obj: null }, + { obj: { a: null } }, + { obj: { a: { b: null } } }, + { obj: { a: { b: { c: "other" } } } }, + ], +}; + +``` + +### Eval output +(kind: ok) "deep" +"default" +"default" +"default" +"other" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce.ts new file mode 100644 index 0000000000..20852905f6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/chained-optionals-with-nullish-coalesce.ts @@ -0,0 +1,17 @@ +// Test chained optional property access with nullish coalescing +function Component(props: {obj: {a?: {b?: {c: string}}} | null}) { + 'use memo'; + return props.obj?.a?.b?.c ?? 'default'; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{obj: {a: {b: {c: 'deep'}}}}], + sequentialRenders: [ + {obj: {a: {b: {c: 'deep'}}}}, + {obj: null}, + {obj: {a: null}}, + {obj: {a: {b: null}}}, + {obj: {a: {b: {c: 'other'}}}}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical-with-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical-with-method.expect.md new file mode 100644 index 0000000000..b58187a175 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical-with-method.expect.md @@ -0,0 +1,135 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +// Test deeply nested: optional in ternary condition with logical fallback using method calls +function Component(props: { + value: {getFlag(): boolean; getData(): string} | null; + fallback: string; +}) { + 'use memo'; + const value = props.value; + const result = (value?.getFlag() ? value?.getData() : null) ?? props.fallback; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + value: {getFlag: () => true, getData: () => 'success'}, + fallback: 'default', + }, + ], + sequentialRenders: [ + { + value: {getFlag: () => true, getData: () => 'success'}, + fallback: 'default', + }, + { + value: {getFlag: () => false, getData: () => 'success'}, + fallback: 'default', + }, + {value: null, fallback: 'default'}, + {value: {getFlag: () => true, getData: () => 'other'}, fallback: 'default'}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +// Test deeply nested: optional in ternary condition with logical fallback using method calls +function Component(props) { + "use memo"; + const $ = _c(5); + + const value = props.value; + let t0; + if ($[0] !== props.fallback || $[1] !== value) { + t0 = (value?.getFlag() ? value?.getData() : null) ?? props.fallback; + $[0] = props.fallback; + $[1] = value; + $[2] = t0; + } else { + t0 = $[2]; + } + const result = t0; + let t1; + if ($[3] !== result) { + t1 = ; + $[3] = result; + $[4] = t1; + } else { + t1 = $[4]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + value: { + getFlag: () => { + return true; + }, + getData: () => { + return "success"; + }, + }, + fallback: "default", + }, + ], + + sequentialRenders: [ + { + value: { + getFlag: () => { + return true; + }, + getData: () => { + return "success"; + }, + }, + fallback: "default", + }, + { + value: { + getFlag: () => { + return false; + }, + getData: () => { + return "success"; + }, + }, + fallback: "default", + }, + { value: null, fallback: "default" }, + { + value: { + getFlag: () => { + return true; + }, + getData: () => { + return "other"; + }, + }, + fallback: "default", + }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"value":"success"}
+
{"value":"default"}
+
{"value":"default"}
+
{"value":"other"}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical-with-method.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical-with-method.tsx new file mode 100644 index 0000000000..64f21291ee --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical-with-method.tsx @@ -0,0 +1,34 @@ +import {Stringify} from 'shared-runtime'; + +// Test deeply nested: optional in ternary condition with logical fallback using method calls +function Component(props: { + value: {getFlag(): boolean; getData(): string} | null; + fallback: string; +}) { + 'use memo'; + const value = props.value; + const result = (value?.getFlag() ? value?.getData() : null) ?? props.fallback; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + value: {getFlag: () => true, getData: () => 'success'}, + fallback: 'default', + }, + ], + sequentialRenders: [ + { + value: {getFlag: () => true, getData: () => 'success'}, + fallback: 'default', + }, + { + value: {getFlag: () => false, getData: () => 'success'}, + fallback: 'default', + }, + {value: null, fallback: 'default'}, + {value: {getFlag: () => true, getData: () => 'other'}, fallback: 'default'}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical.expect.md new file mode 100644 index 0000000000..cdaaae8c64 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +// Test deeply nested: optional in ternary condition with logical fallback +function Component(props: { + value: {flag: boolean; data: string} | null; + fallback: string; +}) { + 'use memo'; + const value = props.value; + return (value?.flag ? value?.data : null) ?? props.fallback; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: {flag: true, data: 'success'}, fallback: 'default'}], + sequentialRenders: [ + // flag true, returns data + {value: {flag: true, data: 'success'}, fallback: 'default'}, + // flag false, ternary returns null, falls back + {value: {flag: false, data: 'success'}, fallback: 'default'}, + // value is null, value?.flag is undefined/falsy, ternary returns null, falls back + {value: null, fallback: 'default'}, + // different data value + {value: {flag: true, data: 'other'}, fallback: 'default'}, + ], +}; + +``` + +## Code + +```javascript +// Test deeply nested: optional in ternary condition with logical fallback +function Component(props) { + "use memo"; + + const value = props.value; + return (value?.flag ? value?.data : null) ?? props.fallback; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: { flag: true, data: "success" }, fallback: "default" }], + sequentialRenders: [ + // flag true, returns data + { value: { flag: true, data: "success" }, fallback: "default" }, + // flag false, ternary returns null, falls back + { value: { flag: false, data: "success" }, fallback: "default" }, + // value is null, value?.flag is undefined/falsy, ternary returns null, falls back + { value: null, fallback: "default" }, + // different data value + { value: { flag: true, data: "other" }, fallback: "default" }, + ], +}; + +``` + +### Eval output +(kind: ok) "success" +"default" +"default" +"other" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical.ts new file mode 100644 index 0000000000..6c86f93e53 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-optional-ternary-logical.ts @@ -0,0 +1,24 @@ +// Test deeply nested: optional in ternary condition with logical fallback +function Component(props: { + value: {flag: boolean; data: string} | null; + fallback: string; +}) { + 'use memo'; + const value = props.value; + return (value?.flag ? value?.data : null) ?? props.fallback; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: {flag: true, data: 'success'}, fallback: 'default'}], + sequentialRenders: [ + // flag true, returns data + {value: {flag: true, data: 'success'}, fallback: 'default'}, + // flag false, ternary returns null, falls back + {value: {flag: false, data: 'success'}, fallback: 'default'}, + // value is null, value?.flag is undefined/falsy, ternary returns null, falls back + {value: null, fallback: 'default'}, + // different data value + {value: {flag: true, data: 'other'}, fallback: 'default'}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-logical-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-logical-expr.expect.md deleted file mode 100644 index 38bb5800a8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-logical-expr.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -import {useNoAlias} from 'shared-runtime'; - -function useFoo(props: {value: {x: string; y: string} | null}) { - const value = props.value; - return useNoAlias(value?.x, value?.y) ?? {}; -} - -export const FIXTURE_ENTRYPONT = { - fn: useFoo, - props: [{value: null}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Todo: Unexpected terminal kind `optional` for logical test block - -error.todo-optional-call-chain-in-logical-expr.ts:5:30 - 3 | function useFoo(props: {value: {x: string; y: string} | null}) { - 4 | const value = props.value; -> 5 | return useNoAlias(value?.x, value?.y) ?? {}; - | ^^^^^^^^ Unexpected terminal kind `optional` for logical test block - 6 | } - 7 | - 8 | export const FIXTURE_ENTRYPONT = { -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-ternary.expect.md deleted file mode 100644 index 897b29ce6a..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-ternary.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -import {useNoAlias} from 'shared-runtime'; - -function useFoo(props: {value: {x: string; y: string} | null}) { - const value = props.value; - return useNoAlias(value?.x, value?.y) ? {} : null; -} - -export const FIXTURE_ENTRYPONT = { - fn: useFoo, - props: [{value: null}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Todo: Unexpected terminal kind `optional` for ternary test block - -error.todo-optional-call-chain-in-ternary.ts:5:30 - 3 | function useFoo(props: {value: {x: string; y: string} | null}) { - 4 | const value = props.value; -> 5 | return useNoAlias(value?.x, value?.y) ? {} : null; - | ^^^^^^^^ Unexpected terminal kind `optional` for ternary test block - 6 | } - 7 | - 8 | export const FIXTURE_ENTRYPONT = { -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain-with-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain-with-method.expect.md new file mode 100644 index 0000000000..f4c4f11d7a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain-with-method.expect.md @@ -0,0 +1,122 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +// Test logical expression as part of optional chain base with method call +function Component(props: { + a: {x: {getY(): string} | null} | null; + b: {x: {getY(): string}} | null; +}) { + 'use memo'; + const result = (props.a || props.b)?.x?.getY(); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: null, b: {x: {getY: () => 'found'}}}], + sequentialRenders: [ + {a: null, b: {x: {getY: () => 'found'}}}, + {a: {x: {getY: () => 'first'}}, b: {x: {getY: () => 'second'}}}, + {a: null, b: null}, + {a: {x: null}, b: {x: {getY: () => 'second'}}}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +// Test logical expression as part of optional chain base with method call +function Component(props) { + "use memo"; + const $ = _c(5); + let t0; + if ($[0] !== props.a || $[1] !== props.b) { + t0 = (props.a || props.b)?.x?.getY(); + $[0] = props.a; + $[1] = props.b; + $[2] = t0; + } else { + t0 = $[2]; + } + const result = t0; + let t1; + if ($[3] !== result) { + t1 = ; + $[3] = result; + $[4] = t1; + } else { + t1 = $[4]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + a: null, + b: { + x: { + getY: () => { + return "found"; + }, + }, + }, + }, + ], + sequentialRenders: [ + { + a: null, + b: { + x: { + getY: () => { + return "found"; + }, + }, + }, + }, + { + a: { + x: { + getY: () => { + return "first"; + }, + }, + }, + b: { + x: { + getY: () => { + return "second"; + }, + }, + }, + }, + { a: null, b: null }, + { + a: { x: null }, + b: { + x: { + getY: () => { + return "second"; + }, + }, + }, + }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"value":"found"}
+
{"value":"first"}
+
{}
+
{}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain-with-method.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain-with-method.tsx new file mode 100644 index 0000000000..a733574aa1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain-with-method.tsx @@ -0,0 +1,22 @@ +import {Stringify} from 'shared-runtime'; + +// Test logical expression as part of optional chain base with method call +function Component(props: { + a: {x: {getY(): string} | null} | null; + b: {x: {getY(): string}} | null; +}) { + 'use memo'; + const result = (props.a || props.b)?.x?.getY(); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: null, b: {x: {getY: () => 'found'}}}], + sequentialRenders: [ + {a: null, b: {x: {getY: () => 'found'}}}, + {a: {x: {getY: () => 'first'}}, b: {x: {getY: () => 'second'}}}, + {a: null, b: null}, + {a: {x: null}, b: {x: {getY: () => 'second'}}}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain.expect.md new file mode 100644 index 0000000000..d0eed0ad01 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain.expect.md @@ -0,0 +1,60 @@ + +## Input + +```javascript +// Test logical expression as part of optional chain base +function Component(props: { + a: {x: {y: string} | null} | null; + b: {x: {y: string}} | null; +}) { + 'use memo'; + return (props.a || props.b)?.x?.y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: null, b: {x: {y: 'found'}}}], + sequentialRenders: [ + // a is null, uses b + {a: null, b: {x: {y: 'found'}}}, + // a is truthy, uses a + {a: {x: {y: 'first'}}, b: {x: {y: 'second'}}}, + // both null + {a: null, b: null}, + // a is truthy but a.x is null + {a: {x: null}, b: {x: {y: 'second'}}}, + ], +}; + +``` + +## Code + +```javascript +// Test logical expression as part of optional chain base +function Component(props) { + "use memo"; + return (props.a || props.b)?.x?.y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: null, b: { x: { y: "found" } } }], + sequentialRenders: [ + // a is null, uses b + { a: null, b: { x: { y: "found" } } }, + // a is truthy, uses a + { a: { x: { y: "first" } }, b: { x: { y: "second" } } }, + // both null + { a: null, b: null }, + // a is truthy but a.x is null + { a: { x: null }, b: { x: { y: "second" } } }, + ], +}; + +``` + +### Eval output +(kind: ok) "found" +"first" + diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain.ts new file mode 100644 index 0000000000..74a5ff88af --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/logical-inside-optional-chain.ts @@ -0,0 +1,23 @@ +// Test logical expression as part of optional chain base +function Component(props: { + a: {x: {y: string} | null} | null; + b: {x: {y: string}} | null; +}) { + 'use memo'; + return (props.a || props.b)?.x?.y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: null, b: {x: {y: 'found'}}}], + sequentialRenders: [ + // a is null, uses b + {a: null, b: {x: {y: 'found'}}}, + // a is truthy, uses a + {a: {x: {y: 'first'}}, b: {x: {y: 'second'}}}, + // both null + {a: null, b: null}, + // a is truthy but a.x is null + {a: {x: null}, b: {x: {y: 'second'}}}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches-with-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches-with-method.expect.md new file mode 100644 index 0000000000..244bcb5d81 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches-with-method.expect.md @@ -0,0 +1,134 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +// Test optional chaining with method calls in both branches of a ternary +function Component(props: { + a: {getX(): string} | null; + b: {getY(): string} | null; + cond: boolean; +}) { + 'use memo'; + const result = props.cond ? props.a?.getX() : props.b?.getY(); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {getX: () => 'hello'}, b: {getY: () => 'world'}, cond: true}], + sequentialRenders: [ + {a: {getX: () => 'hello'}, b: {getY: () => 'world'}, cond: true}, + {a: {getX: () => 'hello'}, b: {getY: () => 'world'}, cond: false}, + {a: null, b: {getY: () => 'world'}, cond: true}, + {a: {getX: () => 'hello'}, b: null, cond: false}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +// Test optional chaining with method calls in both branches of a ternary +function Component(props) { + "use memo"; + const $ = _c(6); + let t0; + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { + t0 = props.cond ? props.a?.getX() : props.b?.getY(); + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = t0; + } else { + t0 = $[3]; + } + const result = t0; + let t1; + if ($[4] !== result) { + t1 = ; + $[4] = result; + $[5] = t1; + } else { + t1 = $[5]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + a: { + getX: () => { + return "hello"; + }, + }, + b: { + getY: () => { + return "world"; + }, + }, + cond: true, + }, + ], + sequentialRenders: [ + { + a: { + getX: () => { + return "hello"; + }, + }, + b: { + getY: () => { + return "world"; + }, + }, + cond: true, + }, + { + a: { + getX: () => { + return "hello"; + }, + }, + b: { + getY: () => { + return "world"; + }, + }, + cond: false, + }, + { + a: null, + b: { + getY: () => { + return "world"; + }, + }, + cond: true, + }, + { + a: { + getX: () => { + return "hello"; + }, + }, + b: null, + cond: false, + }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"value":"hello"}
+
{"value":"world"}
+
{}
+
{}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches-with-method.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches-with-method.tsx new file mode 100644 index 0000000000..e7a685cdb3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches-with-method.tsx @@ -0,0 +1,23 @@ +import {Stringify} from 'shared-runtime'; + +// Test optional chaining with method calls in both branches of a ternary +function Component(props: { + a: {getX(): string} | null; + b: {getY(): string} | null; + cond: boolean; +}) { + 'use memo'; + const result = props.cond ? props.a?.getX() : props.b?.getY(); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {getX: () => 'hello'}, b: {getY: () => 'world'}, cond: true}], + sequentialRenders: [ + {a: {getX: () => 'hello'}, b: {getY: () => 'world'}, cond: true}, + {a: {getX: () => 'hello'}, b: {getY: () => 'world'}, cond: false}, + {a: null, b: {getY: () => 'world'}, cond: true}, + {a: {getX: () => 'hello'}, b: null, cond: false}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches.expect.md new file mode 100644 index 0000000000..44d76d3dce --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// Test optional chaining in both branches of a ternary +function Component(props: { + a: {x: string} | null; + b: {y: string} | null; + cond: boolean; +}) { + 'use memo'; + return props.cond ? props.a?.x : props.b?.y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {x: 'hello'}, b: {y: 'world'}, cond: true}], + sequentialRenders: [ + // cond=true, picks a?.x -> 'hello' + {a: {x: 'hello'}, b: {y: 'world'}, cond: true}, + // cond=false, picks b?.y -> 'world' + {a: {x: 'hello'}, b: {y: 'world'}, cond: false}, + // cond=true, a=null, picks a?.x -> undefined + {a: null, b: {y: 'world'}, cond: true}, + // cond=false, b=null, picks b?.y -> undefined + {a: {x: 'hello'}, b: null, cond: false}, + ], +}; + +``` + +## Code + +```javascript +// Test optional chaining in both branches of a ternary +function Component(props) { + "use memo"; + return props.cond ? props.a?.x : props.b?.y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: { x: "hello" }, b: { y: "world" }, cond: true }], + sequentialRenders: [ + // cond=true, picks a?.x -> 'hello' + { a: { x: "hello" }, b: { y: "world" }, cond: true }, + // cond=false, picks b?.y -> 'world' + { a: { x: "hello" }, b: { y: "world" }, cond: false }, + // cond=true, a=null, picks a?.x -> undefined + { a: null, b: { y: "world" }, cond: true }, + // cond=false, b=null, picks b?.y -> undefined + { a: { x: "hello" }, b: null, cond: false }, + ], +}; + +``` + +### Eval output +(kind: ok) "hello" +"world" + diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches.ts new file mode 100644 index 0000000000..b9d9a95a09 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-optionals-in-ternary-branches.ts @@ -0,0 +1,24 @@ +// Test optional chaining in both branches of a ternary +function Component(props: { + a: {x: string} | null; + b: {y: string} | null; + cond: boolean; +}) { + 'use memo'; + return props.cond ? props.a?.x : props.b?.y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {x: 'hello'}, b: {y: 'world'}, cond: true}], + sequentialRenders: [ + // cond=true, picks a?.x -> 'hello' + {a: {x: 'hello'}, b: {y: 'world'}, cond: true}, + // cond=false, picks b?.y -> 'world' + {a: {x: 'hello'}, b: {y: 'world'}, cond: false}, + // cond=true, a=null, picks a?.x -> undefined + {a: null, b: {y: 'world'}, cond: true}, + // cond=false, b=null, picks b?.y -> undefined + {a: {x: 'hello'}, b: null, cond: false}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr-with-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr-with-method.expect.md new file mode 100644 index 0000000000..1374cb9e64 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr-with-method.expect.md @@ -0,0 +1,85 @@ + +## Input + +```javascript +import {Stringify, useIdentity} from 'shared-runtime'; + +function useFoo(props: {value: {getX(): string; getY(): string} | null}) { + 'use memo'; + const value = props.value; + const result = useIdentity({x: value?.getX(), y: value?.getY()}) ?? {}; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{value: null}], + sequentialRenders: [ + {value: null}, + {value: {getX: () => 'x1', getY: () => 'y1'}}, + {value: {getX: () => 'x2', getY: () => 'y2'}}, + {value: null}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify, useIdentity } from "shared-runtime"; + +function useFoo(props) { + "use memo"; + const $ = _c(2); + + const value = props.value; + const result = useIdentity({ x: value?.getX(), y: value?.getY() }) ?? {}; + let t0; + if ($[0] !== result) { + t0 = ; + $[0] = result; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ value: null }], + sequentialRenders: [ + { value: null }, + { + value: { + getX: () => { + return "x1"; + }, + getY: () => { + return "y1"; + }, + }, + }, + { + value: { + getX: () => { + return "x2"; + }, + getY: () => { + return "y2"; + }, + }, + }, + { value: null }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"value":{}}
+
{"value":{"x":"x1","y":"y1"}}
+
{"value":{"x":"x2","y":"y2"}}
+
{"value":{}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr-with-method.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr-with-method.tsx new file mode 100644 index 0000000000..201e331af3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr-with-method.tsx @@ -0,0 +1,19 @@ +import {Stringify, useIdentity} from 'shared-runtime'; + +function useFoo(props: {value: {getX(): string; getY(): string} | null}) { + 'use memo'; + const value = props.value; + const result = useIdentity({x: value?.getX(), y: value?.getY()}) ?? {}; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{value: null}], + sequentialRenders: [ + {value: null}, + {value: {getX: () => 'x1', getY: () => 'y1'}}, + {value: {getX: () => 'x2', getY: () => 'y2'}}, + {value: null}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr.expect.md new file mode 100644 index 0000000000..01bac6d1a0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +import {useNoAlias} from 'shared-runtime'; + +function useFoo(props: {value: {x: string; y: string} | null}) { + 'use memo'; + const value = props.value; + return useNoAlias(value?.x, value?.y) ?? {}; +} + +export const FIXTURE_ENTRYPONT = { + fn: useFoo, + props: [{value: null}], +}; + +``` + +## Code + +```javascript +import { useNoAlias } from "shared-runtime"; + +function useFoo(props) { + "use memo"; + + const value = props.value; + return useNoAlias(value?.x, value?.y) ?? {}; +} + +export const FIXTURE_ENTRYPONT = { + fn: useFoo, + props: [{ value: null }], +}; + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-logical-expr.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr.ts similarity index 95% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-logical-expr.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr.ts index b96f6aefad..8219fe66a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-logical-expr.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-logical-expr.ts @@ -1,6 +1,7 @@ import {useNoAlias} from 'shared-runtime'; function useFoo(props: {value: {x: string; y: string} | null}) { + 'use memo'; const value = props.value; return useNoAlias(value?.x, value?.y) ?? {}; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary-with-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary-with-method.expect.md new file mode 100644 index 0000000000..ccb2150361 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary-with-method.expect.md @@ -0,0 +1,87 @@ + +## Input + +```javascript +import {Stringify, useIdentity} from 'shared-runtime'; + +function useFoo(props: {value: {getX(): string; getY(): string} | null}) { + 'use memo'; + const value = props.value; + const result = useIdentity({x: value?.getX(), y: value?.getY()}) ? {} : null; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{value: null}], + sequentialRenders: [ + {value: null}, + {value: {getX: () => 'x1', getY: () => 'y1'}}, + {value: {getX: () => 'x2', getY: () => 'y2'}}, + {value: null}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify, useIdentity } from "shared-runtime"; + +function useFoo(props) { + "use memo"; + const $ = _c(2); + + const value = props.value; + const result = useIdentity({ x: value?.getX(), y: value?.getY() }) + ? {} + : null; + let t0; + if ($[0] !== result) { + t0 = ; + $[0] = result; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ value: null }], + sequentialRenders: [ + { value: null }, + { + value: { + getX: () => { + return "x1"; + }, + getY: () => { + return "y1"; + }, + }, + }, + { + value: { + getX: () => { + return "x2"; + }, + getY: () => { + return "y2"; + }, + }, + }, + { value: null }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"value":{}}
+
{"value":{}}
+
{"value":{}}
+
{"value":{}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary-with-method.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary-with-method.tsx new file mode 100644 index 0000000000..e2107ec404 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary-with-method.tsx @@ -0,0 +1,19 @@ +import {Stringify, useIdentity} from 'shared-runtime'; + +function useFoo(props: {value: {getX(): string; getY(): string} | null}) { + 'use memo'; + const value = props.value; + const result = useIdentity({x: value?.getX(), y: value?.getY()}) ? {} : null; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{value: null}], + sequentialRenders: [ + {value: null}, + {value: {getX: () => 'x1', getY: () => 'y1'}}, + {value: {getX: () => 'x2', getY: () => 'y2'}}, + {value: null}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary.expect.md new file mode 100644 index 0000000000..e9ae4a1809 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +import {useNoAlias} from 'shared-runtime'; + +function useFoo(props: {value: {x: string; y: string} | null}) { + 'use memo'; + const value = props.value; + return useNoAlias(value?.x, value?.y) ? {} : null; +} + +export const FIXTURE_ENTRYPONT = { + fn: useFoo, + props: [{value: null}], +}; + +``` + +## Code + +```javascript +import { useNoAlias } from "shared-runtime"; + +function useFoo(props) { + "use memo"; + + const value = props.value; + return useNoAlias(value?.x, value?.y) ? {} : null; +} + +export const FIXTURE_ENTRYPONT = { + fn: useFoo, + props: [{ value: null }], +}; + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-ternary.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary.ts similarity index 95% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-ternary.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary.ts index ac5302da00..90e2db730d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-ternary.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-chain-in-ternary.ts @@ -1,6 +1,7 @@ import {useNoAlias} from 'shared-runtime'; function useFoo(props: {value: {x: string; y: string} | null}) { + 'use memo'; const value = props.value; return useNoAlias(value?.x, value?.y) ? {} : null; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and-with-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and-with-method.expect.md new file mode 100644 index 0000000000..41b799275b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and-with-method.expect.md @@ -0,0 +1,108 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +// Test optional chaining inside logical AND (&&) with method calls +function Component(props: {value: {getX(): string} | null; enabled: boolean}) { + 'use memo'; + const value = props.value; + const result = props.enabled && value?.getX(); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: {getX: () => 'hello'}, enabled: true}], + sequentialRenders: [ + {value: {getX: () => 'hello'}, enabled: true}, + {value: {getX: () => 'hello'}, enabled: false}, + {value: null, enabled: true}, + {value: {getX: () => 'world'}, enabled: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +// Test optional chaining inside logical AND (&&) with method calls +function Component(props) { + "use memo"; + const $ = _c(5); + + const value = props.value; + let t0; + if ($[0] !== props.enabled || $[1] !== value) { + t0 = props.enabled && value?.getX(); + $[0] = props.enabled; + $[1] = value; + $[2] = t0; + } else { + t0 = $[2]; + } + const result = t0; + let t1; + if ($[3] !== result) { + t1 = ; + $[3] = result; + $[4] = t1; + } else { + t1 = $[4]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + value: { + getX: () => { + return "hello"; + }, + }, + enabled: true, + }, + ], + sequentialRenders: [ + { + value: { + getX: () => { + return "hello"; + }, + }, + enabled: true, + }, + { + value: { + getX: () => { + return "hello"; + }, + }, + enabled: false, + }, + { value: null, enabled: true }, + { + value: { + getX: () => { + return "world"; + }, + }, + enabled: true, + }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"value":"hello"}
+
{"value":false}
+
{}
+
{"value":"world"}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and-with-method.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and-with-method.tsx new file mode 100644 index 0000000000..689fbe8e27 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and-with-method.tsx @@ -0,0 +1,20 @@ +import {Stringify} from 'shared-runtime'; + +// Test optional chaining inside logical AND (&&) with method calls +function Component(props: {value: {getX(): string} | null; enabled: boolean}) { + 'use memo'; + const value = props.value; + const result = props.enabled && value?.getX(); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: {getX: () => 'hello'}, enabled: true}], + sequentialRenders: [ + {value: {getX: () => 'hello'}, enabled: true}, + {value: {getX: () => 'hello'}, enabled: false}, + {value: null, enabled: true}, + {value: {getX: () => 'world'}, enabled: true}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and.expect.md new file mode 100644 index 0000000000..86e478d74b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// Test optional chaining inside logical AND (&&) +function Component(props: {value: {x: string} | null; enabled: boolean}) { + 'use memo'; + const value = props.value; + return props.enabled && value?.x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: {x: 'hello'}, enabled: true}], + sequentialRenders: [ + {value: {x: 'hello'}, enabled: true}, + {value: {x: 'hello'}, enabled: false}, + {value: null, enabled: true}, + {value: {x: 'world'}, enabled: true}, + ], +}; + +``` + +## Code + +```javascript +// Test optional chaining inside logical AND (&&) +function Component(props) { + "use memo"; + + const value = props.value; + return props.enabled && value?.x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: { x: "hello" }, enabled: true }], + sequentialRenders: [ + { value: { x: "hello" }, enabled: true }, + { value: { x: "hello" }, enabled: false }, + { value: null, enabled: true }, + { value: { x: "world" }, enabled: true }, + ], +}; + +``` + +### Eval output +(kind: ok) "hello" +false + +"world" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and.ts new file mode 100644 index 0000000000..4fee7ffd96 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-and.ts @@ -0,0 +1,17 @@ +// Test optional chaining inside logical AND (&&) +function Component(props: {value: {x: string} | null; enabled: boolean}) { + 'use memo'; + const value = props.value; + return props.enabled && value?.x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: {x: 'hello'}, enabled: true}], + sequentialRenders: [ + {value: {x: 'hello'}, enabled: true}, + {value: {x: 'hello'}, enabled: false}, + {value: null, enabled: true}, + {value: {x: 'world'}, enabled: true}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or-with-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or-with-method.expect.md new file mode 100644 index 0000000000..b0b2821c0c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or-with-method.expect.md @@ -0,0 +1,92 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +// Test optional chaining inside logical OR (||) with method calls +function Component(props: {value: {getX(): string} | null; fallback: string}) { + 'use memo'; + const value = props.value; + const result = value?.getX() || props.fallback; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: null, fallback: 'default'}], + sequentialRenders: [ + {value: null, fallback: 'default'}, + {value: {getX: () => 'hello'}, fallback: 'default'}, + {value: {getX: () => ''}, fallback: 'default'}, + {value: null, fallback: 'other'}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +// Test optional chaining inside logical OR (||) with method calls +function Component(props) { + "use memo"; + const $ = _c(5); + + const value = props.value; + let t0; + if ($[0] !== props.fallback || $[1] !== value) { + t0 = value?.getX() || props.fallback; + $[0] = props.fallback; + $[1] = value; + $[2] = t0; + } else { + t0 = $[2]; + } + const result = t0; + let t1; + if ($[3] !== result) { + t1 = ; + $[3] = result; + $[4] = t1; + } else { + t1 = $[4]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: null, fallback: "default" }], + sequentialRenders: [ + { value: null, fallback: "default" }, + { + value: { + getX: () => { + return "hello"; + }, + }, + fallback: "default", + }, + { + value: { + getX: () => { + return ""; + }, + }, + fallback: "default", + }, + { value: null, fallback: "other" }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"value":"default"}
+
{"value":"hello"}
+
{"value":"default"}
+
{"value":"other"}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or-with-method.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or-with-method.tsx new file mode 100644 index 0000000000..9997f2ec06 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or-with-method.tsx @@ -0,0 +1,20 @@ +import {Stringify} from 'shared-runtime'; + +// Test optional chaining inside logical OR (||) with method calls +function Component(props: {value: {getX(): string} | null; fallback: string}) { + 'use memo'; + const value = props.value; + const result = value?.getX() || props.fallback; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: null, fallback: 'default'}], + sequentialRenders: [ + {value: null, fallback: 'default'}, + {value: {getX: () => 'hello'}, fallback: 'default'}, + {value: {getX: () => ''}, fallback: 'default'}, + {value: null, fallback: 'other'}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or.expect.md new file mode 100644 index 0000000000..c7fa6f5bbc --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// Test optional chaining inside logical OR (||) +function Component(props: {value: {x: string} | null; fallback: string}) { + 'use memo'; + const value = props.value; + return value?.x || props.fallback; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: null, fallback: 'default'}], + sequentialRenders: [ + {value: null, fallback: 'default'}, + {value: {x: 'hello'}, fallback: 'default'}, + {value: {x: ''}, fallback: 'default'}, + {value: null, fallback: 'other'}, + ], +}; + +``` + +## Code + +```javascript +// Test optional chaining inside logical OR (||) +function Component(props) { + "use memo"; + + const value = props.value; + return value?.x || props.fallback; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: null, fallback: "default" }], + sequentialRenders: [ + { value: null, fallback: "default" }, + { value: { x: "hello" }, fallback: "default" }, + { value: { x: "" }, fallback: "default" }, + { value: null, fallback: "other" }, + ], +}; + +``` + +### Eval output +(kind: ok) "default" +"hello" +"default" +"other" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or.ts new file mode 100644 index 0000000000..0a0ede14f2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-inside-logical-or.ts @@ -0,0 +1,17 @@ +// Test optional chaining inside logical OR (||) +function Component(props: {value: {x: string} | null; fallback: string}) { + 'use memo'; + const value = props.value; + return value?.x || props.fallback; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: null, fallback: 'default'}], + sequentialRenders: [ + {value: null, fallback: 'default'}, + {value: {x: 'hello'}, fallback: 'default'}, + {value: {x: ''}, fallback: 'default'}, + {value: null, fallback: 'other'}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent-with-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent-with-method.expect.md new file mode 100644 index 0000000000..d854b451bb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent-with-method.expect.md @@ -0,0 +1,135 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +// Test ternary expression producing the value used in optional chaining with method call +function Component(props: { + a: {getX(): string} | null; + b: {getX(): string} | null; + cond: boolean; +}) { + 'use memo'; + const obj = props.cond ? props.a : props.b; + const result = obj?.getX(); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {getX: () => 'a'}, b: {getX: () => 'b'}, cond: true}], + sequentialRenders: [ + {a: {getX: () => 'a'}, b: {getX: () => 'b'}, cond: true}, + {a: {getX: () => 'a'}, b: {getX: () => 'b'}, cond: false}, + {a: null, b: {getX: () => 'b'}, cond: true}, + {a: {getX: () => 'a'}, b: null, cond: false}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +// Test ternary expression producing the value used in optional chaining with method call +function Component(props) { + "use memo"; + const $ = _c(4); + + const obj = props.cond ? props.a : props.b; + let t0; + if ($[0] !== obj) { + t0 = obj?.getX(); + $[0] = obj; + $[1] = t0; + } else { + t0 = $[1]; + } + const result = t0; + let t1; + if ($[2] !== result) { + t1 = ; + $[2] = result; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + a: { + getX: () => { + return "a"; + }, + }, + b: { + getX: () => { + return "b"; + }, + }, + cond: true, + }, + ], + sequentialRenders: [ + { + a: { + getX: () => { + return "a"; + }, + }, + b: { + getX: () => { + return "b"; + }, + }, + cond: true, + }, + { + a: { + getX: () => { + return "a"; + }, + }, + b: { + getX: () => { + return "b"; + }, + }, + cond: false, + }, + { + a: null, + b: { + getX: () => { + return "b"; + }, + }, + cond: true, + }, + { + a: { + getX: () => { + return "a"; + }, + }, + b: null, + cond: false, + }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"value":"a"}
+
{"value":"b"}
+
{}
+
{}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent-with-method.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent-with-method.tsx new file mode 100644 index 0000000000..de37973a58 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent-with-method.tsx @@ -0,0 +1,24 @@ +import {Stringify} from 'shared-runtime'; + +// Test ternary expression producing the value used in optional chaining with method call +function Component(props: { + a: {getX(): string} | null; + b: {getX(): string} | null; + cond: boolean; +}) { + 'use memo'; + const obj = props.cond ? props.a : props.b; + const result = obj?.getX(); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {getX: () => 'a'}, b: {getX: () => 'b'}, cond: true}], + sequentialRenders: [ + {a: {getX: () => 'a'}, b: {getX: () => 'b'}, cond: true}, + {a: {getX: () => 'a'}, b: {getX: () => 'b'}, cond: false}, + {a: null, b: {getX: () => 'b'}, cond: true}, + {a: {getX: () => 'a'}, b: null, cond: false}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent.expect.md new file mode 100644 index 0000000000..3f302c43d1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent.expect.md @@ -0,0 +1,56 @@ + +## Input + +```javascript +// Test ternary expression producing the value used in optional chaining +function Component(props: { + a: {x: string} | null; + b: {x: string} | null; + cond: boolean; +}) { + 'use memo'; + const obj = props.cond ? props.a : props.b; + return obj?.x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {x: 'a'}, b: {x: 'b'}, cond: true}], + sequentialRenders: [ + {a: {x: 'a'}, b: {x: 'b'}, cond: true}, // picks a -> 'a' + {a: {x: 'a'}, b: {x: 'b'}, cond: false}, // picks b -> 'b' + {a: null, b: {x: 'b'}, cond: true}, // picks a (null) -> undefined + {a: {x: 'a'}, b: null, cond: false}, // picks b (null) -> undefined + ], +}; + +``` + +## Code + +```javascript +// Test ternary expression producing the value used in optional chaining +function Component(props) { + "use memo"; + + const obj = props.cond ? props.a : props.b; + return obj?.x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: { x: "a" }, b: { x: "b" }, cond: true }], + sequentialRenders: [ + { a: { x: "a" }, b: { x: "b" }, cond: true }, // picks a -> 'a' + { a: { x: "a" }, b: { x: "b" }, cond: false }, // picks b -> 'b' + { a: null, b: { x: "b" }, cond: true }, // picks a (null) -> undefined + { a: { x: "a" }, b: null, cond: false }, // picks b (null) -> undefined + ], +}; + +``` + +### Eval output +(kind: ok) "a" +"b" + diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent.ts new file mode 100644 index 0000000000..1c31d8a4ef --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ternary-inside-optional-consequent.ts @@ -0,0 +1,21 @@ +// Test ternary expression producing the value used in optional chaining +function Component(props: { + a: {x: string} | null; + b: {x: string} | null; + cond: boolean; +}) { + 'use memo'; + const obj = props.cond ? props.a : props.b; + return obj?.x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {x: 'a'}, b: {x: 'b'}, cond: true}], + sequentialRenders: [ + {a: {x: 'a'}, b: {x: 'b'}, cond: true}, // picks a -> 'a' + {a: {x: 'a'}, b: {x: 'b'}, cond: false}, // picks b -> 'b' + {a: null, b: {x: 'b'}, cond: true}, // picks a (null) -> undefined + {a: {x: 'a'}, b: null, cond: false}, // picks b (null) -> undefined + ], +};