Overloaded assertion in inferred-return function expression loses destructured discriminant narrowing for returned property
Steps to reproduce
Repository: https://github.com/adri1wald/typescript-v6-v7-narrowing-repro
Minimal failing code:
type State =
| {
status: 'ready'
payload: {
value?: string
}
}
| {
status: 'pending'
payload?: undefined
}
declare function getState(): State
declare function assert(
condition: boolean,
): asserts condition
declare function assert(
condition: boolean,
message: string,
): asserts condition
const readValue = () => {
const { status, payload } = getState()
if (status !== 'ready') {
throw new Error('Value is not ready.')
}
assert(payload.value !== undefined)
return payload.value
}
Config:
{
"compilerOptions": {
"noEmit": true,
"strict": true
},
"include": ["src/repro.ts"]
}
Commands:
pnpm install
pnpm run check:ts6
pnpm run check:ts7
pnpm run check:controls
The control cases in src/control-cases.ts all pass with both compilers. They narrow the discrepancy to this shape:
- the state is narrowed through correlated destructuring:
const { status, payload } = getState()
status !== 'ready' exits before the assertion
- an overloaded assertion function checks
payload.value
- the enclosing function is a function expression or arrow function with no syntactic return type annotation
- the function directly returns the destructured
payload value or property
Nearby cases that pass with tsgo:
- a single assertion signature
- a single assertion signature with an optional message parameter
- the same overloaded assertion in a function declaration
- the same overloaded assertion in an arrow function with a syntactic return type annotation
- the same overloaded assertion followed by an intermediate
const value: string = payload.value
- the same overloaded assertion using the original
state object instead of correlated destructuring
- an inline guard instead of an assertion function
- the same overloaded assertion when the narrowed
payload value is not returned
Behavior with typescript@6.0
No errors.
$ pnpm run check:ts6
$ tsc6 -p tsconfig.json --pretty false --noEmit
The control cases also pass:
$ pnpm run check:controls:ts6
$ tsc6 --ignoreConfig --strict --noEmit --pretty false src/control-cases.ts
Behavior with tsgo
tsgo reports payload as possibly undefined in the assertion argument:
$ pnpm run check:ts7
$ tsc -p tsconfig.json --pretty false --noEmit
src/repro.ts(31,10): error TS18048: 'payload' is possibly 'undefined'.
After this guard:
if (status !== 'ready') {
throw new Error('Value is not ready.')
}
the remaining union member is the 'ready' member, where payload is defined.
The control cases pass with tsgo:
$ pnpm run check:controls:ts7
$ tsc --ignoreConfig --strict --noEmit --pretty false src/control-cases.ts
I also checked the current nightly:
$ pnpm --package=typescript@next dlx tsc --version
Version 7.1.0-dev.20260708.3
$ pnpm --package=typescript@next dlx tsc --ignoreConfig --strict --noEmit --pretty false src/repro.ts
src/repro.ts(31,10): error TS18048: 'payload' is possibly 'undefined'.
Overloaded assertion in inferred-return function expression loses destructured discriminant narrowing for returned property
Steps to reproduce
Repository: https://github.com/adri1wald/typescript-v6-v7-narrowing-repro
Minimal failing code:
Config:
{ "compilerOptions": { "noEmit": true, "strict": true }, "include": ["src/repro.ts"] }Commands:
The control cases in
src/control-cases.tsall pass with both compilers. They narrow the discrepancy to this shape:const { status, payload } = getState()status !== 'ready'exits before the assertionpayload.valuepayloadvalue or propertyNearby cases that pass with
tsgo:const value: string = payload.valuestateobject instead of correlated destructuringpayloadvalue is not returnedBehavior with
typescript@6.0No errors.
The control cases also pass:
Behavior with
tsgotsgoreportspayloadas possibly undefined in the assertion argument:After this guard:
the remaining union member is the
'ready'member, wherepayloadis defined.The control cases pass with
tsgo:I also checked the current nightly: