diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 770e1a7c1c60a..8868a30354fa0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29297,6 +29297,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { if (hasMatchingArgument(callExpression, reference)) { + // Ordinarily we don't need to to this in control flow analysis because the Binder breaks this down. + // However, we may encounter the need to narrow this down here when analyzing aliased conditional expressions. + if (inlineLevel !== 0 && isOptionalChain(callExpression)) { + type = narrowTypeByOptionality(type, callExpression.expression, assumeTrue); + } const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; const predicate = signature && getTypePredicateOfSignature(signature); if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) { @@ -29393,7 +29398,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type { - if (isMatchingReference(reference, expr)) { + if (isMatchingReference(reference, expr) || optionalChainContainsReference(expr, reference)) { return getAdjustedTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); } const access = getDiscriminantPropertyAccess(expr, type); diff --git a/tests/baselines/reference/controlFlowOptionalChain5.symbols b/tests/baselines/reference/controlFlowOptionalChain5.symbols new file mode 100644 index 0000000000000..7215cb3d81ab0 --- /dev/null +++ b/tests/baselines/reference/controlFlowOptionalChain5.symbols @@ -0,0 +1,39 @@ +//// [tests/cases/conformance/controlFlow/controlFlowOptionalChain5.ts] //// + +=== controlFlowOptionalChain5.ts === +// https://github.com/microsoft/TypeScript/issues/59145 + +function resolve1(id: string | undefined) { +>resolve1 : Symbol(resolve1, Decl(controlFlowOptionalChain5.ts, 0, 0)) +>id : Symbol(id, Decl(controlFlowOptionalChain5.ts, 2, 18)) + + const isBundled = id?.includes("_bundled"); +>isBundled : Symbol(isBundled, Decl(controlFlowOptionalChain5.ts, 3, 7)) +>id?.includes : Symbol(String.includes, Decl(lib.es2015.core.d.ts, --, --)) +>id : Symbol(id, Decl(controlFlowOptionalChain5.ts, 2, 18)) +>includes : Symbol(String.includes, Decl(lib.es2015.core.d.ts, --, --)) + + if (isBundled) { +>isBundled : Symbol(isBundled, Decl(controlFlowOptionalChain5.ts, 3, 7)) + + const str: string = id; +>str : Symbol(str, Decl(controlFlowOptionalChain5.ts, 5, 9)) +>id : Symbol(id, Decl(controlFlowOptionalChain5.ts, 2, 18)) + } +} + +function resolve2(id: string | undefined) { +>resolve2 : Symbol(resolve2, Decl(controlFlowOptionalChain5.ts, 7, 1)) +>id : Symbol(id, Decl(controlFlowOptionalChain5.ts, 9, 18)) + + if (id?.includes("_bundled")) { +>id?.includes : Symbol(String.includes, Decl(lib.es2015.core.d.ts, --, --)) +>id : Symbol(id, Decl(controlFlowOptionalChain5.ts, 9, 18)) +>includes : Symbol(String.includes, Decl(lib.es2015.core.d.ts, --, --)) + + const str: string = id; +>str : Symbol(str, Decl(controlFlowOptionalChain5.ts, 11, 9)) +>id : Symbol(id, Decl(controlFlowOptionalChain5.ts, 9, 18)) + } +} + diff --git a/tests/baselines/reference/controlFlowOptionalChain5.types b/tests/baselines/reference/controlFlowOptionalChain5.types new file mode 100644 index 0000000000000..1ecb4d3dc2678 --- /dev/null +++ b/tests/baselines/reference/controlFlowOptionalChain5.types @@ -0,0 +1,63 @@ +//// [tests/cases/conformance/controlFlow/controlFlowOptionalChain5.ts] //// + +=== controlFlowOptionalChain5.ts === +// https://github.com/microsoft/TypeScript/issues/59145 + +function resolve1(id: string | undefined) { +>resolve1 : (id: string | undefined) => void +> : ^ ^^ ^^^^^^^^^ +>id : string | undefined +> : ^^^^^^^^^^^^^^^^^^ + + const isBundled = id?.includes("_bundled"); +>isBundled : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>id?.includes("_bundled") : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>id?.includes : ((searchString: string, position?: number) => boolean) | undefined +> : ^^ ^^ ^^ ^^^ ^^^^^ ^^^^^^^^^^^^^ +>id : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>includes : ((searchString: string, position?: number) => boolean) | undefined +> : ^^ ^^ ^^ ^^^ ^^^^^ ^^^^^^^^^^^^^ +>"_bundled" : "_bundled" +> : ^^^^^^^^^^ + + if (isBundled) { +>isBundled : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ + + const str: string = id; +>str : string +> : ^^^^^^ +>id : string +> : ^^^^^^ + } +} + +function resolve2(id: string | undefined) { +>resolve2 : (id: string | undefined) => void +> : ^ ^^ ^^^^^^^^^ +>id : string | undefined +> : ^^^^^^^^^^^^^^^^^^ + + if (id?.includes("_bundled")) { +>id?.includes("_bundled") : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>id?.includes : ((searchString: string, position?: number) => boolean) | undefined +> : ^^ ^^ ^^ ^^^ ^^^^^ ^^^^^^^^^^^^^ +>id : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>includes : ((searchString: string, position?: number) => boolean) | undefined +> : ^^ ^^ ^^ ^^^ ^^^^^ ^^^^^^^^^^^^^ +>"_bundled" : "_bundled" +> : ^^^^^^^^^^ + + const str: string = id; +>str : string +> : ^^^^^^ +>id : string +> : ^^^^^^ + } +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowOptionalChain5.ts b/tests/cases/conformance/controlFlow/controlFlowOptionalChain5.ts new file mode 100644 index 0000000000000..0ebfe9aaa7c9e --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowOptionalChain5.ts @@ -0,0 +1,18 @@ +// @strict: true +// @lib: esnext +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/59145 + +function resolve1(id: string | undefined) { + const isBundled = id?.includes("_bundled"); + if (isBundled) { + const str: string = id; + } +} + +function resolve2(id: string | undefined) { + if (id?.includes("_bundled")) { + const str: string = id; + } +}