diff --git a/tsc/internal/checker/flow.go b/tsc/internal/checker/flow.go index d08254cc8ef88..d6a8315755482 100644 --- a/tsc/internal/checker/flow.go +++ b/tsc/internal/checker/flow.go @@ -1596,7 +1596,7 @@ func (c *Checker) reportFlowControlError(node *ast.Node) { func (c *Checker) isMatchingReference(source *ast.Node, target *ast.Node) bool { switch target.Kind { - case ast.KindParenthesizedExpression, ast.KindNonNullExpression: + case ast.KindParenthesizedExpression, ast.KindNonNullExpression, ast.KindSatisfiesExpression: return c.isMatchingReference(source, target.Expression()) case ast.KindBinaryExpression: return ast.IsAssignmentExpression(target, false) && c.isMatchingReference(source, target.AsBinaryExpression().Left) || @@ -1851,7 +1851,7 @@ func (c *Checker) containsMatchingReference(source *ast.Node, target *ast.Node) func (c *Checker) optionalChainContainsReference(source *ast.Node, target *ast.Node) bool { for ast.IsOptionalChain(source) { source = source.Expression() - if c.isMatchingReference(source, target) { + if c.isMatchingReference(target, source) { return true } } diff --git a/tsc/testdata/baselines/reference/compiler/narrowByEquality.errors.txt b/tsc/testdata/baselines/reference/compiler/narrowByEquality.errors.txt index f74a9c08f3a43..8a8c84329e1d4 100644 --- a/tsc/testdata/baselines/reference/compiler/narrowByEquality.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/narrowByEquality.errors.txt @@ -86,4 +86,9 @@ narrowByEquality.ts(55,9): error TS2322: Type 'string | number' is not assignabl xUnknown; } + declare let option: { type: string } | undefined; + + if ((option satisfies { type: string } | undefined) !== undefined) { + option.type; + } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/narrowByEquality.js b/tsc/testdata/baselines/reference/compiler/narrowByEquality.js index 31e88dc25cbd4..cf0e623dd98ea 100644 --- a/tsc/testdata/baselines/reference/compiler/narrowByEquality.js +++ b/tsc/testdata/baselines/reference/compiler/narrowByEquality.js @@ -73,6 +73,11 @@ if (xUnknown != null) { xUnknown; } +declare let option: { type: string } | undefined; + +if ((option satisfies { type: string } | undefined) !== undefined) { + option.type; +} //// [narrowByEquality.js] @@ -129,3 +134,6 @@ if (xUnknown != null) { else { xUnknown; } +if (option !== undefined) { + option.type; +} diff --git a/tsc/testdata/baselines/reference/compiler/narrowByEquality.symbols b/tsc/testdata/baselines/reference/compiler/narrowByEquality.symbols index cc1023e7d510c..cca868f778f2e 100644 --- a/tsc/testdata/baselines/reference/compiler/narrowByEquality.symbols +++ b/tsc/testdata/baselines/reference/compiler/narrowByEquality.symbols @@ -139,4 +139,18 @@ if (xUnknown != null) { >xUnknown : Symbol(xUnknown, Decl(narrowByEquality.ts, 4, 11)) } +declare let option: { type: string } | undefined; +>option : Symbol(option, Decl(narrowByEquality.ts, 72, 11)) +>type : Symbol(type, Decl(narrowByEquality.ts, 72, 21)) + +if ((option satisfies { type: string } | undefined) !== undefined) { +>option : Symbol(option, Decl(narrowByEquality.ts, 72, 11)) +>type : Symbol(type, Decl(narrowByEquality.ts, 74, 23)) +>undefined : Symbol(undefined) + + option.type; +>option.type : Symbol(type, Decl(narrowByEquality.ts, 72, 21)) +>option : Symbol(option, Decl(narrowByEquality.ts, 72, 11)) +>type : Symbol(type, Decl(narrowByEquality.ts, 72, 21)) +} diff --git a/tsc/testdata/baselines/reference/compiler/narrowByEquality.types b/tsc/testdata/baselines/reference/compiler/narrowByEquality.types index 9ddaa99f0f570..d2394441f8faa 100644 --- a/tsc/testdata/baselines/reference/compiler/narrowByEquality.types +++ b/tsc/testdata/baselines/reference/compiler/narrowByEquality.types @@ -160,4 +160,21 @@ if (xUnknown != null) { >xUnknown : null | undefined } +declare let option: { type: string } | undefined; +>option : { type: string; } | undefined +>type : string + +if ((option satisfies { type: string } | undefined) !== undefined) { +>(option satisfies { type: string } | undefined) !== undefined : boolean +>(option satisfies { type: string } | undefined) : { type: string; } | undefined +>option satisfies { type: string } | undefined : { type: string; } | undefined +>option : { type: string; } | undefined +>type : string +>undefined : undefined + + option.type; +>option.type : string +>option : { type: string; } +>type : string +} diff --git a/tsc/testdata/baselines/reference/conformance/controlFlowOptionalChain4.errors.txt b/tsc/testdata/baselines/reference/conformance/controlFlowOptionalChain4.errors.txt new file mode 100644 index 0000000000000..ed18a8a5b8518 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/controlFlowOptionalChain4.errors.txt @@ -0,0 +1,93 @@ +controlFlowOptionalChain4.ts(54,9): error TS18048: 'option' is possibly 'undefined'. +controlFlowOptionalChain4.ts(62,9): error TS18048: 'option' is possibly 'undefined'. +controlFlowOptionalChain4.ts(70,9): error TS18048: 'option' is possibly 'undefined'. + + +==== controlFlowOptionalChain4.ts (3 errors) ==== + // https://github.com/microsoft/TypeScript/issues/56998 + + type Type = { + id: number; + }; + + type InferenceInfo = { + typeParameter: Type; + impliedArity?: number; + }; + + declare function getInferenceInfoForType(type: Type): InferenceInfo | undefined; + + function fn1(t1: Type, t2: Type) { + let info = getInferenceInfoForType(t1); + if (info?.impliedArity !== undefined) { + info.impliedArity; + } + else if ((info = getInferenceInfoForType(t2))?.impliedArity !== undefined) { + info.impliedArity; + } + } + + function fn2(t1: Type, t2: Type) { + let info = getInferenceInfoForType(t1); + if (info?.impliedArity !== undefined) { + info.impliedArity; + } + else if ((info = getInferenceInfoForType(t2))?.impliedArity) { + info.impliedArity; + } + } + + // https://github.com/microsoft/TypeScript/issues/60855 + + type Option = { type: "Some"; value: number; } | { type: "None"; }; + + declare function someOptionalOption(): Option | undefined; + + function test60855(): number | undefined { + let option: Option | undefined; + + if ((option = someOptionalOption())?.type === "Some") { + return option.value; + } + + return undefined; + } + + function testEqualityWithUndefined() { + let option: Option | undefined; + + if ((option = someOptionalOption())?.type === undefined) { + option.type; + ~~~~~~ +!!! error TS18048: 'option' is possibly 'undefined'. + } + } + + function testNegativeEquality() { + let option: Option | undefined; + + if ((option = someOptionalOption())?.type !== "Some") { + option.type; + ~~~~~~ +!!! error TS18048: 'option' is possibly 'undefined'. + } + } + + function testFalsyBranch() { + let option: Option | undefined; + + if (!(option = someOptionalOption())?.type) { + option.type; + ~~~~~~ +!!! error TS18048: 'option' is possibly 'undefined'. + } + } + + function testAssignmentWithSatisfies() { + let option: Option | undefined; + + if ((option = someOptionalOption() satisfies Option | undefined)?.type === "Some") { + option.value; + } + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/controlFlowOptionalChain4.symbols b/tsc/testdata/baselines/reference/conformance/controlFlowOptionalChain4.symbols new file mode 100644 index 0000000000000..d84e8ac099ba2 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/controlFlowOptionalChain4.symbols @@ -0,0 +1,219 @@ +//// [tests/cases/conformance/controlFlow/controlFlowOptionalChain4.ts] //// + +=== controlFlowOptionalChain4.ts === +// https://github.com/microsoft/TypeScript/issues/56998 + +type Type = { +>Type : Symbol(Type, Decl(controlFlowOptionalChain4.ts, 0, 0)) + + id: number; +>id : Symbol(id, Decl(controlFlowOptionalChain4.ts, 2, 13)) + +}; + +type InferenceInfo = { +>InferenceInfo : Symbol(InferenceInfo, Decl(controlFlowOptionalChain4.ts, 4, 2)) + + typeParameter: Type; +>typeParameter : Symbol(typeParameter, Decl(controlFlowOptionalChain4.ts, 6, 22)) +>Type : Symbol(Type, Decl(controlFlowOptionalChain4.ts, 0, 0)) + + impliedArity?: number; +>impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) + +}; + +declare function getInferenceInfoForType(type: Type): InferenceInfo | undefined; +>getInferenceInfoForType : Symbol(getInferenceInfoForType, Decl(controlFlowOptionalChain4.ts, 9, 2)) +>type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 11, 41)) +>Type : Symbol(Type, Decl(controlFlowOptionalChain4.ts, 0, 0)) +>InferenceInfo : Symbol(InferenceInfo, Decl(controlFlowOptionalChain4.ts, 4, 2)) + +function fn1(t1: Type, t2: Type) { +>fn1 : Symbol(fn1, Decl(controlFlowOptionalChain4.ts, 11, 80)) +>t1 : Symbol(t1, Decl(controlFlowOptionalChain4.ts, 13, 13)) +>Type : Symbol(Type, Decl(controlFlowOptionalChain4.ts, 0, 0)) +>t2 : Symbol(t2, Decl(controlFlowOptionalChain4.ts, 13, 22)) +>Type : Symbol(Type, Decl(controlFlowOptionalChain4.ts, 0, 0)) + + let info = getInferenceInfoForType(t1); +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 14, 7)) +>getInferenceInfoForType : Symbol(getInferenceInfoForType, Decl(controlFlowOptionalChain4.ts, 9, 2)) +>t1 : Symbol(t1, Decl(controlFlowOptionalChain4.ts, 13, 13)) + + if (info?.impliedArity !== undefined) { +>info?.impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 14, 7)) +>impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>undefined : Symbol(undefined) + + info.impliedArity; +>info.impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 14, 7)) +>impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) + } + else if ((info = getInferenceInfoForType(t2))?.impliedArity !== undefined) { +>(info = getInferenceInfoForType(t2))?.impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 14, 7)) +>getInferenceInfoForType : Symbol(getInferenceInfoForType, Decl(controlFlowOptionalChain4.ts, 9, 2)) +>t2 : Symbol(t2, Decl(controlFlowOptionalChain4.ts, 13, 22)) +>impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>undefined : Symbol(undefined) + + info.impliedArity; +>info.impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 14, 7)) +>impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) + } +} + +function fn2(t1: Type, t2: Type) { +>fn2 : Symbol(fn2, Decl(controlFlowOptionalChain4.ts, 21, 1)) +>t1 : Symbol(t1, Decl(controlFlowOptionalChain4.ts, 23, 13)) +>Type : Symbol(Type, Decl(controlFlowOptionalChain4.ts, 0, 0)) +>t2 : Symbol(t2, Decl(controlFlowOptionalChain4.ts, 23, 22)) +>Type : Symbol(Type, Decl(controlFlowOptionalChain4.ts, 0, 0)) + + let info = getInferenceInfoForType(t1); +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 24, 7)) +>getInferenceInfoForType : Symbol(getInferenceInfoForType, Decl(controlFlowOptionalChain4.ts, 9, 2)) +>t1 : Symbol(t1, Decl(controlFlowOptionalChain4.ts, 23, 13)) + + if (info?.impliedArity !== undefined) { +>info?.impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 24, 7)) +>impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>undefined : Symbol(undefined) + + info.impliedArity; +>info.impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 24, 7)) +>impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) + } + else if ((info = getInferenceInfoForType(t2))?.impliedArity) { +>(info = getInferenceInfoForType(t2))?.impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 24, 7)) +>getInferenceInfoForType : Symbol(getInferenceInfoForType, Decl(controlFlowOptionalChain4.ts, 9, 2)) +>t2 : Symbol(t2, Decl(controlFlowOptionalChain4.ts, 23, 22)) +>impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) + + info.impliedArity; +>info.impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) +>info : Symbol(info, Decl(controlFlowOptionalChain4.ts, 24, 7)) +>impliedArity : Symbol(impliedArity, Decl(controlFlowOptionalChain4.ts, 7, 24)) + } +} + +// https://github.com/microsoft/TypeScript/issues/60855 + +type Option = { type: "Some"; value: number; } | { type: "None"; }; +>Option : Symbol(Option, Decl(lib.dom.d.ts, --, --), Decl(controlFlowOptionalChain4.ts, 31, 1)) +>type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15)) +>value : Symbol(value, Decl(controlFlowOptionalChain4.ts, 35, 29)) +>type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 50)) + +declare function someOptionalOption(): Option | undefined; +>someOptionalOption : Symbol(someOptionalOption, Decl(controlFlowOptionalChain4.ts, 35, 67)) +>Option : Symbol(Option, Decl(lib.dom.d.ts, --, --), Decl(controlFlowOptionalChain4.ts, 31, 1)) + +function test60855(): number | undefined { +>test60855 : Symbol(test60855, Decl(controlFlowOptionalChain4.ts, 37, 58)) + + let option: Option | undefined; +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 40, 7)) +>Option : Symbol(Option, Decl(lib.dom.d.ts, --, --), Decl(controlFlowOptionalChain4.ts, 31, 1)) + + if ((option = someOptionalOption())?.type === "Some") { +>(option = someOptionalOption())?.type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 40, 7)) +>someOptionalOption : Symbol(someOptionalOption, Decl(controlFlowOptionalChain4.ts, 35, 67)) +>type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) + + return option.value; +>option.value : Symbol(value, Decl(controlFlowOptionalChain4.ts, 35, 29)) +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 40, 7)) +>value : Symbol(value, Decl(controlFlowOptionalChain4.ts, 35, 29)) + } + + return undefined; +>undefined : Symbol(undefined) +} + +function testEqualityWithUndefined() { +>testEqualityWithUndefined : Symbol(testEqualityWithUndefined, Decl(controlFlowOptionalChain4.ts, 47, 1)) + + let option: Option | undefined; +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 50, 7)) +>Option : Symbol(Option, Decl(lib.dom.d.ts, --, --), Decl(controlFlowOptionalChain4.ts, 31, 1)) + + if ((option = someOptionalOption())?.type === undefined) { +>(option = someOptionalOption())?.type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 50, 7)) +>someOptionalOption : Symbol(someOptionalOption, Decl(controlFlowOptionalChain4.ts, 35, 67)) +>type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) +>undefined : Symbol(undefined) + + option.type; +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 50, 7)) + } +} + +function testNegativeEquality() { +>testNegativeEquality : Symbol(testNegativeEquality, Decl(controlFlowOptionalChain4.ts, 55, 1)) + + let option: Option | undefined; +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 58, 7)) +>Option : Symbol(Option, Decl(lib.dom.d.ts, --, --), Decl(controlFlowOptionalChain4.ts, 31, 1)) + + if ((option = someOptionalOption())?.type !== "Some") { +>(option = someOptionalOption())?.type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 58, 7)) +>someOptionalOption : Symbol(someOptionalOption, Decl(controlFlowOptionalChain4.ts, 35, 67)) +>type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) + + option.type; +>option.type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 50)) +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 58, 7)) +>type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 50)) + } +} + +function testFalsyBranch() { +>testFalsyBranch : Symbol(testFalsyBranch, Decl(controlFlowOptionalChain4.ts, 63, 1)) + + let option: Option | undefined; +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 66, 7)) +>Option : Symbol(Option, Decl(lib.dom.d.ts, --, --), Decl(controlFlowOptionalChain4.ts, 31, 1)) + + if (!(option = someOptionalOption())?.type) { +>(option = someOptionalOption())?.type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 66, 7)) +>someOptionalOption : Symbol(someOptionalOption, Decl(controlFlowOptionalChain4.ts, 35, 67)) +>type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) + + option.type; +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 66, 7)) + } +} + +function testAssignmentWithSatisfies() { +>testAssignmentWithSatisfies : Symbol(testAssignmentWithSatisfies, Decl(controlFlowOptionalChain4.ts, 71, 1)) + + let option: Option | undefined; +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 74, 7)) +>Option : Symbol(Option, Decl(lib.dom.d.ts, --, --), Decl(controlFlowOptionalChain4.ts, 31, 1)) + + if ((option = someOptionalOption() satisfies Option | undefined)?.type === "Some") { +>(option = someOptionalOption() satisfies Option | undefined)?.type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 74, 7)) +>someOptionalOption : Symbol(someOptionalOption, Decl(controlFlowOptionalChain4.ts, 35, 67)) +>Option : Symbol(Option, Decl(lib.dom.d.ts, --, --), Decl(controlFlowOptionalChain4.ts, 31, 1)) +>type : Symbol(type, Decl(controlFlowOptionalChain4.ts, 35, 15), Decl(controlFlowOptionalChain4.ts, 35, 50)) + + option.value; +>option.value : Symbol(value, Decl(controlFlowOptionalChain4.ts, 35, 29)) +>option : Symbol(option, Decl(controlFlowOptionalChain4.ts, 74, 7)) +>value : Symbol(value, Decl(controlFlowOptionalChain4.ts, 35, 29)) + } +} + diff --git a/tsc/testdata/baselines/reference/conformance/controlFlowOptionalChain4.types b/tsc/testdata/baselines/reference/conformance/controlFlowOptionalChain4.types new file mode 100644 index 0000000000000..0a63d3e95ce9a --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/controlFlowOptionalChain4.types @@ -0,0 +1,244 @@ +//// [tests/cases/conformance/controlFlow/controlFlowOptionalChain4.ts] //// + +=== controlFlowOptionalChain4.ts === +// https://github.com/microsoft/TypeScript/issues/56998 + +type Type = { +>Type : Type + + id: number; +>id : number + +}; + +type InferenceInfo = { +>InferenceInfo : InferenceInfo + + typeParameter: Type; +>typeParameter : Type + + impliedArity?: number; +>impliedArity : number | undefined + +}; + +declare function getInferenceInfoForType(type: Type): InferenceInfo | undefined; +>getInferenceInfoForType : (type: Type) => InferenceInfo | undefined +>type : Type + +function fn1(t1: Type, t2: Type) { +>fn1 : (t1: Type, t2: Type) => void +>t1 : Type +>t2 : Type + + let info = getInferenceInfoForType(t1); +>info : InferenceInfo | undefined +>getInferenceInfoForType(t1) : InferenceInfo | undefined +>getInferenceInfoForType : (type: Type) => InferenceInfo | undefined +>t1 : Type + + if (info?.impliedArity !== undefined) { +>info?.impliedArity !== undefined : boolean +>info?.impliedArity : number | undefined +>info : InferenceInfo | undefined +>impliedArity : number | undefined +>undefined : undefined + + info.impliedArity; +>info.impliedArity : number +>info : InferenceInfo +>impliedArity : number + } + else if ((info = getInferenceInfoForType(t2))?.impliedArity !== undefined) { +>(info = getInferenceInfoForType(t2))?.impliedArity !== undefined : boolean +>(info = getInferenceInfoForType(t2))?.impliedArity : number | undefined +>(info = getInferenceInfoForType(t2)) : InferenceInfo | undefined +>info = getInferenceInfoForType(t2) : InferenceInfo | undefined +>info : InferenceInfo | undefined +>getInferenceInfoForType(t2) : InferenceInfo | undefined +>getInferenceInfoForType : (type: Type) => InferenceInfo | undefined +>t2 : Type +>impliedArity : number | undefined +>undefined : undefined + + info.impliedArity; +>info.impliedArity : number +>info : InferenceInfo +>impliedArity : number + } +} + +function fn2(t1: Type, t2: Type) { +>fn2 : (t1: Type, t2: Type) => void +>t1 : Type +>t2 : Type + + let info = getInferenceInfoForType(t1); +>info : InferenceInfo | undefined +>getInferenceInfoForType(t1) : InferenceInfo | undefined +>getInferenceInfoForType : (type: Type) => InferenceInfo | undefined +>t1 : Type + + if (info?.impliedArity !== undefined) { +>info?.impliedArity !== undefined : boolean +>info?.impliedArity : number | undefined +>info : InferenceInfo | undefined +>impliedArity : number | undefined +>undefined : undefined + + info.impliedArity; +>info.impliedArity : number +>info : InferenceInfo +>impliedArity : number + } + else if ((info = getInferenceInfoForType(t2))?.impliedArity) { +>(info = getInferenceInfoForType(t2))?.impliedArity : number | undefined +>(info = getInferenceInfoForType(t2)) : InferenceInfo | undefined +>info = getInferenceInfoForType(t2) : InferenceInfo | undefined +>info : InferenceInfo | undefined +>getInferenceInfoForType(t2) : InferenceInfo | undefined +>getInferenceInfoForType : (type: Type) => InferenceInfo | undefined +>t2 : Type +>impliedArity : number | undefined + + info.impliedArity; +>info.impliedArity : number +>info : InferenceInfo +>impliedArity : number + } +} + +// https://github.com/microsoft/TypeScript/issues/60855 + +type Option = { type: "Some"; value: number; } | { type: "None"; }; +>Option : Option +>type : "Some" +>value : number +>type : "None" + +declare function someOptionalOption(): Option | undefined; +>someOptionalOption : () => Option | undefined + +function test60855(): number | undefined { +>test60855 : () => number | undefined + + let option: Option | undefined; +>option : Option | undefined + + if ((option = someOptionalOption())?.type === "Some") { +>(option = someOptionalOption())?.type === "Some" : boolean +>(option = someOptionalOption())?.type : "None" | "Some" | undefined +>(option = someOptionalOption()) : Option | undefined +>option = someOptionalOption() : Option | undefined +>option : Option | undefined +>someOptionalOption() : Option | undefined +>someOptionalOption : () => Option | undefined +>type : "None" | "Some" | undefined +>"Some" : "Some" + + return option.value; +>option.value : number +>option : { type: "Some"; value: number; } +>value : number + } + + return undefined; +>undefined : undefined +} + +function testEqualityWithUndefined() { +>testEqualityWithUndefined : () => void + + let option: Option | undefined; +>option : Option | undefined + + if ((option = someOptionalOption())?.type === undefined) { +>(option = someOptionalOption())?.type === undefined : boolean +>(option = someOptionalOption())?.type : "None" | "Some" | undefined +>(option = someOptionalOption()) : Option | undefined +>option = someOptionalOption() : Option | undefined +>option : Option | undefined +>someOptionalOption() : Option | undefined +>someOptionalOption : () => Option | undefined +>type : "None" | "Some" | undefined +>undefined : undefined + + option.type; +>option.type : any +>option : undefined +>type : any + } +} + +function testNegativeEquality() { +>testNegativeEquality : () => void + + let option: Option | undefined; +>option : Option | undefined + + if ((option = someOptionalOption())?.type !== "Some") { +>(option = someOptionalOption())?.type !== "Some" : boolean +>(option = someOptionalOption())?.type : "None" | "Some" | undefined +>(option = someOptionalOption()) : Option | undefined +>option = someOptionalOption() : Option | undefined +>option : Option | undefined +>someOptionalOption() : Option | undefined +>someOptionalOption : () => Option | undefined +>type : "None" | "Some" | undefined +>"Some" : "Some" + + option.type; +>option.type : "None" +>option : { type: "None"; } | undefined +>type : "None" + } +} + +function testFalsyBranch() { +>testFalsyBranch : () => void + + let option: Option | undefined; +>option : Option | undefined + + if (!(option = someOptionalOption())?.type) { +>!(option = someOptionalOption())?.type : boolean +>(option = someOptionalOption())?.type : "None" | "Some" | undefined +>(option = someOptionalOption()) : Option | undefined +>option = someOptionalOption() : Option | undefined +>option : Option | undefined +>someOptionalOption() : Option | undefined +>someOptionalOption : () => Option | undefined +>type : "None" | "Some" | undefined + + option.type; +>option.type : any +>option : undefined +>type : any + } +} + +function testAssignmentWithSatisfies() { +>testAssignmentWithSatisfies : () => void + + let option: Option | undefined; +>option : Option | undefined + + if ((option = someOptionalOption() satisfies Option | undefined)?.type === "Some") { +>(option = someOptionalOption() satisfies Option | undefined)?.type === "Some" : boolean +>(option = someOptionalOption() satisfies Option | undefined)?.type : "None" | "Some" | undefined +>(option = someOptionalOption() satisfies Option | undefined) : Option | undefined +>option = someOptionalOption() satisfies Option | undefined : Option | undefined +>option : Option | undefined +>someOptionalOption() satisfies Option | undefined : Option | undefined +>someOptionalOption() : Option | undefined +>someOptionalOption : () => Option | undefined +>type : "None" | "Some" | undefined +>"Some" : "Some" + + option.value; +>option.value : number +>option : { type: "Some"; value: number; } +>value : number + } +} + diff --git a/tsc/testdata/tests/cases/compiler/narrowByEquality.ts b/tsc/testdata/tests/cases/compiler/narrowByEquality.ts index 0763a794a9b31..bf98d7f813b9c 100644 --- a/tsc/testdata/tests/cases/compiler/narrowByEquality.ts +++ b/tsc/testdata/tests/cases/compiler/narrowByEquality.ts @@ -73,3 +73,8 @@ if (xUnknown != null) { xUnknown; } +declare let option: { type: string } | undefined; + +if ((option satisfies { type: string } | undefined) !== undefined) { + option.type; +} diff --git a/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowOptionalChain4.ts b/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowOptionalChain4.ts new file mode 100644 index 0000000000000..35e4a37948091 --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowOptionalChain4.ts @@ -0,0 +1,83 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/56998 + +type Type = { + id: number; +}; + +type InferenceInfo = { + typeParameter: Type; + impliedArity?: number; +}; + +declare function getInferenceInfoForType(type: Type): InferenceInfo | undefined; + +function fn1(t1: Type, t2: Type) { + let info = getInferenceInfoForType(t1); + if (info?.impliedArity !== undefined) { + info.impliedArity; + } + else if ((info = getInferenceInfoForType(t2))?.impliedArity !== undefined) { + info.impliedArity; + } +} + +function fn2(t1: Type, t2: Type) { + let info = getInferenceInfoForType(t1); + if (info?.impliedArity !== undefined) { + info.impliedArity; + } + else if ((info = getInferenceInfoForType(t2))?.impliedArity) { + info.impliedArity; + } +} + +// https://github.com/microsoft/TypeScript/issues/60855 + +type Option = { type: "Some"; value: number; } | { type: "None"; }; + +declare function someOptionalOption(): Option | undefined; + +function test60855(): number | undefined { + let option: Option | undefined; + + if ((option = someOptionalOption())?.type === "Some") { + return option.value; + } + + return undefined; +} + +function testEqualityWithUndefined() { + let option: Option | undefined; + + if ((option = someOptionalOption())?.type === undefined) { + option.type; + } +} + +function testNegativeEquality() { + let option: Option | undefined; + + if ((option = someOptionalOption())?.type !== "Some") { + option.type; + } +} + +function testFalsyBranch() { + let option: Option | undefined; + + if (!(option = someOptionalOption())?.type) { + option.type; + } +} + +function testAssignmentWithSatisfies() { + let option: Option | undefined; + + if ((option = someOptionalOption() satisfies Option | undefined)?.type === "Some") { + option.value; + } +}