From 398d3d2c1c531207c82a911206ec4d761b1faf72 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 15 Mar 2024 11:59:09 -0700 Subject: [PATCH] Revert "Allow (non-assert) type predicates to narrow by discriminant" (#57750) --- src/compiler/checker.ts | 98 ++++++------- src/compiler/commandLineParser.ts | 1 + .../discriminantNarrowingCouldBeCircular.js | 69 +++++++++ ...scriminantNarrowingCouldBeCircular.symbols | 129 ++++++++++++++++ ...discriminantNarrowingCouldBeCircular.types | 138 ++++++++++++++++++ ...ePredicatesCanNarrowByDiscriminant.symbols | 8 +- ...ypePredicatesCanNarrowByDiscriminant.types | 12 +- .../discriminantNarrowingCouldBeCircular.ts | 40 +++++ 8 files changed, 434 insertions(+), 61 deletions(-) create mode 100644 tests/baselines/reference/discriminantNarrowingCouldBeCircular.js create mode 100644 tests/baselines/reference/discriminantNarrowingCouldBeCircular.symbols create mode 100644 tests/baselines/reference/discriminantNarrowingCouldBeCircular.types create mode 100644 tests/cases/compiler/discriminantNarrowingCouldBeCircular.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 583c4827e63c8..cf16285cdb1a8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26741,11 +26741,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) { if (expression.arguments) { for (const argument of expression.arguments) { - if ( - isOrContainsMatchingReference(reference, argument) - || optionalChainContainsReference(argument, reference) - || getCandidateDiscriminantPropertyAccess(argument, reference) - ) { + if (isOrContainsMatchingReference(reference, argument) || optionalChainContainsReference(argument, reference)) { return true; } } @@ -26759,51 +26755,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } - function getCandidateDiscriminantPropertyAccess(expr: Expression, reference: Node) { - if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference) || isObjectLiteralMethod(reference)) { - // When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in - // getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or - // parameter declared in the same parameter list is a candidate. - if (isIdentifier(expr)) { - const symbol = getResolvedSymbol(expr); - const declaration = symbol.valueDeclaration; - if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) { - return declaration; - } - } - } - else if (isAccessExpression(expr)) { - // An access expression is a candidate if the reference matches the left hand expression. - if (isMatchingReference(reference, expr.expression)) { - return expr; - } - } - else if (isIdentifier(expr)) { - const symbol = getResolvedSymbol(expr); - if (isConstantVariable(symbol)) { - const declaration = symbol.valueDeclaration!; - // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' - if ( - isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) && - isMatchingReference(reference, declaration.initializer.expression) - ) { - return declaration.initializer; - } - // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' - if (isBindingElement(declaration) && !declaration.initializer) { - const parent = declaration.parent.parent; - if ( - isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) && - isMatchingReference(reference, parent.initializer) - ) { - return declaration; - } - } - } - } - return undefined; - } - function getFlowNodeId(flow: FlowNode): number { if (!flow.id || flow.id < 0) { flow.id = nextFlowId; @@ -28160,12 +28111,57 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } + function getCandidateDiscriminantPropertyAccess(expr: Expression) { + if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference) || isObjectLiteralMethod(reference)) { + // When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in + // getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or + // parameter declared in the same parameter list is a candidate. + if (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + const declaration = symbol.valueDeclaration; + if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) { + return declaration; + } + } + } + else if (isAccessExpression(expr)) { + // An access expression is a candidate if the reference matches the left hand expression. + if (isMatchingReference(reference, expr.expression)) { + return expr; + } + } + else if (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + if (isConstantVariable(symbol)) { + const declaration = symbol.valueDeclaration!; + // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' + if ( + isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) && + isMatchingReference(reference, declaration.initializer.expression) + ) { + return declaration.initializer; + } + // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' + if (isBindingElement(declaration) && !declaration.initializer) { + const parent = declaration.parent.parent; + if ( + isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) && + isMatchingReference(reference, parent.initializer) + ) { + return declaration; + } + } + } + } + return undefined; + } + function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { // As long as the computed type is a subset of the declared type, we use the full declared type to detect // a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type // predicate narrowing, we use the actual computed type. if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) { - const access = getCandidateDiscriminantPropertyAccess(expr, reference); + const access = getCandidateDiscriminantPropertyAccess(expr); if (access) { const name = getAccessedPropertyName(access); if (name) { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index feee1493eb16a..d513c5793dadf 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -3505,6 +3505,7 @@ export function convertJsonOption( convertJsonOption(opt.element, value, basePath, errors, propertyAssignment, valueExpression, sourceFile); } else if (!isString(opt.type)) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors, valueExpression, sourceFile); } const validatedValue = validateJsonOptionValue(opt, value, errors, valueExpression, sourceFile); diff --git a/tests/baselines/reference/discriminantNarrowingCouldBeCircular.js b/tests/baselines/reference/discriminantNarrowingCouldBeCircular.js new file mode 100644 index 0000000000000..2cc50e16b98cd --- /dev/null +++ b/tests/baselines/reference/discriminantNarrowingCouldBeCircular.js @@ -0,0 +1,69 @@ +//// [tests/cases/compiler/discriminantNarrowingCouldBeCircular.ts] //// + +//// [discriminantNarrowingCouldBeCircular.ts] +// #57705, 57690 +declare function is(v: T): v is T; +const o: Record | undefined = {}; +if (o) { + for (const key in o) { + const value = o[key]; + if (is(value)) { + } + } +} + +type SomeRecord = { a: string }; +declare const kPresentationInheritanceParents: { [tagName: string]: string[] }; +declare function parentElementOrShadowHost(element: SomeRecord): SomeRecord | undefined; + +function getImplicitAriaRole(element: SomeRecord) { + let ancestor: SomeRecord | null = element; + while (ancestor) { + const parent = parentElementOrShadowHost(ancestor); + const parents = kPresentationInheritanceParents[ancestor.a]; + if (!parents || !parent || !parents.includes(parent.a)) + break; + ancestor = parent; + } +} + +declare function isPlainObject2( + data: unknown, + ): data is Record; + + declare const myObj2: unknown; + if (isPlainObject2(myObj2)) { + for (const key of ["a", "b", "c"]) { + const deeper = myObj2[key]; + const deeperKeys = isPlainObject2(deeper) ? Object.keys(deeper) : []; + } + } + + +//// [discriminantNarrowingCouldBeCircular.js] +"use strict"; +var o = {}; +if (o) { + for (var key in o) { + var value = o[key]; + if (is(value)) { + } + } +} +function getImplicitAriaRole(element) { + var ancestor = element; + while (ancestor) { + var parent = parentElementOrShadowHost(ancestor); + var parents = kPresentationInheritanceParents[ancestor.a]; + if (!parents || !parent || !parents.includes(parent.a)) + break; + ancestor = parent; + } +} +if (isPlainObject2(myObj2)) { + for (var _i = 0, _a = ["a", "b", "c"]; _i < _a.length; _i++) { + var key = _a[_i]; + var deeper = myObj2[key]; + var deeperKeys = isPlainObject2(deeper) ? Object.keys(deeper) : []; + } +} diff --git a/tests/baselines/reference/discriminantNarrowingCouldBeCircular.symbols b/tests/baselines/reference/discriminantNarrowingCouldBeCircular.symbols new file mode 100644 index 0000000000000..63c7e9dc7f5af --- /dev/null +++ b/tests/baselines/reference/discriminantNarrowingCouldBeCircular.symbols @@ -0,0 +1,129 @@ +//// [tests/cases/compiler/discriminantNarrowingCouldBeCircular.ts] //// + +=== discriminantNarrowingCouldBeCircular.ts === +// #57705, 57690 +declare function is(v: T): v is T; +>is : Symbol(is, Decl(discriminantNarrowingCouldBeCircular.ts, 0, 0)) +>T : Symbol(T, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 20)) +>v : Symbol(v, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 23)) +>T : Symbol(T, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 20)) +>v : Symbol(v, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 23)) +>T : Symbol(T, Decl(discriminantNarrowingCouldBeCircular.ts, 1, 20)) + +const o: Record | undefined = {}; +>o : Symbol(o, Decl(discriminantNarrowingCouldBeCircular.ts, 2, 5)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + +if (o) { +>o : Symbol(o, Decl(discriminantNarrowingCouldBeCircular.ts, 2, 5)) + + for (const key in o) { +>key : Symbol(key, Decl(discriminantNarrowingCouldBeCircular.ts, 4, 12)) +>o : Symbol(o, Decl(discriminantNarrowingCouldBeCircular.ts, 2, 5)) + + const value = o[key]; +>value : Symbol(value, Decl(discriminantNarrowingCouldBeCircular.ts, 5, 9)) +>o : Symbol(o, Decl(discriminantNarrowingCouldBeCircular.ts, 2, 5)) +>key : Symbol(key, Decl(discriminantNarrowingCouldBeCircular.ts, 4, 12)) + + if (is(value)) { +>is : Symbol(is, Decl(discriminantNarrowingCouldBeCircular.ts, 0, 0)) +>value : Symbol(value, Decl(discriminantNarrowingCouldBeCircular.ts, 5, 9)) + } + } +} + +type SomeRecord = { a: string }; +>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1)) +>a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19)) + +declare const kPresentationInheritanceParents: { [tagName: string]: string[] }; +>kPresentationInheritanceParents : Symbol(kPresentationInheritanceParents, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 13)) +>tagName : Symbol(tagName, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 50)) + +declare function parentElementOrShadowHost(element: SomeRecord): SomeRecord | undefined; +>parentElementOrShadowHost : Symbol(parentElementOrShadowHost, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 79)) +>element : Symbol(element, Decl(discriminantNarrowingCouldBeCircular.ts, 13, 43)) +>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1)) +>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1)) + +function getImplicitAriaRole(element: SomeRecord) { +>getImplicitAriaRole : Symbol(getImplicitAriaRole, Decl(discriminantNarrowingCouldBeCircular.ts, 13, 88)) +>element : Symbol(element, Decl(discriminantNarrowingCouldBeCircular.ts, 15, 29)) +>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1)) + + let ancestor: SomeRecord | null = element; +>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5)) +>SomeRecord : Symbol(SomeRecord, Decl(discriminantNarrowingCouldBeCircular.ts, 9, 1)) +>element : Symbol(element, Decl(discriminantNarrowingCouldBeCircular.ts, 15, 29)) + + while (ancestor) { +>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5)) + + const parent = parentElementOrShadowHost(ancestor); +>parent : Symbol(parent, Decl(discriminantNarrowingCouldBeCircular.ts, 18, 9)) +>parentElementOrShadowHost : Symbol(parentElementOrShadowHost, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 79)) +>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5)) + + const parents = kPresentationInheritanceParents[ancestor.a]; +>parents : Symbol(parents, Decl(discriminantNarrowingCouldBeCircular.ts, 19, 9)) +>kPresentationInheritanceParents : Symbol(kPresentationInheritanceParents, Decl(discriminantNarrowingCouldBeCircular.ts, 12, 13)) +>ancestor.a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19)) +>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5)) +>a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19)) + + if (!parents || !parent || !parents.includes(parent.a)) +>parents : Symbol(parents, Decl(discriminantNarrowingCouldBeCircular.ts, 19, 9)) +>parent : Symbol(parent, Decl(discriminantNarrowingCouldBeCircular.ts, 18, 9)) +>parents.includes : Symbol(Array.includes, Decl(lib.es2016.array.include.d.ts, --, --)) +>parents : Symbol(parents, Decl(discriminantNarrowingCouldBeCircular.ts, 19, 9)) +>includes : Symbol(Array.includes, Decl(lib.es2016.array.include.d.ts, --, --)) +>parent.a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19)) +>parent : Symbol(parent, Decl(discriminantNarrowingCouldBeCircular.ts, 18, 9)) +>a : Symbol(a, Decl(discriminantNarrowingCouldBeCircular.ts, 11, 19)) + + break; + ancestor = parent; +>ancestor : Symbol(ancestor, Decl(discriminantNarrowingCouldBeCircular.ts, 16, 5)) +>parent : Symbol(parent, Decl(discriminantNarrowingCouldBeCircular.ts, 18, 9)) + } +} + +declare function isPlainObject2( +>isPlainObject2 : Symbol(isPlainObject2, Decl(discriminantNarrowingCouldBeCircular.ts, 24, 1)) +>T : Symbol(T, Decl(discriminantNarrowingCouldBeCircular.ts, 26, 32)) + + data: unknown, +>data : Symbol(data, Decl(discriminantNarrowingCouldBeCircular.ts, 26, 35)) + + ): data is Record; +>data : Symbol(data, Decl(discriminantNarrowingCouldBeCircular.ts, 26, 35)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>PropertyKey : Symbol(PropertyKey, Decl(lib.es5.d.ts, --, --)) + + declare const myObj2: unknown; +>myObj2 : Symbol(myObj2, Decl(discriminantNarrowingCouldBeCircular.ts, 30, 15)) + + if (isPlainObject2(myObj2)) { +>isPlainObject2 : Symbol(isPlainObject2, Decl(discriminantNarrowingCouldBeCircular.ts, 24, 1)) +>myObj2 : Symbol(myObj2, Decl(discriminantNarrowingCouldBeCircular.ts, 30, 15)) + + for (const key of ["a", "b", "c"]) { +>key : Symbol(key, Decl(discriminantNarrowingCouldBeCircular.ts, 32, 16)) + + const deeper = myObj2[key]; +>deeper : Symbol(deeper, Decl(discriminantNarrowingCouldBeCircular.ts, 33, 13)) +>myObj2 : Symbol(myObj2, Decl(discriminantNarrowingCouldBeCircular.ts, 30, 15)) +>key : Symbol(key, Decl(discriminantNarrowingCouldBeCircular.ts, 32, 16)) + + const deeperKeys = isPlainObject2(deeper) ? Object.keys(deeper) : []; +>deeperKeys : Symbol(deeperKeys, Decl(discriminantNarrowingCouldBeCircular.ts, 34, 13)) +>isPlainObject2 : Symbol(isPlainObject2, Decl(discriminantNarrowingCouldBeCircular.ts, 24, 1)) +>deeper : Symbol(deeper, Decl(discriminantNarrowingCouldBeCircular.ts, 33, 13)) +>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>deeper : Symbol(deeper, Decl(discriminantNarrowingCouldBeCircular.ts, 33, 13)) + } + } + diff --git a/tests/baselines/reference/discriminantNarrowingCouldBeCircular.types b/tests/baselines/reference/discriminantNarrowingCouldBeCircular.types new file mode 100644 index 0000000000000..63b27fb6c352a --- /dev/null +++ b/tests/baselines/reference/discriminantNarrowingCouldBeCircular.types @@ -0,0 +1,138 @@ +//// [tests/cases/compiler/discriminantNarrowingCouldBeCircular.ts] //// + +=== discriminantNarrowingCouldBeCircular.ts === +// #57705, 57690 +declare function is(v: T): v is T; +>is : (v: T) => v is T +>v : T + +const o: Record | undefined = {}; +>o : Record | undefined +>{} : {} + +if (o) { +>o : Record + + for (const key in o) { +>key : string +>o : Record + + const value = o[key]; +>value : string +>o[key] : string +>o : Record +>key : string + + if (is(value)) { +>is(value) : boolean +>is : (v: T) => v is T +>value : string + } + } +} + +type SomeRecord = { a: string }; +>SomeRecord : { a: string; } +>a : string + +declare const kPresentationInheritanceParents: { [tagName: string]: string[] }; +>kPresentationInheritanceParents : { [tagName: string]: string[]; } +>tagName : string + +declare function parentElementOrShadowHost(element: SomeRecord): SomeRecord | undefined; +>parentElementOrShadowHost : (element: SomeRecord) => SomeRecord | undefined +>element : SomeRecord + +function getImplicitAriaRole(element: SomeRecord) { +>getImplicitAriaRole : (element: SomeRecord) => void +>element : SomeRecord + + let ancestor: SomeRecord | null = element; +>ancestor : SomeRecord | null +>element : SomeRecord + + while (ancestor) { +>ancestor : SomeRecord + + const parent = parentElementOrShadowHost(ancestor); +>parent : SomeRecord | undefined +>parentElementOrShadowHost(ancestor) : SomeRecord | undefined +>parentElementOrShadowHost : (element: SomeRecord) => SomeRecord | undefined +>ancestor : SomeRecord + + const parents = kPresentationInheritanceParents[ancestor.a]; +>parents : string[] +>kPresentationInheritanceParents[ancestor.a] : string[] +>kPresentationInheritanceParents : { [tagName: string]: string[]; } +>ancestor.a : string +>ancestor : SomeRecord +>a : string + + if (!parents || !parent || !parents.includes(parent.a)) +>!parents || !parent || !parents.includes(parent.a) : boolean +>!parents || !parent : boolean +>!parents : false +>parents : string[] +>!parent : boolean +>parent : SomeRecord | undefined +>!parents.includes(parent.a) : boolean +>parents.includes(parent.a) : boolean +>parents.includes : (searchElement: string, fromIndex?: number | undefined) => boolean +>parents : string[] +>includes : (searchElement: string, fromIndex?: number | undefined) => boolean +>parent.a : string +>parent : SomeRecord +>a : string + + break; + ancestor = parent; +>ancestor = parent : SomeRecord +>ancestor : SomeRecord | null +>parent : SomeRecord + } +} + +declare function isPlainObject2( +>isPlainObject2 : (data: unknown) => data is Record + + data: unknown, +>data : unknown + + ): data is Record; + + declare const myObj2: unknown; +>myObj2 : unknown + + if (isPlainObject2(myObj2)) { +>isPlainObject2(myObj2) : boolean +>isPlainObject2 : (data: unknown) => data is Record +>myObj2 : unknown + + for (const key of ["a", "b", "c"]) { +>key : string +>["a", "b", "c"] : string[] +>"a" : "a" +>"b" : "b" +>"c" : "c" + + const deeper = myObj2[key]; +>deeper : unknown +>myObj2[key] : unknown +>myObj2 : Record +>key : string + + const deeperKeys = isPlainObject2(deeper) ? Object.keys(deeper) : []; +>deeperKeys : string[] +>isPlainObject2(deeper) ? Object.keys(deeper) : [] : string[] +>isPlainObject2(deeper) : boolean +>isPlainObject2 : (data: unknown) => data is Record +>deeper : unknown +>Object.keys(deeper) : string[] +>Object.keys : { (o: object): string[]; (o: {}): string[]; } +>Object : ObjectConstructor +>keys : { (o: object): string[]; (o: {}): string[]; } +>deeper : Record +>[] : never[] + } + } + diff --git a/tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.symbols b/tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.symbols index e1bacc007b26a..6c33043410585 100644 --- a/tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.symbols +++ b/tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.symbols @@ -28,9 +28,9 @@ if (isOneOf(fruit.kind, ['apple', 'banana'] as const)) { >const : Symbol(const) fruit.kind ->fruit.kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 22), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 41)) +>fruit.kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 22), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 41), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 62)) >fruit : Symbol(fruit, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 13)) ->kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 22), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 41)) +>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 22), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 41), Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 62)) fruit >fruit : Symbol(fruit, Decl(typePredicatesCanNarrowByDiscriminant.ts, 1, 13)) @@ -54,9 +54,9 @@ if (isOneOf(kind, ['apple', 'banana'] as const)) { >const : Symbol(const) fruit2.kind ->fruit2.kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 23), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 42)) +>fruit2.kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 23), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 42), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 63)) >fruit2 : Symbol(fruit2, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 13)) ->kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 23), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 42)) +>kind : Symbol(kind, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 23), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 42), Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 63)) fruit2 >fruit2 : Symbol(fruit2, Decl(typePredicatesCanNarrowByDiscriminant.ts, 9, 13)) diff --git a/tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.types b/tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.types index 3dc7156b1fa08..bc14536bc8c5f 100644 --- a/tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.types +++ b/tests/baselines/reference/typePredicatesCanNarrowByDiscriminant.types @@ -26,11 +26,11 @@ if (isOneOf(fruit.kind, ['apple', 'banana'] as const)) { fruit.kind >fruit.kind : "apple" | "banana" ->fruit : { kind: "apple"; } | { kind: "banana"; } +>fruit : { kind: "apple"; } | { kind: "banana"; } | { kind: "cherry"; } >kind : "apple" | "banana" fruit ->fruit : { kind: "apple"; } | { kind: "banana"; } +>fruit : { kind: "apple"; } | { kind: "banana"; } | { kind: "cherry"; } } declare const fruit2: { kind: 'apple'} | { kind: 'banana' } | { kind: 'cherry' } @@ -55,10 +55,10 @@ if (isOneOf(kind, ['apple', 'banana'] as const)) { >'banana' : "banana" fruit2.kind ->fruit2.kind : "apple" | "banana" ->fruit2 : { kind: "apple"; } | { kind: "banana"; } ->kind : "apple" | "banana" +>fruit2.kind : "apple" | "banana" | "cherry" +>fruit2 : { kind: "apple"; } | { kind: "banana"; } | { kind: "cherry"; } +>kind : "apple" | "banana" | "cherry" fruit2 ->fruit2 : { kind: "apple"; } | { kind: "banana"; } +>fruit2 : { kind: "apple"; } | { kind: "banana"; } | { kind: "cherry"; } } diff --git a/tests/cases/compiler/discriminantNarrowingCouldBeCircular.ts b/tests/cases/compiler/discriminantNarrowingCouldBeCircular.ts new file mode 100644 index 0000000000000..1c85d9e757307 --- /dev/null +++ b/tests/cases/compiler/discriminantNarrowingCouldBeCircular.ts @@ -0,0 +1,40 @@ +// @strict: true +// @lib: es2022 + +// #57705, 57690 +declare function is(v: T): v is T; +const o: Record | undefined = {}; +if (o) { + for (const key in o) { + const value = o[key]; + if (is(value)) { + } + } +} + +type SomeRecord = { a: string }; +declare const kPresentationInheritanceParents: { [tagName: string]: string[] }; +declare function parentElementOrShadowHost(element: SomeRecord): SomeRecord | undefined; + +function getImplicitAriaRole(element: SomeRecord) { + let ancestor: SomeRecord | null = element; + while (ancestor) { + const parent = parentElementOrShadowHost(ancestor); + const parents = kPresentationInheritanceParents[ancestor.a]; + if (!parents || !parent || !parents.includes(parent.a)) + break; + ancestor = parent; + } +} + +declare function isPlainObject2( + data: unknown, + ): data is Record; + + declare const myObj2: unknown; + if (isPlainObject2(myObj2)) { + for (const key of ["a", "b", "c"]) { + const deeper = myObj2[key]; + const deeperKeys = isPlainObject2(deeper) ? Object.keys(deeper) : []; + } + }